Merge commit '4c921950dd521be22d536e6029034b523a705ed5' into dev-release
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassAccessFlags.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassAccessFlags.java
new file mode 100644
index 0000000..df8e32c
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassAccessFlags.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.annotations;
+
+/**
+ * Valid matches on class access flags and their negations.
+ *
+ * <p>The negated elements make it easier to express the inverse as we cannot use a "not/negation"
+ * operation syntactically.
+ */
+public enum ClassAccessFlags {
+  PUBLIC,
+  NON_PUBLIC,
+  PACKAGE_PRIVATE,
+  NON_PACKAGE_PRIVATE,
+  FINAL,
+  NON_FINAL,
+  INTERFACE,
+  NON_INTERFACE,
+  ABSTRACT,
+  NON_ABSTRACT,
+  SYNTHETIC,
+  NON_SYNTHETIC,
+  ANNOTATION,
+  NON_ANNOTATION,
+  ENUM,
+  NON_ENUM
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/FieldAccessFlags.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/FieldAccessFlags.java
new file mode 100644
index 0000000..dd5a682
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/FieldAccessFlags.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.annotations;
+
+/**
+ * Valid matches on field access flags and their negations.
+ *
+ * <p>The negated elements make it easier to express the inverse as we cannot use a "not/negation"
+ * operation syntactically.
+ */
+public enum FieldAccessFlags {
+  // General member flags.
+  PUBLIC,
+  NON_PUBLIC,
+  PRIVATE,
+  NON_PRIVATE,
+  PROTECTED,
+  NON_PROTECTED,
+  PACKAGE_PRIVATE,
+  NON_PACKAGE_PRIVATE,
+  STATIC,
+  NON_STATIC,
+  FINAL,
+  NON_FINAL,
+  SYNTHETIC,
+  NON_SYNTHETIC,
+  // Field specific flags.
+  VOLATILE,
+  NON_VOLATILE,
+  TRANSIENT,
+  NON_TRANSIENT,
+  // ENUM - No PG parser support.
+}
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
new file mode 100644
index 0000000..44756ea
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to mark a class, field or method as part of a library API surface.
+ *
+ * <p>When a class is annotated, member patterns can be used to define which members are to be kept.
+ * When no member patterns are specified the default pattern is to match all public and protected
+ * members.
+ *
+ * <p>When a member is annotated, the member patterns cannot be used as the annotated member itself
+ * fully defines the item to be kept (i.e., itself).
+ */
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface KeepForApi {
+  String description() default "";
+
+  /** Additional targets to be kept as part of the API surface. */
+  KeepTarget[] additionalTargets() default {};
+
+  /**
+   * The target kind to be kept.
+   *
+   * <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>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 it to be accessible.
+   */
+  KeepItemKind kind() default KeepItemKind.DEFAULT;
+
+  // Member patterns. See KeepTarget for documentation.
+  MemberAccessFlags[] memberAccess() default {};
+
+  MethodAccessFlags[] methodAccess() default {};
+
+  String methodName() default "";
+
+  String methodReturnType() default "";
+
+  String[] methodParameters() default {""};
+
+  FieldAccessFlags[] fieldAccess() default {};
+
+  String fieldName() default "";
+
+  String fieldType() default "";
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index a2e9c4f..c9626f2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -100,6 +100,21 @@
   String memberFromBinding() default "";
 
   /**
+   * Define the member pattern by matching on access flags.
+   *
+   * <p>Mutually exclusive with all field and method patterns as use restricts the match to both
+   * types of members.
+   */
+  MemberAccessFlags[] memberAccess() default {};
+
+  /**
+   * Define the method pattern by matching on access flags.
+   *
+   * <p>Mutually exclusive with any field properties.
+   */
+  MethodAccessFlags[] methodAccess() default {};
+
+  /**
    * Define the method-name pattern by an exact method name.
    *
    * <p>Mutually exclusive with any field properties.
@@ -127,6 +142,13 @@
   String[] methodParameters() default {""};
 
   /**
+   * Define the field pattern by matching on field access flags.
+   *
+   * <p>Mutually exclusive with any method properties.
+   */
+  FieldAccessFlags[] fieldAccess() default {};
+
+  /**
    * Define the field-name pattern by an exact field name.
    *
    * <p>Mutually exclusive with any method properties.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/MemberAccessFlags.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/MemberAccessFlags.java
new file mode 100644
index 0000000..b3f1a50
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/MemberAccessFlags.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.annotations;
+
+/**
+ * Valid matches on member access flags and their negations.
+ *
+ * <p>The negated elements make it easier to express the inverse as we cannot use a "not/negation"
+ * operation syntactically.
+ */
+public enum MemberAccessFlags {
+  PUBLIC,
+  NON_PUBLIC,
+  PROTECTED,
+  NON_PROTECTED,
+  PACKAGE_PRIVATE,
+  NON_PACKAGE_PRIVATE,
+  PRIVATE,
+  NON_PRIVATE,
+  STATIC,
+  NON_STATIC,
+  FINAL,
+  NON_FINAL,
+  SYNTHETIC,
+  NON_SYNTHETIC
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/MethodAccessFlags.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/MethodAccessFlags.java
new file mode 100644
index 0000000..a287365
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/MethodAccessFlags.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.annotations;
+
+/**
+ * Valid matches on method access flags and their negations.
+ *
+ * <p>The negated elements make it easier to express the inverse as we cannot use a "not/negation"
+ * operation syntactically.
+ */
+public enum MethodAccessFlags {
+  // General member flags.
+  PUBLIC,
+  NON_PUBLIC,
+  PRIVATE,
+  NON_PRIVATE,
+  PROTECTED,
+  NON_PROTECTED,
+  PACKAGE_PRIVATE,
+  NON_PACKAGE_PRIVATE,
+  STATIC,
+  NON_STATIC,
+  FINAL,
+  NON_FINAL,
+  SYNTHETIC,
+  NON_SYNTHETIC,
+  // Method specific flags.
+  SYNCHRONIZED,
+  NON_SYNCHRONIZED,
+  BRIDGE,
+  NON_BRIDGE,
+  // VARARGS - No PG parser support
+  NATIVE,
+  NON_NATIVE,
+  ABSTRACT,
+  NON_ABSTRACT,
+  STRICT_FP,
+  NON_STRICT_FP
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 1d33cb1..73ec688 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -3,14 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.asm;
 
+import com.android.tools.r8.keepanno.ast.AccessVisibility;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Binding;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.FieldAccess;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ForApi;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Kind;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.MemberAccess;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Option;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflection;
 import com.android.tools.r8.keepanno.ast.KeepBindings;
 import com.android.tools.r8.keepanno.ast.KeepClassReference;
 import com.android.tools.r8.keepanno.ast.KeepCondition;
@@ -19,13 +25,16 @@
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
 import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
 import com.android.tools.r8.keepanno.ast.KeepExtendsPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
 import com.android.tools.r8.keepanno.ast.KeepItemKind;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemReference;
+import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -42,6 +51,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
@@ -103,6 +113,9 @@
                 .build();
         return new UsesReflectionVisitor(parent, this::setContext, classItem);
       }
+      if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
+        return new ForApiClassVisitor(parent, this::setContext, className);
+      }
       return null;
     }
 
@@ -173,6 +186,9 @@
       if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
         return new UsesReflectionVisitor(parent, this::setContext, createItemContext());
       }
+      if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
+        return new ForApiMemberVisitor(parent, this::setContext, createItemContext());
+      }
       return null;
     }
 
@@ -227,6 +243,9 @@
       if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
         return new UsesReflectionVisitor(parent, this::setContext, createItemContext());
       }
+      if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
+        return new ForApiMemberVisitor(parent, this::setContext, createItemContext());
+      }
       return null;
     }
   }
@@ -242,24 +261,31 @@
       super(ASM_VERSION);
     }
 
+    public abstract String getAnnotationName();
+
+    private String errorMessagePrefix() {
+      return " @" + getAnnotationName() + ": ";
+    }
+
     @Override
     public void visit(String name, Object value) {
-      throw new KeepEdgeException("Unexpected value in @KeepEdge: " + name + " = " + value);
+      throw new KeepEdgeException(
+          "Unexpected value in" + errorMessagePrefix() + name + " = " + value);
     }
 
     @Override
     public AnnotationVisitor visitAnnotation(String name, String descriptor) {
-      throw new KeepEdgeException("Unexpected annotation in @KeepEdge: " + name);
+      throw new KeepEdgeException("Unexpected annotation in" + errorMessagePrefix() + name);
     }
 
     @Override
     public void visitEnum(String name, String descriptor, String value) {
-      throw new KeepEdgeException("Unexpected enum in @KeepEdge: " + name);
+      throw new KeepEdgeException("Unexpected enum in" + errorMessagePrefix() + name);
     }
 
     @Override
     public AnnotationVisitor visitArray(String name) {
-      throw new KeepEdgeException("Unexpected array in @KeepEdge: " + name);
+      throw new KeepEdgeException("Unexpected array in" + errorMessagePrefix() + name);
     }
   }
 
@@ -274,6 +300,11 @@
     }
 
     @Override
+    public String getAnnotationName() {
+      return "KeepEdge";
+    }
+
+    @Override
     public void visit(String name, Object value) {
       if (name.equals(Edge.description) && value instanceof String) {
         metaInfoBuilder.setDescription((String) value);
@@ -285,13 +316,13 @@
     @Override
     public AnnotationVisitor visitArray(String name) {
       if (name.equals(Edge.bindings)) {
-        return new KeepBindingsVisitor(builder::setBindings);
+        return new KeepBindingsVisitor(getAnnotationName(), builder::setBindings);
       }
       if (name.equals(Edge.preconditions)) {
-        return new KeepPreconditionsVisitor(builder::setPreconditions);
+        return new KeepPreconditionsVisitor(getAnnotationName(), builder::setPreconditions);
       }
       if (name.equals(Edge.consequences)) {
-        return new KeepConsequencesVisitor(builder::setConsequences);
+        return new KeepConsequencesVisitor(getAnnotationName(), builder::setConsequences);
       }
       return super.visitArray(name);
     }
@@ -302,6 +333,156 @@
     }
   }
 
+  /**
+   * Parsing of @KeepForApi on a class context.
+   *
+   * <p>When used on a class context the annotation allows the member related content of a normal
+   * item. This parser extends the base item visitor and throws an error if any class specific
+   * properties are encountered.
+   */
+  private static class ForApiClassVisitor extends KeepItemVisitorBase {
+    private final String className;
+    private final Parent<KeepEdge> parent;
+    private final KeepEdge.Builder builder = KeepEdge.builder();
+    private final KeepConsequences.Builder consequences = KeepConsequences.builder();
+    private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+
+    ForApiClassVisitor(
+        Parent<KeepEdge> parent, Consumer<KeepEdgeMetaInfo.Builder> addContext, String className) {
+      this.className = className;
+      this.parent = parent;
+      addContext.accept(metaInfoBuilder);
+      // The class context/holder is the annotated class.
+      visit(Item.className, className);
+      // The default kind is to target the class and its members.
+      visitEnum(null, Kind.DESCRIPTOR, Kind.CLASS_AND_MEMBERS);
+    }
+
+    @Override
+    public String getAnnotationName() {
+      return ForApi.CLASS.getSimpleName();
+    }
+
+    @Override
+    public void visit(String name, Object value) {
+      if (name.equals(Edge.description) && value instanceof String) {
+        metaInfoBuilder.setDescription((String) value);
+        return;
+      }
+      super.visit(name, value);
+    }
+
+    @Override
+    public AnnotationVisitor visitArray(String name) {
+      if (name.equals(ForApi.additionalTargets)) {
+        return new KeepConsequencesVisitor(
+            getAnnotationName(),
+            additionalConsequences -> {
+              additionalConsequences.forEachTarget(consequences::addTarget);
+            });
+      }
+      return super.visitArray(name);
+    }
+
+    @Override
+    public void visitEnd() {
+      if (!getKind().equals(KeepItemKind.ONLY_CLASS) && isDefaultMemberDeclaration()) {
+        // If no member declarations have been made, set public & protected as the default.
+        AnnotationVisitor v = visitArray(Item.memberAccess);
+        v.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PUBLIC);
+        v.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PROTECTED);
+      }
+      super.visitEnd();
+      KeepItemReference item = getItemReference();
+      if (item.isBindingReference()) {
+        throw new KeepEdgeException("@KeepForApi cannot reference bindings");
+      }
+      KeepItemPattern itemPattern = item.asItemPattern();
+      String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
+      String itemDescriptor =
+          itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
+      if (!descriptor.equals(itemDescriptor)) {
+        throw new KeepEdgeException("@KeepForApi must reference its class context " + className);
+      }
+      if (itemPattern.getKind().equals(KeepItemKind.ONLY_MEMBERS)) {
+        throw new KeepEdgeException("@KeepForApi kind must include its class");
+      }
+      if (!itemPattern.getExtendsPattern().isAny()) {
+        throw new KeepEdgeException("@KeepForApi cannot define an 'extends' pattern.");
+      }
+      consequences.addTarget(KeepTarget.builder().setItemPattern(itemPattern).build());
+      parent.accept(
+          builder
+              .setMetaInfo(metaInfoBuilder.build())
+              .setConsequences(consequences.build())
+              .build());
+    }
+  }
+
+  /**
+   * Parsing of @KeepForApi on a member context.
+   *
+   * <p>When used on a member context the annotation does not allow member related patterns.
+   */
+  private static class ForApiMemberVisitor extends AnnotationVisitorBase {
+    private final Parent<KeepEdge> parent;
+    private final KeepEdge.Builder builder = KeepEdge.builder();
+    private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+
+    private final KeepConsequences.Builder consequences = KeepConsequences.builder();
+
+    ForApiMemberVisitor(
+        Parent<KeepEdge> parent,
+        Consumer<KeepEdgeMetaInfo.Builder> addContext,
+        KeepItemPattern context) {
+      this.parent = parent;
+      addContext.accept(metaInfoBuilder);
+      consequences.addTarget(
+          KeepTarget.builder()
+              .setItemPattern(
+                  KeepItemPattern.builder()
+                      .copyFrom(context)
+                      .setKind(KeepItemKind.CLASS_AND_MEMBERS)
+                      .build())
+              .build());
+    }
+
+    @Override
+    public String getAnnotationName() {
+      return ForApi.CLASS.getSimpleName();
+    }
+
+    @Override
+    public void visit(String name, Object value) {
+      if (name.equals(Edge.description) && value instanceof String) {
+        metaInfoBuilder.setDescription((String) value);
+        return;
+      }
+      super.visit(name, value);
+    }
+
+    @Override
+    public AnnotationVisitor visitArray(String name) {
+      if (name.equals(ForApi.additionalTargets)) {
+        return new KeepConsequencesVisitor(
+            getAnnotationName(),
+            additionalConsequences -> {
+              additionalConsequences.forEachTarget(consequences::addTarget);
+            });
+      }
+      return super.visitArray(name);
+    }
+
+    @Override
+    public void visitEnd() {
+      parent.accept(
+          builder
+              .setMetaInfo(metaInfoBuilder.build())
+              .setConsequences(consequences.build())
+              .build());
+    }
+  }
+
   private static class UsesReflectionVisitor extends AnnotationVisitorBase {
     private final Parent<KeepEdge> parent;
     private final KeepEdge.Builder builder = KeepEdge.builder();
@@ -318,6 +499,11 @@
     }
 
     @Override
+    public String getAnnotationName() {
+      return UsesReflection.CLASS.getSimpleName();
+    }
+
+    @Override
     public void visit(String name, Object value) {
       if (name.equals(Edge.description) && value instanceof String) {
         metaInfoBuilder.setDescription((String) value);
@@ -329,10 +515,11 @@
     @Override
     public AnnotationVisitor visitArray(String name) {
       if (name.equals(AnnotationConstants.UsesReflection.value)) {
-        return new KeepConsequencesVisitor(builder::setConsequences);
+        return new KeepConsequencesVisitor(getAnnotationName(), builder::setConsequences);
       }
       if (name.equals(AnnotationConstants.UsesReflection.additionalPreconditions)) {
         return new KeepPreconditionsVisitor(
+            getAnnotationName(),
             additionalPreconditions -> {
               additionalPreconditions.forEach(preconditions::addCondition);
             });
@@ -351,14 +538,21 @@
   }
 
   private static class KeepBindingsVisitor extends AnnotationVisitorBase {
+    private final String annotationName;
     private final Parent<KeepBindings> parent;
     private final KeepBindings.Builder builder = KeepBindings.builder();
 
-    public KeepBindingsVisitor(Parent<KeepBindings> parent) {
+    public KeepBindingsVisitor(String annotationName, Parent<KeepBindings> parent) {
+      this.annotationName = annotationName;
       this.parent = parent;
     }
 
     @Override
+    public String getAnnotationName() {
+      return annotationName;
+    }
+
+    @Override
     public AnnotationVisitor visitAnnotation(String name, String descriptor) {
       if (descriptor.equals(AnnotationConstants.Binding.DESCRIPTOR)) {
         return new KeepBindingVisitor(builder);
@@ -373,14 +567,21 @@
   }
 
   private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
+    private final String annotationName;
     private final Parent<KeepPreconditions> parent;
     private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
 
-    public KeepPreconditionsVisitor(Parent<KeepPreconditions> parent) {
+    public KeepPreconditionsVisitor(String annotationName, Parent<KeepPreconditions> parent) {
+      this.annotationName = annotationName;
       this.parent = parent;
     }
 
     @Override
+    public String getAnnotationName() {
+      return annotationName;
+    }
+
+    @Override
     public AnnotationVisitor visitAnnotation(String name, String descriptor) {
       if (descriptor.equals(Condition.DESCRIPTOR)) {
         return new KeepConditionVisitor(builder::addCondition);
@@ -395,14 +596,21 @@
   }
 
   private static class KeepConsequencesVisitor extends AnnotationVisitorBase {
+    private final String annotationName;
     private final Parent<KeepConsequences> parent;
     private final KeepConsequences.Builder builder = KeepConsequences.builder();
 
-    public KeepConsequencesVisitor(Parent<KeepConsequences> parent) {
+    public KeepConsequencesVisitor(String annotationName, Parent<KeepConsequences> parent) {
+      this.annotationName = annotationName;
       this.parent = parent;
     }
 
     @Override
+    public String getAnnotationName() {
+      return annotationName;
+    }
+
+    @Override
     public AnnotationVisitor visitAnnotation(String name, String descriptor) {
       if (descriptor.equals(Target.DESCRIPTOR)) {
         return KeepTargetVisitor.create(builder::addTarget);
@@ -419,6 +627,8 @@
   private abstract static class Declaration<T> {
     abstract String kind();
 
+    abstract boolean isDefault();
+
     abstract T getValue();
 
     boolean tryParse(String name, Object value) {
@@ -443,6 +653,11 @@
       return null;
     }
 
+    @Override
+    boolean isDefault() {
+      return !hasDeclaration();
+    }
+
     private boolean hasDeclaration() {
       return declarationValue != null || declarationVisitor != null;
     }
@@ -551,9 +766,14 @@
   }
 
   private static class MethodDeclaration extends Declaration<KeepMethodPattern> {
-
+    private final String annotationName;
+    private KeepMethodAccessPattern.Builder accessBuilder = null;
     private KeepMethodPattern.Builder builder = null;
 
+    private MethodDeclaration(String annotationName) {
+      this.annotationName = annotationName;
+    }
+
     private KeepMethodPattern.Builder getBuilder() {
       if (builder == null) {
         builder = KeepMethodPattern.builder();
@@ -567,7 +787,15 @@
     }
 
     @Override
+    boolean isDefault() {
+      return accessBuilder == null && builder == null;
+    }
+
+    @Override
     KeepMethodPattern getValue() {
+      if (accessBuilder != null) {
+        getBuilder().setAccessPattern(accessBuilder.build());
+      }
       return builder != null ? builder.build() : null;
     }
 
@@ -593,8 +821,13 @@
 
     @Override
     AnnotationVisitor tryParseArray(String name, Consumer<KeepMethodPattern> ignored) {
+      if (name.equals(Item.methodAccess)) {
+        accessBuilder = KeepMethodAccessPattern.builder();
+        return new MethodAccessVisitor(annotationName, accessBuilder);
+      }
       if (name.equals(Item.methodParameters)) {
         return new StringArrayVisitor(
+            annotationName,
             params -> {
               if (Arrays.asList(Item.methodParametersDefaultValue).equals(params)) {
                 return;
@@ -612,9 +845,14 @@
   }
 
   private static class FieldDeclaration extends Declaration<KeepFieldPattern> {
-
+    private final String annotationName;
+    private KeepFieldAccessPattern.Builder accessBuilder = null;
     private KeepFieldPattern.Builder builder = null;
 
+    public FieldDeclaration(String annotationName) {
+      this.annotationName = annotationName;
+    }
+
     private KeepFieldPattern.Builder getBuilder() {
       if (builder == null) {
         builder = KeepFieldPattern.builder();
@@ -628,7 +866,15 @@
     }
 
     @Override
+    boolean isDefault() {
+      return accessBuilder == null && builder == null;
+    }
+
+    @Override
     KeepFieldPattern getValue() {
+      if (accessBuilder != null) {
+        getBuilder().setAccessPattern(accessBuilder.build());
+      }
       return builder != null ? builder.build() : null;
     }
 
@@ -653,12 +899,28 @@
       }
       return false;
     }
+
+    @Override
+    AnnotationVisitor tryParseArray(String name, Consumer<KeepFieldPattern> onValue) {
+      if (name.equals(Item.fieldAccess)) {
+        accessBuilder = KeepFieldAccessPattern.builder();
+        return new FieldAccessVisitor(annotationName, accessBuilder);
+      }
+      return super.tryParseArray(name, onValue);
+    }
   }
 
   private static class MemberDeclaration extends Declaration<KeepMemberPattern> {
+    private final String annotationName;
+    private KeepMemberAccessPattern.Builder accessBuilder = null;
+    private final MethodDeclaration methodDeclaration;
+    private final FieldDeclaration fieldDeclaration;
 
-    private MethodDeclaration methodDeclaration = new MethodDeclaration();
-    private FieldDeclaration fieldDeclaration = new FieldDeclaration();
+    MemberDeclaration(String annotationName) {
+      this.annotationName = annotationName;
+      methodDeclaration = new MethodDeclaration(annotationName);
+      fieldDeclaration = new FieldDeclaration(annotationName);
+    }
 
     @Override
     String kind() {
@@ -666,9 +928,21 @@
     }
 
     @Override
+    public boolean isDefault() {
+      return accessBuilder == null && methodDeclaration.isDefault() && fieldDeclaration.isDefault();
+    }
+
+    @Override
     public KeepMemberPattern getValue() {
       KeepMethodPattern method = methodDeclaration.getValue();
       KeepFieldPattern field = fieldDeclaration.getValue();
+      if (accessBuilder != null) {
+        if (method != null || field != null) {
+          throw new KeepEdgeException(
+              "Cannot define common member access as well as field or method pattern");
+        }
+        return KeepMemberPattern.memberBuilder().setAccessPattern(accessBuilder.build()).build();
+      }
       if (method != null && field != null) {
         throw new KeepEdgeException("Cannot define both a field and a method pattern");
       }
@@ -688,6 +962,10 @@
 
     @Override
     AnnotationVisitor tryParseArray(String name, Consumer<KeepMemberPattern> ignored) {
+      if (name.equals(Item.memberAccess)) {
+        accessBuilder = KeepMemberAccessPattern.memberBuilder();
+        return new MemberAccessVisitor(annotationName, accessBuilder);
+      }
       AnnotationVisitor visitor = methodDeclaration.tryParseArray(name, v -> {});
       if (visitor != null) {
         return visitor;
@@ -697,23 +975,32 @@
   }
 
   private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
-    private Parent<KeepItemReference> parent;
     private String memberBindingReference = null;
     private KeepItemKind kind = null;
     private final ClassDeclaration classDeclaration = new ClassDeclaration();
     private final ExtendsDeclaration extendsDeclaration = new ExtendsDeclaration();
-    private final MemberDeclaration memberDeclaration = new MemberDeclaration();
+    private final MemberDeclaration memberDeclaration;
 
-    public KeepItemVisitorBase(Parent<KeepItemReference> parent) {
-      setParent(parent);
+    // Constructed item available once visitEnd has been called.
+    private KeepItemReference itemReference = null;
+
+    KeepItemVisitorBase() {
+      memberDeclaration = new MemberDeclaration(getAnnotationName());
     }
 
-    public KeepItemVisitorBase() {}
+    public KeepItemReference getItemReference() {
+      if (itemReference == null) {
+        throw new KeepEdgeException("Item reference not finalized. Missing call to visitEnd()");
+      }
+      return itemReference;
+    }
 
-    void setParent(Parent<KeepItemReference> parent) {
-      assert parent != null;
-      assert this.parent == null;
-      this.parent = parent;
+    public KeepItemKind getKind() {
+      return kind;
+    }
+
+    public boolean isDefaultMemberDeclaration() {
+      return memberDeclaration.isDefault();
     }
 
     @Override
@@ -772,7 +1059,7 @@
           throw new KeepEdgeException(
               "Cannot define an item explicitly and via a member-binding reference");
         }
-        parent.accept(KeepItemReference.fromBindingReference(memberBindingReference));
+        itemReference = KeepItemReference.fromBindingReference(memberBindingReference);
       } else {
         KeepMemberPattern memberPattern = memberDeclaration.getValue();
         // If the kind is not set (default) then the content of the members determines the kind.
@@ -783,14 +1070,14 @@
         if (!kind.equals(KeepItemKind.ONLY_CLASS) && memberPattern.isNone()) {
           memberPattern = KeepMemberPattern.allMembers();
         }
-        parent.accept(
+        itemReference =
             KeepItemReference.fromItemPattern(
                 KeepItemPattern.builder()
                     .setKind(kind)
                     .setClassReference(classDeclaration.getValue())
                     .setExtendsPattern(extendsDeclaration.getValue())
                     .setMemberPattern(memberPattern)
-                    .build()));
+                    .build());
       }
     }
   }
@@ -799,24 +1086,14 @@
 
     private final KeepBindings.Builder builder;
     private String bindingName;
-    private KeepItemPattern item;
 
     public KeepBindingVisitor(KeepBindings.Builder builder) {
       this.builder = builder;
-      setParent(
-          item -> {
-            // The language currently disallows aliasing bindings, thus a binding should directly be
-            // defined by a reference to another binding.
-            if (item.isBindingReference()) {
-              throw new KeepEdgeException(
-                  "Invalid binding reference to '"
-                      + item.asBindingReference()
-                      + "' in binding definition of '"
-                      + bindingName
-                      + "'");
-            }
-            this.item = item.asItemPattern();
-          });
+    }
+
+    @Override
+    public String getAnnotationName() {
+      return Binding.CLASS.getSimpleName();
     }
 
     @Override
@@ -831,20 +1108,37 @@
     @Override
     public void visitEnd() {
       super.visitEnd();
-      builder.addBinding(bindingName, item);
+      KeepItemReference item = getItemReference();
+      // The language currently disallows aliasing bindings, thus a binding should directly be
+      // defined by a reference to another binding.
+      if (item.isBindingReference()) {
+        throw new KeepEdgeException(
+            "Invalid binding reference to '"
+                + item.asBindingReference()
+                + "' in binding definition of '"
+                + bindingName
+                + "'");
+      }
+      builder.addBinding(bindingName, item.asItemPattern());
     }
   }
 
   private static class StringArrayVisitor extends AnnotationVisitorBase {
-
+    private final String annotationName;
     private final Consumer<List<String>> fn;
     private final List<String> strings = new ArrayList<>();
 
-    public StringArrayVisitor(Consumer<List<String>> fn) {
+    public StringArrayVisitor(String annotationName, Consumer<List<String>> fn) {
+      this.annotationName = annotationName;
       this.fn = fn;
     }
 
     @Override
+    public String getAnnotationName() {
+      return annotationName;
+    }
+
+    @Override
     public void visit(String name, Object value) {
       if (value instanceof String) {
         strings.add((String) value);
@@ -862,6 +1156,12 @@
 
   private static class OptionsDeclaration extends SingleDeclaration<KeepOptions> {
 
+    private final String annotationName;
+
+    public OptionsDeclaration(String annotationName) {
+      this.annotationName = annotationName;
+    }
+
     @Override
     String kind() {
       return "options";
@@ -881,10 +1181,12 @@
     AnnotationVisitor parseArray(String name, Consumer<KeepOptions> setValue) {
       if (name.equals(AnnotationConstants.Target.disallow)) {
         return new KeepOptionsVisitor(
+            annotationName,
             options -> setValue.accept(KeepOptions.disallowBuilder().addAll(options).build()));
       }
       if (name.equals(AnnotationConstants.Target.allow)) {
         return new KeepOptionsVisitor(
+            annotationName,
             options -> setValue.accept(KeepOptions.allowBuilder().addAll(options).build()));
       }
       return null;
@@ -893,17 +1195,22 @@
 
   private static class KeepTargetVisitor extends KeepItemVisitorBase {
 
-    private final KeepTarget.Builder builder;
-    private final OptionsDeclaration optionsDeclaration = new OptionsDeclaration();
+    private final Parent<KeepTarget> parent;
+    private final KeepTarget.Builder builder = KeepTarget.builder();
+    private final OptionsDeclaration optionsDeclaration =
+        new OptionsDeclaration(getAnnotationName());
 
     static KeepTargetVisitor create(Parent<KeepTarget> parent) {
-      KeepTarget.Builder builder = KeepTarget.builder();
-      return new KeepTargetVisitor(parent, builder);
+      return new KeepTargetVisitor(parent);
     }
 
-    private KeepTargetVisitor(Parent<KeepTarget> parent, KeepTarget.Builder builder) {
-      super(item -> parent.accept(builder.setItemReference(item).build()));
-      this.builder = builder;
+    private KeepTargetVisitor(Parent<KeepTarget> parent) {
+      this.parent = parent;
+    }
+
+    @Override
+    public String getAnnotationName() {
+      return Target.CLASS.getSimpleName();
     }
 
     @Override
@@ -914,25 +1221,51 @@
       }
       return super.visitArray(name);
     }
+
+    @Override
+    public void visitEnd() {
+      super.visitEnd();
+      parent.accept(builder.setItemReference(getItemReference()).build());
+    }
   }
 
   private static class KeepConditionVisitor extends KeepItemVisitorBase {
 
+    private final Parent<KeepCondition> parent;
+
     public KeepConditionVisitor(Parent<KeepCondition> parent) {
-      super(item -> parent.accept(KeepCondition.builder().setItemReference(item).build()));
+      this.parent = parent;
+    }
+
+    @Override
+    public String getAnnotationName() {
+      return Condition.CLASS.getSimpleName();
+    }
+
+    @Override
+    public void visitEnd() {
+      super.visitEnd();
+      parent.accept(KeepCondition.builder().setItemReference(getItemReference()).build());
     }
   }
 
   private static class KeepOptionsVisitor extends AnnotationVisitorBase {
 
+    private final String annotationName;
     private final Parent<Collection<KeepOption>> parent;
     private final Set<KeepOption> options = new HashSet<>();
 
-    public KeepOptionsVisitor(Parent<Collection<KeepOption>> parent) {
+    public KeepOptionsVisitor(String annotationName, Parent<Collection<KeepOption>> parent) {
+      this.annotationName = annotationName;
       this.parent = parent;
     }
 
     @Override
+    public String getAnnotationName() {
+      return annotationName;
+    }
+
+    @Override
     public void visitEnum(String ignore, String descriptor, String value) {
       if (!descriptor.equals(AnnotationConstants.Option.DESCRIPTOR)) {
         super.visitEnum(ignore, descriptor, value);
@@ -967,4 +1300,156 @@
       super.visitEnd();
     }
   }
+
+  private static class MemberAccessVisitor extends AnnotationVisitorBase {
+    private final String annotationName;
+    KeepMemberAccessPattern.BuilderBase<?, ?> builder;
+
+    public MemberAccessVisitor(
+        String annotationName, KeepMemberAccessPattern.BuilderBase<?, ?> builder) {
+      this.annotationName = annotationName;
+      this.builder = builder;
+    }
+
+    @Override
+    public String getAnnotationName() {
+      return annotationName;
+    }
+
+    static boolean withNormalizedAccessFlag(String flag, BiPredicate<String, Boolean> fn) {
+      boolean allow = !flag.startsWith(MemberAccess.NEGATION_PREFIX);
+      return allow
+          ? fn.test(flag, true)
+          : fn.test(flag.substring(MemberAccess.NEGATION_PREFIX.length()), false);
+    }
+
+    @Override
+    public void visitEnum(String ignore, String descriptor, String value) {
+      if (!descriptor.equals(AnnotationConstants.MemberAccess.DESCRIPTOR)) {
+        super.visitEnum(ignore, descriptor, value);
+      }
+      boolean handled =
+          withNormalizedAccessFlag(
+              value,
+              (flag, allow) -> {
+                AccessVisibility visibility = getAccessVisibilityFromString(flag);
+                if (visibility != null) {
+                  builder.setAccessVisibility(visibility, allow);
+                  return true;
+                }
+                switch (flag) {
+                  case MemberAccess.STATIC:
+                    builder.setStatic(allow);
+                    return true;
+                  case MemberAccess.FINAL:
+                    builder.setFinal(allow);
+                    return true;
+                  case MemberAccess.SYNTHETIC:
+                    builder.setSynthetic(allow);
+                    return true;
+                  default:
+                    return false;
+                }
+              });
+      if (!handled) {
+        super.visitEnum(ignore, descriptor, value);
+      }
+    }
+
+    private AccessVisibility getAccessVisibilityFromString(String value) {
+      switch (value) {
+        case MemberAccess.PUBLIC:
+          return AccessVisibility.PUBLIC;
+        case MemberAccess.PROTECTED:
+          return AccessVisibility.PROTECTED;
+        case MemberAccess.PACKAGE_PRIVATE:
+          return AccessVisibility.PACKAGE_PRIVATE;
+        case MemberAccess.PRIVATE:
+          return AccessVisibility.PRIVATE;
+        default:
+          return null;
+      }
+    }
+  }
+
+  private static class MethodAccessVisitor extends MemberAccessVisitor {
+
+    KeepMethodAccessPattern.Builder builder;
+
+    public MethodAccessVisitor(String annotationName, KeepMethodAccessPattern.Builder builder) {
+      super(annotationName, builder);
+      this.builder = builder;
+    }
+
+    @Override
+    public void visitEnum(String ignore, String descriptor, String value) {
+      if (!descriptor.equals(AnnotationConstants.MethodAccess.DESCRIPTOR)) {
+        super.visitEnum(ignore, descriptor, value);
+      }
+      boolean handled =
+          withNormalizedAccessFlag(
+              value,
+              (flag, allow) -> {
+                switch (flag) {
+                  case MethodAccess.SYNCHRONIZED:
+                    builder.setSynchronized(allow);
+                    return true;
+                  case MethodAccess.BRIDGE:
+                    builder.setBridge(allow);
+                    return true;
+                  case MethodAccess.NATIVE:
+                    builder.setNative(allow);
+                    return true;
+                  case MethodAccess.ABSTRACT:
+                    builder.setAbstract(allow);
+                    return true;
+                  case MethodAccess.STRICT_FP:
+                    builder.setStrictFp(allow);
+                    return true;
+                  default:
+                    return false;
+                }
+              });
+      if (!handled) {
+        // Continue visitation with the "member" descriptor to allow matching the common values.
+        super.visitEnum(ignore, MemberAccess.DESCRIPTOR, value);
+      }
+    }
+  }
+
+  private static class FieldAccessVisitor extends MemberAccessVisitor {
+
+    KeepFieldAccessPattern.Builder builder;
+
+    public FieldAccessVisitor(String annotationName, KeepFieldAccessPattern.Builder builder) {
+      super(annotationName, builder);
+      this.builder = builder;
+    }
+
+    @Override
+    public void visitEnum(String ignore, String descriptor, String value) {
+      if (!descriptor.equals(AnnotationConstants.FieldAccess.DESCRIPTOR)) {
+        super.visitEnum(ignore, descriptor, value);
+      }
+      boolean handled =
+          withNormalizedAccessFlag(
+              value,
+              (flag, allow) -> {
+                switch (flag) {
+                  case FieldAccess.VOLATILE:
+                    builder.setVolatile(allow);
+                    return true;
+                  case FieldAccess.TRANSIENT:
+                    builder.setTransient(allow);
+                    return true;
+                  default:
+                    return false;
+                }
+              });
+      if (!handled) {
+        // Continue visitation with the "member" descriptor to allow matching the common values.
+        super.visitEnum(ignore, MemberAccess.DESCRIPTOR, value);
+      }
+    }
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AccessVisibility.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AccessVisibility.java
new file mode 100644
index 0000000..690bae6
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AccessVisibility.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.ast;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import java.util.HashSet;
+import java.util.Set;
+
+public enum AccessVisibility {
+  PUBLIC,
+  PROTECTED,
+  PACKAGE_PRIVATE,
+  PRIVATE;
+
+  private static final ImmutableSet<AccessVisibility> ALL = ImmutableSortedSet.copyOf(values());
+
+  public String toSourceSyntax() {
+    switch (this) {
+      case PUBLIC:
+        return "public";
+      case PROTECTED:
+        return "protected";
+      case PACKAGE_PRIVATE:
+        throw new KeepEdgeException("No source syntax for package-private visibility.");
+      case PRIVATE:
+        return "private";
+      default:
+        throw new KeepEdgeException("Unexpected access visibility: " + this);
+    }
+  }
+
+  public static boolean containsAll(Set<AccessVisibility> visibilities) {
+    return visibilities.size() == AccessVisibility.values().length;
+  }
+
+  public static Set<AccessVisibility> createSet() {
+    return new HashSet<>();
+  }
+
+  public static Set<AccessVisibility> all() {
+    return ALL;
+  }
+}
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 68cd0df..0daeda1 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
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+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.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;
 
 /**
  * Utility class for referencing the various keep annotations and their structure.
@@ -19,13 +23,17 @@
 public final class AnnotationConstants {
 
   public static String getDescriptor(Class<?> clazz) {
-    return "L" + clazz.getTypeName().replace('.', '/') + ";";
+    return getDescriptorFromClassTypeName(clazz.getTypeName());
   }
 
   public static String getBinaryNameFromClassTypeName(String classTypeName) {
     return classTypeName.replace('.', '/');
   }
 
+  public static String getDescriptorFromClassTypeName(String classTypeName) {
+    return "L" + getBinaryNameFromClassTypeName(classTypeName) + ";";
+  }
+
   public static boolean isKeepAnnotation(String descriptor, boolean visible) {
     if (visible) {
       return false;
@@ -45,6 +53,14 @@
     public static final String consequences = "consequences";
   }
 
+  public static final class ForApi {
+    public static final Class<KeepForApi> CLASS = KeepForApi.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+    public static final String description = "description";
+    public static final String additionalTargets = "additionalTargets";
+    public static final String memberAccess = "memberAccess";
+  }
+
   public static final class UsesReflection {
     public static final Class<com.android.tools.r8.keepanno.annotations.UsesReflection> CLASS =
         com.android.tools.r8.keepanno.annotations.UsesReflection.class;
@@ -65,10 +81,14 @@
     public static final String extendsClassName = "extendsClassName";
     public static final String extendsClassConstant = "extendsClassConstant";
 
+    public static final String memberAccess = "memberAccess";
+
+    public static final String methodAccess = "methodAccess";
     public static final String methodName = "methodName";
     public static final String methodReturnType = "methodReturnType";
     public static final String methodParameters = "methodParameters";
 
+    public static final String fieldAccess = "fieldAccess";
     public static final String fieldName = "fieldName";
     public static final String fieldType = "fieldType";
 
@@ -129,4 +149,39 @@
     public static final String ACCESS_MODIFICATION = "ACCESS_MODIFICATION";
     public static final String ANNOTATION_REMOVAL = "ANNOTATION_REMOVAL";
   }
+
+  public static final class MemberAccess {
+    public static final Class<MemberAccessFlags> CLASS = MemberAccessFlags.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+
+    public static final String NEGATION_PREFIX = "NON_";
+
+    public static final String PUBLIC = "PUBLIC";
+    public static final String PROTECTED = "PROTECTED";
+    public static final String PACKAGE_PRIVATE = "PACKAGE_PRIVATE";
+    public static final String PRIVATE = "PRIVATE";
+
+    public static final String STATIC = "STATIC";
+    public static final String FINAL = "FINAL";
+    public static final String SYNTHETIC = "SYNTHETIC";
+  }
+
+  public static final class MethodAccess {
+    public static final Class<MethodAccessFlags> CLASS = MethodAccessFlags.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+
+    public static final String SYNCHRONIZED = "SYNCHRONIZED";
+    public static final String BRIDGE = "BRIDGE";
+    public static final String NATIVE = "NATIVE";
+    public static final String ABSTRACT = "ABSTRACT";
+    public static final String STRICT_FP = "STRICT_FP";
+  }
+
+  public static final class FieldAccess {
+    public static final Class<FieldAccessFlags> CLASS = FieldAccessFlags.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+
+    public static final String VOLATILE = "VOLATILE";
+    public static final String TRANSIENT = "TRANSIENT";
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index f1ed988..dd63480 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -70,10 +70,19 @@
  *           METHOD_NAME_PATTERN
  *           METHOD_PARAMETERS_PATTERN
  *
- *   METHOD_ACCESS_PATTERN ::= any
+ *   FIELD_ACCESS_PATTERN ::= any | FIELD_ACCESS_FLAG* | (!FIELD_ACCESS_FLAG)*
+ *   FIELD_ACCESS_FLAG ::= MEMBER_ACCESS_FLAG | volatile | transient
+ *
+ *   METHOD_ACCESS_PATTERN ::= any | METHOD_ACCESS_FLAG* | (!METHOD_ACCESS_FLAG)*
  *   METHOD_NAME_PATTERN ::= any | exact method-name
  *   METHOD_RETURN_TYPE_PATTERN ::= void | TYPE_PATTERN
  *   METHOD_PARAMETERS_PATTERN ::= any | none | (TYPE_PATTERN+)
+ *   METHOD_ACCESS_FLAG
+ *     ::= MEMBER_ACCESS_FLAG
+ *       | synchronized | bridge | native | abstract | strict-fp
+ *
+ *   MEMBER_ACCESS_FLAG
+ *     ::= public | protected | package-private | private | static | final | synthetic
  * </pre>
  */
 public final class KeepEdge {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
index 056430b..55c15c2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
@@ -3,41 +3,108 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
-// TODO: finish this.
-public abstract class KeepFieldAccessPattern {
+import com.android.tools.r8.keepanno.keeprules.RulePrinter;
+import com.android.tools.r8.keepanno.keeprules.RulePrintingUtils;
+import java.util.Set;
 
-  public static KeepFieldAccessPattern any() {
-    return Any.getInstance();
+public class KeepFieldAccessPattern extends KeepMemberAccessPattern {
+
+  private static final KeepFieldAccessPattern ANY =
+      new KeepFieldAccessPattern(
+          AccessVisibility.all(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any());
+
+  public static KeepFieldAccessPattern anyFieldAccess() {
+    return ANY;
   }
 
-  public abstract boolean isAny();
+  public static Builder builder() {
+    return new Builder();
+  }
 
-  private static class Any extends KeepFieldAccessPattern {
+  private final ModifierPattern volatilePattern;
+  private final ModifierPattern transientPattern;
 
-    private static final Any INSTANCE = new Any();
+  public KeepFieldAccessPattern(
+      Set<AccessVisibility> allowedVisibilities,
+      ModifierPattern staticPattern,
+      ModifierPattern finalPattern,
+      ModifierPattern volatilePattern,
+      ModifierPattern transientPattern,
+      ModifierPattern syntheticPattern) {
+    super(allowedVisibilities, staticPattern, finalPattern, syntheticPattern);
+    this.volatilePattern = volatilePattern;
+    this.transientPattern = transientPattern;
+  }
 
-    private static Any getInstance() {
-      return INSTANCE;
-    }
-
-    @Override
-    public boolean isAny() {
-      return true;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      return this == obj;
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public String toString() {
+  @Override
+  public String toString() {
+    if (isAny()) {
       return "*";
     }
+    StringBuilder builder = new StringBuilder();
+    RulePrintingUtils.printFieldAccess(RulePrinter.withoutBackReferences(builder), this);
+    return builder.toString();
+  }
+
+  @Override
+  public boolean isAny() {
+    return super.isAny() && volatilePattern.isAny() && transientPattern.isAny();
+  }
+
+  public ModifierPattern getVolatilePattern() {
+    return volatilePattern;
+  }
+
+  public ModifierPattern getTransientPattern() {
+    return transientPattern;
+  }
+
+  public static class Builder extends BuilderBase<KeepFieldAccessPattern, Builder> {
+
+    private ModifierPattern volatilePattern = ModifierPattern.any();
+    private ModifierPattern transientPattern = ModifierPattern.any();
+
+    private Builder() {}
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public KeepFieldAccessPattern build() {
+      KeepFieldAccessPattern pattern =
+          new KeepFieldAccessPattern(
+              getAllowedVisibilities(),
+              getStaticPattern(),
+              getFinalPattern(),
+              getVolatilePattern(),
+              getTransientPattern(),
+              getSyntheticPattern());
+      return pattern.isAny() ? anyFieldAccess() : pattern;
+    }
+
+    public Builder setVolatile(boolean allow) {
+      volatilePattern = ModifierPattern.fromAllowValue(allow);
+      return self();
+    }
+
+    public Builder setTransient(boolean allow) {
+      transientPattern = ModifierPattern.fromAllowValue(allow);
+      return self();
+    }
+
+    public ModifierPattern getVolatilePattern() {
+      return volatilePattern;
+    }
+
+    public ModifierPattern getTransientPattern() {
+      return transientPattern;
+    }
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
index dd104eb..23e27cc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -17,7 +17,7 @@
 
   public static class Builder {
 
-    private KeepFieldAccessPattern accessPattern = KeepFieldAccessPattern.any();
+    private KeepFieldAccessPattern accessPattern = KeepFieldAccessPattern.anyFieldAccess();
     private KeepFieldNamePattern namePattern = KeepFieldNamePattern.any();
     private KeepFieldTypePattern typePattern = KeepFieldTypePattern.any();
 
@@ -72,6 +72,7 @@
     return accessPattern.isAny() && namePattern.isAny() && typePattern.isAny();
   }
 
+  @Override
   public KeepFieldAccessPattern getAccessPattern() {
     return accessPattern;
   }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberAccessPattern.java
new file mode 100644
index 0000000..8b18105
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberAccessPattern.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.ast;
+
+import com.android.tools.r8.keepanno.keeprules.RulePrinter;
+import com.android.tools.r8.keepanno.keeprules.RulePrintingUtils;
+import java.util.Set;
+
+public class KeepMemberAccessPattern {
+
+  private static final KeepMemberAccessPattern ANY =
+      new KeepMemberAccessPattern(
+          AccessVisibility.all(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any());
+
+  public static KeepMemberAccessPattern anyMemberAccess() {
+    return ANY;
+  }
+
+  public static Builder memberBuilder() {
+    return new Builder();
+  }
+
+  private final Set<AccessVisibility> allowedVisibilities;
+  private final ModifierPattern staticPattern;
+  private final ModifierPattern finalPattern;
+  private final ModifierPattern syntheticPattern;
+
+  public KeepMemberAccessPattern(
+      Set<AccessVisibility> allowedVisibilities,
+      ModifierPattern staticPattern,
+      ModifierPattern finalPattern,
+      ModifierPattern syntheticPattern) {
+    this.allowedVisibilities = allowedVisibilities;
+    this.staticPattern = staticPattern;
+    this.finalPattern = finalPattern;
+    this.syntheticPattern = syntheticPattern;
+  }
+
+  @Override
+  public String toString() {
+    if (isAny()) {
+      return "*";
+    }
+    StringBuilder builder = new StringBuilder();
+    RulePrinter printer = RulePrinter.withoutBackReferences(builder);
+    RulePrintingUtils.printMemberAccess(printer, this);
+    return builder.toString();
+  }
+
+  /** True if this matches any possible access flag. */
+  public boolean isAny() {
+    return isAnyVisibility()
+        && staticPattern.isAny()
+        && finalPattern.isAny()
+        && syntheticPattern.isAny();
+  }
+
+  public boolean isAnyVisibility() {
+    return AccessVisibility.containsAll(allowedVisibilities);
+  }
+
+  public boolean isVisibilityAllowed(AccessVisibility visibility) {
+    return allowedVisibilities.contains(visibility);
+  }
+
+  public Set<AccessVisibility> getAllowedAccessVisibilities() {
+    return allowedVisibilities;
+  }
+
+  public ModifierPattern getStaticPattern() {
+    return staticPattern;
+  }
+
+  public ModifierPattern getFinalPattern() {
+    return finalPattern;
+  }
+
+  public ModifierPattern getSyntheticPattern() {
+    return syntheticPattern;
+  }
+
+  public static class Builder extends BuilderBase<KeepMemberAccessPattern, Builder> {
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public KeepMemberAccessPattern build() {
+      Set<AccessVisibility> allowedVisibilities = getAllowedVisibilities();
+      ModifierPattern staticPattern = getStaticPattern();
+      ModifierPattern finalPattern = getFinalPattern();
+      ModifierPattern syntheticPattern = getSyntheticPattern();
+      if (AccessVisibility.containsAll(allowedVisibilities)
+          && staticPattern.isAny()
+          && finalPattern.isAny()
+          && syntheticPattern.isAny()) {
+        return KeepMemberAccessPattern.anyMemberAccess();
+      }
+      KeepMemberAccessPattern pattern =
+          new KeepMemberAccessPattern(
+              allowedVisibilities, staticPattern, finalPattern, syntheticPattern);
+      assert !pattern.isAny();
+      return pattern;
+    }
+  }
+
+  public abstract static class BuilderBase<
+      M extends KeepMemberAccessPattern, B extends BuilderBase<M, B>> {
+    private final Set<AccessVisibility> allowed = AccessVisibility.createSet();
+    private final Set<AccessVisibility> disallowed = AccessVisibility.createSet();
+    private ModifierPattern staticPattern = ModifierPattern.any();
+    private ModifierPattern finalPattern = ModifierPattern.any();
+    private ModifierPattern syntheticPattern = ModifierPattern.any();
+
+    BuilderBase() {}
+
+    public abstract B self();
+
+    public abstract M build();
+
+    public B copyOfMemberAccess(KeepMemberAccessPattern accessPattern) {
+      allowed.clear();
+      disallowed.clear();
+      allowed.addAll(accessPattern.getAllowedAccessVisibilities());
+      staticPattern = accessPattern.getStaticPattern();
+      finalPattern = accessPattern.getFinalPattern();
+      return self();
+    }
+
+    public ModifierPattern getStaticPattern() {
+      return staticPattern;
+    }
+
+    public ModifierPattern getFinalPattern() {
+      return finalPattern;
+    }
+
+    public ModifierPattern getSyntheticPattern() {
+      return syntheticPattern;
+    }
+
+    public Set<AccessVisibility> getAllowedVisibilities() {
+      // Fast path for any visibility pattern.
+      if (allowed.isEmpty() && disallowed.isEmpty()) {
+        return AccessVisibility.all();
+      }
+      // If no explict "allows" have been set, all visibilities are allowed.
+      Set<AccessVisibility> result = AccessVisibility.createSet();
+      if (allowed.isEmpty()) {
+        result.addAll(AccessVisibility.all());
+      } else {
+        result.addAll(allowed);
+      }
+      // Any explict disallow narrows the allowed visibilities.
+      result.removeAll(disallowed);
+      if (result.isEmpty()) {
+        throw new KeepEdgeException("Empty access visibility pattern will never match a member");
+      }
+      return result;
+    }
+
+    public B setAccessVisibility(AccessVisibility visibility, boolean allow) {
+      Set<AccessVisibility> set = allow ? allowed : disallowed;
+      set.add(visibility);
+      return self();
+    }
+
+    public B setStatic(boolean allow) {
+      staticPattern = ModifierPattern.fromAllowValue(allow);
+      return self();
+    }
+
+    public B setFinal(boolean allow) {
+      finalPattern = ModifierPattern.fromAllowValue(allow);
+      return self();
+    }
+
+    public B setSynthetic(boolean allow) {
+      syntheticPattern = ModifierPattern.fromAllowValue(allow);
+      return self();
+    }
+  }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
index d994d84..941a325 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -13,6 +13,61 @@
     return All.getInstance();
   }
 
+  public static Builder memberBuilder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private KeepMemberAccessPattern accessPattern = KeepMemberAccessPattern.anyMemberAccess();
+
+    public Builder setAccessPattern(KeepMemberAccessPattern accessPattern) {
+      this.accessPattern = accessPattern;
+      return this;
+    }
+
+    public KeepMemberPattern build() {
+      if (accessPattern.isAny()) {
+        return allMembers();
+      }
+      return new Some(accessPattern);
+    }
+  }
+
+  private static class Some extends KeepMemberPattern {
+    private final KeepMemberAccessPattern accessPattern;
+
+    public Some(KeepMemberAccessPattern accessPattern) {
+      this.accessPattern = accessPattern;
+    }
+
+    @Override
+    public KeepMemberAccessPattern getAccessPattern() {
+      return accessPattern;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Some some = (Some) o;
+      return accessPattern.equals(some.accessPattern);
+    }
+
+    @Override
+    public int hashCode() {
+      return accessPattern.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return "Member{" + "access=" + accessPattern + '}';
+    }
+  }
+
   private static class All extends KeepMemberPattern {
 
     private static final All INSTANCE = new All();
@@ -27,6 +82,11 @@
     }
 
     @Override
+    public KeepMemberAccessPattern getAccessPattern() {
+      return KeepMemberAccessPattern.anyMemberAccess();
+    }
+
+    @Override
     public boolean equals(Object obj) {
       return this == obj;
     }
@@ -81,6 +141,10 @@
     return false;
   }
 
+  public final boolean isGeneralMember() {
+    return !isNone() && !isMethod() && !isField();
+  }
+
   public final boolean isMethod() {
     return asMethod() != null;
   }
@@ -96,4 +160,8 @@
   public KeepFieldPattern asField() {
     return null;
   }
+
+  public KeepMemberAccessPattern getAccessPattern() {
+    throw new KeepEdgeException("Invalid access to member access pattern");
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java
index 4417b2e..c8f83b2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java
@@ -3,42 +3,169 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+import com.android.tools.r8.keepanno.keeprules.RulePrinter;
+import com.android.tools.r8.keepanno.keeprules.RulePrintingUtils;
+import java.util.Set;
 
-// TODO: finish this.
-public abstract class KeepMethodAccessPattern {
+public class KeepMethodAccessPattern extends KeepMemberAccessPattern {
 
-  public static KeepMethodAccessPattern any() {
-    return Any.getInstance();
+  private static final KeepMethodAccessPattern ANY =
+      new KeepMethodAccessPattern(
+          AccessVisibility.all(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any(),
+          ModifierPattern.any());
+
+  public static KeepMethodAccessPattern anyMethodAccess() {
+    return ANY;
   }
 
-  public abstract boolean isAny();
+  public static Builder builder() {
+    return new Builder();
+  }
 
-  private static class Any extends KeepMethodAccessPattern {
+  private final ModifierPattern synchronizedPattern;
+  private final ModifierPattern bridgePattern;
+  private final ModifierPattern nativePattern;
+  private final ModifierPattern abstractPattern;
+  private final ModifierPattern strictFpPattern;
 
-    private static final Any INSTANCE = new Any();
+  public KeepMethodAccessPattern(
+      Set<AccessVisibility> allowedVisibilities,
+      ModifierPattern staticPattern,
+      ModifierPattern finalPattern,
+      ModifierPattern synchronizedPattern,
+      ModifierPattern bridgePattern,
+      ModifierPattern nativePattern,
+      ModifierPattern abstractPattern,
+      ModifierPattern syntheticPattern,
+      ModifierPattern strictFpPattern) {
+    super(allowedVisibilities, staticPattern, finalPattern, syntheticPattern);
+    this.synchronizedPattern = synchronizedPattern;
+    this.bridgePattern = bridgePattern;
+    this.nativePattern = nativePattern;
+    this.abstractPattern = abstractPattern;
+    this.strictFpPattern = strictFpPattern;
+  }
 
-    private static Any getInstance() {
-      return INSTANCE;
-    }
-
-    @Override
-    public boolean isAny() {
-      return true;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      return this == obj;
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public String toString() {
+  @Override
+  public String toString() {
+    if (isAny()) {
       return "*";
     }
+    StringBuilder builder = new StringBuilder();
+    RulePrintingUtils.printMethodAccess(RulePrinter.withoutBackReferences(builder), this);
+    return builder.toString();
+  }
+
+  @Override
+  public boolean isAny() {
+    return super.isAny()
+        && synchronizedPattern.isAny()
+        && bridgePattern.isAny()
+        && nativePattern.isAny()
+        && abstractPattern.isAny()
+        && strictFpPattern.isAny();
+  }
+
+  public ModifierPattern getSynchronizedPattern() {
+    return synchronizedPattern;
+  }
+
+  public ModifierPattern getBridgePattern() {
+    return bridgePattern;
+  }
+
+  public ModifierPattern getNativePattern() {
+    return nativePattern;
+  }
+
+  public ModifierPattern getAbstractPattern() {
+    return abstractPattern;
+  }
+
+  public ModifierPattern getStrictFpPattern() {
+    return strictFpPattern;
+  }
+
+  public static class Builder extends BuilderBase<KeepMethodAccessPattern, Builder> {
+    private ModifierPattern synchronizedPattern = ModifierPattern.any();
+    private ModifierPattern bridgePattern = ModifierPattern.any();
+    private ModifierPattern nativePattern = ModifierPattern.any();
+    private ModifierPattern abstractPattern = ModifierPattern.any();
+    private ModifierPattern strictFpPattern = ModifierPattern.any();
+
+    private Builder() {}
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public KeepMethodAccessPattern build() {
+      KeepMethodAccessPattern pattern =
+          new KeepMethodAccessPattern(
+              getAllowedVisibilities(),
+              getStaticPattern(),
+              getFinalPattern(),
+              getSynchronizedPattern(),
+              getBridgePattern(),
+              getNativePattern(),
+              getAbstractPattern(),
+              getSyntheticPattern(),
+              getStrictFpPattern());
+      return pattern.isAny() ? anyMethodAccess() : pattern;
+    }
+
+    public Builder setSynchronized(boolean allow) {
+      synchronizedPattern = ModifierPattern.fromAllowValue(allow);
+      return this;
+    }
+
+    public Builder setBridge(boolean allow) {
+      bridgePattern = ModifierPattern.fromAllowValue(allow);
+      return this;
+    }
+
+    public Builder setNative(boolean allow) {
+      nativePattern = ModifierPattern.fromAllowValue(allow);
+      return this;
+    }
+
+    public Builder setAbstract(boolean allow) {
+      abstractPattern = ModifierPattern.fromAllowValue(allow);
+      return this;
+    }
+
+    public Builder setStrictFp(boolean allow) {
+      strictFpPattern = ModifierPattern.fromAllowValue(allow);
+      return this;
+    }
+
+    public ModifierPattern getSynchronizedPattern() {
+      return synchronizedPattern;
+    }
+
+    public ModifierPattern getBridgePattern() {
+      return bridgePattern;
+    }
+
+    public ModifierPattern getNativePattern() {
+      return nativePattern;
+    }
+
+    public ModifierPattern getAbstractPattern() {
+      return abstractPattern;
+    }
+
+    public ModifierPattern getStrictFpPattern() {
+      return strictFpPattern;
+    }
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
index 4104392..a7a4977 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -12,13 +12,13 @@
     return new Builder();
   }
 
-  public static KeepMemberPattern allMethods() {
+  public static KeepMethodPattern allMethods() {
     return builder().build();
   }
 
   public static class Builder {
 
-    private KeepMethodAccessPattern accessPattern = KeepMethodAccessPattern.any();
+    private KeepMethodAccessPattern accessPattern = KeepMethodAccessPattern.anyMethodAccess();
     private KeepMethodNamePattern namePattern = KeepMethodNamePattern.any();
     private KeepMethodReturnTypePattern returnTypePattern = KeepMethodReturnTypePattern.any();
     private KeepMethodParametersPattern parametersPattern = KeepMethodParametersPattern.any();
@@ -100,6 +100,7 @@
         && parametersPattern.isAny();
   }
 
+  @Override
   public KeepMethodAccessPattern getAccessPattern() {
     return accessPattern;
   }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ModifierPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ModifierPattern.java
new file mode 100644
index 0000000..77ce8ad
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ModifierPattern.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.ast;
+
+/** Three-point valued matcher on an access modifier. */
+public class ModifierPattern {
+
+  private static final ModifierPattern ANY =
+      new ModifierPattern() {
+        @Override
+        public boolean isAny() {
+          return true;
+        }
+      };
+
+  private static final ModifierPattern POSITIVE =
+      new ModifierPattern() {
+        @Override
+        public boolean isOnlyPositive() {
+          return true;
+        }
+      };
+
+  private static final ModifierPattern NEGATIVE =
+      new ModifierPattern() {
+        @Override
+        public boolean isOnlyNegative() {
+          return true;
+        }
+      };
+
+  public static ModifierPattern fromAllowValue(boolean allow) {
+    return allow ? onlyPositive() : onlyNegative();
+  }
+
+  public static ModifierPattern any() {
+    return ANY;
+  }
+
+  public static ModifierPattern onlyPositive() {
+    return POSITIVE;
+  }
+
+  public static ModifierPattern onlyNegative() {
+    return NEGATIVE;
+  }
+
+  private ModifierPattern() {}
+
+  public boolean isAny() {
+    return false;
+  }
+
+  public boolean isOnlyPositive() {
+    return false;
+  }
+
+  public boolean isOnlyNegative() {
+    return false;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
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 ee80978..7d70e08 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
@@ -8,10 +8,12 @@
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
 import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
+import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemReference;
 import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepOptions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
@@ -349,12 +351,12 @@
           List<String> nonAllMemberTargets = new ArrayList<>(targetMembers.size());
           for (String targetMember : targetMembers) {
             KeepMemberPattern memberPattern = memberPatterns.get(targetMember);
-            if (memberPattern.isAllMembers() && conditionMembers.contains(targetMember)) {
-              // This pattern is a pattern for "any member" and it is bound by a condition.
+            if (memberPattern.isGeneralMember() && conditionMembers.contains(targetMember)) {
+              // This pattern is on "members in general" and it is bound by a condition.
               // Since backrefs can't reference a *-member we split this target in two, one for
               // fields and one for methods.
               HashMap<String, KeepMemberPattern> copyWithMethod = new HashMap<>(memberPatterns);
-              copyWithMethod.put(targetMember, KeepMethodPattern.allMethods());
+              copyWithMethod.put(targetMember, copyMethodFromMember(memberPattern));
               rules.add(
                   new PgDependentMembersRule(
                       metaInfo,
@@ -365,7 +367,7 @@
                       Collections.singletonList(targetMember),
                       targetKeepKind));
               HashMap<String, KeepMemberPattern> copyWithField = new HashMap<>(memberPatterns);
-              copyWithField.put(targetMember, KeepFieldPattern.allFields());
+              copyWithField.put(targetMember, copyFieldFromMember(memberPattern));
               rules.add(
                   new PgDependentMembersRule(
                       metaInfo,
@@ -394,6 +396,18 @@
         });
   }
 
+  private static KeepMethodPattern copyMethodFromMember(KeepMemberPattern pattern) {
+    KeepMethodAccessPattern accessPattern =
+        KeepMethodAccessPattern.builder().copyOfMemberAccess(pattern.getAccessPattern()).build();
+    return KeepMethodPattern.builder().setAccessPattern(accessPattern).build();
+  }
+
+  private static KeepFieldPattern copyFieldFromMember(KeepMemberPattern pattern) {
+    KeepFieldAccessPattern accessPattern =
+        KeepFieldAccessPattern.builder().copyOfMemberAccess(pattern.getAccessPattern()).build();
+    return KeepFieldPattern.builder().setAccessPattern(accessPattern).build();
+  }
+
   private static KeepQualifiedClassNamePattern getClassNamePattern(
       KeepItemPattern itemPattern, KeepBindings bindings) {
     return itemPattern.getClassReference().isClassNamePattern()
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 6f08712..8084164 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.keeprules;
 
+import com.android.tools.r8.keepanno.ast.AccessVisibility;
 import com.android.tools.r8.keepanno.ast.KeepClassReference;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
 import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
@@ -11,6 +12,7 @@
 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.KeepMemberAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
@@ -23,8 +25,10 @@
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTypePattern;
 import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ModifierPattern;
 import com.android.tools.r8.keepanno.utils.Unimplemented;
 import java.util.List;
+import java.util.Set;
 import java.util.function.BiConsumer;
 
 public abstract class RulePrintingUtils {
@@ -110,11 +114,15 @@
     if (member.isField()) {
       return printField(member.asField(), printer);
     }
-    throw new Unimplemented();
+    // 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 just the access pattern.
+    assert !member.getAccessPattern().isAny();
+    printMemberAccess(printer, member.getAccessPattern());
+    return printer.appendWithoutBackReferenceAssert("*").append(";");
   }
 
   private static RulePrinter printField(KeepFieldPattern fieldPattern, RulePrinter builder) {
-    printFieldAccess(builder, " ", fieldPattern.getAccessPattern());
+    printFieldAccess(builder, fieldPattern.getAccessPattern());
     printType(builder, fieldPattern.getTypePattern().asType());
     builder.append(" ");
     printFieldName(builder, fieldPattern.getNamePattern());
@@ -122,7 +130,7 @@
   }
 
   private static RulePrinter printMethod(KeepMethodPattern methodPattern, RulePrinter builder) {
-    printMethodAccess(builder, " ", methodPattern.getAccessPattern());
+    printMethodAccess(builder, methodPattern.getAccessPattern());
     printReturnType(builder, methodPattern.getReturnTypePattern());
     builder.append(" ");
     printMethodName(builder, methodPattern.getNamePattern());
@@ -174,24 +182,69 @@
     return builder.append(descriptorToJavaType(typePattern.getDescriptor()));
   }
 
-  private static RulePrinter printMethodAccess(
-      RulePrinter builder, String indent, KeepMethodAccessPattern accessPattern) {
+  public static RulePrinter printMemberAccess(
+      RulePrinter printer, KeepMemberAccessPattern accessPattern) {
     if (accessPattern.isAny()) {
       // No text will match any access pattern.
       // Don't print the indent in this case.
-      return builder;
+      return printer;
     }
-    throw new Unimplemented();
+    printVisibilityModifiers(printer, accessPattern);
+    printModifier(printer, accessPattern.getStaticPattern(), "static");
+    printModifier(printer, accessPattern.getFinalPattern(), "final");
+    printModifier(printer, accessPattern.getSyntheticPattern(), "synthetic");
+    return printer;
   }
 
-  private static RulePrinter printFieldAccess(
-      RulePrinter builder, String indent, KeepFieldAccessPattern accessPattern) {
-    if (accessPattern.isAny()) {
-      // No text will match any access pattern.
-      // Don't print the indent in this case.
-      return builder;
+  public static void printVisibilityModifiers(
+      RulePrinter printer, KeepMemberAccessPattern accessPattern) {
+    if (accessPattern.isAnyVisibility()) {
+      return;
     }
-    throw new Unimplemented();
+    Set<AccessVisibility> allowed = accessPattern.getAllowedAccessVisibilities();
+    // Package private does not have an actual representation it must be matched by its absence.
+    // Thus, in the case of package-private the match is the negation of those not-present.
+    boolean negated = allowed.contains(AccessVisibility.PACKAGE_PRIVATE);
+    for (AccessVisibility visibility : AccessVisibility.values()) {
+      if (!visibility.equals(AccessVisibility.PACKAGE_PRIVATE)) {
+        if (!negated == allowed.contains(visibility)) {
+          if (negated) {
+            printer.append("!");
+          }
+          printer.append(visibility.toSourceSyntax()).append(" ");
+        }
+      }
+    }
+  }
+
+  public static void printModifier(
+      RulePrinter printer, ModifierPattern modifierPattern, String syntax) {
+    if (modifierPattern.isAny()) {
+      return;
+    }
+    if (modifierPattern.isOnlyNegative()) {
+      printer.append("!");
+    }
+    printer.append(syntax).append(" ");
+  }
+
+  public static RulePrinter printMethodAccess(
+      RulePrinter printer, KeepMethodAccessPattern accessPattern) {
+    printMemberAccess(printer, accessPattern);
+    printModifier(printer, accessPattern.getSynchronizedPattern(), "synchronized");
+    printModifier(printer, accessPattern.getBridgePattern(), "bridge");
+    printModifier(printer, accessPattern.getNativePattern(), "native");
+    printModifier(printer, accessPattern.getAbstractPattern(), "abstract");
+    printModifier(printer, accessPattern.getStrictFpPattern(), "strictfp");
+    return printer;
+  }
+
+  public static RulePrinter printFieldAccess(
+      RulePrinter printer, KeepFieldAccessPattern accessPattern) {
+    printMemberAccess(printer, accessPattern);
+    RulePrintingUtils.printModifier(printer, accessPattern.getVolatilePattern(), "volatile");
+    RulePrintingUtils.printModifier(printer, accessPattern.getTransientPattern(), "transient");
+    return printer;
   }
 
   public static RulePrinter printClassName(
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index a233e49..4d1269c 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.ProgramConsumerUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.nio.file.Path;
@@ -147,6 +148,7 @@
 
   void dumpBaseCommandOptions(DumpOptions.Builder builder) {
     builder
+        .setBackend(ProgramConsumerUtils.getBackend(programConsumer))
         .setCompilationMode(getMode())
         .setMinApi(getMinApiLevel())
         .setOptimizeMultidexForLinearAlloc(isOptimizeMultidexForLinearAlloc())
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 7508aa1..55438d2 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -201,7 +201,7 @@
       System.out.println("D8 is running with free memory:" + runtime.freeMemory());
       System.out.println("D8 is running with max memory:" + runtime.maxMemory());
     }
-    Timing timing = Timing.create("D8", options);
+    Timing timing = Timing.create("D8 " + Version.LABEL, options);
     try {
       timing.begin("Pre conversion");
       // Synthetic assertion to check that testing assertions works and can be enabled.
@@ -436,7 +436,7 @@
     return finalDexApp.build();
   }
 
-  static class ConvertedCfFiles implements DexIndexedConsumer, ProgramResourceProvider {
+  public static class ConvertedCfFiles implements DexIndexedConsumer, ProgramResourceProvider {
 
     private final List<ProgramResource> resources = new ArrayList<>();
 
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 7d1c9bd..3c17174 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.MainDexInfo;
@@ -65,8 +66,12 @@
 
     SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
 
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.nop();
     MainDexRootSet mainDexRootSet =
-        MainDexRootSet.builder(appView, subtypingInfo, options.mainDexKeepRules).build(executor);
+        MainDexRootSet.builder(
+                appView, artProfileCollectionAdditions, subtypingInfo, options.mainDexKeepRules)
+            .build(executor);
     appView.setMainDexRootSet(mainDexRootSet);
 
     GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 97467f1..fc7bb85 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -163,7 +163,7 @@
     if (options.printMemory) {
       System.gc();
     }
-    timing = Timing.create("R8", options);
+    timing = Timing.create("R8 " + Version.LABEL, options);
   }
 
   /**
@@ -303,13 +303,11 @@
           appView.dexItemFactory());
 
       // Upfront desugaring generation: Generates new program classes to be added in the app.
-      ArtProfileCollectionAdditions artProfileCollectionAdditions =
-          ArtProfileCollectionAdditions.create(appView);
       CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer =
-          CfClassSynthesizerDesugaringEventConsumer.create(artProfileCollectionAdditions);
+          CfClassSynthesizerDesugaringEventConsumer.createForR8(appView);
       CfClassSynthesizerDesugaringCollection.create(appView)
           .synthesizeClasses(executorService, classSynthesizerEventConsumer);
-      artProfileCollectionAdditions.commit(appView);
+      classSynthesizerEventConsumer.finished(appView);
       if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
         appView.setAppInfo(
             appView
@@ -333,11 +331,14 @@
                     options.itemFactory, options.getMinApiLevel()));
           }
         }
+        ArtProfileCollectionAdditions artProfileCollectionAdditions =
+            ArtProfileCollectionAdditions.create(appView);
         AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
         SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
         appView.setRootSet(
             RootSet.builder(
                     appView,
+                    artProfileCollectionAdditions,
                     subtypingInfo,
                     Iterables.concat(
                         options.getProguardConfiguration().getRules(), synthesizedProguardRules))
@@ -351,7 +352,11 @@
           assert appView.graphLens().isIdentityLens();
           // Find classes which may have code executed before secondary dex files installation.
           MainDexRootSet mainDexRootSet =
-              MainDexRootSet.builder(appView, subtypingInfo, options.mainDexKeepRules)
+              MainDexRootSet.builder(
+                      appView,
+                      artProfileCollectionAdditions,
+                      subtypingInfo,
+                      options.mainDexKeepRules)
                   .build(executorService);
           appView.setMainDexRootSet(mainDexRootSet);
           appView.appInfo().unsetObsolete();
@@ -366,6 +371,7 @@
                 annotationRemoverBuilder,
                 executorService,
                 appView,
+                artProfileCollectionAdditions,
                 subtypingInfo,
                 classMergingEnqueuerExtensionBuilder);
         timing.end();
@@ -473,7 +479,7 @@
       // should therefore be run after the publicizer.
       new NestReducer(appViewWithLiveness).run(executorService, timing);
 
-      appView.setGraphLens(new MemberRebindingAnalysis(appViewWithLiveness).run(executorService));
+      new MemberRebindingAnalysis(appViewWithLiveness).run(executorService);
       appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView);
 
       boolean isKotlinLibraryCompilationWithInlinePassThrough =
@@ -961,12 +967,14 @@
       AnnotationRemover.Builder annotationRemoverBuilder,
       ExecutorService executorService,
       AppView<AppInfoWithClassHierarchy> appView,
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
       SubtypingInfo subtypingInfo,
       RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder)
       throws ExecutionException {
     timing.begin("Set up enqueuer");
     Enqueuer enqueuer =
-        EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
+        EnqueuerFactory.createForInitialTreeShaking(
+            appView, artProfileCollectionAdditions, executorService, subtypingInfo);
     enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
@@ -986,6 +994,7 @@
     timing.begin("Trace application");
     EnqueuerResult enqueuerResult =
         enqueuer.traceApplication(appView.rootSet(), executorService, timing);
+    assert artProfileCollectionAdditions.verifyIsCommitted();
     timing.end();
     timing.begin("Finalize enqueuer result");
     AppView<AppInfoWithLiveness> appViewWithLiveness =
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index b4cca13..ee35e7f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -700,15 +700,17 @@
                               Version.getPatchVersion())
                           : fakeCompilerVersion;
                   if (compilerVersion.getMajor() < 0) {
-                    compilerVersion = SemanticVersion.parse(Version.ACTIVE_DEV_VERSION);
+                    compilerVersion =
+                        SemanticVersion.create(
+                            Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
                     reporter.warning(
                         "Running R8 version "
                             + Version.getVersionString()
-                            + " which cannot be represented as a semantic version. Using"
-                            + " version "
-                            + compilerVersion
-                            + " for selecting Proguard configurations embedded under"
-                            + " META-INF/");
+                            + ", which cannot be represented as a semantic version. Using"
+                            + " an artificial version newer than any known version for selecting"
+                            + " Proguard configurations embedded under META-INF/. This means that"
+                            + " all rules with a '-max-' qualifier will be excluded and all rules"
+                            + " with a -min- qualifier will be included.");
                   }
                   return compilerVersion;
                 });
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 4138faf..33388ee 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -15,11 +15,6 @@
   // Therefore, changing this field could break our release scripts.
   public static final String LABEL = "8.1.19-dev";
 
-  // The prefix of the active dev version line being worked on from this branch. This is used in the
-  // few cases where the compiler makes decisions based in the compiler version and where version
-  // 'main' cannot be used.
-  public static final String ACTIVE_DEV_VERSION = "8.1.0";
-
   private Version() {
   }
 
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubberEventConsumer.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubberEventConsumer.java
index ac79346..7c42536 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubberEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubberEventConsumer.java
@@ -7,9 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingApiReferenceStubberEventConsumer;
-import com.android.tools.r8.profile.art.rewriting.ConcreteArtProfileCollectionAdditions;
 
 public interface ApiReferenceStubberEventConsumer {
 
@@ -23,20 +21,7 @@
   boolean isEmpty();
 
   static ApiReferenceStubberEventConsumer create(AppView<?> appView) {
-    if (appView.options().getArtProfileOptions().isIncludingApiReferenceStubs()) {
-      ArtProfileCollectionAdditions artProfileCollectionAdditions =
-          ArtProfileCollectionAdditions.create(appView);
-      if (!artProfileCollectionAdditions.isNop()) {
-        return create(artProfileCollectionAdditions.asConcrete());
-      }
-    }
-    return empty();
-  }
-
-  static ApiReferenceStubberEventConsumer create(
-      ConcreteArtProfileCollectionAdditions artProfileCollectionAdditions) {
-    return ArtProfileRewritingApiReferenceStubberEventConsumer.attach(
-        artProfileCollectionAdditions, empty());
+    return ArtProfileRewritingApiReferenceStubberEventConsumer.attach(appView, empty());
   }
 
   static EmptyApiReferenceStubberEventConsumer empty() {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 4300df0..99cb304 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.D8.ConvertedCfFiles;
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
@@ -432,7 +433,9 @@
       // Fail if there are pending errors, e.g., the program consumers may have reported errors.
       options.reporter.failIfPendingErrors();
       // Supply info to all additional resource consumers.
-      supplyAdditionalConsumers(appView);
+      if (!(programConsumer instanceof ConvertedCfFiles)) {
+        supplyAdditionalConsumers(appView);
+      }
     } finally {
       timing.end();
     }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
index 77f653a..68f379f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -146,6 +147,7 @@
       }
 
       offset = section.getLayout().getEndOfFile();
+      assert BitUtils.isAligned(4, offset);
       sections.add(section);
       fileTiming.end();
       timings.add(fileTiming);
@@ -192,7 +194,32 @@
         List<MapItem> mapItems =
             section
                 .getLayout()
-                .generateMapInfo(section.getFileWriter(), stringIdsSize, stringIdsOffset);
+                .generateMapInfo(
+                    section.getFileWriter(),
+                    section.getLayout().headerOffset,
+                    stringIdsSize,
+                    stringIdsOffset,
+                    lastSection.getLayout().getStringDataOffsets());
+        int originalSize = dexOutputBuffer.getInt();
+        int size = 0;
+        for (MapItem mapItem : mapItems) {
+          size += mapItem.write(dexOutputBuffer);
+        }
+        assert originalSize == size;
+        // Calculate signature and checksum after the map is written.
+        section.getFileWriter().writeSignature(section.getLayout(), dexOutputBuffer);
+        section.getFileWriter().writeChecksum(section.getLayout(), dexOutputBuffer);
+      } else {
+        dexOutputBuffer.moveTo(section.getLayout().getMapOffset());
+        List<MapItem> mapItems =
+            section
+                .getLayout()
+                .generateMapInfo(
+                    section.getFileWriter(),
+                    section.getLayout().headerOffset,
+                    stringIdsSize,
+                    stringIdsOffset,
+                    lastSection.getLayout().getStringDataOffsets());
         int originalSize = dexOutputBuffer.getInt();
         int size = 0;
         for (MapItem mapItem : mapItems) {
@@ -214,6 +241,7 @@
       DexOutputBuffer outputBuffer,
       boolean last) {
     assert !virtualFile.isEmpty();
+    assert BitUtils.isAligned(4, offset);
     printItemUseInfo(virtualFile);
 
     timing.begin("Reindex for lazy strings");
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 6d43e68..86182c2 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -795,6 +795,12 @@
 
   private byte[] dexVersionBytes() {
     if (options.testing.dexContainerExperiment) {
+      return DexVersion.V41.getBytes();
+    }
+    // TODO(b/269089718): Remove this testing option and always emit DEX version 040 if DEX contains
+    //  identifiers with whitespace.
+    if (options.testing.dexVersion40FromApiLevel30
+        && options.getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
       return DexVersion.V40.getBytes();
     }
     return options.testing.forceDexVersionBytes != null
@@ -809,7 +815,7 @@
     dest.putByte(Constants.DEX_FILE_MAGIC_SUFFIX);
     // Leave out checksum and signature for now.
     dest.moveTo(layout.headerOffset + Constants.FILE_SIZE_OFFSET);
-    dest.putInt(layout.getEndOfFile());
+    dest.putInt(layout.getEndOfFile() - layout.headerOffset);
     dest.putInt(Constants.TYPE_HEADER_ITEM_SIZE);
     dest.putInt(Constants.ENDIAN_CONSTANT);
     dest.putInt(0);
@@ -912,7 +918,7 @@
     }
 
     public int size() {
-      return length == 0 ? 0 : 1;
+      return length == 0 && type != Constants.TYPE_STRING_DATA_ITEM ? 0 : 1;
     }
   }
 
@@ -1123,13 +1129,21 @@
 
     public List<MapItem> generateMapInfo(FileWriter fileWriter) {
       return generateMapInfo(
-          fileWriter, fileWriter.mixedSectionOffsets.getStringData().size(), stringIdsOffset);
+          fileWriter,
+          0,
+          fileWriter.mixedSectionOffsets.getStringData().size(),
+          stringIdsOffset,
+          getStringDataOffsets());
     }
 
     public List<MapItem> generateMapInfo(
-        FileWriter fileWriter, int stringIdsSize, int stringIdsOffset) {
+        FileWriter fileWriter,
+        int headerOffset,
+        int stringIdsSize,
+        int stringIdsOffset,
+        int stringDataOffsets) {
       List<MapItem> mapItems = new ArrayList<>();
-      mapItems.add(new MapItem(Constants.TYPE_HEADER_ITEM, 0, 1));
+      mapItems.add(new MapItem(Constants.TYPE_HEADER_ITEM, headerOffset, 1));
       mapItems.add(
           new MapItem(
               Constants.TYPE_STRING_ID_ITEM,
@@ -1182,8 +1196,8 @@
       mapItems.add(
           new MapItem(
               Constants.TYPE_STRING_DATA_ITEM,
-              getStringDataOffsets(),
-              getStringDataOffsets() == 0 ? 0 : stringIdsSize));
+              stringDataOffsets,
+              stringDataOffsets == 0 ? 0 : stringIdsSize));
       mapItems.add(
           new MapItem(
               Constants.TYPE_ANNOTATION_ITEM,
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index ef3a443..2ecf1d3 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.dump;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
@@ -21,6 +22,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.TreeMap;
 
 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
 public class DumpOptions {
@@ -28,6 +30,7 @@
   // The following keys and values should not be changed to keep the dump utility backward
   // compatible with previous versions. They are also used by the python script compileDump and
   // the corresponding CompileDumpCompatR8 java class.
+  private static final String BACKEND_KEY = "backend";
   private static final String TOOL_KEY = "tool";
   private static final String MODE_KEY = "mode";
   private static final String DEBUG_MODE_VALUE = "debug";
@@ -42,12 +45,13 @@
   private static final String TREE_SHAKING_KEY = "tree-shaking";
   private static final String MINIFICATION_KEY = "minification";
   private static final String FORCE_PROGUARD_COMPATIBILITY_KEY = "force-proguard-compatibility";
-  private static final String SYSTEM_PROPERTY_PREFIX = "system-property-";
+  public static final String SYSTEM_PROPERTY_PREFIX = "system-property-";
   private static final String ENABLE_MISSING_LIBRARY_API_MODELING =
       "enable-missing-library-api-modeling";
   private static final String ANDROID_PLATFORM_BUILD = "android-platform-build";
   private static final String TRACE_REFERENCES_CONSUMER = "trace_references_consumer";
 
+  private final Backend backend;
   private final Tool tool;
   private final CompilationMode compilationMode;
   private final int minApi;
@@ -78,6 +82,7 @@
   private final boolean dumpInputToFile;
 
   private DumpOptions(
+      Backend backend,
       Tool tool,
       CompilationMode compilationMode,
       int minAPI,
@@ -99,6 +104,7 @@
       Map<String, String> systemProperties,
       boolean dumpInputToFile,
       String traceReferencesConsumer) {
+    this.backend = backend;
     this.tool = tool;
     this.compilationMode = compilationMode;
     this.minApi = minAPI;
@@ -137,6 +143,7 @@
     }
     if (tool != Tool.TraceReferences) {
       // We keep the following values for backward compatibility.
+      addDumpEntry(buildProperties, BACKEND_KEY, backend.name());
       addDumpEntry(
           buildProperties,
           MODE_KEY,
@@ -183,6 +190,9 @@
 
   private static void parseKeyValue(Builder builder, String key, String value) {
     switch (key) {
+      case BACKEND_KEY:
+        builder.setBackend(Backend.valueOf(value));
+        return;
       case TOOL_KEY:
         builder.setTool(Tool.valueOf(value));
         return;
@@ -303,6 +313,8 @@
   }
 
   public static class Builder {
+    // Initialize backend to DEX for backwards compatibility.
+    private Backend backend = Backend.DEX;
     private Tool tool;
     private CompilationMode compilationMode;
     private int minApi;
@@ -333,6 +345,11 @@
 
     public Builder() {}
 
+    public Builder setBackend(Backend backend) {
+      this.backend = backend;
+      return this;
+    }
+
     public Builder setTool(Tool tool) {
       this.tool = tool;
       return this;
@@ -442,21 +459,29 @@
     }
 
     public Builder readCurrentSystemProperties() {
+      getCurrentSystemProperties().forEach(this::setSystemProperty);
+      return this;
+    }
+
+    public static Map<String, String> getCurrentSystemProperties() {
+      Map<String, String> systemProperties = new TreeMap<>();
       System.getProperties()
           .stringPropertyNames()
           .forEach(
               name -> {
                 if (name.startsWith("com.android.tools.r8.")) {
                   String value = System.getProperty(name);
-                  setSystemProperty(name, value);
+                  systemProperties.put(name, value);
                 }
               });
-      return this;
+      return systemProperties;
     }
 
     public DumpOptions build() {
       assert tool != null;
+      assert tool == Tool.TraceReferences || backend != null;
       return new DumpOptions(
+          backend,
           tool,
           compilationMode,
           minApi,
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 b5fe77a..9560129 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -916,10 +916,19 @@
             appView
                 .withLiveness()
                 .setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens));
+          } else {
+            assert appView.hasClassHierarchy();
+            AppView<AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+                appView.withClassHierarchy();
+            AppInfoWithClassHierarchy appInfo = appViewWithClassHierarchy.appInfo();
+            MainDexInfo rewrittenMainDexInfo =
+                appInfo.getMainDexInfo().rewrittenWithLens(appView.getSyntheticItems(), lens);
+            appViewWithClassHierarchy.setAppInfo(
+                appInfo.rebuildWithMainDexInfo(rewrittenMainDexInfo));
           }
           appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
           appView.setArtProfileCollection(
-              appView.getArtProfileCollection().rewrittenWithLens(lens));
+              appView.getArtProfileCollection().rewrittenWithLens(appView, lens));
           appView.setAssumeInfoCollection(
               appView.getAssumeInfoCollection().rewrittenWithLens(appView, lens));
           if (appView.hasInitClassLens()) {
@@ -948,7 +957,8 @@
     boolean changed = appView.setGraphLens(lens);
     assert changed;
 
-    appView.setArtProfileCollection(appView.getArtProfileCollection().rewrittenWithLens(lens));
+    appView.setArtProfileCollection(
+        appView.getArtProfileCollection().rewrittenWithLens(appView, lens));
   }
 
   public void setAlreadyLibraryDesugared(Set<DexType> alreadyLibraryDesugared) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index aff4bff..e24a22c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -56,6 +59,9 @@
   public final int visibility;
   public final DexEncodedAnnotation annotation;
 
+  private static final int UNKNOWN_API_LEVEL = -1;
+  private static final int NOT_SET_API_LEVEL = -2;
+
   private static void specify(StructuralSpecification<DexAnnotation, ?> spec) {
     spec.withItem(a -> a.annotation).withInt(a -> a.visibility);
   }
@@ -469,26 +475,37 @@
   }
 
   public static DexAnnotation createAnnotationSynthesizedClass(
-      SyntheticKind kind, DexItemFactory dexItemFactory) {
+      SyntheticKind kind, DexItemFactory dexItemFactory, ComputedApiLevel computedApiLevel) {
     DexString versionHash =
         dexItemFactory.createString(dexItemFactory.getSyntheticNaming().getVersionHash());
     DexAnnotationElement kindElement =
         new DexAnnotationElement(dexItemFactory.kindString, DexValueInt.create(kind.getId()));
     DexAnnotationElement versionHashElement =
         new DexAnnotationElement(dexItemFactory.versionHashString, new DexValueString(versionHash));
-    DexAnnotationElement[] elements = new DexAnnotationElement[] {kindElement, versionHashElement};
+    int apiLevel = getApiLevelForSerialization(computedApiLevel);
+    DexAnnotationElement apiLevelElement =
+        new DexAnnotationElement(dexItemFactory.apiLevelString, DexValueInt.create(apiLevel));
+    DexAnnotationElement[] elements =
+        new DexAnnotationElement[] {apiLevelElement, kindElement, versionHashElement};
     return new DexAnnotation(
         VISIBILITY_BUILD,
         new DexEncodedAnnotation(dexItemFactory.annotationSynthesizedClass, elements));
   }
 
   public static boolean hasSynthesizedClassAnnotation(
-      DexAnnotationSet annotations, DexItemFactory factory, SyntheticItems synthetics) {
-    return getSynthesizedClassAnnotationInfo(annotations, factory, synthetics) != null;
+      DexAnnotationSet annotations,
+      DexItemFactory factory,
+      SyntheticItems synthetics,
+      AndroidApiLevelCompute apiLevelCompute) {
+    return getSynthesizedClassAnnotationInfo(annotations, factory, synthetics, apiLevelCompute)
+        != null;
   }
 
-  public static SyntheticKind getSynthesizedClassAnnotationInfo(
-      DexAnnotationSet annotations, DexItemFactory factory, SyntheticItems synthetics) {
+  public static SynthesizedAnnotationClassInfo getSynthesizedClassAnnotationInfo(
+      DexAnnotationSet annotations,
+      DexItemFactory factory,
+      SyntheticItems synthetics,
+      AndroidApiLevelCompute apiLevelCompute) {
     if (annotations.size() != 1) {
       return null;
     }
@@ -497,12 +514,13 @@
       return null;
     }
     int length = annotation.annotation.elements.length;
-    if (length != 2) {
+    if (length != 3) {
       return null;
     }
     assert factory.kindString.isLessThan(factory.versionHashString);
-    DexAnnotationElement kindElement = annotation.annotation.elements[0];
-    DexAnnotationElement versionHashElement = annotation.annotation.elements[1];
+    DexAnnotationElement apiLevelElement = annotation.annotation.elements[0];
+    DexAnnotationElement kindElement = annotation.annotation.elements[1];
+    DexAnnotationElement versionHashElement = annotation.annotation.elements[2];
     if (kindElement.name != factory.kindString) {
       return null;
     }
@@ -515,14 +533,43 @@
     if (!versionHashElement.value.isDexValueString()) {
       return null;
     }
+    if (apiLevelElement.name != factory.apiLevelString || !apiLevelElement.value.isDexValueInt()) {
+      return null;
+    }
     String currentVersionHash = synthetics.getNaming().getVersionHash();
     String syntheticVersionHash = versionHashElement.value.asDexValueString().getValue().toString();
     if (!currentVersionHash.equals(syntheticVersionHash)) {
       return null;
     }
-    SyntheticKind kind =
+    int apiLevelValue = apiLevelElement.value.asDexValueInt().getValue();
+    ComputedApiLevel computedApiLevel = getSerializedApiLevel(apiLevelCompute, apiLevelValue);
+    SyntheticKind syntheticKind =
         synthetics.getNaming().fromId(kindElement.value.asDexValueInt().getValue());
-    return kind;
+    assert syntheticKind != synthetics.getNaming().API_MODEL_OUTLINE
+        || computedApiLevel.isKnownApiLevel();
+    return SynthesizedAnnotationClassInfo.create(syntheticKind, computedApiLevel);
+  }
+
+  private static int getApiLevelForSerialization(ComputedApiLevel computedApiLevel) {
+    if (computedApiLevel.isNotSetApiLevel()) {
+      return NOT_SET_API_LEVEL;
+    } else if (computedApiLevel.isUnknownApiLevel()) {
+      return UNKNOWN_API_LEVEL;
+    } else {
+      assert computedApiLevel.isKnownApiLevel();
+      return computedApiLevel.asKnownApiLevel().getApiLevel().getLevel();
+    }
+  }
+
+  private static ComputedApiLevel getSerializedApiLevel(
+      AndroidApiLevelCompute apiLevelCompute, int apiLevelValue) {
+    if (apiLevelValue == NOT_SET_API_LEVEL) {
+      return ComputedApiLevel.notSet();
+    } else if (apiLevelValue == UNKNOWN_API_LEVEL) {
+      return ComputedApiLevel.unknown();
+    } else {
+      return apiLevelCompute.of(AndroidApiLevel.getAndroidApiLevel(apiLevelValue));
+    }
   }
 
   public DexAnnotation rewrite(Function<DexEncodedAnnotation, DexEncodedAnnotation> rewriter) {
@@ -535,4 +582,29 @@
     }
     return new DexAnnotation(visibility, rewritten);
   }
+
+  public static class SynthesizedAnnotationClassInfo {
+
+    private final SyntheticKind syntheticKind;
+    private final ComputedApiLevel computedApiLevel;
+
+    private SynthesizedAnnotationClassInfo(
+        SyntheticKind syntheticKind, ComputedApiLevel computedApiLevel) {
+      this.syntheticKind = syntheticKind;
+      this.computedApiLevel = computedApiLevel;
+    }
+
+    private static SynthesizedAnnotationClassInfo create(
+        SyntheticKind syntheticKind, ComputedApiLevel computedApiLevel) {
+      return new SynthesizedAnnotationClassInfo(syntheticKind, computedApiLevel);
+    }
+
+    public SyntheticKind getSyntheticKind() {
+      return syntheticKind;
+    }
+
+    public ComputedApiLevel getComputedApiLevel() {
+      return computedApiLevel;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 4ffba91..df3709f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -356,6 +356,7 @@
   public final DexString valueString = createString("value");
   public final DexString kindString = createString("kind");
   public final DexString versionHashString = createString("versionHash");
+  public final DexString apiLevelString = createString("apiLevel");
 
   // Prefix for runtime affecting yet potential class-retained annotations.
   public final DexString dalvikAnnotationOptimizationPrefix =
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 22d3978..01fa0e0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -265,6 +265,10 @@
     forEachStaticField(field -> consumer.accept(new ProgramField(this, field)));
   }
 
+  public void forEachProgramStaticMethod(Consumer<? super ProgramMethod> consumer) {
+    forEachProgramDirectMethodMatching(DexEncodedMethod::isStatic, consumer);
+  }
+
   public void forEachProgramMember(Consumer<? super ProgramMember<?, ?>> consumer) {
     forEachProgramField(consumer);
     forEachProgramMethod(consumer);
@@ -310,7 +314,7 @@
   }
 
   public void forEachProgramDirectMethodMatching(
-      Predicate<DexEncodedMethod> predicate, Consumer<ProgramMethod> consumer) {
+      Predicate<DexEncodedMethod> predicate, Consumer<? super ProgramMethod> consumer) {
     methodCollection.forEachDirectMethodMatching(
         predicate, method -> consumer.accept(new ProgramMethod(this, method)));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 43558a8..b43256b 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
 import com.android.tools.r8.optimize.MemberRebindingLens;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -600,6 +601,10 @@
     return false;
   }
 
+  public EnumUnboxingLens asEnumUnboxerLens() {
+    return null;
+  }
+
   public boolean isHorizontalClassMergerGraphLens() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
index 11f9bd9..a56b0ef 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -205,9 +205,12 @@
                     && !superClass.isLibraryClass()) {
                   return;
                 }
-                builder.addResolutionResult(
+                MethodResolutionResult superTypeResult =
                     resolveMethodOnClassStep2(
-                        superClass, methodProto, methodName, initialResolutionHolder));
+                        superClass, methodProto, methodName, initialResolutionHolder);
+                if (superTypeResult != null) {
+                  builder.addResolutionResult(superTypeResult);
+                }
               });
       return builder.buildOrIfEmpty(null, clazz.superType);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 8c949fe..ef0d7ff 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -1213,6 +1213,10 @@
       super(typesCausingError);
     }
 
+    public static NoSuchMethodResult getEmptyNoSuchMethodResult() {
+      return INSTANCE;
+    }
+
     @Override
     public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
       return true;
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 b84809e..b8782e2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -154,7 +154,12 @@
     appView.setHorizontallyMergedClasses(mergedClasses, mode);
 
     HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
-        createLens(mergedClasses, lensBuilder, mode, syntheticArgumentClass);
+        createLens(
+            mergedClasses,
+            lensBuilder,
+            mode,
+            artProfileCollectionAdditions,
+            syntheticArgumentClass);
     artProfileCollectionAdditions =
         artProfileCollectionAdditions.rewriteMethodReferences(
             horizontalClassMergerGraphLens::getNextMethodToInvoke);
@@ -185,7 +190,7 @@
                       .appInfo()
                       .getMainDexInfo()
                       .rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens)));
-      appView.setGraphLens(horizontalClassMergerGraphLens);
+      appView.rewriteWithD8Lens(horizontalClassMergerGraphLens);
     }
     codeProvider.setGraphLens(horizontalClassMergerGraphLens);
 
@@ -391,8 +396,15 @@
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode,
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
       SyntheticArgumentClass syntheticArgumentClass) {
-    return new TreeFixer(appView, mergedClasses, lensBuilder, mode, syntheticArgumentClass)
+    return new TreeFixer(
+            appView,
+            mergedClasses,
+            lensBuilder,
+            mode,
+            artProfileCollectionAdditions,
+            syntheticArgumentClass)
         .fixupTypeReferences();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 51b4939..53f08a6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -99,7 +99,7 @@
 
     private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField>
         newFieldSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
-    private final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> methodMap =
+    final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> methodMap =
         BidirectionalManyToOneHashMap.newIdentityHashMap();
     private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
         newMethodSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
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 be7bf1c..1e3e943 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.ComputeApiLevelOfSyntheticClass;
 import com.android.tools.r8.horizontalclassmerging.policies.FinalizeMergeGroup;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitInterfaceGroups;
@@ -58,7 +57,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
 import com.android.tools.r8.horizontalclassmerging.policies.SameMainDexGroup;
 import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
-import com.android.tools.r8.horizontalclassmerging.policies.SamePackageForApiOutline;
+import com.android.tools.r8.horizontalclassmerging.policies.SamePackageForNonGlobalMergeSynthetic;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.VerifyMultiClassPolicyAlwaysSatisfied;
@@ -143,8 +142,7 @@
     ImmutableList.Builder<SingleClassPolicy> builder =
         ImmutableList.<SingleClassPolicy>builder()
             .add(new CheckSyntheticClasses(appView))
-            .add(new OnlyClassesWithStaticDefinitionsAndNoClassInitializer())
-            .add(new ComputeApiLevelOfSyntheticClass(appView));
+            .add(new OnlyClassesWithStaticDefinitionsAndNoClassInitializer());
     assert verifySingleClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
     return builder.build();
   }
@@ -267,7 +265,7 @@
         new SameParentClass(),
         new SyntheticItemsPolicy(appView, mode),
         new NoApiOutlineWithNonApiOutline(appView),
-        new SamePackageForApiOutline(appView, mode),
+        new SamePackageForNonGlobalMergeSynthetic(appView),
         new NoDifferentApiReferenceLevel(appView),
         new LimitClassGroups(appView));
     assert verifyMultiClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 31753bf..a4b8ae0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -21,8 +21,10 @@
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -47,6 +49,7 @@
   private final Mode mode;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
   private final DexItemFactory dexItemFactory;
+  private final ArtProfileCollectionAdditions artProfileCollectionAdditions;
   private final SyntheticArgumentClass syntheticArgumentClass;
 
   private final Map<DexProgramClass, DexType> originalSuperTypes = new IdentityHashMap<>();
@@ -58,12 +61,14 @@
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode,
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
       SyntheticArgumentClass syntheticArgumentClass) {
     super(appView);
     this.appView = appView;
     this.mergedClasses = mergedClasses;
     this.mode = mode;
     this.lensBuilder = lensBuilder;
+    this.artProfileCollectionAdditions = artProfileCollectionAdditions;
     this.syntheticArgumentClass = syntheticArgumentClass;
     this.dexItemFactory = appView.dexItemFactory();
   }
@@ -289,15 +294,36 @@
 
       if (method.isInstanceInitializer()) {
         // If the method is an instance initializer, then add extra nulls.
+        Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
         newMethodReference =
             dexItemFactory.createInstanceInitializerWithFreshProto(
                 newMethodReference,
                 syntheticArgumentClass.getArgumentClasses(),
-                tryMethod -> !newMethods.contains(tryMethod.getSignature()));
+                tryMethod -> !newMethods.contains(tryMethod.getSignature()),
+                usedSyntheticArgumentClasses::set);
         lensBuilder.addExtraParameters(
             originalMethodReference,
             ExtraUnusedNullParameter.computeExtraUnusedNullParameters(
                 originalMethodReference, newMethodReference));
+
+        // Amend the art profile collection.
+        if (usedSyntheticArgumentClasses.isSet()) {
+          Set<DexMethod> previousMethodReferences =
+              lensBuilder.methodMap.getKeys(originalMethodReference);
+          if (previousMethodReferences.isEmpty()) {
+            artProfileCollectionAdditions.applyIfContextIsInProfile(
+                originalMethodReference,
+                additionsBuilder ->
+                    usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
+          } else {
+            for (DexMethod previousMethodReference : previousMethodReferences) {
+              artProfileCollectionAdditions.applyIfContextIsInProfile(
+                  previousMethodReference,
+                  additionsBuilder ->
+                      usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
+            }
+          }
+        }
       } else {
         newMethodReference =
             dexItemFactory.createFreshMethodNameWithoutHolder(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ComputeApiLevelOfSyntheticClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ComputeApiLevelOfSyntheticClass.java
deleted file mode 100644
index 9038a57..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ComputeApiLevelOfSyntheticClass.java
+++ /dev/null
@@ -1,205 +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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
-import com.android.tools.r8.androidapi.ComputedApiLevel;
-import com.android.tools.r8.dex.code.CfOrDexInstruction;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-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.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.synthesis.SyntheticItems;
-import java.util.ListIterator;
-
-public class ComputeApiLevelOfSyntheticClass extends SingleClassPolicy {
-
-  private final AppView<?> appView;
-  private final SyntheticItems syntheticItems;
-
-  public ComputeApiLevelOfSyntheticClass(AppView<?> appView) {
-    this.appView = appView;
-    this.syntheticItems = appView.getSyntheticItems();
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass clazz) {
-    assert syntheticItems.isSyntheticClass(clazz);
-    clazz.forEachProgramMethod(
-        programMethod -> {
-          DexEncodedMethod definition = programMethod.getDefinition();
-          if (definition.getApiLevelForCode().isNotSetApiLevel()) {
-            ComputeApiLevelUseRegistry computeApiLevel =
-                new ComputeApiLevelUseRegistry(appView, programMethod, appView.apiLevelCompute());
-            computeApiLevel.accept(programMethod);
-            ComputedApiLevel maxApiReferenceLevel = computeApiLevel.getMaxApiReferenceLevel();
-            assert !maxApiReferenceLevel.isNotSetApiLevel();
-            definition.setApiLevelForCode(maxApiReferenceLevel);
-            definition.setApiLevelForDefinition(computeApiLevel.getMaxApiReferenceLevel());
-          }
-        });
-    return true;
-  }
-
-  @Override
-  public String getName() {
-    return "ComputeApiLevelOfSyntheticClass";
-  }
-
-  private static class ComputeApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
-
-    private final AppView<?> appView;
-    private final AndroidApiLevelCompute apiLevelCompute;
-    private ComputedApiLevel maxApiReferenceLevel;
-
-    public ComputeApiLevelUseRegistry(
-        AppView<?> appView, ProgramMethod context, AndroidApiLevelCompute apiLevelCompute) {
-      super(appView, context);
-      this.appView = appView;
-      this.apiLevelCompute = apiLevelCompute;
-      maxApiReferenceLevel = appView.computedMinApiLevel();
-    }
-
-    @Override
-    public void registerInitClass(DexType clazz) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    @Override
-    public void registerRecordFieldValues(DexField[] fields) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    @Override
-    public void registerInvokeVirtual(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeDirect(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeStatic(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeInterface(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeSuper(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInstanceFieldRead(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerInstanceFieldReadFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerNewInstance(DexType type) {
-      setMaxApiReferenceLevel(type);
-    }
-
-    @Override
-    public void registerStaticFieldRead(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerStaticFieldReadFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerStaticFieldWrite(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerStaticFieldWriteFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerConstClass(
-        DexType type,
-        ListIterator<? extends CfOrDexInstruction> iterator,
-        boolean ignoreCompatRules) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerSafeCheckCast(DexType type) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerTypeReference(DexType type) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInstanceOf(DexType type) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerExceptionGuard(DexType guard) {
-      setMaxApiReferenceLevel(guard);
-    }
-
-    @Override
-    public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    @Override
-    public void registerCallSite(DexCallSite callSite) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    private void setMaxApiReferenceLevel(DexReference reference) {
-      maxApiReferenceLevel =
-          maxApiReferenceLevel.max(apiLevelCompute.computeApiLevelForLibraryReference(reference));
-    }
-
-    public ComputedApiLevel getMaxApiReferenceLevel() {
-      return maxApiReferenceLevel;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
index a021a28..0e899f7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.horizontalclassmerging.policies;
 
 import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.getMembersApiReferenceLevelForMerging;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -15,14 +16,16 @@
 public class NoDifferentApiReferenceLevel extends MultiClassSameReferencePolicy<ComputedApiLevel> {
 
   private final AndroidApiLevelCompute apiLevelCompute;
-  private final AppView<?> appView;
   private final boolean enableApiCallerIdentification;
+  private final boolean enableWholeProgramOptimization;
+  private final ComputedApiLevel minApiLevel;
 
   public NoDifferentApiReferenceLevel(AppView<?> appView) {
-    this.appView = appView;
     apiLevelCompute = appView.apiLevelCompute();
     enableApiCallerIdentification =
         appView.options().apiModelingOptions().isApiCallerIdentificationEnabled();
+    enableWholeProgramOptimization = appView.enableWholeProgramOptimizations();
+    minApiLevel = appView.computedMinApiLevel();
   }
 
   @Override
@@ -38,6 +41,13 @@
   @Override
   public ComputedApiLevel getMergeKey(DexProgramClass clazz) {
     assert enableApiCallerIdentification;
-    return getApiReferenceLevelForMerging(appView, apiLevelCompute, clazz);
+    ComputedApiLevel apiReferenceLevelForMerging =
+        enableWholeProgramOptimization
+            ? getApiReferenceLevelForMerging(apiLevelCompute, clazz)
+            : getMembersApiReferenceLevelForMerging(clazz, minApiLevel);
+    if (apiReferenceLevelForMerging.isUnknownApiLevel()) {
+      return ineligibleForClassMerging();
+    }
+    return apiReferenceLevelForMerging;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForApiOutline.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForNonGlobalMergeSynthetic.java
similarity index 83%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForApiOutline.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForNonGlobalMergeSynthetic.java
index 30a3200..db6d282 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForApiOutline.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForNonGlobalMergeSynthetic.java
@@ -9,23 +9,21 @@
 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.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-public class SamePackageForApiOutline extends MultiClassPolicy {
+public class SamePackageForNonGlobalMergeSynthetic extends MultiClassPolicy {
 
   private final AppView<AppInfo> appView;
-  private final Mode mode;
 
-  public SamePackageForApiOutline(AppView<AppInfo> appView, Mode mode) {
+  public SamePackageForNonGlobalMergeSynthetic(AppView<AppInfo> appView) {
     this.appView = appView;
-    this.mode = mode;
   }
 
   /** Sort unrestricted classes into restricted classes if they are in the same package. */
@@ -50,7 +48,12 @@
 
     // Sort all restricted classes into packages.
     for (DexProgramClass clazz : group) {
-      if (syntheticItems.isSyntheticOfKind(clazz.getType(), k -> k.API_MODEL_OUTLINE)) {
+      assert syntheticItems.isSynthetic(clazz.getType());
+      if (Iterables.any(
+          syntheticItems.getSyntheticKinds(clazz.getType()),
+          kind ->
+              !kind.isSyntheticMethodKind()
+                  || !kind.asSyntheticMethodKind().isAllowGlobalMerging())) {
         restrictedClasses
             .computeIfAbsent(
                 clazz.getType().getPackageDescriptor(), ignoreArgument(MergeGroup::new))
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
index 51d33b7..47824f9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
@@ -18,7 +18,7 @@
 
   @Override
   public boolean canMerge(DexProgramClass program) {
-    assert policy.canMerge(program);
+    assert policy.canMerge(program) : "Verification of single class policies failed";
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 182b9b4..587ef11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.util.Set;
 
 /**
@@ -177,7 +177,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addArgument(index, knownToBeBoolean);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 5715e54..b3c3bbc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 
 public class ArrayLength extends Instruction {
 
@@ -154,7 +154,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addArrayLength(array());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index c462cf6..04857de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -28,7 +28,7 @@
 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;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.NumberUtils;
 import java.util.Set;
@@ -346,7 +346,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addConstNumber(outType(), value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 900fe91..7201b71 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.io.UTFDataFormatException;
 
 public class ConstString extends ConstInstruction {
@@ -182,7 +182,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addConstString(value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 834fd24..8c0eba5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 
 /**
  * Instruction introducing an SSA value with attached local information.
@@ -90,7 +90,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addDebugLocalWrite(src());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 651fb35..361dd22 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 
 public class DebugPosition extends Instruction {
 
@@ -101,7 +101,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addDebugPosition(getPosition());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index 4f99960..eac1fbf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.util.function.Function;
 
 public class Div extends ArithmeticBinop {
@@ -151,7 +151,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addDiv(type, leftValue(), rightValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index b953a68..886ca9a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.List;
 import java.util.ListIterator;
@@ -128,7 +128,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addGoto(getTarget());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 12089ec..1cd76a4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOutputMode;
@@ -292,7 +292,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     ValueType ifType = inValues.get(0).outType();
     if (inValues.size() == 1) {
       builder.addIf(type, ifType, inValues.get(0), getTrueTarget());
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 8892da0..8d7088f 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
@@ -35,7 +35,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1563,7 +1563,7 @@
     return false;
   }
 
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     throw new Unimplemented("Missing impl for " + getClass().getSimpleName());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 9203e93..14fdd11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -29,7 +29,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 
@@ -218,7 +218,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addInvokeDirect(getInvokedMethod(), arguments());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 5809552..3cee2ff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -27,7 +27,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 
@@ -204,7 +204,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addInvokeVirtual(getInvokedMethod(), arguments());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index acd2cc4..9cf1d30 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class MoveException extends Instruction {
@@ -134,7 +134,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addMoveException(exceptionType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index d8882e4..1eb399b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 
 public class Return extends JumpInstruction {
 
@@ -153,7 +153,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     if (hasReturnValue()) {
       builder.addReturn(returnValue());
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index afad1ff..4d767b0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -32,7 +32,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.lightir.LIRBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
@@ -300,7 +300,7 @@
   }
 
   @Override
-  public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+  public void buildLir(LirBuilder<Value, BasicBlock> builder) {
     builder.addStaticGet(getField());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 7b3641f..7cbb122 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -117,7 +117,8 @@
     ArtProfileCollectionAdditions artProfileCollectionAdditions =
         methodProcessor.getArtProfileCollectionAdditions();
     CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer =
-        CfClassSynthesizerDesugaringEventConsumer.create(artProfileCollectionAdditions);
+        CfClassSynthesizerDesugaringEventConsumer.createForD8(
+            appView, artProfileCollectionAdditions);
     converter.classSynthesisDesugaring(executorService, classSynthesizerEventConsumer);
     if (!classSynthesizerEventConsumer.getSynthesizedClasses().isEmpty()) {
       classes =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index 7a04167..9a81a28 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -50,7 +49,7 @@
       ExecutorService executorService) {
     this.artProfileCollectionAdditions = artProfileCollectionAdditions;
     this.converter = converter;
-    this.eventConsumer = MethodProcessorEventConsumer.create(artProfileCollectionAdditions);
+    this.eventConsumer = MethodProcessorEventConsumer.createForD8(artProfileCollectionAdditions);
     this.executorService = executorService;
     this.processorContext = converter.appView.createProcessorContext();
   }
@@ -90,7 +89,7 @@
   }
 
   public void scheduleMethodForProcessing(
-      ProgramMethod method, D8CfInstructionDesugaringEventConsumer eventConsumer) {
+      ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
     // TODO(b/179755192): By building up waves of methods in the class converter, we can avoid the
     //  following check and always process the method asynchronously.
     if (!scheduled.contains(method.getHolderType())
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 6d6edbf..4eeb553 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -60,9 +60,9 @@
 import com.android.tools.r8.ir.optimize.outliner.Outliner;
 import com.android.tools.r8.ir.optimize.string.StringBuilderAppendOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
-import com.android.tools.r8.lightir.IR2LIRConverter;
-import com.android.tools.r8.lightir.LIR2IRConverter;
-import com.android.tools.r8.lightir.LIRCode;
+import com.android.tools.r8.lightir.IR2LirConverter;
+import com.android.tools.r8.lightir.Lir2IRConverter;
+import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorIROptimizer;
@@ -220,7 +220,7 @@
             : CfInstructionDesugaringCollection.create(appView, appView.apiLevelCompute());
     this.covariantReturnTypeAnnotationTransformer =
         options.processCovariantReturnTypeAnnotations
-            ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
+            ? new CovariantReturnTypeAnnotationTransformer(appView, this)
             : null;
     if (appView.options().desugarState.isOn()
         && appView.options().apiModelingOptions().enableOutliningOfMethods) {
@@ -633,7 +633,7 @@
     if (serviceLoaderRewriter != null) {
       assert appView.appInfo().hasLiveness();
       timing.begin("Rewrite service loaders");
-      serviceLoaderRewriter.rewrite(code, methodProcessingContext);
+      serviceLoaderRewriter.rewrite(code, methodProcessor, methodProcessingContext);
       timing.end();
     }
 
@@ -765,7 +765,8 @@
     timing.end();
     if (assertionErrorTwoArgsConstructorRewriter != null) {
       timing.begin("Rewrite AssertionError");
-      assertionErrorTwoArgsConstructorRewriter.rewrite(code, methodProcessingContext);
+      assertionErrorTwoArgsConstructorRewriter.rewrite(
+          code, methodProcessor, methodProcessingContext);
       timing.end();
     }
     timing.begin("Run CSE");
@@ -1078,8 +1079,8 @@
       OptimizationFeedback feedback,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
-    if (options.testing.roundtripThroughLIR) {
-      code = roundtripThroughLIR(code, feedback, bytecodeMetadataProvider, timing);
+    if (options.testing.roundtripThroughLir) {
+      code = roundtripThroughLir(code, feedback, bytecodeMetadataProvider, timing);
     }
     if (options.isGeneratingClassFiles()) {
       timing.begin("IR->CF");
@@ -1094,16 +1095,16 @@
     printMethod(code.context(), "After finalization");
   }
 
-  private IRCode roundtripThroughLIR(
+  private IRCode roundtripThroughLir(
       IRCode code,
       OptimizationFeedback feedback,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
     timing.begin("IR->LIR");
-    LIRCode lirCode = IR2LIRConverter.translate(code, appView.dexItemFactory());
+    LirCode lirCode = IR2LirConverter.translate(code, appView.dexItemFactory());
     timing.end();
     timing.begin("LIR->IR");
-    IRCode irCode = LIR2IRConverter.translate(code.context(), lirCode, appView);
+    IRCode irCode = Lir2IRConverter.translate(code.context(), lirCode, appView);
     timing.end();
     return irCode;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorEventConsumer.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorEventConsumer.java
index d002cd5..e360a95 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorEventConsumer.java
@@ -4,24 +4,38 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriterEventConsumer;
+import com.android.tools.r8.ir.optimize.ServiceLoaderRewriterEventConsumer;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizationsEventConsumer;
 import com.android.tools.r8.ir.optimize.api.InstanceInitializerOutlinerEventConsumer;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxerMethodProcessorEventConsumer;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingMethodProcessorEventConsumer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class MethodProcessorEventConsumer
-    implements EnumUnboxerMethodProcessorEventConsumer,
+    implements AssertionErrorTwoArgsConstructorRewriterEventConsumer,
+        EnumUnboxerMethodProcessorEventConsumer,
         InstanceInitializerOutlinerEventConsumer,
+        ServiceLoaderRewriterEventConsumer,
         UtilityMethodsForCodeOptimizationsEventConsumer {
 
-  public static MethodProcessorEventConsumer create(
+  public void finished(AppView<AppInfoWithLiveness> appView) {}
+
+  public static MethodProcessorEventConsumer createForD8(
       ArtProfileCollectionAdditions artProfileCollectionAdditions) {
     return ArtProfileRewritingMethodProcessorEventConsumer.attach(
         artProfileCollectionAdditions, empty());
   }
 
+  public static MethodProcessorEventConsumer createForR8(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return ArtProfileRewritingMethodProcessorEventConsumer.attach(appView, empty());
+  }
+
   public static MethodProcessorEventConsumer empty() {
     return EmptyMethodProcessorEventConsumer.getInstance();
   }
@@ -38,6 +52,11 @@
     }
 
     @Override
+    public void acceptAssertionErrorCreateMethod(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptEnumUnboxerCheckNotZeroContext(ProgramMethod method, ProgramMethod context) {
       // Intentionally empty.
     }
@@ -60,6 +79,11 @@
     }
 
     @Override
+    public void acceptServiceLoaderLoadUtilityMethod(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
       // Intentionally empty.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
index 1d06a25..9d03bb5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformerEventConsumer;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
@@ -91,7 +92,7 @@
       new L8InnerOuterAttributeEraser(appView).run();
     }
 
-    processCovariantReturnTypeAnnotations(builder);
+    processCovariantReturnTypeAnnotations(builder, artProfileCollectionAdditions);
 
     timing.end();
 
@@ -308,6 +309,7 @@
       throws ExecutionException {
     CfPostProcessingDesugaringEventConsumer eventConsumer =
         CfPostProcessingDesugaringEventConsumer.createForD8(
+            appView,
             methodProcessor.getArtProfileCollectionAdditions(),
             methodProcessor,
             instructionDesugaring);
@@ -338,9 +340,13 @@
     programAdditions.apply(executorService);
   }
 
-  private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
+  private void processCovariantReturnTypeAnnotations(
+      Builder<?> builder, ArtProfileCollectionAdditions artProfileCollectionAdditions) {
     if (covariantReturnTypeAnnotationTransformer != null) {
-      covariantReturnTypeAnnotationTransformer.process(builder);
+      covariantReturnTypeAnnotationTransformer.process(
+          builder,
+          CovariantReturnTypeAnnotationTransformerEventConsumer.create(
+              artProfileCollectionAdditions));
     }
   }
 
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 aae02ab..3b7fcef 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
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.Timing;
@@ -82,10 +81,8 @@
         new PostMethodProcessor.Builder(graphLensForPrimaryOptimizationPass);
     {
       timing.begin("Build primary method processor");
-      ArtProfileCollectionAdditions artProfileCollectionAdditions =
-          ArtProfileCollectionAdditions.create(appView);
       MethodProcessorEventConsumer eventConsumer =
-          MethodProcessorEventConsumer.create(artProfileCollectionAdditions);
+          MethodProcessorEventConsumer.createForR8(appView);
       PrimaryMethodProcessor primaryMethodProcessor =
           PrimaryMethodProcessor.create(
               appView.withLiveness(), eventConsumer, executorService, timing);
@@ -101,7 +98,7 @@
           timing,
           executorService);
       lastWaveDone(postMethodProcessorBuilder, executorService);
-      timing.time("Commit profile additions", () -> artProfileCollectionAdditions.commit(appView));
+      eventConsumer.finished(appView);
       assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
       timing.end();
     }
@@ -155,17 +152,14 @@
 
     {
       timing.begin("IR conversion phase 2");
-      ArtProfileCollectionAdditions artProfileCollectionAdditions =
-          ArtProfileCollectionAdditions.create(appView);
+      MethodProcessorEventConsumer eventConsumer =
+          MethodProcessorEventConsumer.createForR8(appView);
       PostMethodProcessor postMethodProcessor =
           timing.time(
               "Build post method processor",
-              () -> {
-                MethodProcessorEventConsumer eventConsumer =
-                    MethodProcessorEventConsumer.create(artProfileCollectionAdditions);
-                return postMethodProcessorBuilder.build(
-                    appView, eventConsumer, executorService, timing);
-              });
+              () ->
+                  postMethodProcessorBuilder.build(
+                      appView, eventConsumer, executorService, timing));
       if (postMethodProcessor != null) {
         assert !options.debug;
         assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
@@ -179,8 +173,7 @@
             timing);
         timing.end();
         timing.time("Update visible optimization info", feedback::updateVisibleOptimizationInfo);
-        timing.time(
-            "Commit profile additions", () -> artProfileCollectionAdditions.commit(appView));
+        eventConsumer.finished(appView);
         assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
       }
       timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
index f2cdd81..49a7dd8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
@@ -25,14 +28,24 @@
 
   protected CfClassSynthesizerDesugaringEventConsumer() {}
 
-  public static CfClassSynthesizerDesugaringEventConsumer create(
-      ArtProfileCollectionAdditions artProfileCollectionAdditions) {
+  public static CfClassSynthesizerDesugaringEventConsumer createForD8(
+      AppView<?> appView, ArtProfileCollectionAdditions artProfileCollectionAdditions) {
     CfClassSynthesizerDesugaringEventConsumer eventConsumer =
         new D8R8CfClassSynthesizerDesugaringEventConsumer();
     return ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.attach(
-        artProfileCollectionAdditions, eventConsumer);
+        appView, eventConsumer, artProfileCollectionAdditions);
   }
 
+  public static CfClassSynthesizerDesugaringEventConsumer createForR8(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    CfClassSynthesizerDesugaringEventConsumer eventConsumer =
+        new D8R8CfClassSynthesizerDesugaringEventConsumer();
+    return ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.attach(
+        appView, eventConsumer);
+  }
+
+  public void finished(AppView<? extends AppInfoWithClassHierarchy> appView) {}
+
   public abstract Set<DexProgramClass> getSynthesizedClasses();
 
   private static class D8R8CfClassSynthesizerDesugaringEventConsumer
@@ -77,12 +90,18 @@
     }
 
     @Override
+    public void acceptVarHandleDesugaringClassContext(
+        DexProgramClass clazz, ProgramDefinition context) {
+      // Intentionally empty.
+    }
+
+    @Override
     public Set<DexProgramClass> getSynthesizedClasses() {
       return synthesizedClasses;
     }
 
     @Override
-    public void acceptCollectionConversion(ProgramMethod arrayConversion) {
+    public void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context) {
       synthesizedClasses.add(arrayConversion.getHolder());
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 595d1db..7290623 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -8,8 +8,10 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
+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.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
@@ -71,11 +73,14 @@
       ArtProfileCollectionAdditions artProfileCollectionAdditions,
       ClassConverterResult.Builder classConverterResultBuilder,
       D8MethodProcessor methodProcessor) {
-    CfInstructionDesugaringEventConsumer eventConsumer =
+    D8CfInstructionDesugaringEventConsumer eventConsumer =
         new D8CfInstructionDesugaringEventConsumer(
             appView, classConverterResultBuilder, methodProcessor);
-    return ArtProfileRewritingCfInstructionDesugaringEventConsumer.attach(
-        appView, artProfileCollectionAdditions, eventConsumer);
+    CfInstructionDesugaringEventConsumer outermostEventConsumer =
+        ArtProfileRewritingCfInstructionDesugaringEventConsumer.attach(
+            appView, artProfileCollectionAdditions, eventConsumer);
+    eventConsumer.setOutermostEventConsumer(outermostEventConsumer);
+    return outermostEventConsumer;
   }
 
   public static CfInstructionDesugaringEventConsumer createForR8(
@@ -114,6 +119,8 @@
     private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
     private final List<ConstantDynamicClass> synthesizedConstantDynamicClasses = new ArrayList<>();
 
+    private CfInstructionDesugaringEventConsumer outermostEventConsumer = this;
+
     private D8CfInstructionDesugaringEventConsumer(
         AppView<?> appView,
         ClassConverterResult.Builder classConverterResultBuilder,
@@ -123,6 +130,11 @@
       this.methodProcessor = methodProcessor;
     }
 
+    public void setOutermostEventConsumer(
+        CfInstructionDesugaringEventConsumer outermostEventConsumer) {
+      this.outermostEventConsumer = outermostEventConsumer;
+    }
+
     @Override
     public void acceptDefaultAsCompanionMethod(
         ProgramMethod method, ProgramMethod companionMethod) {
@@ -151,25 +163,27 @@
     }
 
     @Override
-    public void acceptCollectionConversion(ProgramMethod arrayConversion) {
-      methodProcessor.scheduleMethodForProcessing(arrayConversion, this);
+    public void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context) {
+      methodProcessor.scheduleMethodForProcessing(arrayConversion, outermostEventConsumer);
     }
 
     @Override
-    public void acceptCovariantRetargetMethod(ProgramMethod method) {
-      methodProcessor.scheduleMethodForProcessing(method, this);
+    public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
+      methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer);
     }
 
     @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
-      methodProcessor.scheduleMethodForProcessing(backportedMethod, this);
+      methodProcessor.scheduleMethodForProcessing(backportedMethod, outermostEventConsumer);
     }
 
     @Override
     public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
       backportedClass
           .programMethods()
-          .forEach(method -> methodProcessor.scheduleMethodForProcessing(method, this));
+          .forEach(
+              method ->
+                  methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer));
     }
 
     @Override
@@ -215,7 +229,15 @@
     public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
       clazz
           .programMethods()
-          .forEach(method -> methodProcessor.scheduleMethodForProcessing(method, this));
+          .forEach(
+              method ->
+                  methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer));
+    }
+
+    @Override
+    public void acceptVarHandleDesugaringClassContext(
+        DexProgramClass clazz, ProgramDefinition context) {
+      // Intentionally empty.
     }
 
     @Override
@@ -234,6 +256,12 @@
     }
 
     @Override
+    public void acceptConstantDynamicRewrittenBootstrapMethod(
+        ProgramMethod bootstrapMethod, DexMethod oldSignature) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptNestConstructorBridge(
         ProgramMethod target,
         ProgramMethod bridge,
@@ -262,7 +290,7 @@
 
     @Override
     public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
-      methodProcessor.scheduleMethodForProcessing(closeMethod, this);
+      methodProcessor.scheduleMethodForProcessing(closeMethod, outermostEventConsumer);
     }
 
     @Override
@@ -326,7 +354,7 @@
     }
 
     @Override
-    public void acceptAPIConversion(ProgramMethod method) {
+    public void acceptAPIConversionOutline(ProgramMethod method, ProgramMethod context) {
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
@@ -385,7 +413,7 @@
 
     private void finalizeConstantDynamicDesugaring(Consumer<ProgramMethod> needsProcessing) {
       for (ConstantDynamicClass constantDynamicClass : synthesizedConstantDynamicClasses) {
-        constantDynamicClass.rewriteBootstrapMethodSignatureIfNeeded();
+        constantDynamicClass.rewriteBootstrapMethodSignatureIfNeeded(outermostEventConsumer);
         constantDynamicClass.getConstantDynamicProgramClass().forEachProgramMethod(needsProcessing);
       }
       synthesizedConstantDynamicClasses.clear();
@@ -488,7 +516,13 @@
     }
 
     @Override
-    public void acceptCollectionConversion(ProgramMethod arrayConversion) {
+    public void acceptVarHandleDesugaringClassContext(
+        DexProgramClass clazz, ProgramDefinition context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
 
@@ -514,7 +548,7 @@
     }
 
     @Override
-    public void acceptCovariantRetargetMethod(ProgramMethod method) {
+    public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
 
@@ -580,7 +614,7 @@
     }
 
     @Override
-    public void acceptAPIConversion(ProgramMethod method) {
+    public void acceptAPIConversionOutline(ProgramMethod method, ProgramMethod context) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
 
@@ -623,6 +657,12 @@
     }
 
     @Override
+    public void acceptConstantDynamicRewrittenBootstrapMethod(
+        ProgramMethod bootstrapMethod, DexMethod oldSignature) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptNestConstructorBridge(
         ProgramMethod target,
         ProgramMethod bridge,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 29cb618..7a38cf1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -3,15 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
@@ -35,16 +38,18 @@
         DesugaredLibraryAPICallbackSynthesizorEventConsumer {
 
   public static CfPostProcessingDesugaringEventConsumer createForD8(
+      AppView<?> appView,
       ArtProfileCollectionAdditions artProfileCollectionAdditions,
       D8MethodProcessor methodProcessor,
       CfInstructionDesugaringCollection instructionDesugaring) {
     CfPostProcessingDesugaringEventConsumer eventConsumer =
         new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, instructionDesugaring);
     return ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.attach(
-        artProfileCollectionAdditions, eventConsumer);
+        appView, artProfileCollectionAdditions, eventConsumer);
   }
 
   public static CfPostProcessingDesugaringEventConsumer createForR8(
+      AppView<?> appView,
       SyntheticAdditions additions,
       ArtProfileCollectionAdditions artProfileCollectionAdditions,
       CfInstructionDesugaringCollection desugaring,
@@ -52,7 +57,7 @@
     CfPostProcessingDesugaringEventConsumer eventConsumer =
         new R8PostProcessingDesugaringEventConsumer(additions, desugaring, missingClassConsumer);
     return ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.attach(
-        artProfileCollectionAdditions, eventConsumer);
+        appView, artProfileCollectionAdditions, eventConsumer);
   }
 
   public abstract Set<DexMethod> getNewlyLiveMethods();
@@ -93,7 +98,7 @@
     }
 
     @Override
-    public void acceptCovariantRetargetMethod(ProgramMethod method) {
+    public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
       addMethodToReprocess(method);
     }
 
@@ -109,7 +114,8 @@
     }
 
     @Override
-    public void acceptDesugaredLibraryRetargeterForwardingMethod(ProgramMethod method) {
+    public void acceptDesugaredLibraryRetargeterForwardingMethod(
+        ProgramMethod method, EmulatedDispatchMethodDescriptor descriptor) {
       addMethodToReprocess(method);
     }
 
@@ -120,12 +126,13 @@
     }
 
     @Override
-    public void acceptThrowingMethod(ProgramMethod method, DexType errorType) {
+    public void acceptThrowingMethod(
+        ProgramMethod method, DexType errorType, FailedResolutionResult resolutionResult) {
       addMethodToReprocess(method);
     }
 
     @Override
-    public void acceptCollectionConversion(ProgramMethod method) {
+    public void acceptCollectionConversion(ProgramMethod method, ProgramMethod context) {
       addMethodToReprocess(method);
     }
 
@@ -143,8 +150,9 @@
     }
 
     @Override
-    public void acceptAPIConversionCallback(ProgramMethod method) {
-      addMethodToReprocess(method);
+    public void acceptAPIConversionCallback(
+        ProgramMethod callbackMethod, ProgramMethod convertedMethod) {
+      addMethodToReprocess(callbackMethod);
     }
 
     @Override
@@ -215,12 +223,13 @@
     }
 
     @Override
-    public void acceptCovariantRetargetMethod(ProgramMethod method) {
+    public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
       additions.addLiveMethod(method);
     }
 
     @Override
-    public void acceptDesugaredLibraryRetargeterForwardingMethod(ProgramMethod method) {
+    public void acceptDesugaredLibraryRetargeterForwardingMethod(
+        ProgramMethod method, EmulatedDispatchMethodDescriptor descriptor) {
       additions.addLiveMethod(method);
     }
 
@@ -231,19 +240,21 @@
     }
 
     @Override
-    public void acceptThrowingMethod(ProgramMethod method, DexType errorType) {
+    public void acceptThrowingMethod(
+        ProgramMethod method, DexType errorType, FailedResolutionResult resolutionResult) {
       additions.addLiveMethod(method);
     }
 
     @Override
-    public void acceptCollectionConversion(ProgramMethod method) {
+    public void acceptCollectionConversion(ProgramMethod method, ProgramMethod context) {
       additions.addLiveMethod(method);
     }
 
     @Override
-    public void acceptAPIConversionCallback(ProgramMethod method) {
-      assert !desugaring.needsDesugaring(method);
-      additions.addLiveMethod(method);
+    public void acceptAPIConversionCallback(
+        ProgramMethod callbackMethod, ProgramMethod convertedMethod) {
+      assert !desugaring.needsDesugaring(callbackMethod);
+      additions.addLiveMethod(callbackMethod);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 1143afd..788afd1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexApplication;
@@ -53,23 +54,31 @@
 // the contained CovariantReturnType annotations.
 public final class CovariantReturnTypeAnnotationTransformer {
 
+  private final AppView<?> appView;
   private final IRConverter converter;
-  private final MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
+  private final MethodProcessorEventConsumer methodProcessorEventConsumer =
+      MethodProcessorEventConsumer.empty();
   private final DexItemFactory factory;
 
-  public CovariantReturnTypeAnnotationTransformer(IRConverter converter, DexItemFactory factory) {
+  public CovariantReturnTypeAnnotationTransformer(AppView<?> appView, IRConverter converter) {
+    this.appView = appView;
     this.converter = converter;
-    this.factory = factory;
+    this.factory = appView.dexItemFactory();
   }
 
-  public void process(DexApplication.Builder<?> builder) {
+  public void process(
+      DexApplication.Builder<?> builder,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
     // List of methods that should be added to the next class.
     List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation = new LinkedList<>();
     List<DexEncodedMethod> covariantReturnTypeMethods = new LinkedList<>();
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       // Construct the methods that should be added to clazz.
       buildCovariantReturnTypeMethodsForClass(
-          clazz, methodsWithCovariantReturnTypeAnnotation, covariantReturnTypeMethods);
+          clazz,
+          methodsWithCovariantReturnTypeAnnotation,
+          covariantReturnTypeMethods,
+          eventConsumer);
       if (covariantReturnTypeMethods.isEmpty()) {
         continue;
       }
@@ -111,12 +120,14 @@
   private void buildCovariantReturnTypeMethodsForClass(
       DexProgramClass clazz,
       List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation,
-      List<DexEncodedMethod> covariantReturnTypeMethods) {
+      List<DexEncodedMethod> covariantReturnTypeMethods,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
     clazz.forEachProgramVirtualMethod(
         method -> {
           if (methodHasCovariantReturnTypeAnnotation(method.getDefinition())) {
             methodsWithCovariantReturnTypeAnnotation.add(method.getDefinition());
-            buildCovariantReturnTypeMethodsForMethod(method, covariantReturnTypeMethods);
+            buildCovariantReturnTypeMethodsForMethod(
+                method, covariantReturnTypeMethods, eventConsumer);
           }
         });
   }
@@ -134,11 +145,13 @@
   // variantReturnTypes annotations on the given method. Adds the newly constructed, synthetic
   // methods to the list covariantReturnTypeMethods.
   private void buildCovariantReturnTypeMethodsForMethod(
-      ProgramMethod method, List<DexEncodedMethod> covariantReturnTypeMethods) {
+      ProgramMethod method,
+      List<DexEncodedMethod> covariantReturnTypeMethods,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
     assert methodHasCovariantReturnTypeAnnotation(method.getDefinition());
     for (DexType covariantReturnType : getCovariantReturnTypes(method)) {
       DexEncodedMethod covariantReturnTypeMethod =
-          buildCovariantReturnTypeMethod(method, covariantReturnType);
+          buildCovariantReturnTypeMethod(method, covariantReturnType, eventConsumer);
       covariantReturnTypeMethods.add(covariantReturnTypeMethod);
     }
   }
@@ -149,7 +162,9 @@
   //
   // Note: any "synchronized" or "strictfp" modifier could be dropped safely.
   private DexEncodedMethod buildCovariantReturnTypeMethod(
-      ProgramMethod method, DexType covariantReturnType) {
+      ProgramMethod method,
+      DexType covariantReturnType,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
     DexProgramClass methodHolder = method.getHolder();
     DexMethod methodReference = method.getReference();
     DexEncodedMethod methodDefinition = method.getDefinition();
@@ -183,7 +198,8 @@
             .build();
     // Optimize to generate DexCode instead of CfCode.
     ProgramMethod programMethod = new ProgramMethod(methodHolder, newVirtualMethod);
-    converter.optimizeSynthesizedMethod(programMethod, eventConsumer);
+    converter.optimizeSynthesizedMethod(programMethod, methodProcessorEventConsumer);
+    eventConsumer.acceptCovariantReturnTypeBridgeMethod(programMethod, method);
     return newVirtualMethod;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformerEventConsumer.java
new file mode 100644
index 0000000..dc4add0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformerEventConsumer.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer;
+
+public interface CovariantReturnTypeAnnotationTransformerEventConsumer {
+
+  void acceptCovariantReturnTypeBridgeMethod(ProgramMethod bridge, ProgramMethod target);
+
+  static CovariantReturnTypeAnnotationTransformerEventConsumer create(
+      ArtProfileCollectionAdditions artProfileCollectionAdditions) {
+    if (artProfileCollectionAdditions.isNop()) {
+      return empty();
+    }
+    return ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.attach(
+        artProfileCollectionAdditions, empty());
+  }
+
+  static EmptyCovariantReturnTypeAnnotationTransformerEventConsumer empty() {
+    return EmptyCovariantReturnTypeAnnotationTransformerEventConsumer.getInstance();
+  }
+
+  class EmptyCovariantReturnTypeAnnotationTransformerEventConsumer
+      implements CovariantReturnTypeAnnotationTransformerEventConsumer {
+
+    private static final EmptyCovariantReturnTypeAnnotationTransformerEventConsumer INSTANCE =
+        new EmptyCovariantReturnTypeAnnotationTransformerEventConsumer();
+
+    private EmptyCovariantReturnTypeAnnotationTransformerEventConsumer() {}
+
+    static EmptyCovariantReturnTypeAnnotationTransformerEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptCovariantReturnTypeBridgeMethod(ProgramMethod bridge, ProgramMethod target) {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 292d844..5f65c95 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -94,25 +94,8 @@
     if (context.getDefinition().isD8R8Synthesized()) {
       return appView.computedMinApiLevel();
     }
-    DexReference reference;
-    if (instruction.isInvoke()) {
-      CfInvoke cfInvoke = instruction.asInvoke();
-      if (cfInvoke.isInvokeSpecial()) {
-        return appView.computedMinApiLevel();
-      }
-      reference = cfInvoke.getMethod();
-    } else if (instruction.isFieldInstruction()) {
-      reference = instruction.asFieldInstruction().getField();
-    } else if (instruction.isCheckCast()) {
-      reference = instruction.asCheckCast().getType();
-    } else if (instruction.isInstanceOf()) {
-      reference = instruction.asInstanceOf().getType();
-    } else if (instruction.isConstClass()) {
-      reference = instruction.asConstClass().getType();
-    } else {
-      return appView.computedMinApiLevel();
-    }
-    if (!reference.getContextType().isClassType()) {
+    DexReference reference = getReferenceFromInstruction(instruction);
+    if (reference == null || !reference.getContextType().isClassType()) {
       return appView.computedMinApiLevel();
     }
     DexClass holder = appView.definitionFor(reference.getContextType());
@@ -158,6 +141,22 @@
     }
   }
 
+  private DexReference getReferenceFromInstruction(CfInstruction instruction) {
+    if (instruction.isFieldInstruction()) {
+      return instruction.asFieldInstruction().getField();
+    } else if (instruction.isCheckCast()) {
+      return instruction.asCheckCast().getType();
+    } else if (instruction.isInstanceOf()) {
+      return instruction.asInstanceOf().getType();
+    } else if (instruction.isConstClass()) {
+      return instruction.asConstClass().getType();
+    } else if (instruction.isInvoke() && !instruction.asInvoke().isInvokeSpecial()) {
+      return instruction.asInvoke().getMethod();
+    } else {
+      return null;
+    }
+  }
+
   private DexEncodedMember<?, ?> simpleLookupInClassHierarchy(
       DexLibraryClass holder, Function<DexClass, DexEncodedMember<?, ?>> lookup) {
     DexEncodedMember<?, ?> result = lookup.apply(holder);
@@ -208,10 +207,20 @@
       ComputedApiLevel apiLevel,
       DexItemFactory factory,
       ProgramMethod programContext) {
+    DexReference reference = getReferenceFromInstruction(instruction);
+    assert reference != null;
+    DexClass holder = appView.definitionFor(reference.getContextType());
+    assert holder != null;
     return appView
         .getSyntheticItems()
         .createMethod(
-            kinds -> kinds.API_MODEL_OUTLINE,
+            kinds ->
+                // We've already checked that the definition the reference is targeting is public
+                // when computing the api-level for desugaring. We still have to ensure that the
+                // class cannot be merged globally if it is package private.
+                holder.isPublic()
+                    ? kinds.API_MODEL_OUTLINE
+                    : kinds.API_MODEL_OUTLINE_WITHOUT_GLOBAL_MERGING,
             context,
             appView,
             syntheticMethodBuilder -> {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 5105707..161262d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -355,13 +355,14 @@
     this.clazz = clazz;
   }
 
-  public void rewriteBootstrapMethodSignatureIfNeeded() {
+  public void rewriteBootstrapMethodSignatureIfNeeded(
+      ConstantDynamicDesugaringEventConsumer eventConsumer) {
     if (!shouldRewriteBootstrapMethodSignature() || behaviour != CACHE_CONSTANT) {
       return;
     }
     DexProgramClass bootstrapMethodHolder =
         appView.definitionFor(bootstrapMethodReference.getHolderType()).asProgramClass();
-    DexEncodedMethod replacement =
+    DexEncodedMethod finalDefinition =
         bootstrapMethodHolder
             .getMethodCollection()
             .replaceDirectMethod(
@@ -385,18 +386,22 @@
                   newMethod.copyMetadata(appView, encodedMethod);
                   return newMethod;
                 });
-    if (replacement != null) {
+    ProgramMethod finalMethod;
+    if (finalDefinition != null) {
       // Since we've copied the code object from an existing method, the code should already be
       // processed, and thus we don't need to schedule it for processing in D8.
-      assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
-      assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
+      assert !appView.options().isGeneratingClassFiles() || finalDefinition.getCode().isCfCode();
+      assert !appView.options().isGeneratingDex() || finalDefinition.getCode().isDexCode();
+      finalMethod = finalDefinition.asProgramMethod(bootstrapMethodHolder);
+      eventConsumer.acceptConstantDynamicRewrittenBootstrapMethod(
+          finalMethod, bootstrapMethodReference);
+    } else {
+      finalMethod = bootstrapMethodHolder.lookupProgramMethod(finalBootstrapMethodReference);
     }
     // The method might already have been moved by another dynamic constant targeting it.
     // If so, it must be defined on the holder.
-    ProgramMethod modified =
-        bootstrapMethodHolder.lookupProgramMethod(finalBootstrapMethodReference);
-    assert modified != null;
-    assert modified.getDefinition().isPublicMethod();
+    assert finalMethod != null;
+    assert finalMethod.getDefinition().isPublicMethod();
   }
 
   private DexType mapLookupTypeToObject(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
index 052b8b9..d487ccb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
@@ -3,11 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.constantdynamic;
 
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizationsEventConsumer;
 
 public interface ConstantDynamicDesugaringEventConsumer
     extends UtilityMethodsForCodeOptimizationsEventConsumer {
 
-  void acceptConstantDynamicClass(ConstantDynamicClass lambdaClass, ProgramMethod context);
+  void acceptConstantDynamicClass(ConstantDynamicClass constantDynamicClass, ProgramMethod context);
+
+  void acceptConstantDynamicRewrittenBootstrapMethod(
+      ProgramMethod bootstrapMethod, DexMethod oldSignature);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index 7907037..9a75734 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -213,9 +213,9 @@
     if (method.getDefinition().isLibraryMethodOverride().isTrue()) {
       newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
-    ProgramMethod callback = new ProgramMethod(clazz, newMethod);
+    ProgramMethod callback = newMethod.asProgramMethod(clazz);
     assert eventConsumer != null;
-    eventConsumer.acceptAPIConversionCallback(callback);
+    eventConsumer.acceptAPIConversionCallback(callback, method);
     return callback;
   }
 
@@ -260,7 +260,7 @@
                                         parameterConversions,
                                         invoke.getOpcode())
                                     .generateCfCode()));
-    eventConsumer.acceptAPIConversion(outline);
+    eventConsumer.acceptAPIConversionOutline(outline, context);
     return outline;
   }
 
@@ -362,7 +362,8 @@
                             methodSignature ->
                                 computeParameterConversionCfCode(
                                     methodSignature.holder, invokedMethod, parameterConversions)));
-    eventConsumer.acceptAPIConversion(parameterConversion);
+    eventConsumer.acceptAPIConversionOutline(
+        parameterConversion, methodProcessingContext.getMethodContext());
     cfInstructions.add(
         new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false));
     int arrayLocal = freshLocalProvider.getFreshLocal(ValueType.OBJECT.requiredRegisters());
@@ -461,6 +462,7 @@
                 destIsVivified,
                 apiConversionCollection,
                 eventConsumer,
+                context,
                 contextSupplier),
         context);
   }
@@ -479,6 +481,7 @@
                 destIsVivified,
                 apiConversionCollection,
                 eventConsumer,
+                context,
                 contextSupplier),
         context);
   }
@@ -507,7 +510,12 @@
         wrapperSynthesizer,
         (argType, apiGenericTypesConversion) ->
             wrapperSynthesizer.ensureConversionMethod(
-                argType, destIsVivified, apiGenericTypesConversion, eventConsumer, contextSupplier),
+                argType,
+                destIsVivified,
+                apiGenericTypesConversion,
+                eventConsumer,
+                context,
+                contextSupplier),
         context);
   }
 
@@ -522,7 +530,12 @@
         wrapperSynthesizer,
         (argType, apiGenericTypesConversion) ->
             wrapperSynthesizer.getExistingProgramConversionMethod(
-                argType, destIsVivified, apiGenericTypesConversion, eventConsumer, contextSupplier),
+                argType,
+                destIsVivified,
+                apiGenericTypesConversion,
+                eventConsumer,
+                context,
+                contextSupplier),
         context);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 40f9d67..cc5d7d6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -197,6 +197,7 @@
       boolean destIsVivified,
       DexMethod apiGenericTypesConversion,
       DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      ProgramMethod context,
       Supplier<UniqueContext> contextSupplier) {
     if (apiGenericTypesConversion != null) {
       assert !type.isArrayType();
@@ -205,7 +206,8 @@
     DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
     DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
     if (type.isArrayType()) {
-      return ensureArrayConversionMethod(type, srcType, destType, eventConsumer, contextSupplier);
+      return ensureArrayConversionMethod(
+          type, srcType, destType, eventConsumer, context, contextSupplier);
     }
     DexMethod customConversion = getCustomConversion(type, srcType, destType);
     if (customConversion != null) {
@@ -231,6 +233,7 @@
       DexType srcType,
       DexType destType,
       DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      ProgramMethod context,
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         ensureConversionMethod(
@@ -238,9 +241,10 @@
             srcType == type,
             null,
             eventConsumer,
+            context,
             contextSupplier);
     return ensureArrayConversionMethod(
-        srcType, destType, eventConsumer, contextSupplier, conversion);
+        srcType, destType, eventConsumer, context, contextSupplier, conversion);
   }
 
   private DexMethod ensureArrayConversionMethodFromExistingBaseConversion(
@@ -248,6 +252,7 @@
       DexType srcType,
       DexType destType,
       DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+      ProgramMethod context,
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         getExistingProgramConversionMethod(
@@ -255,15 +260,17 @@
             srcType == type,
             null,
             eventConsumer,
+            context,
             contextSupplier);
     return ensureArrayConversionMethod(
-        srcType, destType, eventConsumer, contextSupplier, conversion);
+        srcType, destType, eventConsumer, context, contextSupplier, conversion);
   }
 
   private DexMethod ensureArrayConversionMethod(
       DexType srcType,
       DexType destType,
       DesugaredLibraryWrapperSynthesizerEventConsumer eventConsumer,
+      ProgramMethod context,
       Supplier<UniqueContext> contextSupplier,
       DexMethod conversion) {
     ProgramMethod arrayConversion =
@@ -286,7 +293,7 @@
                                         destType,
                                         conversion)
                                     .generateCfCode()));
-    eventConsumer.acceptCollectionConversion(arrayConversion);
+    eventConsumer.acceptCollectionConversion(arrayConversion, context);
     return arrayConversion.getReference();
   }
 
@@ -295,6 +302,7 @@
       boolean destIsVivified,
       DexMethod apiGenericTypesConversion,
       DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+      ProgramMethod context,
       Supplier<UniqueContext> contextSupplier) {
     if (apiGenericTypesConversion != null) {
       assert !type.isArrayType();
@@ -304,7 +312,7 @@
     DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
     if (type.isArrayType()) {
       return ensureArrayConversionMethodFromExistingBaseConversion(
-          type, srcType, destType, eventConsumer, contextSupplier);
+          type, srcType, destType, eventConsumer, context, contextSupplier);
     }
     DexMethod customConversion = getCustomConversion(type, srcType, destType);
     if (customConversion != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java
index 4adc11f..9a57f6b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizerEventConsumer.java
@@ -10,7 +10,7 @@
 
 public interface DesugaredLibraryWrapperSynthesizerEventConsumer {
 
-  void acceptCollectionConversion(ProgramMethod arrayConversion);
+  void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context);
 
   interface DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer
       extends DesugaredLibraryWrapperSynthesizerEventConsumer {
@@ -33,12 +33,12 @@
   interface DesugaredLibraryAPIConverterEventConsumer
       extends DesugaredLibraryClasspathWrapperSynthesizeEventConsumer {
 
-    void acceptAPIConversion(ProgramMethod method);
+    void acceptAPIConversionOutline(ProgramMethod method, ProgramMethod context);
   }
 
   interface DesugaredLibraryAPICallbackSynthesizorEventConsumer
       extends DesugaredLibraryClasspathWrapperSynthesizeEventConsumer {
 
-    void acceptAPIConversionCallback(ProgramMethod method);
+    void acceptAPIConversionCallback(ProgramMethod callbackMethod, ProgramMethod convertedMethod);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
index 859bc95..df5c8d0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -14,6 +13,7 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.FieldAnnotation;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.SupportedClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -24,7 +24,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
-import java.util.stream.StreamSupport;
 
 public class GenerateHtmlDoc extends AbstractGenerateFiles {
 
@@ -91,8 +90,8 @@
 
   private abstract static class SourceBuilder<B extends GenerateHtmlDoc.SourceBuilder> {
 
-    protected final DexClass clazz;
-    protected List<DexEncodedField> fields = new ArrayList<>();
+    protected Map<DexEncodedField, FieldAnnotation> fields =
+        new TreeMap<>(Comparator.comparing(DexEncodedField::getReference));
     protected Map<DexEncodedMethod, MethodAnnotation> constructors =
         new TreeMap<>(Comparator.comparing(DexEncodedMethod::getReference));
     protected Map<DexEncodedMethod, MethodAnnotation> methods =
@@ -101,17 +100,16 @@
     String className;
     String packageName;
 
-    private SourceBuilder(DexClass clazz) {
-      this.clazz = clazz;
-      this.className = clazz.type.toSourceString();
+    private SourceBuilder(DexType classType) {
+      this.className = classType.toSourceString();
       int index = this.className.lastIndexOf('.');
       this.packageName = index > 0 ? this.className.substring(0, index) : "";
     }
 
     public abstract B self();
 
-    private B addField(DexEncodedField field) {
-      fields.add(field);
+    private B addField(DexEncodedField field, FieldAnnotation fieldAnnotation) {
+      fields.put(field, fieldAnnotation);
       return self();
     }
 
@@ -386,8 +384,8 @@
     private boolean unsupportedInMinApiRange = false;
     private boolean covariantReturnSupported = false;
 
-    public HTMLSourceBuilder(DexClass clazz, ClassAnnotation classAnnotation) {
-      super(clazz);
+    public HTMLSourceBuilder(DexType classType, ClassAnnotation classAnnotation) {
+      super(classType);
       this.classAnnotation = classAnnotation;
     }
 
@@ -396,6 +394,18 @@
       return this;
     }
 
+    private String getTextAnnotations(FieldAnnotation annotation) {
+      if (annotation == null) {
+        return "";
+      }
+      StringBuilder stringBuilder = new StringBuilder();
+      if (annotation.unsupportedInMinApiRange) {
+        stringBuilder.append(SUP_3);
+        unsupportedInMinApiRange = true;
+      }
+      return stringBuilder.toString();
+    }
+
     private String getTextAnnotations(MethodAnnotation annotation) {
       if (annotation == null) {
         return "";
@@ -434,13 +444,14 @@
               "ul style=\"list-style-position:inside; list-style-type: none !important;"
                   + " margin-left:0px;padding-left:0px !important;\"");
       if (!fields.isEmpty()) {
-        for (DexEncodedField field : fields) {
+        for (DexEncodedField field : fields.keySet()) {
           builder.appendLiCode(
               accessFlags(field.accessFlags)
                   + " "
                   + typeInPackage(field.getReference().type)
                   + " "
-                  + field.getReference().name);
+                  + field.getReference().name
+                  + getTextAnnotations(fields.get(field)));
         }
       }
       if (!constructors.isEmpty()) {
@@ -470,16 +481,19 @@
       if (classAnnotation.isFullySupported()) {
         commentBuilder.append("Fully implemented class.").append(HTML_SPLIT);
       }
+      if (classAnnotation.isAdditionalMembersOnClass()) {
+        commentBuilder.append("Additional methods on existing class.").append(HTML_SPLIT);
+      }
       if (parallelStreamMethod) {
         commentBuilder
             .append(SUP_1)
-            .append("Supported only on devices which API level is 21 or higher.")
+            .append(" Supported only on devices which API level is 21 or higher.")
             .append(HTML_SPLIT);
       }
       if (missingFromLatestAndroidJar) {
         commentBuilder
             .append(SUP_2)
-            .append("Not present in Android ")
+            .append(" Not present in Android ")
             .append(MAX_TESTED_ANDROID_API_LEVEL)
             .append(" (May not resolve at compilation).")
             .append(HTML_SPLIT);
@@ -511,18 +525,13 @@
   }
 
   private void generateClassHTML(PrintStream ps, SupportedClass supportedClass) {
-    DexClass clazz = supportedClass.getClazz();
+    DexType classType = supportedClass.getType();
     SourceBuilder<HTMLSourceBuilder> builder =
-        new HTMLSourceBuilder(clazz, supportedClass.getClassAnnotation());
-    // We need to extend to support fields.
-    StreamSupport.stream(clazz.fields().spliterator(), false)
-        .filter(field -> field.accessFlags.isPublic() || field.accessFlags.isProtected())
-        .sorted(Comparator.comparing(DexEncodedField::toSourceString))
-        .forEach(builder::addField);
+        new HTMLSourceBuilder(classType, supportedClass.getClassAnnotation());
+    supportedClass.forEachFieldAndAnnotation(builder::addField);
     supportedClass.forEachMethodAndAnnotation(
         (method, methodAnnotation) -> {
-          if ((method.accessFlags.isPublic() || method.accessFlags.isProtected())
-              && !method.accessFlags.isBridge()) {
+          if (!method.accessFlags.isBridge()) {
             builder.addMethod(method, methodAnnotation);
           }
         });
@@ -531,10 +540,15 @@
 
   @Override
   AndroidApiLevel run() throws Exception {
-    PrintStream ps = new PrintStream(Files.newOutputStream(outputDirectory.resolve("apis.html")));
+    return run("apis.html");
+  }
+
+  public AndroidApiLevel run(String outputFileName) throws Exception {
+    PrintStream ps =
+        new PrintStream(Files.newOutputStream(outputDirectory.resolve(outputFileName)));
 
     SupportedClasses supportedClasses =
-        new SupportedMethodsGenerator(options)
+        new SupportedClassesGenerator(options)
             .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
 
     // Full classes added.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index b7a1a27..63dec3c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -76,7 +76,7 @@
   }
 
   private void addMethodsToHeaderJar(
-      DexApplication.Builder builder, DexClass clazz, List<DexEncodedMethod> methods) {
+      DexApplication.Builder builder, DexClass clazz, Collection<DexEncodedMethod> methods) {
     if (methods.size() == 0) {
       return;
     }
@@ -244,7 +244,7 @@
     AndroidApiLevel compilationLevel =
         desugaredLibrarySpecification.getRequiredCompilationApiLevel();
     SupportedClasses supportedMethods =
-        new SupportedMethodsGenerator(options)
+        new SupportedClassesGenerator(options)
             .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
     System.out.println("Generating lint files for compile API " + compilationLevel);
     generateLintFiles(compilationLevel, AndroidApiLevel.B, supportedMethods);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
index 3c1cfd6..f6eb4e4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -5,17 +5,20 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSortedMap;
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.SortedMap;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -34,18 +37,27 @@
 
     private final DexClass clazz;
     private final ClassAnnotation classAnnotation;
-    private final List<DexEncodedMethod> supportedMethods;
+    // We need the encoded version to be able to access the flags and infer parameter names from
+    // the debug info. However, we analyze multiple applications and the encoded versions are not
+    // unique, but the keys are.
+    private final SortedMap<DexMethod, DexEncodedMethod> supportedMethods;
+    private final SortedMap<DexField, DexEncodedField> supportedFields;
     private final Map<DexMethod, MethodAnnotation> methodAnnotations;
+    private final Map<DexField, FieldAnnotation> fieldAnnotations;
 
     private SupportedClass(
         DexClass clazz,
         ClassAnnotation classAnnotation,
-        List<DexEncodedMethod> supportedMethods,
-        Map<DexMethod, MethodAnnotation> methodAnnotations) {
+        SortedMap<DexMethod, DexEncodedMethod> supportedMethods,
+        SortedMap<DexField, DexEncodedField> supportedFields,
+        Map<DexMethod, MethodAnnotation> methodAnnotations,
+        Map<DexField, FieldAnnotation> fieldAnnotations) {
       this.clazz = clazz;
       this.classAnnotation = classAnnotation;
       this.supportedMethods = supportedMethods;
+      this.supportedFields = supportedFields;
       this.methodAnnotations = methodAnnotations;
+      this.fieldAnnotations = fieldAnnotations;
     }
 
     public DexType getType() {
@@ -60,13 +72,14 @@
       return classAnnotation;
     }
 
-    public List<DexEncodedMethod> getSupportedMethods() {
-      return supportedMethods;
+    public Collection<DexEncodedMethod> getSupportedMethods() {
+      // Since the map is sorted, the values are sorted.
+      return supportedMethods.values();
     }
 
     public void forEachMethodAndAnnotation(
         BiConsumer<DexEncodedMethod, MethodAnnotation> biConsumer) {
-      for (DexEncodedMethod supportedMethod : supportedMethods) {
+      for (DexEncodedMethod supportedMethod : supportedMethods.values()) {
         biConsumer.accept(supportedMethod, getMethodAnnotation(supportedMethod.getReference()));
       }
     }
@@ -75,6 +88,16 @@
       return methodAnnotations.get(method);
     }
 
+    public void forEachFieldAndAnnotation(BiConsumer<DexEncodedField, FieldAnnotation> biConsumer) {
+      for (DexEncodedField supportedField : supportedFields.values()) {
+        biConsumer.accept(supportedField, getFieldAnnotation(supportedField.getReference()));
+      }
+    }
+
+    public FieldAnnotation getFieldAnnotation(DexField field) {
+      return fieldAnnotations.get(field);
+    }
+
     static Builder builder(DexClass clazz) {
       return new Builder(clazz);
     }
@@ -83,31 +106,44 @@
 
       private final DexClass clazz;
       private ClassAnnotation classAnnotation;
-      private final List<DexEncodedMethod> supportedMethods = new ArrayList<>();
+      private final Map<DexMethod, DexEncodedMethod> supportedMethods = new IdentityHashMap<>();
+      private final Map<DexField, DexEncodedField> supportedFields = new IdentityHashMap<>();
       private final Map<DexMethod, MethodAnnotation> methodAnnotations = new HashMap<>();
+      private final Map<DexField, FieldAnnotation> fieldAnnotations = new HashMap<>();
 
       private Builder(DexClass clazz) {
         this.clazz = clazz;
       }
 
-      void forEachMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
-        biConsumer.accept(clazz, supportedMethods);
+      void forEachMethods(BiConsumer<DexClass, Collection<DexEncodedMethod>> biConsumer) {
+        biConsumer.accept(clazz, supportedMethods.values());
       }
 
       void forEachMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
-        for (DexEncodedMethod dexEncodedMethod : supportedMethods) {
+        for (DexEncodedMethod dexEncodedMethod : supportedMethods.values()) {
           biConsumer.accept(clazz, dexEncodedMethod);
         }
       }
 
+      void forEachField(BiConsumer<DexClass, DexEncodedField> biConsumer) {
+        for (DexEncodedField dexEncodedField : supportedFields.values()) {
+          biConsumer.accept(clazz, dexEncodedField);
+        }
+      }
+
       void addSupportedMethod(DexEncodedMethod method) {
         assert method.getHolderType() == clazz.type;
-        supportedMethods.add(method);
+        supportedMethods.put(method.getReference(), method);
+      }
+
+      void addSupportedField(DexEncodedField field) {
+        assert field.getHolderType() == clazz.type;
+        supportedFields.put(field.getReference(), field);
       }
 
       void annotateClass(ClassAnnotation annotation) {
         assert annotation != null;
-        assert classAnnotation == null;
+        assert classAnnotation == null || annotation == classAnnotation;
         classAnnotation = annotation;
       }
 
@@ -118,14 +154,24 @@
         methodAnnotations.put(method, annotation.combine(prev));
       }
 
+      void annotateField(DexField field, FieldAnnotation annotation) {
+        assert field.getHolderType() == clazz.type;
+        FieldAnnotation prev = fieldAnnotations.getOrDefault(field, FieldAnnotation.getDefault());
+        fieldAnnotations.put(field, annotation.combine(prev));
+      }
+
       MethodAnnotation getMethodAnnotation(DexMethod method) {
         return methodAnnotations.get(method);
       }
 
       SupportedClass build() {
-        supportedMethods.sort(Comparator.comparing(DexEncodedMethod::getReference));
         return new SupportedClass(
-            clazz, classAnnotation, ImmutableList.copyOf(supportedMethods), methodAnnotations);
+            clazz,
+            classAnnotation,
+            ImmutableSortedMap.copyOf(supportedMethods),
+            ImmutableSortedMap.copyOf(supportedFields),
+            methodAnnotations,
+            fieldAnnotations);
       }
     }
   }
@@ -138,7 +184,13 @@
 
     Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
 
-    void forEachClassAndMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
+    ClassAnnotation getClassAnnotation(DexType type) {
+      SupportedClass.Builder builder = supportedClassBuilders.get(type);
+      assert builder != null;
+      return builder.classAnnotation;
+    }
+
+    void forEachClassAndMethods(BiConsumer<DexClass, Collection<DexEncodedMethod>> biConsumer) {
       supportedClassBuilders
           .values()
           .forEach(classBuilder -> classBuilder.forEachMethods(biConsumer));
@@ -150,6 +202,12 @@
           .forEach(classBuilder -> classBuilder.forEachMethod(biConsumer));
     }
 
+    void forEachClassAndField(BiConsumer<DexClass, DexEncodedField> biConsumer) {
+      supportedClassBuilders
+          .values()
+          .forEach(classBuilder -> classBuilder.forEachField(biConsumer));
+    }
+
     void addSupportedMethod(DexClass holder, DexEncodedMethod method) {
       SupportedClass.Builder classBuilder =
           supportedClassBuilders.computeIfAbsent(
@@ -157,6 +215,13 @@
       classBuilder.addSupportedMethod(method);
     }
 
+    void addSupportedField(DexClass holder, DexEncodedField field) {
+      SupportedClass.Builder classBuilder =
+          supportedClassBuilders.computeIfAbsent(
+              holder.type, clazz -> SupportedClass.builder(holder));
+      classBuilder.addSupportedField(field);
+    }
+
     void annotateClass(DexType type, ClassAnnotation annotation) {
       SupportedClass.Builder classBuilder = supportedClassBuilders.get(type);
       assert classBuilder != null;
@@ -169,6 +234,12 @@
       classBuilder.annotateMethod(method, annotation);
     }
 
+    void annotateField(DexField field, FieldAnnotation annotation) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(field.getHolderType());
+      assert classBuilder != null;
+      classBuilder.annotateField(field, annotation);
+    }
+
     void annotateMethodIfPresent(DexMethod method, MethodAnnotation annotation) {
       SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
       if (classBuilder == null) {
@@ -195,16 +266,34 @@
 
   static class ClassAnnotation {
 
+    private final boolean additionalMembersOnClass;
     private final boolean fullySupported;
     // Methods in latest android.jar but unsupported.
     private final List<DexMethod> unsupportedMethods;
 
     public ClassAnnotation(boolean fullySupported, List<DexMethod> unsupportedMethods) {
+      this.additionalMembersOnClass = false;
       this.fullySupported = fullySupported;
       unsupportedMethods.sort(Comparator.naturalOrder());
       this.unsupportedMethods = ImmutableList.copyOf(unsupportedMethods);
     }
 
+    private ClassAnnotation() {
+      this.additionalMembersOnClass = true;
+      this.fullySupported = false;
+      this.unsupportedMethods = ImmutableList.of();
+    }
+
+    private static final ClassAnnotation ADDITIONNAL_MEMBERS_ON_CLASS = new ClassAnnotation();
+
+    public static ClassAnnotation getAdditionnalMembersOnClass() {
+      return ADDITIONNAL_MEMBERS_ON_CLASS;
+    }
+
+    public boolean isAdditionalMembersOnClass() {
+      return additionalMembersOnClass;
+    }
+
     public boolean isFullySupported() {
       return fullySupported;
     }
@@ -214,62 +303,17 @@
     }
   }
 
-  public static class MethodAnnotation {
-
-    private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
-        new MethodAnnotation(false, false, true, false, -1, -1);
-    private static final MethodAnnotation DEFAULT =
-        new MethodAnnotation(false, false, false, false, -1, -1);
-    private static final MethodAnnotation PARALLEL_STREAM_METHOD =
-        new MethodAnnotation(true, false, false, false, -1, -1);
-    private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
-        new MethodAnnotation(false, true, false, false, -1, -1);
-
-    // ParallelStream methods are not supported when the runtime api level is strictly below 21.
-    final boolean parallelStreamMethod;
-    // Methods not in the latest android jar but still fully supported.
-    final boolean missingFromLatestAndroidJar;
-    // Methods not supported in a given min api range.
+  public abstract static class MemberAnnotation {
     final boolean unsupportedInMinApiRange;
-    final boolean covariantReturnSupported;
     final int minRange;
     final int maxRange;
 
-    MethodAnnotation(
-        boolean parallelStreamMethod,
-        boolean missingFromLatestAndroidJar,
-        boolean covariantReturnSupported,
-        boolean unsupportedInMinApiRange,
-        int minRange,
-        int maxRange) {
-      this.parallelStreamMethod = parallelStreamMethod;
-      this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
-      this.covariantReturnSupported = covariantReturnSupported;
+    MemberAnnotation(boolean unsupportedInMinApiRange, int minRange, int maxRange) {
       this.unsupportedInMinApiRange = unsupportedInMinApiRange;
       this.minRange = minRange;
       this.maxRange = maxRange;
     }
 
-    public static MethodAnnotation getCovariantReturnSupported() {
-      return COVARIANT_RETURN_SUPPORTED;
-    }
-
-    public static MethodAnnotation getDefault() {
-      return DEFAULT;
-    }
-
-    public static MethodAnnotation getParallelStreamMethod() {
-      return PARALLEL_STREAM_METHOD;
-    }
-
-    public static MethodAnnotation getMissingFromLatestAndroidJar() {
-      return MISSING_FROM_LATEST_ANDROID_JAR;
-    }
-
-    public static MethodAnnotation createMissingInMinApi(int api) {
-      return new MethodAnnotation(false, false, false, true, api, api);
-    }
-
     public boolean isUnsupportedInMinApiRange() {
       return unsupportedInMinApiRange;
     }
@@ -282,17 +326,7 @@
       return maxRange;
     }
 
-    public boolean isCovariantReturnSupported() {
-      return covariantReturnSupported;
-    }
-
-    public MethodAnnotation combine(MethodAnnotation other) {
-      if (this == getDefault()) {
-        return other;
-      }
-      if (other == getDefault()) {
-        return this;
-      }
+    int combineRange(MemberAnnotation other) {
       int newMin, newMax;
       if (!unsupportedInMinApiRange && !other.unsupportedInMinApiRange) {
         newMin = newMax = -1;
@@ -320,6 +354,106 @@
           }
         }
       }
+      assert newMax < (1 << 15) && newMin < (1 << 15);
+      return (newMax << 16) + newMin;
+    }
+  }
+
+  public static class FieldAnnotation extends MemberAnnotation {
+
+    private static final FieldAnnotation DEFAULT = new FieldAnnotation(false, -1, -1);
+
+    FieldAnnotation(boolean unsupportedInMinApiRange, int minRange, int maxRange) {
+      super(unsupportedInMinApiRange, minRange, maxRange);
+    }
+
+    public static FieldAnnotation getDefault() {
+      return DEFAULT;
+    }
+
+    public static FieldAnnotation createMissingInMinApi(int api) {
+      return new FieldAnnotation(true, api, api);
+    }
+
+    public FieldAnnotation combine(FieldAnnotation other) {
+      if (this == getDefault()) {
+        return other;
+      }
+      if (other == getDefault()) {
+        return this;
+      }
+      int newRange = combineRange(other);
+      int newMax = newRange >> 16;
+      int newMin = newRange & 0xFF;
+      return new FieldAnnotation(
+          unsupportedInMinApiRange || other.unsupportedInMinApiRange, newMin, newMax);
+    }
+  }
+
+  public static class MethodAnnotation extends MemberAnnotation {
+
+    private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
+        new MethodAnnotation(false, false, true, false, -1, -1);
+    private static final MethodAnnotation DEFAULT =
+        new MethodAnnotation(false, false, false, false, -1, -1);
+    private static final MethodAnnotation PARALLEL_STREAM_METHOD =
+        new MethodAnnotation(true, false, false, false, -1, -1);
+    private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
+        new MethodAnnotation(false, true, false, false, -1, -1);
+
+    // ParallelStream methods are not supported when the runtime api level is strictly below 21.
+    final boolean parallelStreamMethod;
+    // Methods not in the latest android jar but still fully supported.
+    final boolean missingFromLatestAndroidJar;
+    // Methods not supported in a given min api range.
+    final boolean covariantReturnSupported;
+    MethodAnnotation(
+        boolean parallelStreamMethod,
+        boolean missingFromLatestAndroidJar,
+        boolean covariantReturnSupported,
+        boolean unsupportedInMinApiRange,
+        int minRange,
+        int maxRange) {
+      super(unsupportedInMinApiRange, minRange, maxRange);
+      this.parallelStreamMethod = parallelStreamMethod;
+      this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
+      this.covariantReturnSupported = covariantReturnSupported;
+    }
+
+    public static MethodAnnotation getCovariantReturnSupported() {
+      return COVARIANT_RETURN_SUPPORTED;
+    }
+
+    public static MethodAnnotation getDefault() {
+      return DEFAULT;
+    }
+
+    public static MethodAnnotation getParallelStreamMethod() {
+      return PARALLEL_STREAM_METHOD;
+    }
+
+    public static MethodAnnotation getMissingFromLatestAndroidJar() {
+      return MISSING_FROM_LATEST_ANDROID_JAR;
+    }
+
+    public static MethodAnnotation createMissingInMinApi(int api) {
+      return new MethodAnnotation(false, false, false, true, api, api);
+    }
+
+    public boolean isCovariantReturnSupported() {
+      return covariantReturnSupported;
+    }
+
+    public MethodAnnotation combine(MethodAnnotation other) {
+      if (this == getDefault()) {
+        return other;
+      }
+      if (other == getDefault()) {
+        return this;
+      }
+      int newRange = combineRange(other);
+      int newMax = newRange >> 16;
+      int newMin = newRange & 0xFF;
       return new MethodAnnotation(
           parallelStreamMethod || other.parallelStreamMethod,
           missingFromLatestAndroidJar || other.missingFromLatestAndroidJar,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
similarity index 84%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
index 752262f..397c5f6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -15,12 +15,14 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -28,6 +30,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.FieldAnnotation;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.shaking.MainDexInfo;
@@ -47,13 +50,13 @@
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
-public class SupportedMethodsGenerator {
+public class SupportedClassesGenerator {
 
   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
 
   private final InternalOptions options;
 
-  public SupportedMethodsGenerator(InternalOptions options) {
+  public SupportedClassesGenerator(InternalOptions options) {
     this.options = options;
   }
 
@@ -61,7 +64,7 @@
       throws IOException {
     SupportedClasses.Builder builder = SupportedClasses.builder();
     // First analyze everything which is supported when desugaring for api 1.
-    collectSupportedMethodsInB(desugaredLibraryImplementation, specification, builder);
+    collectSupportedMembersInB(desugaredLibraryImplementation, specification, builder);
     // Second annotate all apis which are partially and/or fully supported.
     AndroidApp library =
         AndroidApp.builder()
@@ -71,7 +74,7 @@
         new ApplicationReader(library, options, Timing.empty()).read().toDirect();
     annotateMethodsNotOnLatestAndroidJar(appForMax, builder);
     annotateParallelMethods(builder);
-    annotatePartialDesugaringMethods(builder, specification);
+    annotatePartialDesugaringMembers(builder, specification);
     annotateClasses(builder, appForMax);
     return builder.build();
   }
@@ -81,6 +84,10 @@
 
     builder.forEachClassAndMethods(
         (clazz, methods) -> {
+          ClassAnnotation classAnnotation = builder.getClassAnnotation(clazz.type);
+          if (classAnnotation != null && classAnnotation.isAdditionalMembersOnClass()) {
+            return;
+          }
           DexClass maxClass = appForMax.definitionFor(clazz.type);
           List<DexMethod> missing = new ArrayList<>();
           boolean fullySupported = true;
@@ -94,6 +101,8 @@
               missing.add(method.getReference());
               fullySupported = false;
             }
+          }
+          for (DexEncodedMethod method : clazz.methods()) {
             MethodAnnotation methodAnnotation = builder.getMethodAnnotation(method.getReference());
             if (methodAnnotation != null && !methodAnnotation.isCovariantReturnSupported()) {
               fullySupported = false;
@@ -103,7 +112,7 @@
         });
   }
 
-  private void annotatePartialDesugaringMethods(
+  private void annotatePartialDesugaringMembers(
       SupportedClasses.Builder builder, Path specification) throws IOException {
     for (int api = AndroidApiLevel.K.getLevel();
         api <= MAX_TESTED_ANDROID_API_LEVEL.getLevel();
@@ -149,8 +158,7 @@
             if (machineSpecification.getEmulatedInterfaces().containsKey(dexMethod.getHolderType())
                 && encodedMethod.isStatic()) {
               // Static methods on emulated interfaces are always supported if the emulated
-              // interface is
-              // supported.
+              // interface is supported.
               return;
             }
             MethodResolutionResult methodResolutionResult =
@@ -163,6 +171,23 @@
               builder.annotateMethod(dexMethod, MethodAnnotation.createMissingInMinApi(finalApi));
             }
           });
+
+      builder.forEachClassAndField(
+          (clazz, encodedField) -> {
+            if (machineSpecification.isContextTypeMaintainedOrRewritten(
+                    encodedField.getHolderType())
+                || machineSpecification
+                    .getStaticFieldRetarget()
+                    .containsKey(encodedField.getReference())) {
+              return;
+            }
+            FieldResolutionResult fieldResolutionResult =
+                appInfo.resolveField(encodedField.getReference());
+            if (fieldResolutionResult.isFailedResolution()) {
+              builder.annotateField(
+                  encodedField.getReference(), FieldAnnotation.createMissingInMinApi(finalApi));
+            }
+          });
     }
   }
 
@@ -193,7 +218,7 @@
     return Paths.get(jar);
   }
 
-  private void collectSupportedMethodsInB(
+  private void collectSupportedMembersInB(
       Collection<Path> desugaredLibraryImplementation,
       Path specification,
       SupportedClasses.Builder builder)
@@ -252,6 +277,7 @@
           builder.addSupportedMethod(clazz, method);
         }
         addBackports(clazz, backports, builder, amendedAppForMax);
+        builder.annotateClass(clazz.type, ClassAnnotation.getAdditionnalMembersOnClass());
       } else {
         // All methods in maintained or rewritten classes are supported.
         if ((clazz.accessFlags.isPublic() || clazz.accessFlags.isProtected())
@@ -264,6 +290,12 @@
             builder.addSupportedMethod(clazz, method);
           }
           addBackports(clazz, backports, builder, amendedAppForMax);
+          for (DexEncodedField field : clazz.fields()) {
+            if (!field.isPublic() && !field.isProtected()) {
+              continue;
+            }
+            builder.addSupportedField(clazz, field);
+          }
         }
       }
     }
@@ -276,6 +308,7 @@
             DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
             if (dexEncodedMethod != null) {
               builder.addSupportedMethod(dexClass, dexEncodedMethod);
+              builder.annotateClass(dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
               return;
             }
           }
@@ -283,7 +316,29 @@
           DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
           assert dexEncodedMethod != null;
           builder.addSupportedMethod(dexClass, dexEncodedMethod);
+          builder.annotateClass(dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
         });
+
+    machineSpecification
+        .getStaticFieldRetarget()
+        .forEach(
+            (field, rewritten) -> {
+              DexClass dexClass = implementationApplication.definitionFor(field.getHolderType());
+              if (dexClass != null) {
+                DexEncodedField dexEncodedField = dexClass.lookupField(field);
+                if (dexEncodedField != null) {
+                  builder.addSupportedField(dexClass, dexEncodedField);
+                  builder.annotateClass(
+                      dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
+                  return;
+                }
+              }
+              dexClass = amendedAppForMax.definitionFor(field.getHolderType());
+              DexEncodedField dexEncodedField = dexClass.lookupField(field);
+              assert dexEncodedField != null;
+              builder.addSupportedField(dexClass, dexEncodedField);
+              builder.annotateClass(dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
+            });
   }
 
   private void addBackports(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
index cc5abfd..dcf5c0a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -138,7 +138,7 @@
         DexEncodedMethod newMethod = createForwardingMethod(itfMethod, descriptor, clazz);
         clazz.addVirtualMethod(newMethod);
         eventConsumer.acceptDesugaredLibraryRetargeterForwardingMethod(
-            new ProgramMethod(clazz, newMethod));
+            new ProgramMethod(clazz, newMethod), descriptor);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSynthesizerEventConsumer.java
index 63dcba2..6e491ff 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSynthesizerEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSynthesizerEventConsumer.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 
 public interface DesugaredLibraryRetargeterSynthesizerEventConsumer {
 
@@ -18,13 +19,14 @@
   interface DesugaredLibraryRetargeterInstructionEventConsumer {
     void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz);
 
-    void acceptCovariantRetargetMethod(ProgramMethod method);
+    void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context);
   }
 
   interface DesugaredLibraryRetargeterPostProcessingEventConsumer
       extends DesugaredLibraryRetargeterInstructionEventConsumer {
     void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface);
 
-    void acceptDesugaredLibraryRetargeterForwardingMethod(ProgramMethod method);
+    void acceptDesugaredLibraryRetargeterForwardingMethod(
+        ProgramMethod method, EmulatedDispatchMethodDescriptor descriptor);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index ae5814a..52fa933 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -57,7 +57,7 @@
                                     .setNonStaticSource(target)
                                     .setCastResult()
                                     .build()));
-    eventConsumer.acceptCovariantRetargetMethod(method);
+    eventConsumer.acceptCovariantRetargetMethod(method, methodProcessingContext.getMethodContext());
     return method.getReference();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 32e2c37..6a74cf3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -30,6 +30,8 @@
 import com.android.tools.r8.graph.LookupMethodTarget;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode;
@@ -126,16 +128,23 @@
   private static class SyntheticThrowingMethodInfo extends SyntheticMethodInfo {
 
     private final DexType errorType;
+    private final FailedResolutionResult resolutionResult;
 
-    SyntheticThrowingMethodInfo(ProgramMethod method, DexType errorType) {
+    SyntheticThrowingMethodInfo(
+        ProgramMethod method, DexType errorType, FailedResolutionResult resolutionResult) {
       super(method);
       this.errorType = errorType;
+      this.resolutionResult = resolutionResult;
     }
 
     DexType getErrorType() {
       return errorType;
     }
 
+    FailedResolutionResult getResolutionResult() {
+      return resolutionResult;
+    }
+
     @Override
     SyntheticThrowingMethodInfo asThrowingMethodInfo() {
       return this;
@@ -511,8 +520,11 @@
               eventConsumer.acceptInterfaceMethodDesugaringForwardingMethod(
                   info.getMethod(), info.asForwardingMethodInfo().getBaseMethod());
             } else {
+              SyntheticThrowingMethodInfo throwingMethodInfo = info.asThrowingMethodInfo();
               eventConsumer.acceptThrowingMethod(
-                  info.getMethod(), info.asThrowingMethodInfo().getErrorType());
+                  info.getMethod(),
+                  throwingMethodInfo.getErrorType(),
+                  throwingMethodInfo.getResolutionResult());
             }
           }
         },
@@ -842,15 +854,15 @@
       }
       if (resolutionResult.isFailedResolution()) {
         if (resolutionResult.isIncompatibleClassChangeErrorResult()) {
-          addICCEThrowingMethod(method, clazz);
+          addICCEThrowingMethod(method, clazz, resolutionResult.asFailedResolution());
           return;
         }
         if (resolutionResult.isNoSuchMethodErrorResult(clazz, appInfo)) {
-          addNoSuchMethodErrorThrowingMethod(method, clazz);
+          addNoSuchMethodErrorThrowingMethod(method, clazz, resolutionResult.asFailedResolution());
           return;
         }
         assert resolutionResult.isIllegalAccessErrorResult(clazz, appInfo);
-        addIllegalAccessErrorThrowingMethod(method, clazz);
+        addIllegalAccessErrorThrowingMethod(method, clazz, resolutionResult.asFailedResolution());
         return;
       }
     }
@@ -878,7 +890,8 @@
                   + "). Please report this issue in the D8/R8 bug tracker at"
                   + " https://issuetracker.google.com/issues/237507594.");
       // To be able to resume compilation we add a NoSuchMethodErrorThrowingMethod.
-      addNoSuchMethodErrorThrowingMethod(method, clazz);
+      addNoSuchMethodErrorThrowingMethod(
+          method, clazz, NoSuchMethodResult.getEmptyNoSuchMethodResult());
       return;
     }
     DexClassAndMethod virtualDispatchTarget = lookupMethodTarget.getTarget();
@@ -919,27 +932,37 @@
     assert existingMethodInfo == null;
   }
 
-  private void addSyntheticThrowingMethod(ProgramMethod method, DexType errorType) {
+  private void addSyntheticThrowingMethod(
+      ProgramMethod method, DexType errorType, FailedResolutionResult resolutionResult) {
     SyntheticMethodInfo existingMethodInfo =
         newSyntheticMethods
             .computeIfAbsent(method.getHolder(), key -> new ConcurrentHashMap<>())
-            .put(method.getReference(), new SyntheticThrowingMethodInfo(method, errorType));
+            .put(
+                method.getReference(),
+                new SyntheticThrowingMethodInfo(method, errorType, resolutionResult));
     assert existingMethodInfo == null;
   }
 
-  private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
-    addThrowingMethod(method, clazz, dexItemFactory.icceType);
+  private void addICCEThrowingMethod(
+      DexMethod method, DexClass clazz, FailedResolutionResult resolutionResult) {
+    addThrowingMethod(method, clazz, dexItemFactory.icceType, resolutionResult);
   }
 
-  private void addIllegalAccessErrorThrowingMethod(DexMethod method, DexClass clazz) {
-    addThrowingMethod(method, clazz, dexItemFactory.illegalAccessErrorType);
+  private void addIllegalAccessErrorThrowingMethod(
+      DexMethod method, DexClass clazz, FailedResolutionResult resolutionResult) {
+    addThrowingMethod(method, clazz, dexItemFactory.illegalAccessErrorType, resolutionResult);
   }
 
-  private void addNoSuchMethodErrorThrowingMethod(DexMethod method, DexClass clazz) {
-    addThrowingMethod(method, clazz, dexItemFactory.noSuchMethodErrorType);
+  private void addNoSuchMethodErrorThrowingMethod(
+      DexMethod method, DexClass clazz, FailedResolutionResult resolutionResult) {
+    addThrowingMethod(method, clazz, dexItemFactory.noSuchMethodErrorType, resolutionResult);
   }
 
-  private void addThrowingMethod(DexMethod method, DexClass clazz, DexType errorType) {
+  private void addThrowingMethod(
+      DexMethod method,
+      DexClass clazz,
+      DexType errorType,
+      FailedResolutionResult resolutionResult) {
     if (!clazz.isProgramClass()) {
       return;
     }
@@ -953,7 +976,8 @@
                 createExceptionThrowingCfCode(newMethod, accessFlags, errorType, dexItemFactory))
             .disableAndroidApiLevelCheck()
             .build();
-    addSyntheticThrowingMethod(newEncodedMethod.asProgramMethod(clazz.asProgramClass()), errorType);
+    addSyntheticThrowingMethod(
+        newEncodedMethod.asProgramMethod(clazz.asProgramClass()), errorType, resolutionResult);
   }
 
   private static CfCode createExceptionThrowingCfCode(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
index dde1382..a918d81 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 
 public interface InterfaceProcessingDesugaringEventConsumer {
@@ -18,7 +19,8 @@
   void acceptEmulatedInterfaceMarkerInterface(
       DexProgramClass clazz, DexClasspathClass newInterface);
 
-  void acceptThrowingMethod(ProgramMethod method, DexType errorType);
+  void acceptThrowingMethod(
+      ProgramMethod method, DexType errorType, FailedResolutionResult resolutionResult);
 
   void warnMissingInterface(
       DexProgramClass context, DexType missing, InterfaceDesugaringSyntheticHelper helper);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index 34324ff..19df173 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -19,7 +19,8 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingNestBasedAccessDesugaringEventConsumer;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
@@ -81,7 +82,8 @@
   }
 
   public void synthesizeBridgesForNestBasedAccessesOnClasspath(
-      MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException {
+      D8MethodProcessor methodProcessor, ExecutorService executorService)
+      throws ExecutionException {
     List<DexClasspathClass> classpathClassesInNests = new ArrayList<>();
     forEachNest(
         nest -> {
@@ -92,35 +94,37 @@
         });
 
     NestBasedAccessDesugaringEventConsumer eventConsumer =
-        new NestBasedAccessDesugaringEventConsumer() {
+        ArtProfileRewritingNestBasedAccessDesugaringEventConsumer.attach(
+            methodProcessor.getArtProfileCollectionAdditions(),
+            new NestBasedAccessDesugaringEventConsumer() {
 
-          @Override
-          public void acceptNestConstructorBridge(
-              ProgramMethod target,
-              ProgramMethod bridge,
-              DexProgramClass argumentClass,
-              DexClassAndMethod context) {
-            methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
-          }
+              @Override
+              public void acceptNestConstructorBridge(
+                  ProgramMethod target,
+                  ProgramMethod bridge,
+                  DexProgramClass argumentClass,
+                  DexClassAndMethod context) {
+                methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+              }
 
-          @Override
-          public void acceptNestFieldGetBridge(
-              ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
-            methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
-          }
+              @Override
+              public void acceptNestFieldGetBridge(
+                  ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+                methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+              }
 
-          @Override
-          public void acceptNestFieldPutBridge(
-              ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
-            methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
-          }
+              @Override
+              public void acceptNestFieldPutBridge(
+                  ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+                methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+              }
 
-          @Override
-          public void acceptNestMethodBridge(
-              ProgramMethod target, ProgramMethod bridge, DexClassAndMethod context) {
-            methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
-          }
-        };
+              @Override
+              public void acceptNestMethodBridge(
+                  ProgramMethod target, ProgramMethod bridge, DexClassAndMethod context) {
+                methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+              }
+            });
     ThreadUtils.processItems(
         classpathClassesInNests,
         clazz -> synthesizeBridgesForNestBasedAccessesOnClasspath(clazz, eventConsumer),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaringEventConsumer.java
index dac3668..afbeedc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaringEventConsumer.java
@@ -25,4 +25,48 @@
 
   void acceptNestMethodBridge(
       ProgramMethod target, ProgramMethod bridge, DexClassAndMethod context);
+
+  static EmptyNestBasedAccessDesugaringEventConsumer empty() {
+    return EmptyNestBasedAccessDesugaringEventConsumer.getInstance();
+  }
+
+  class EmptyNestBasedAccessDesugaringEventConsumer
+      implements NestBasedAccessDesugaringEventConsumer {
+
+    private static final EmptyNestBasedAccessDesugaringEventConsumer INSTANCE =
+        new EmptyNestBasedAccessDesugaringEventConsumer();
+
+    private EmptyNestBasedAccessDesugaringEventConsumer() {}
+
+    static EmptyNestBasedAccessDesugaringEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptNestConstructorBridge(
+        ProgramMethod target,
+        ProgramMethod bridge,
+        DexProgramClass argumentClass,
+        DexClassAndMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptNestFieldGetBridge(
+        ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptNestFieldPutBridge(
+        ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptNestMethodBridge(
+        ProgramMethod target, ProgramMethod bridge, DexClassAndMethod context) {
+      // Intentionally empty.
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
index d8382b7..ceb6b75 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -174,19 +175,24 @@
   }
 
   private void ensureMethodHandlesLookupClass(
-      VarHandleDesugaringEventConsumer eventConsumer, Collection<ProgramDefinition> contexts) {
-    appView
-        .getSyntheticItems()
-        .ensureGlobalClass(
-            () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
-            kinds -> kinds.METHOD_HANDLES_LOOKUP,
-            factory.lookupType,
-            contexts,
-            appView,
-            builder ->
-                VarHandleDesugaringMethods.generateDesugarMethodHandlesLookupClass(
-                    builder, appView.dexItemFactory()),
-            eventConsumer::acceptVarHandleDesugaringClass);
+      VarHandleDesugaringEventConsumer eventConsumer,
+      Collection<? extends ProgramDefinition> contexts) {
+    DexProgramClass clazz =
+        appView
+            .getSyntheticItems()
+            .ensureGlobalClass(
+                () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
+                kinds -> kinds.METHOD_HANDLES_LOOKUP,
+                factory.lookupType,
+                contexts,
+                appView,
+                builder ->
+                    VarHandleDesugaringMethods.generateDesugarMethodHandlesLookupClass(
+                        builder, appView.dexItemFactory()),
+                eventConsumer::acceptVarHandleDesugaringClass);
+    for (ProgramDefinition context : contexts) {
+      eventConsumer.acceptVarHandleDesugaringClassContext(clazz, context);
+    }
   }
 
   private void ensureMethodHandlesLookupClass(
@@ -195,19 +201,24 @@
   }
 
   private void ensureVarHandleClass(
-      VarHandleDesugaringEventConsumer eventConsumer, Collection<ProgramDefinition> contexts) {
-    appView
-        .getSyntheticItems()
-        .ensureGlobalClass(
-            () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
-            kinds -> kinds.VAR_HANDLE,
-            factory.varHandleType,
-            contexts,
-            appView,
-            builder ->
-                VarHandleDesugaringMethods.generateDesugarVarHandleClass(
-                    builder, appView.dexItemFactory()),
-            eventConsumer::acceptVarHandleDesugaringClass);
+      VarHandleDesugaringEventConsumer eventConsumer,
+      Collection<? extends ProgramDefinition> contexts) {
+    DexProgramClass clazz =
+        appView
+            .getSyntheticItems()
+            .ensureGlobalClass(
+                () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
+                kinds -> kinds.VAR_HANDLE,
+                factory.varHandleType,
+                contexts,
+                appView,
+                builder ->
+                    VarHandleDesugaringMethods.generateDesugarVarHandleClass(
+                        builder, appView.dexItemFactory()),
+                eventConsumer::acceptVarHandleDesugaringClass);
+    for (ProgramDefinition context : contexts) {
+      eventConsumer.acceptVarHandleDesugaringClassContext(clazz, context);
+    }
   }
 
   private void ensureVarHandleClass(
@@ -598,9 +609,9 @@
       DexApplicationReadFlags flags,
       Predicate<DexApplicationReadFlags> hasReadReferenceFromProgramClass,
       Function<DexApplicationReadFlags, Set<DexType>> getWitnesses,
-      Consumer<List<ProgramDefinition>> consumeProgramWitnesses) {
+      Consumer<? super List<DexProgramClass>> consumeProgramWitnesses) {
     if (hasReadReferenceFromProgramClass.test(flags)) {
-      List<ProgramDefinition> classes = new ArrayList<>();
+      List<DexProgramClass> classes = new ArrayList<>();
       for (DexType witness : getWitnesses.apply(flags)) {
         DexClass dexClass = appView.contextIndependentDefinitionFor(witness);
         assert dexClass != null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringEventConsumer.java
index 8251809..d27e4d8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringEventConsumer.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.ir.desugar.varhandle;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
 
 public interface VarHandleDesugaringEventConsumer {
 
-  void acceptVarHandleDesugaringClass(DexProgramClass varHandleClass);
+  void acceptVarHandleDesugaringClass(DexProgramClass clazz);
+
+  void acceptVarHandleDesugaringClassContext(DexProgramClass clazz, ProgramDefinition context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java
index 6d5083c..e464f9c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriter.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.utils.InternalOptions;
@@ -42,7 +43,10 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
-  public void rewrite(IRCode code, MethodProcessingContext methodProcessingContext) {
+  public void rewrite(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
     if (options.canUseAssertionErrorTwoArgumentConstructor()) {
       return;
     }
@@ -66,7 +70,8 @@
             }
             InvokeStatic invoke =
                 InvokeStatic.builder()
-                    .setMethod(createSynthetic(methodProcessingContext).getReference())
+                    .setMethod(
+                        createSynthetic(methodProcessor, methodProcessingContext).getReference())
                     .setFreshOutValue(
                         code, dexItemFactory.assertionErrorType.toTypeElement(appView))
                     .setPosition(current)
@@ -94,7 +99,8 @@
     return synthesizedMethods;
   }
 
-  private ProgramMethod createSynthetic(MethodProcessingContext methodProcessingContext) {
+  private ProgramMethod createSynthetic(
+      MethodProcessor methodProcessor, MethodProcessingContext methodProcessingContext) {
     DexItemFactory factory = appView.dexItemFactory();
     DexProto proto =
         factory.createProto(factory.assertionErrorType, factory.stringType, factory.throwableType);
@@ -126,6 +132,9 @@
                       .toTypeElement(appView, Nullability.definitelyNotNull())
                       .asClassType()));
     }
+    methodProcessor
+        .getEventConsumer()
+        .acceptAssertionErrorCreateMethod(method, methodProcessingContext.getMethodContext());
     return method;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriterEventConsumer.java
new file mode 100644
index 0000000..8b15685
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionErrorTwoArgsConstructorRewriterEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface AssertionErrorTwoArgsConstructorRewriterEventConsumer {
+
+  void acceptAssertionErrorCreateMethod(ProgramMethod method, ProgramMethod context);
+}
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 12030bf..534b5a9 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
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanBox;
@@ -75,7 +76,10 @@
     return serviceLoadMethods;
   }
 
-  public void rewrite(IRCode code, MethodProcessingContext methodProcessingContext) {
+  public void rewrite(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
     DexItemFactory factory = appView.dexItemFactory();
     InstructionListIterator instructionIterator = code.instructionListIterator();
     // Create a map from service type to loader methods local to this context since two
@@ -175,7 +179,8 @@
               constClass.getValue(),
               service -> {
                 DexEncodedMethod addedMethod =
-                    createSynthesizedMethod(service, classes, methodProcessingContext);
+                    createSynthesizedMethod(
+                        service, classes, methodProcessor, methodProcessingContext);
                 if (appView.options().isGeneratingClassFiles()) {
                   addedMethod.upgradeClassFileVersion(
                       code.context().getDefinition().getClassFileVersion());
@@ -191,6 +196,7 @@
   private DexEncodedMethod createSynthesizedMethod(
       DexType serviceType,
       List<DexClass> classes,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     DexProto proto = appView.dexItemFactory().createProto(appView.dexItemFactory().iteratorType);
     ProgramMethod method =
@@ -215,6 +221,9 @@
     synchronized (serviceLoadMethods) {
       serviceLoadMethods.add(method);
     }
+    methodProcessor
+        .getEventConsumer()
+        .acceptServiceLoaderLoadUtilityMethod(method, methodProcessingContext.getMethodContext());
     return method.getDefinition();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriterEventConsumer.java
new file mode 100644
index 0000000..379021b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriterEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface ServiceLoaderRewriterEventConsumer {
+
+  void acceptServiceLoaderLoadUtilityMethod(ProgramMethod method, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index d3f9395..a76e4d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -34,7 +34,7 @@
 import java.util.Map;
 import java.util.Set;
 
-class EnumUnboxingLens extends NestedGraphLens {
+public class EnumUnboxingLens extends NestedGraphLens {
 
   private final AbstractValueFactory abstractValueFactory;
   private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
@@ -64,6 +64,11 @@
   }
 
   @Override
+  public EnumUnboxingLens asEnumUnboxerLens() {
+    return this;
+  }
+
+  @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
       RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
     // Rewrite the single value of the given RewrittenPrototypeDescription if it is referring to an
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineOptimizationEventConsumer.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineOptimizationEventConsumer.java
index 4d8a9ed..86ac8ab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineOptimizationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineOptimizationEventConsumer.java
@@ -4,32 +4,20 @@
 
 package com.android.tools.r8.ir.optimize.outliner;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
-import com.android.tools.r8.profile.art.rewriting.ConcreteArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingOutlineOptimizationEventConsumer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
 
 public interface OutlineOptimizationEventConsumer {
 
   void acceptOutlineMethod(ProgramMethod method, Collection<ProgramMethod> contexts);
 
-  static OutlineOptimizationEventConsumer create(
-      ArtProfileCollectionAdditions collectionAdditions) {
-    if (collectionAdditions.isNop()) {
-      return empty();
-    }
-    return create(collectionAdditions.asConcrete());
-  }
+  void finished(AppView<AppInfoWithLiveness> appView);
 
-  static OutlineOptimizationEventConsumer create(
-      ConcreteArtProfileCollectionAdditions collectionAdditions) {
-    return (method, contexts) -> {
-      for (ProgramMethod context : contexts) {
-        collectionAdditions.applyIfContextIsInProfile(
-            context,
-            additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
-      }
-    };
+  static OutlineOptimizationEventConsumer create(AppView<AppInfoWithLiveness> appView) {
+    return ArtProfileRewritingOutlineOptimizationEventConsumer.attach(appView, empty());
   }
 
   static EmptyOutlineOptimizationEventConsumer empty() {
@@ -51,5 +39,10 @@
     public void acceptOutlineMethod(ProgramMethod method, Collection<ProgramMethod> contexts) {
       // Intentionally empty.
     }
+
+    @Override
+    public void finished(AppView<AppInfoWithLiveness> appView) {
+      // Intentionally empty.
+    }
   }
 }
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 7382a1e..b56f246 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
@@ -68,7 +68,6 @@
 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.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
@@ -1342,6 +1341,8 @@
     timing.begin("IR conversion phase 3");
     ProgramMethodSet methodsSelectedForOutlining = selectMethodsForOutlining();
     if (!methodsSelectedForOutlining.isEmpty()) {
+      OutlineOptimizationEventConsumer eventConsumer =
+          OutlineOptimizationEventConsumer.create(appView);
       forEachSelectedOutliningMethod(
           converter,
           methodsSelectedForOutlining,
@@ -1350,14 +1351,11 @@
             identifyOutlineSites(code);
           },
           executorService);
-      ArtProfileCollectionAdditions artProfileCollectionAdditions =
-          ArtProfileCollectionAdditions.create(appView);
-      List<ProgramMethod> outlineMethods =
-          buildOutlineMethods(
-              OutlineOptimizationEventConsumer.create(artProfileCollectionAdditions));
-      artProfileCollectionAdditions.commit(appView);
-      MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
-      converter.optimizeSynthesizedMethods(outlineMethods, eventConsumer, executorService);
+      List<ProgramMethod> outlineMethods = buildOutlineMethods(eventConsumer);
+      MethodProcessorEventConsumer methodProcessorEventConsumer =
+          MethodProcessorEventConsumer.empty();
+      converter.optimizeSynthesizedMethods(
+          outlineMethods, methodProcessorEventConsumer, executorService);
       feedback.updateVisibleOptimizationInfo();
       forEachSelectedOutliningMethod(
           converter,
@@ -1374,6 +1372,7 @@
       feedback.updateVisibleOptimizationInfo();
       assert checkAllOutlineSitesFoundAgain();
       outlineMethods.forEach(m -> m.getDefinition().markNotProcessed());
+      eventConsumer.finished(appView);
     }
     timing.end();
   }
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteWriter.java b/src/main/java/com/android/tools/r8/lightir/ByteWriter.java
index 0a2a75e..30b5355 100644
--- a/src/main/java/com/android/tools/r8/lightir/ByteWriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/ByteWriter.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
-/** Most primitive interface for providing consumer to the LIRWriter. */
+/** Most primitive interface for providing consumer to the {@link LirWriter}. */
 public interface ByteWriter {
 
   /** Put a byte value, must represent an unsigned byte (int between 0 and 255). */
diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java
similarity index 93%
rename from src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
rename to src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java
index b1ad627..fbd5571 100644
--- a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.lightir.LIRBuilder.BlockIndexGetter;
+import com.android.tools.r8.lightir.LirBuilder.BlockIndexGetter;
 import com.android.tools.r8.utils.ListUtils;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -25,25 +25,25 @@
 import java.util.Comparator;
 import java.util.List;
 
-public class IR2LIRConverter {
+public class IR2LirConverter {
 
   private final DexItemFactory factory;
   private final IRCode irCode;
   private final Reference2IntMap<BasicBlock> blocks = new Reference2IntOpenHashMap<>();
   private final Reference2IntMap<Value> values = new Reference2IntOpenHashMap<>();
-  private final LIRBuilder<Value, BasicBlock> builder;
+  private final LirBuilder<Value, BasicBlock> builder;
 
-  private IR2LIRConverter(DexItemFactory factory, IRCode irCode) {
+  private IR2LirConverter(DexItemFactory factory, IRCode irCode) {
     this.factory = factory;
     this.irCode = irCode;
     this.builder =
-        new LIRBuilder<Value, BasicBlock>(
+        new LirBuilder<Value, BasicBlock>(
                 irCode.context().getReference(), values::getInt, blocks::getInt, factory)
             .setMetadata(irCode.metadata());
   }
 
-  public static LIRCode translate(IRCode irCode, DexItemFactory factory) {
-    return new IR2LIRConverter(factory, irCode).internalTranslate();
+  public static LirCode translate(IRCode irCode, DexItemFactory factory) {
+    return new IR2LirConverter(factory, irCode).internalTranslate();
   }
 
   private void recordBlock(BasicBlock block, int blockIndex) {
@@ -57,7 +57,7 @@
     }
   }
 
-  private LIRCode internalTranslate() {
+  private LirCode internalTranslate() {
     irCode.traceBlocks();
     computeBlockAndValueTables();
     computeInstructions();
@@ -108,7 +108,7 @@
             continue;
           }
         }
-        instruction.buildLIR(builder);
+        instruction.buildLir(builder);
         currentValueIndex++;
       }
       assert builder.verifyCurrentValueIndex(currentValueIndex);
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRUtils.java b/src/main/java/com/android/tools/r8/lightir/LIRUtils.java
deleted file mode 100644
index 09dcd81..0000000
--- a/src/main/java/com/android/tools/r8/lightir/LIRUtils.java
+++ /dev/null
@@ -1,17 +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.lightir;
-
-public class LIRUtils {
-
-  private LIRUtils() {}
-
-  public static int encodeValueIndex(int absoluteValueIndex, int referencingValueContext) {
-    return referencingValueContext - absoluteValueIndex;
-  }
-
-  public static int decodeValueIndex(int encodedValueIndex, int referencingValueContext) {
-    return referencingValueContext - encodedValueIndex;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
rename to src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 56fb836..d0677f4 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -38,7 +38,7 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.lightir.LIRCode.PositionEntry;
+import com.android.tools.r8.lightir.LirCode.PositionEntry;
 import com.android.tools.r8.utils.ListUtils;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -48,11 +48,11 @@
 import java.util.LinkedList;
 import java.util.List;
 
-public class LIR2IRConverter {
+public class Lir2IRConverter {
 
-  private LIR2IRConverter() {}
+  private Lir2IRConverter() {}
 
-  public static IRCode translate(ProgramMethod method, LIRCode lirCode, AppView<?> appView) {
+  public static IRCode translate(ProgramMethod method, LirCode lirCode, AppView<?> appView) {
     Parser parser = new Parser(lirCode, method.getReference(), appView);
     parser.parseArguments(method);
     lirCode.forEach(view -> view.accept(parser));
@@ -63,12 +63,12 @@
    * When building IR the structured LIR parser is used to obtain the decoded operand indexes. The
    * below parser subclass handles translation of indexes to SSA values.
    */
-  private static class Parser extends LIRParsedInstructionCallback {
+  private static class Parser extends LirParsedInstructionCallback {
 
     private static final int ENTRY_BLOCK_INDEX = -1;
 
     private final AppView<?> appView;
-    private final LIRCode code;
+    private final LirCode code;
     private final NumberGenerator valueNumberGenerator = new NumberGenerator();
     private final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
 
@@ -82,7 +82,7 @@
     private PositionEntry nextPositionEntry = null;
     private int nextIndexInPositionsTable = 0;
 
-    public Parser(LIRCode code, DexMethod method, AppView<?> appView) {
+    public Parser(LirCode code, DexMethod method, AppView<?> appView) {
       super(code);
       assert code.getPositionTable().length > 0;
       assert code.getPositionTable()[0].fromInstructionIndex == 0;
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
similarity index 75%
rename from src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
rename to src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index 9095c8b..7ec7244 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -20,9 +20,9 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.lightir.LIRCode.DebugLocalInfoTable;
-import com.android.tools.r8.lightir.LIRCode.PositionEntry;
-import com.android.tools.r8.lightir.LIRCode.TryCatchTable;
+import com.android.tools.r8.lightir.LirCode.DebugLocalInfoTable;
+import com.android.tools.r8.lightir.LirCode.PositionEntry;
+import com.android.tools.r8.lightir.LirCode.TryCatchTable;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -43,7 +43,7 @@
  * @param <B> Type of basic blocks. This is abstract to ensure that basic block internals are not
  *     used in building.
  */
-public class LIRBuilder<V, B> {
+public class LirBuilder<V, B> {
 
   // Abstraction for the only accessible properties of an SSA value.
   public interface ValueIndexGetter<V> {
@@ -57,7 +57,7 @@
 
   private final DexItemFactory factory;
   private final ByteArrayWriter byteWriter = new ByteArrayWriter();
-  private final LIRWriter writer = new LIRWriter(byteWriter);
+  private final LirWriter writer = new LirWriter(byteWriter);
   private final Reference2IntMap<DexItem> constants;
   private final ValueIndexGetter<V> valueIndexGetter;
   private final BlockIndexGetter<B> blockIndexGetter;
@@ -65,6 +65,7 @@
   private int argumentCount = 0;
   private int instructionCount = 0;
   private IRMetadata metadata = null;
+  private final LirSsaValueStrategy ssaValueStrategy = LirSsaValueStrategy.get();
 
   private Position currentPosition;
   private Position flushedPosition;
@@ -82,7 +83,7 @@
   private static final int MAX_VALUE_COUNT = 10;
   private int[] valueIndexBuffer = new int[MAX_VALUE_COUNT];
 
-  public LIRBuilder(
+  public LirBuilder(
       DexMethod method,
       ValueIndexGetter<V> valueIndexGetter,
       BlockIndexGetter<B> blockIndexGetter,
@@ -115,7 +116,7 @@
     tryCatchRanges.put(blockIndex, handlers);
   }
 
-  public LIRBuilder<V, B> setCurrentPosition(Position position) {
+  public LirBuilder<V, B> setCurrentPosition(Position position) {
     assert position != null;
     assert position != Position.none();
     currentPosition = position;
@@ -150,13 +151,13 @@
 
   private int valueIndexSize(int valueIndex, int referencingInstructionIndex) {
     int referencingValueIndex = referencingInstructionIndex + argumentCount;
-    int encodedValueIndex = LIRUtils.encodeValueIndex(valueIndex, referencingValueIndex);
+    int encodedValueIndex = ssaValueStrategy.encodeValueIndex(valueIndex, referencingValueIndex);
     return ByteUtils.intEncodingSize(encodedValueIndex);
   }
 
   private void writeValueIndex(int valueIndex, int referencingInstructionIndex) {
     int referencingValueIndex = referencingInstructionIndex + argumentCount;
-    int encodedValueIndex = LIRUtils.encodeValueIndex(valueIndex, referencingValueIndex);
+    int encodedValueIndex = ssaValueStrategy.encodeValueIndex(valueIndex, referencingValueIndex);
     ByteUtils.writeEncodedInt(encodedValueIndex, writer::writeOperand);
   }
 
@@ -172,18 +173,18 @@
     ByteUtils.writeEncodedInt(index, writer::writeOperand);
   }
 
-  public LIRBuilder<V, B> setMetadata(IRMetadata metadata) {
+  public LirBuilder<V, B> setMetadata(IRMetadata metadata) {
     this.metadata = metadata;
     return this;
   }
 
-  public LIRBuilder<V, B> setDebugValue(DebugLocalInfo debugInfo, int valueIndex) {
+  public LirBuilder<V, B> setDebugValue(DebugLocalInfo debugInfo, int valueIndex) {
     DebugLocalInfo old = debugLocals.put(valueIndex, debugInfo);
     assert old == null;
     return this;
   }
 
-  public LIRBuilder<V, B> setDebugLocalEnds(int instructionValueIndex, Set<V> endValues) {
+  public LirBuilder<V, B> setDebugLocalEnds(int instructionValueIndex, Set<V> endValues) {
     int size = endValues.size();
     int[] indices = new int[size];
     Iterator<V> iterator = endValues.iterator();
@@ -194,7 +195,7 @@
     return this;
   }
 
-  public LIRBuilder<V, B> addArgument(int index, boolean knownToBeBoolean) {
+  public LirBuilder<V, B> addArgument(int index, boolean knownToBeBoolean) {
     // Arguments are implicitly given by method descriptor and not an actual instruction.
     assert argumentCount == index;
     argumentCount++;
@@ -209,22 +210,22 @@
     return instructionCount++;
   }
 
-  private LIRBuilder<V, B> addNoOperandInstruction(int opcode) {
+  private LirBuilder<V, B> addNoOperandInstruction(int opcode) {
     advanceInstructionState();
     writer.writeOneByteInstruction(opcode);
     return this;
   }
 
-  private LIRBuilder<V, B> addOneItemInstruction(int opcode, DexItem item) {
+  private LirBuilder<V, B> addOneItemInstruction(int opcode, DexItem item) {
     return addInstructionTemplate(opcode, Collections.singletonList(item), Collections.emptyList());
   }
 
-  private LIRBuilder<V, B> addOneValueInstruction(int opcode, V value) {
+  private LirBuilder<V, B> addOneValueInstruction(int opcode, V value) {
     return addInstructionTemplate(
         opcode, Collections.emptyList(), Collections.singletonList(value));
   }
 
-  private LIRBuilder<V, B> addInstructionTemplate(int opcode, List<DexItem> items, List<V> values) {
+  private LirBuilder<V, B> addInstructionTemplate(int opcode, List<DexItem> items, List<V> values) {
     assert values.size() < MAX_VALUE_COUNT;
     int instructionIndex = advanceInstructionState();
     int operandSize = 0;
@@ -247,22 +248,22 @@
     return this;
   }
 
-  public LIRBuilder<V, B> addConstNull() {
-    return addNoOperandInstruction(LIROpcodes.ACONST_NULL);
+  public LirBuilder<V, B> addConstNull() {
+    return addNoOperandInstruction(LirOpcodes.ACONST_NULL);
   }
 
-  public LIRBuilder<V, B> addConstInt(int value) {
+  public LirBuilder<V, B> addConstInt(int value) {
     if (-1 <= value && value <= 5) {
-      addNoOperandInstruction(LIROpcodes.ICONST_0 + value);
+      addNoOperandInstruction(LirOpcodes.ICONST_0 + value);
     } else {
       advanceInstructionState();
-      writer.writeInstruction(LIROpcodes.ICONST, ByteUtils.intEncodingSize(value));
+      writer.writeInstruction(LirOpcodes.ICONST, ByteUtils.intEncodingSize(value));
       ByteUtils.writeEncodedInt(value, writer::writeOperand);
     }
     return this;
   }
 
-  public LIRBuilder<V, B> addConstNumber(ValueType type, long value) {
+  public LirBuilder<V, B> addConstNumber(ValueType type, long value) {
     switch (type) {
       case OBJECT:
         return addConstNull();
@@ -276,11 +277,11 @@
     }
   }
 
-  public LIRBuilder<V, B> addConstString(DexString string) {
-    return addOneItemInstruction(LIROpcodes.LDC, string);
+  public LirBuilder<V, B> addConstString(DexString string) {
+    return addOneItemInstruction(LirOpcodes.LDC, string);
   }
 
-  public LIRBuilder<V, B> addDiv(NumericType type, V leftValue, V rightValue) {
+  public LirBuilder<V, B> addDiv(NumericType type, V leftValue, V rightValue) {
     switch (type) {
       case BYTE:
       case CHAR:
@@ -288,7 +289,7 @@
       case INT:
         {
           return addInstructionTemplate(
-              LIROpcodes.IDIV, Collections.emptyList(), ImmutableList.of(leftValue, rightValue));
+              LirOpcodes.IDIV, Collections.emptyList(), ImmutableList.of(leftValue, rightValue));
         }
       case LONG:
       case FLOAT:
@@ -298,72 +299,72 @@
     }
   }
 
-  public LIRBuilder<V, B> addArrayLength(V array) {
-    return addOneValueInstruction(LIROpcodes.ARRAYLENGTH, array);
+  public LirBuilder<V, B> addArrayLength(V array) {
+    return addOneValueInstruction(LirOpcodes.ARRAYLENGTH, array);
   }
 
-  public LIRBuilder<V, B> addStaticGet(DexField field) {
-    return addOneItemInstruction(LIROpcodes.GETSTATIC, field);
+  public LirBuilder<V, B> addStaticGet(DexField field) {
+    return addOneItemInstruction(LirOpcodes.GETSTATIC, field);
   }
 
-  public LIRBuilder<V, B> addInvokeInstruction(int opcode, DexMethod method, List<V> arguments) {
+  public LirBuilder<V, B> addInvokeInstruction(int opcode, DexMethod method, List<V> arguments) {
     return addInstructionTemplate(opcode, Collections.singletonList(method), arguments);
   }
 
-  public LIRBuilder<V, B> addInvokeDirect(DexMethod method, List<V> arguments) {
-    return addInvokeInstruction(LIROpcodes.INVOKEDIRECT, method, arguments);
+  public LirBuilder<V, B> addInvokeDirect(DexMethod method, List<V> arguments) {
+    return addInvokeInstruction(LirOpcodes.INVOKEDIRECT, method, arguments);
   }
 
-  public LIRBuilder<V, B> addInvokeVirtual(DexMethod method, List<V> arguments) {
-    return addInvokeInstruction(LIROpcodes.INVOKEVIRTUAL, method, arguments);
+  public LirBuilder<V, B> addInvokeVirtual(DexMethod method, List<V> arguments) {
+    return addInvokeInstruction(LirOpcodes.INVOKEVIRTUAL, method, arguments);
   }
 
-  public LIRBuilder<V, B> addReturn(V value) {
+  public LirBuilder<V, B> addReturn(V value) {
     throw new Unimplemented();
   }
 
-  public LIRBuilder<V, B> addReturnVoid() {
-    return addNoOperandInstruction(LIROpcodes.RETURN);
+  public LirBuilder<V, B> addReturnVoid() {
+    return addNoOperandInstruction(LirOpcodes.RETURN);
   }
 
-  public LIRBuilder<V, B> addDebugPosition(Position position) {
+  public LirBuilder<V, B> addDebugPosition(Position position) {
     assert currentPosition == position;
-    return addNoOperandInstruction(LIROpcodes.DEBUGPOS);
+    return addNoOperandInstruction(LirOpcodes.DEBUGPOS);
   }
 
   public void addFallthrough() {
-    addNoOperandInstruction(LIROpcodes.FALLTHROUGH);
+    addNoOperandInstruction(LirOpcodes.FALLTHROUGH);
   }
 
-  public LIRBuilder<V, B> addGoto(B target) {
+  public LirBuilder<V, B> addGoto(B target) {
     int targetIndex = getBlockIndex(target);
     int operandSize = blockIndexSize(targetIndex);
     advanceInstructionState();
-    writer.writeInstruction(LIROpcodes.GOTO, operandSize);
+    writer.writeInstruction(LirOpcodes.GOTO, operandSize);
     writeBlockIndex(targetIndex);
     return this;
   }
 
-  public LIRBuilder<V, B> addIf(Type ifKind, ValueType valueType, V value, B trueTarget) {
+  public LirBuilder<V, B> addIf(Type ifKind, ValueType valueType, V value, B trueTarget) {
     int opcode;
     switch (ifKind) {
       case EQ:
-        opcode = valueType.isObject() ? LIROpcodes.IFNULL : LIROpcodes.IFEQ;
+        opcode = valueType.isObject() ? LirOpcodes.IFNULL : LirOpcodes.IFEQ;
         break;
       case GE:
-        opcode = LIROpcodes.IFGE;
+        opcode = LirOpcodes.IFGE;
         break;
       case GT:
-        opcode = LIROpcodes.IFGT;
+        opcode = LirOpcodes.IFGT;
         break;
       case LE:
-        opcode = LIROpcodes.IFLE;
+        opcode = LirOpcodes.IFLE;
         break;
       case LT:
-        opcode = LIROpcodes.IFLT;
+        opcode = LirOpcodes.IFLT;
         break;
       case NE:
-        opcode = valueType.isObject() ? LIROpcodes.IFNONNULL : LIROpcodes.IFNE;
+        opcode = valueType.isObject() ? LirOpcodes.IFNONNULL : LirOpcodes.IFNE;
         break;
       default:
         throw new Unreachable("Unexpected if kind: " + ifKind);
@@ -378,27 +379,27 @@
     return this;
   }
 
-  public LIRBuilder<V, B> addIfCmp(
+  public LirBuilder<V, B> addIfCmp(
       Type ifKind, ValueType valueType, List<V> inValues, B trueTarget) {
     int opcode;
     switch (ifKind) {
       case EQ:
-        opcode = valueType.isObject() ? LIROpcodes.IF_ACMPEQ : LIROpcodes.IF_ICMPEQ;
+        opcode = valueType.isObject() ? LirOpcodes.IF_ACMPEQ : LirOpcodes.IF_ICMPEQ;
         break;
       case GE:
-        opcode = LIROpcodes.IF_ICMPGE;
+        opcode = LirOpcodes.IF_ICMPGE;
         break;
       case GT:
-        opcode = LIROpcodes.IF_ICMPGT;
+        opcode = LirOpcodes.IF_ICMPGT;
         break;
       case LE:
-        opcode = LIROpcodes.IF_ICMPLE;
+        opcode = LirOpcodes.IF_ICMPLE;
         break;
       case LT:
-        opcode = LIROpcodes.IF_ICMPLT;
+        opcode = LirOpcodes.IF_ICMPLT;
         break;
       case NE:
-        opcode = valueType.isObject() ? LIROpcodes.IF_ACMPNE : LIROpcodes.IF_ICMPNE;
+        opcode = valueType.isObject() ? LirOpcodes.IF_ACMPNE : LirOpcodes.IF_ICMPNE;
         break;
       default:
         throw new Unreachable("Unexpected if kind " + ifKind);
@@ -418,27 +419,27 @@
     return this;
   }
 
-  public LIRBuilder<V, B> addMoveException(DexType exceptionType) {
-    return addOneItemInstruction(LIROpcodes.MOVEEXCEPTION, exceptionType);
+  public LirBuilder<V, B> addMoveException(DexType exceptionType) {
+    return addOneItemInstruction(LirOpcodes.MOVEEXCEPTION, exceptionType);
   }
 
-  public LIRBuilder<V, B> addPhi(TypeElement type, List<V> operands) {
+  public LirBuilder<V, B> addPhi(TypeElement type, List<V> operands) {
     DexType dexType = toDexType(type);
-    return addInstructionTemplate(LIROpcodes.PHI, Collections.singletonList(dexType), operands);
+    return addInstructionTemplate(LirOpcodes.PHI, Collections.singletonList(dexType), operands);
   }
 
-  public LIRBuilder<V, B> addDebugLocalWrite(V src) {
-    return addOneValueInstruction(LIROpcodes.DEBUGLOCALWRITE, src);
+  public LirBuilder<V, B> addDebugLocalWrite(V src) {
+    return addOneValueInstruction(LirOpcodes.DEBUGLOCALWRITE, src);
   }
 
-  public LIRCode build() {
+  public LirCode build() {
     assert metadata != null;
     int constantsCount = constants.size();
     DexItem[] constantTable = new DexItem[constantsCount];
     constants.forEach((item, index) -> constantTable[index] = item);
     DebugLocalInfoTable debugTable =
         debugLocals.isEmpty() ? null : new DebugLocalInfoTable(debugLocals, debugLocalEnds);
-    return new LIRCode(
+    return new LirCode(
         metadata,
         constantTable,
         positionTable.toArray(new PositionEntry[positionTable.size()]),
@@ -446,6 +447,7 @@
         byteWriter.toByteArray(),
         instructionCount,
         new TryCatchTable(tryCatchRanges),
-        debugTable);
+        debugTable,
+        ssaValueStrategy);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
similarity index 83%
rename from src/main/java/com/android/tools/r8/lightir/LIRCode.java
rename to src/main/java/com/android/tools/r8/lightir/LirCode.java
index e39820a..3851ba5 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -10,11 +10,11 @@
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.lightir.LIRBuilder.BlockIndexGetter;
-import com.android.tools.r8.lightir.LIRBuilder.ValueIndexGetter;
+import com.android.tools.r8.lightir.LirBuilder.BlockIndexGetter;
+import com.android.tools.r8.lightir.LirBuilder.ValueIndexGetter;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 
-public class LIRCode implements Iterable<LIRInstructionView> {
+public class LirCode implements Iterable<LirInstructionView> {
 
   public static class PositionEntry {
     final int fromInstructionIndex;
@@ -52,6 +52,8 @@
     }
   }
 
+  private final LirSsaValueStrategy ssaValueStrategy;
+
   private final IRMetadata metadata;
 
   /** Constant pool of items. */
@@ -74,16 +76,16 @@
   /** Table of debug local information for each SSA value (if present). */
   private final DebugLocalInfoTable debugLocalInfoTable;
 
-  public static <V, B> LIRBuilder<V, B> builder(
+  public static <V, B> LirBuilder<V, B> builder(
       DexMethod method,
       ValueIndexGetter<V> valueIndexGetter,
       BlockIndexGetter<B> blockIndexGetter,
       DexItemFactory factory) {
-    return new LIRBuilder<V, B>(method, valueIndexGetter, blockIndexGetter, factory);
+    return new LirBuilder<V, B>(method, valueIndexGetter, blockIndexGetter, factory);
   }
 
-  // Should be constructed using LIRBuilder.
-  LIRCode(
+  /** Should be constructed using {@link LirBuilder}. */
+  LirCode(
       IRMetadata metadata,
       DexItem[] constants,
       PositionEntry[] positions,
@@ -91,7 +93,8 @@
       byte[] instructions,
       int instructionCount,
       TryCatchTable tryCatchTable,
-      DebugLocalInfoTable debugLocalInfoTable) {
+      DebugLocalInfoTable debugLocalInfoTable,
+      LirSsaValueStrategy ssaValueStrategy) {
     this.metadata = metadata;
     this.constants = constants;
     this.positionTable = positions;
@@ -100,6 +103,11 @@
     this.instructionCount = instructionCount;
     this.tryCatchTable = tryCatchTable;
     this.debugLocalInfoTable = debugLocalInfoTable;
+    this.ssaValueStrategy = ssaValueStrategy;
+  }
+
+  public int decodeValueIndex(int encodedValueIndex, int currentValueIndex) {
+    return ssaValueStrategy.decodeValueIndex(encodedValueIndex, currentValueIndex);
   }
 
   public int getArgumentCount() {
@@ -145,12 +153,12 @@
   }
 
   @Override
-  public LIRIterator iterator() {
-    return new LIRIterator(new ByteArrayIterator(instructions));
+  public LirIterator iterator() {
+    return new LirIterator(new ByteArrayIterator(instructions));
   }
 
   @Override
   public String toString() {
-    return new LIRPrinter(this).prettyPrint();
+    return new LirPrinter(this).prettyPrint();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirInstructionCallback.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java
rename to src/main/java/com/android/tools/r8/lightir/LirInstructionCallback.java
index 42a0955..35b6910 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirInstructionCallback.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.lightir;
 
 /** Convenience interface to accept a LIR instruction view. */
-public interface LIRInstructionCallback {
+public interface LirInstructionCallback {
 
-  void onInstructionView(LIRInstructionView view);
+  void onInstructionView(LirInstructionView view);
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java b/src/main/java/com/android/tools/r8/lightir/LirInstructionView.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
rename to src/main/java/com/android/tools/r8/lightir/LirInstructionView.java
index 428c01f..3eef40d 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRInstructionView.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirInstructionView.java
@@ -10,15 +10,15 @@
  * can change. The view callbacks allow interpreting the instruction at different levels of
  * abstraction depending on need.
  */
-public interface LIRInstructionView {
+public interface LirInstructionView {
 
   /** Convenience method to forward control to a callback. */
-  void accept(LIRInstructionCallback eventCallback);
+  void accept(LirInstructionCallback eventCallback);
 
   /** Get the instruction index. */
   int getInstructionIndex();
 
-  /** The opcode of the instruction (See {@code LIROpcodes} for values). */
+  /** The opcode of the instruction (See {@link LirOpcodes} for values). */
   int getOpcode();
 
   /** The remaining size of the instruction's payload. */
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java b/src/main/java/com/android/tools/r8/lightir/LirIterator.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/lightir/LIRIterator.java
rename to src/main/java/com/android/tools/r8/lightir/LirIterator.java
index 882e960..dd29c93 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRIterator.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirIterator.java
@@ -12,7 +12,7 @@
  * <p>This iterator is internally a zero-allocation parser with the "elements" as a view onto the
  * current state.
  */
-public class LIRIterator implements Iterator<LIRInstructionView>, LIRInstructionView {
+public class LirIterator implements Iterator<LirInstructionView>, LirInstructionView {
 
   private final ByteIterator iterator;
 
@@ -24,7 +24,7 @@
   private int currentInstructionIndex = -1;
   private int currentOpcode = -1;
 
-  public LIRIterator(ByteIterator iterator) {
+  public LirIterator(ByteIterator iterator) {
     this.iterator = iterator;
   }
 
@@ -41,11 +41,11 @@
   }
 
   @Override
-  public LIRInstructionView next() {
+  public LirInstructionView next() {
     skipRemainingOperands();
     ++currentInstructionIndex;
     currentOpcode = u1();
-    if (LIROpcodes.isOneByteInstruction(currentOpcode)) {
+    if (LirOpcodes.isOneByteInstruction(currentOpcode)) {
       endOfCurrentInstruction = currentByteIndex;
     } else {
       // Any instruction that is not a single byte has a two-byte header. The second byte is the
@@ -57,7 +57,7 @@
   }
 
   @Override
-  public void accept(LIRInstructionCallback eventCallback) {
+  public void accept(LirInstructionCallback eventCallback) {
     eventCallback.onInstructionView(this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
similarity index 99%
rename from src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
rename to src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index abfea89..0d30efd 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -10,7 +10,7 @@
  *
  * <p>The constants generally follow the bytecode values as defined by the classfile format.
  */
-public interface LIROpcodes {
+public interface LirOpcodes {
 
   static boolean isOneByteInstruction(int opcode) {
     assert opcode >= ACONST_NULL;
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
similarity index 81%
rename from src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
rename to src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index 40395bb..dba40f8 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -23,13 +23,13 @@
  * onInvokeVirtual will default dispatch to onInvokedMethodInstruction).
  *
  * <p>Due to the parsing of the individual instructions, this parser has a higher overhead than
- * using the basic {@code LIRInstructionView}.
+ * using the basic {@link LirInstructionView}.
  */
-public abstract class LIRParsedInstructionCallback implements LIRInstructionCallback {
+public abstract class LirParsedInstructionCallback implements LirInstructionCallback {
 
-  private final LIRCode code;
+  private final LirCode code;
 
-  public LIRParsedInstructionCallback(LIRCode code) {
+  public LirParsedInstructionCallback(LirCode code) {
     this.code = code;
   }
 
@@ -41,10 +41,10 @@
   }
 
   private int getActualValueIndex(int relativeValueIndex) {
-    return LIRUtils.decodeValueIndex(relativeValueIndex, getCurrentValueIndex());
+    return code.decodeValueIndex(relativeValueIndex, getCurrentValueIndex());
   }
 
-  private int getNextValueOperand(LIRInstructionView view) {
+  private int getNextValueOperand(LirInstructionView view) {
     return getActualValueIndex(view.getNextValueOperand());
   }
 
@@ -135,15 +135,15 @@
   }
 
   @Override
-  public void onInstructionView(LIRInstructionView view) {
+  public void onInstructionView(LirInstructionView view) {
     int opcode = view.getOpcode();
     switch (opcode) {
-      case LIROpcodes.ACONST_NULL:
+      case LirOpcodes.ACONST_NULL:
         {
           onConstNull();
           return;
         }
-      case LIROpcodes.LDC:
+      case LirOpcodes.LDC:
         {
           DexItem item = getConstantItem(view.getNextConstantOperand());
           if (item instanceof DexString) {
@@ -152,80 +152,80 @@
           }
           throw new Unimplemented();
         }
-      case LIROpcodes.ICONST_M1:
-      case LIROpcodes.ICONST_0:
-      case LIROpcodes.ICONST_1:
-      case LIROpcodes.ICONST_2:
-      case LIROpcodes.ICONST_3:
-      case LIROpcodes.ICONST_4:
-      case LIROpcodes.ICONST_5:
+      case LirOpcodes.ICONST_M1:
+      case LirOpcodes.ICONST_0:
+      case LirOpcodes.ICONST_1:
+      case LirOpcodes.ICONST_2:
+      case LirOpcodes.ICONST_3:
+      case LirOpcodes.ICONST_4:
+      case LirOpcodes.ICONST_5:
         {
-          int value = opcode - LIROpcodes.ICONST_0;
+          int value = opcode - LirOpcodes.ICONST_0;
           onConstInt(value);
           return;
         }
-      case LIROpcodes.ICONST:
+      case LirOpcodes.ICONST:
         {
           int value = view.getNextIntegerOperand();
           onConstInt(value);
           return;
         }
-      case LIROpcodes.IDIV:
+      case LirOpcodes.IDIV:
         {
           int leftValueIndex = getNextValueOperand(view);
           int rightValueIndex = getNextValueOperand(view);
           onDivInt(leftValueIndex, rightValueIndex);
           return;
         }
-      case LIROpcodes.IFNE:
+      case LirOpcodes.IFNE:
         {
           int blockIndex = view.getNextBlockOperand();
           int valueIndex = getNextValueOperand(view);
           onIf(If.Type.NE, blockIndex, valueIndex);
           return;
         }
-      case LIROpcodes.GOTO:
+      case LirOpcodes.GOTO:
         {
           int blockIndex = view.getNextBlockOperand();
           onGoto(blockIndex);
           return;
         }
-      case LIROpcodes.INVOKEDIRECT:
+      case LirOpcodes.INVOKEDIRECT:
         {
           DexMethod target = getInvokeInstructionTarget(view);
           IntList arguments = getInvokeInstructionArguments(view);
           onInvokeDirect(target, arguments);
           return;
         }
-      case LIROpcodes.INVOKEVIRTUAL:
+      case LirOpcodes.INVOKEVIRTUAL:
         {
           DexMethod target = getInvokeInstructionTarget(view);
           IntList arguments = getInvokeInstructionArguments(view);
           onInvokeVirtual(target, arguments);
           return;
         }
-      case LIROpcodes.GETSTATIC:
+      case LirOpcodes.GETSTATIC:
         {
           DexField field = (DexField) getConstantItem(view.getNextConstantOperand());
           onStaticGet(field);
           return;
         }
-      case LIROpcodes.RETURN:
+      case LirOpcodes.RETURN:
         {
           onReturnVoid();
           return;
         }
-      case LIROpcodes.ARRAYLENGTH:
+      case LirOpcodes.ARRAYLENGTH:
         {
           onArrayLength(getNextValueOperand(view));
           return;
         }
-      case LIROpcodes.DEBUGPOS:
+      case LirOpcodes.DEBUGPOS:
         {
           onDebugPosition();
           return;
         }
-      case LIROpcodes.PHI:
+      case LirOpcodes.PHI:
         {
           DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
           IntList operands = new IntArrayList();
@@ -235,33 +235,33 @@
           onPhi(type, operands);
           return;
         }
-      case LIROpcodes.FALLTHROUGH:
+      case LirOpcodes.FALLTHROUGH:
         {
           onFallthrough();
           return;
         }
-      case LIROpcodes.MOVEEXCEPTION:
+      case LirOpcodes.MOVEEXCEPTION:
         {
           DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
           onMoveException(type);
           return;
         }
-      case LIROpcodes.DEBUGLOCALWRITE:
+      case LirOpcodes.DEBUGLOCALWRITE:
         {
           int srcIndex = getNextValueOperand(view);
           onDebugLocalWrite(srcIndex);
           return;
         }
       default:
-        throw new Unimplemented("No dispatch for opcode " + LIROpcodes.toString(opcode));
+        throw new Unimplemented("No dispatch for opcode " + LirOpcodes.toString(opcode));
     }
   }
 
-  private DexMethod getInvokeInstructionTarget(LIRInstructionView view) {
+  private DexMethod getInvokeInstructionTarget(LirInstructionView view) {
     return (DexMethod) getConstantItem(view.getNextConstantOperand());
   }
 
-  private IntList getInvokeInstructionArguments(LIRInstructionView view) {
+  private IntList getInvokeInstructionArguments(LirInstructionView view) {
     IntList arguments = new IntArrayList();
     while (view.hasMoreOperands()) {
       arguments.add(getNextValueOperand(view));
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/lightir/LIRPrinter.java
rename to src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index c8af439..93d117b 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -13,19 +13,19 @@
 import com.android.tools.r8.utils.StringUtils;
 import it.unimi.dsi.fastutil.ints.IntList;
 
-public class LIRPrinter extends LIRParsedInstructionCallback {
+public class LirPrinter extends LirParsedInstructionCallback {
 
   private static final String SEPERATOR = "\n";
-  private final LIRCode code;
+  private final LirCode code;
   private final StringBuilder builder = new StringBuilder();
 
   private final int instructionIndexPadding;
   private final int instructionNamePadding;
 
   private int valueIndex = 0;
-  private LIRInstructionView view;
+  private LirInstructionView view;
 
-  public LIRPrinter(LIRCode code) {
+  public LirPrinter(LirCode code) {
     super(code);
     this.code = code;
     instructionIndexPadding =
@@ -33,8 +33,8 @@
             fmtInsnIndex(-code.getArgumentCount()).length(),
             fmtInsnIndex(code.getInstructionCount() - 1).length());
     int maxNameLength = 0;
-    for (LIRInstructionView view : code) {
-      maxNameLength = Math.max(maxNameLength, LIROpcodes.toString(view.getOpcode()).length());
+    for (LirInstructionView view : code) {
+      maxNameLength = Math.max(maxNameLength, LirOpcodes.toString(view.getOpcode()).length());
     }
     instructionNamePadding = maxNameLength;
   }
@@ -73,12 +73,12 @@
   }
 
   @Override
-  public void onInstructionView(LIRInstructionView view) {
+  public void onInstructionView(LirInstructionView view) {
     this.view = view;
     assert view.getInstructionIndex() == getCurrentInstructionIndex();
     int operandSizeInBytes = view.getRemainingOperandSizeInBytes();
     int instructionSizeInBytes = operandSizeInBytes == 0 ? 1 : 2 + operandSizeInBytes;
-    String opcode = LIROpcodes.toString(view.getOpcode());
+    String opcode = LirOpcodes.toString(view.getOpcode());
     addInstructionHeader(opcode, instructionSizeInBytes);
     super.onInstructionView(view);
     advanceToNextValueIndex();
@@ -101,7 +101,7 @@
   @Override
   public void onInstruction() {
     throw new Unimplemented(
-        "Printing of instruction missing: " + LIROpcodes.toString(view.getOpcode()));
+        "Printing of instruction missing: " + LirOpcodes.toString(view.getOpcode()));
   }
 
   private StringBuilder appendOutValue() {
diff --git a/src/main/java/com/android/tools/r8/lightir/LirSsaValueStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirSsaValueStrategy.java
new file mode 100644
index 0000000..b89dca8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LirSsaValueStrategy.java
@@ -0,0 +1,50 @@
+// 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;
+
+/**
+ * Abstraction for how to encode SSA values.
+ *
+ * <p>At high level the unencoded SSA value index is the absolute index to the instruction defining
+ * the SSA out value. This strategy provides a way to opaquely encode any references of an SSA value
+ * as the relative offset from the referencing instruction.
+ */
+public abstract class LirSsaValueStrategy {
+
+  private static final LirSsaValueStrategy INSTANCE = new RelativeStrategy();
+
+  public static LirSsaValueStrategy get() {
+    return INSTANCE;
+  }
+
+  public abstract int encodeValueIndex(int absoluteValueIndex, int currentValueIndex);
+
+  public abstract int decodeValueIndex(int encodedValueIndex, int currentValueIndex);
+
+  private static class AbsoluteStrategy extends LirSsaValueStrategy {
+
+    @Override
+    public int encodeValueIndex(int absoluteValueIndex, int currentValueIndex) {
+      return absoluteValueIndex;
+    }
+
+    @Override
+    public int decodeValueIndex(int encodedValueIndex, int currentValueIndex) {
+      return encodedValueIndex;
+    }
+  }
+
+  private static class RelativeStrategy extends LirSsaValueStrategy {
+
+    @Override
+    public int encodeValueIndex(int absoluteValueIndex, int currentValueIndex) {
+      return currentValueIndex - absoluteValueIndex;
+    }
+
+    @Override
+    public int decodeValueIndex(int encodedValueIndex, int currentValueIndex) {
+      return currentValueIndex - encodedValueIndex;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRWriter.java b/src/main/java/com/android/tools/r8/lightir/LirWriter.java
similarity index 86%
rename from src/main/java/com/android/tools/r8/lightir/LIRWriter.java
rename to src/main/java/com/android/tools/r8/lightir/LirWriter.java
index 074726d..3f9e6b4 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRWriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirWriter.java
@@ -7,19 +7,19 @@
  * Lowest level writer for constructing LIR encoded data.
  *
  * <p>This writer deals with just the instruction and operand encodings. For higher level structure,
- * such as the constant pool, see LIRBuilder.
+ * such as the constant pool, see {@link LirBuilder}.
  */
-public class LIRWriter {
+public class LirWriter {
 
   private final ByteWriter writer;
   private int pendingOperandBytes = 0;
 
-  public LIRWriter(ByteWriter writer) {
+  public LirWriter(ByteWriter writer) {
     this.writer = writer;
   }
 
   public void writeOneByteInstruction(int opcode) {
-    assert LIROpcodes.isOneByteInstruction(opcode);
+    assert LirOpcodes.isOneByteInstruction(opcode);
     assert pendingOperandBytes == 0;
     writer.put(ByteUtils.ensureU1(opcode));
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 2ac83b2..b6abede 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -50,6 +50,7 @@
 
   private final AndroidApiLevelCompute androidApiLevelCompute;
   private final AppView<AppInfoWithLiveness> appView;
+  private final MemberRebindingEventConsumer eventConsumer;
   private final InternalOptions options;
 
   private final MemberRebindingLens.Builder lensBuilder;
@@ -58,6 +59,7 @@
     assert appView.graphLens().isContextFreeForMethods();
     this.androidApiLevelCompute = appView.apiLevelCompute();
     this.appView = appView;
+    this.eventConsumer = MemberRebindingEventConsumer.create(appView);
     this.options = appView.options();
     this.lensBuilder = MemberRebindingLens.builder(appView);
   }
@@ -383,6 +385,8 @@
                             target.isLibraryMethod(), OptionalBool.TRUE);
                       });
               bridgeHolder.addMethod(bridgeMethodDefinition);
+              eventConsumer.acceptMemberRebindingBridgeMethod(
+                  bridgeMethodDefinition.asProgramMethod(bridgeHolder), target);
             }
             assert resolver.apply(method).getResolvedMethod().getReference() == bridgeMethod;
           }
@@ -507,12 +511,14 @@
     fieldAccessInfoCollection.forEach(lensBuilder::recordNonReboundFieldAccesses);
   }
 
-  public MemberRebindingLens run(ExecutorService executorService) throws ExecutionException {
+  public void run(ExecutorService executorService) throws ExecutionException {
     AppInfoWithLiveness appInfo = appView.appInfo();
     computeMethodRebinding(appInfo.getMethodAccessInfoCollection());
     recordNonReboundFieldAccesses(executorService);
     appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
-    return lensBuilder.build();
+    MemberRebindingLens memberRebindingLens = lensBuilder.build();
+    appView.setGraphLens(memberRebindingLens);
+    eventConsumer.finished(appView, memberRebindingLens);
   }
 
   private boolean verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingEventConsumer.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingEventConsumer.java
new file mode 100644
index 0000000..dff6597
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingEventConsumer.java
@@ -0,0 +1,46 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingMemberRebindingEventConsumer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public interface MemberRebindingEventConsumer {
+
+  void acceptMemberRebindingBridgeMethod(
+      ProgramMethod bridgeMethod, DexClassAndMethod targetMethod);
+
+  default void finished(
+      AppView<AppInfoWithLiveness> appView, MemberRebindingLens memberRebindingLens) {}
+
+  static MemberRebindingEventConsumer create(AppView<AppInfoWithLiveness> appView) {
+    return ArtProfileRewritingMemberRebindingEventConsumer.attach(appView, empty());
+  }
+
+  static EmptyMemberRebindingEventConsumer empty() {
+    return EmptyMemberRebindingEventConsumer.getInstance();
+  }
+
+  class EmptyMemberRebindingEventConsumer implements MemberRebindingEventConsumer {
+
+    private static final EmptyMemberRebindingEventConsumer INSTANCE =
+        new EmptyMemberRebindingEventConsumer();
+
+    private EmptyMemberRebindingEventConsumer() {}
+
+    static EmptyMemberRebindingEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptMemberRebindingBridgeMethod(
+        ProgramMethod bridgeMethod, DexClassAndMethod targetMethod) {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 16edb6a..1a2eaa6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -36,7 +36,6 @@
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder;
 import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepMethodInfo;
@@ -194,10 +193,8 @@
       Timing timing)
       throws ExecutionException {
     timing.begin("Optimize components");
-    ArtProfileCollectionAdditions artProfileCollectionAdditions =
-        ArtProfileCollectionAdditions.create(appView);
     ArgumentPropagatorSyntheticEventConsumer eventConsumer =
-        ArgumentPropagatorSyntheticEventConsumer.create(artProfileCollectionAdditions);
+        ArgumentPropagatorSyntheticEventConsumer.create(appView);
     ProcessorContext processorContext = appView.createProcessorContext();
     Collection<Builder> partialGraphLensBuilders =
         ThreadUtils.processItemsWithResults(
@@ -210,7 +207,7 @@
                             classes, DexMethodSignatureSet.empty()),
                         affectedClassConsumer),
             executorService);
-    artProfileCollectionAdditions.commit(appView);
+    eventConsumer.finished(appView);
     timing.end();
 
     // Merge all the partial, disjoint graph lens builders into a single graph lens.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorSyntheticEventConsumer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorSyntheticEventConsumer.java
index aae0602..799cd92 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorSyntheticEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorSyntheticEventConsumer.java
@@ -4,28 +4,20 @@
 
 package com.android.tools.r8.optimize.argumentpropagation;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
-import com.android.tools.r8.profile.art.rewriting.ConcreteArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public interface ArgumentPropagatorSyntheticEventConsumer {
 
   void acceptInitializerArgumentClass(DexProgramClass clazz, ProgramMethod context);
 
-  static ArgumentPropagatorSyntheticEventConsumer create(
-      ArtProfileCollectionAdditions collectionAdditions) {
-    if (collectionAdditions.isNop()) {
-      return empty();
-    }
-    return create(collectionAdditions.asConcrete());
-  }
+  void finished(AppView<AppInfoWithLiveness> appView);
 
-  static ArgumentPropagatorSyntheticEventConsumer create(
-      ConcreteArtProfileCollectionAdditions collectionAdditions) {
-    return (clazz, context) ->
-        collectionAdditions.applyIfContextIsInProfile(
-            context, additionsBuilder -> additionsBuilder.addRule(clazz));
+  static ArgumentPropagatorSyntheticEventConsumer create(AppView<AppInfoWithLiveness> appView) {
+    return ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer.attach(appView, empty());
   }
 
   static ArgumentPropagatorSyntheticEventConsumer empty() {
@@ -48,5 +40,10 @@
     public void acceptInitializerArgumentClass(DexProgramClass clazz, ProgramMethod context) {
       // Intentionally empty.
     }
+
+    @Override
+    public void finished(AppView<AppInfoWithLiveness> appView) {
+      // Intentionally empty.
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
index 55d7ef6..80a9913 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -8,16 +8,19 @@
 
 import com.android.tools.r8.TextInputStream;
 import com.android.tools.r8.TextOutputStream;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.TriConsumer;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.UncheckedIOException;
@@ -77,24 +80,60 @@
     return (ArtProfileMethodRule) rules.get(method);
   }
 
-  public ArtProfile rewrittenWithLens(GraphLens lens) {
+  public ArtProfile rewrittenWithLens(AppView<?> appView, GraphLens lens) {
+    if (lens.isEnumUnboxerLens()) {
+      return rewrittenWithLens(appView, lens.asEnumUnboxerLens());
+    }
     return transform(
-        (classRule, builderFactory) -> builderFactory.accept(lens.lookupType(classRule.getType())),
-        (methodRule, builderFactory) ->
-            builderFactory
+        (classRule, classRuleBuilderFactory) -> {
+          DexType newClassRule = lens.lookupType(classRule.getType());
+          assert newClassRule.isClassType();
+          classRuleBuilderFactory.accept(newClassRule);
+        },
+        (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) ->
+            methodRuleBuilderFactory
                 .apply(lens.getRenamedMethodSignature(methodRule.getMethod()))
                 .acceptMethodRuleInfoBuilder(
                     methodRuleInfoBuilder ->
                         methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())));
   }
 
+  public ArtProfile rewrittenWithLens(AppView<?> appView, EnumUnboxingLens lens) {
+    return transform(
+        (classRule, classRuleBuilderFactory) -> {
+          DexType newClassRule = lens.lookupType(classRule.getType());
+          if (newClassRule.isClassType()) {
+            classRuleBuilderFactory.accept(newClassRule);
+          } else {
+            assert newClassRule.isIntType();
+          }
+        },
+        (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> {
+          DexMethod newMethod = lens.getRenamedMethodSignature(methodRule.getMethod());
+          // When moving non-synthetic methods from an enum class to its enum utility class we also
+          // add a rule for the utility class.
+          if (newMethod.getHolderType() != methodRule.getMethod().getHolderType()) {
+            assert appView
+                .getSyntheticItems()
+                .isSyntheticOfKind(
+                    newMethod.getHolderType(), naming -> naming.ENUM_UNBOXING_LOCAL_UTILITY_CLASS);
+            classRuleBuilderFactory.accept(newMethod.getHolderType());
+          }
+          methodRuleBuilderFactory
+              .apply(newMethod)
+              .acceptMethodRuleInfoBuilder(
+                  methodRuleInfoBuilder ->
+                      methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo()));
+        });
+  }
+
   public ArtProfile rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) {
     assert !lens.isIdentityLens();
     return transform(
-        (classRule, builderFactory) ->
-            builderFactory.accept(lens.lookupType(classRule.getType(), dexItemFactory)),
-        (methodRule, builderFactory) ->
-            builderFactory
+        (classRule, classRuleBuilderFactory) ->
+            classRuleBuilderFactory.accept(lens.lookupType(classRule.getType(), dexItemFactory)),
+        (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) ->
+            methodRuleBuilderFactory
                 .apply(lens.lookupMethod(methodRule.getMethod(), dexItemFactory))
                 .acceptMethodRuleInfoBuilder(
                     methodRuleInfoBuilder ->
@@ -103,14 +142,14 @@
 
   public ArtProfile withoutPrunedItems(PrunedItems prunedItems) {
     return transform(
-        (classRule, builderFactory) -> {
+        (classRule, classRuleBuilderFactory) -> {
           if (!prunedItems.isRemoved(classRule.getType())) {
-            builderFactory.accept(classRule.getType());
+            classRuleBuilderFactory.accept(classRule.getType());
           }
         },
-        (methodRule, builderFactory) -> {
+        (methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> {
           if (!prunedItems.isRemoved(methodRule.getMethod())) {
-            builderFactory
+            methodRuleBuilderFactory
                 .apply(methodRule.getMethod())
                 .acceptMethodRuleInfoBuilder(
                     methodRuleInfoBuilder ->
@@ -121,34 +160,35 @@
 
   private ArtProfile transform(
       BiConsumer<ArtProfileClassRule, Consumer<DexType>> classTransformation,
-      BiConsumer<ArtProfileMethodRule, Function<DexMethod, ArtProfileMethodRule.Builder>>
+      TriConsumer<
+              ArtProfileMethodRule,
+              Consumer<DexType>,
+              Function<DexMethod, ArtProfileMethodRule.Builder>>
           methodTransformation) {
     Map<DexReference, ArtProfileRule.Builder> ruleBuilders = new LinkedHashMap<>();
+    Consumer<DexType> classRuleBuilderFactory =
+        newType ->
+            ruleBuilders
+                .computeIfAbsent(
+                    newType, ignoreKey(() -> ArtProfileClassRule.builder().setType(newType)))
+                .asClassRuleBuilder();
+    Function<DexMethod, ArtProfileMethodRule.Builder> methodRuleBuilderFactory =
+        newMethod ->
+            ruleBuilders
+                .computeIfAbsent(
+                    newMethod, ignoreKey(() -> ArtProfileMethodRule.builder().setMethod(newMethod)))
+                .asMethodRuleBuilder();
     forEachRule(
         // Supply a factory method for creating a builder. If the current rule should be included in
         // the rewritten profile, the caller should call the provided builder factory method to
         // create a class rule builder. If two rules are mapped to the same reference, the same rule
         // builder is reused so that the two rules are merged into a single rule (with their flags
         // merged).
-        classRule ->
-            classTransformation.accept(
-                classRule,
-                newType ->
-                    ruleBuilders
-                        .computeIfAbsent(
-                            newType,
-                            ignoreKey(() -> ArtProfileClassRule.builder().setType(newType)))
-                        .asClassRuleBuilder()),
+        classRule -> classTransformation.accept(classRule, classRuleBuilderFactory),
         // As above.
         methodRule ->
             methodTransformation.accept(
-                methodRule,
-                newMethod ->
-                    ruleBuilders
-                        .computeIfAbsent(
-                            newMethod,
-                            ignoreKey(() -> ArtProfileMethodRule.builder().setMethod(newMethod)))
-                        .asMethodRuleBuilder()));
+                methodRule, classRuleBuilderFactory, methodRuleBuilderFactory));
     return builder().addRuleBuilders(ruleBuilders.values()).build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
index e7012b8..87b0e95 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
@@ -65,7 +65,7 @@
 
   public abstract NonEmptyArtProfileCollection asNonEmpty();
 
-  public abstract ArtProfileCollection rewrittenWithLens(GraphLens lens);
+  public abstract ArtProfileCollection rewrittenWithLens(AppView<?> appView, GraphLens lens);
 
   public abstract ArtProfileCollection rewrittenWithLens(
       NamingLens lens, DexItemFactory dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
index 21a7635..d7fde7d 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
@@ -19,6 +19,21 @@
 import java.util.List;
 import java.util.Set;
 
+/**
+ * Verifies that, if given an ART profile containing all program classes and methods in the input,
+ * then the residual ART profile also contains all classes and methods in the output.
+ *
+ * <p>If this check fails, either:
+ *
+ * <ul>
+ *   <li>The current change added new synthetics to the program that need to be added to each
+ *       profile that contains the synthesizing context, or
+ *   <li>The existing rewriting of ART profiles and inclusion of synthetics in incomplete.
+ * </ul>
+ *
+ * <p>In the latter case, create a tracking bug and suppress the assertion failure by setting {@link
+ * ArtProfileOptions#setEnableCompletenessCheckForTesting(boolean)} to false.
+ */
 public class ArtProfileCompletenessChecker {
 
   public enum CompletenessExceptions {
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
index df469c9..fe7bd78 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
@@ -113,6 +113,10 @@
       this.dexItemFactory = dexItemFactory;
     }
 
+    ArtProfileMethodRuleInfoImpl.Builder getMethodRuleInfoBuilder() {
+      return methodRuleInfoBuilder;
+    }
+
     @Override
     public boolean isMethodRuleBuilder() {
       return true;
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
index 252cdac..cb482d8 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
@@ -25,6 +25,10 @@
     return EMPTY;
   }
 
+  public int getFlags() {
+    return flags;
+  }
+
   public boolean isEmpty() {
     return flags == 0;
   }
@@ -97,6 +101,10 @@
       return this;
     }
 
+    public int getFlags() {
+      return flags;
+    }
+
     public Builder merge(ArtProfileMethodRuleInfo methodRuleInfo) {
       if (methodRuleInfo.isHot()) {
         setIsHot();
@@ -141,7 +149,12 @@
     }
 
     public Builder joinFlags(ArtProfileMethodRuleInfoImpl methodRuleInfo) {
-      flags |= methodRuleInfo.flags;
+      flags |= methodRuleInfo.getFlags();
+      return this;
+    }
+
+    public Builder joinFlags(ArtProfileMethodRule.Builder methodRuleBuilder) {
+      flags |= methodRuleBuilder.getMethodRuleInfoBuilder().getFlags();
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
index d7c3f12..d5f91da 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -6,24 +6,34 @@
 
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
 
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collection;
 import java.util.Collections;
 
 public class ArtProfileOptions {
 
+  public static final String COMPLETENESS_PROPERTY_KEY =
+      "com.android.tools.r8.artprofilerewritingcompletenesscheck";
+
   private Collection<ArtProfileForRewriting> artProfilesForRewriting = Collections.emptyList();
   private boolean enableCompletenessCheckForTesting =
-      parseSystemPropertyOrDefault(
-          "com.android.tools.r8.artprofilerewritingcompletenesscheck", false);
+      parseSystemPropertyOrDefault(COMPLETENESS_PROPERTY_KEY, false);
 
-  public ArtProfileOptions() {}
+  private final InternalOptions options;
+
+  public ArtProfileOptions(InternalOptions options) {
+    this.options = options;
+  }
 
   public Collection<ArtProfileForRewriting> getArtProfilesForRewriting() {
     return artProfilesForRewriting;
   }
 
   public boolean isCompletenessCheckForTestingEnabled() {
-    return enableCompletenessCheckForTesting;
+    return enableCompletenessCheckForTesting
+        && !options.isDesugaredLibraryCompilation()
+        && !options.getStartupOptions().isStartupCompletenessCheckForTestingEnabled()
+        && !options.getStartupInstrumentationOptions().isStartupInstrumentationEnabled();
   }
 
   public boolean isIncludingApiReferenceStubs() {
@@ -33,6 +43,36 @@
     return enableCompletenessCheckForTesting;
   }
 
+  public boolean isIncludingBackportedClasses() {
+    // Similar to isIncludingVarHandleClasses().
+    return enableCompletenessCheckForTesting;
+  }
+
+  public boolean isIncludingConstantDynamicClass() {
+    // Similar to isIncludingVarHandleClasses().
+    return enableCompletenessCheckForTesting;
+  }
+
+  public boolean isIncludingDesugaredLibraryRetargeterForwardingMethodsUnconditionally() {
+    // TODO(b/265729283): If we get as input the profile for the desugared library, maybe we can
+    //  tell if the method targeted by the forwarding method is in the profile, e.g.:
+    //  java.time.Instant java.util.DesugarDate.toInstant(java.util.Date).
+    return enableCompletenessCheckForTesting;
+  }
+
+  public boolean isIncludingThrowingMethods() {
+    // The throw methods we insert should generally be dead a runtime, so no need for them to be
+    // optimized.
+    return enableCompletenessCheckForTesting;
+  }
+
+  public boolean isIncludingVarHandleClasses() {
+    // We only include var handle classes in the residual ART profiles for completeness testing,
+    // since the classes synthesized by var handle desugaring are fairly large and may not be that
+    // important for runtime performance.
+    return enableCompletenessCheckForTesting;
+  }
+
   public ArtProfileOptions setArtProfilesForRewriting(Collection<ArtProfileForRewriting> inputs) {
     this.artProfilesForRewriting = inputs;
     return this;
diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
index 0540aa4..d4f3f9f 100644
--- a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public ArtProfileCollection rewrittenWithLens(GraphLens lens) {
+  public ArtProfileCollection rewrittenWithLens(AppView<?> appView, GraphLens lens) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
index d7f72ef..5251d56 100644
--- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
@@ -46,8 +46,8 @@
   }
 
   @Override
-  public NonEmptyArtProfileCollection rewrittenWithLens(GraphLens lens) {
-    return map(artProfile -> artProfile.rewrittenWithLens(lens));
+  public NonEmptyArtProfileCollection rewrittenWithLens(AppView<?> appView, GraphLens lens) {
+    return map(artProfile -> artProfile.rewrittenWithLens(appView, lens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
index b210019..f4400c7 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -16,26 +18,46 @@
 import com.android.tools.r8.profile.art.ArtProfileMethodRule;
 import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
 import com.android.tools.r8.profile.art.ArtProfileRule;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /** Mutable extension of an existing ArtProfile. */
 public class ArtProfileAdditions {
 
   public interface ArtProfileAdditionsBuilder {
 
-    ArtProfileAdditionsBuilder addRule(ProgramDefinition definition);
+    default ArtProfileAdditionsBuilder addRule(ProgramDefinition definition) {
+      return addRule(definition.getReference());
+    }
 
-    ArtProfileAdditionsBuilder addRule(DexReference reference);
+    default ArtProfileAdditionsBuilder addRule(DexReference reference) {
+      if (reference.isDexType()) {
+        return addClassRule(reference.asDexType());
+      } else {
+        assert reference.isDexMethod();
+        return addMethodRule(reference.asDexMethod());
+      }
+    }
 
-    ArtProfileAdditionsBuilder removeMovedMethodRule(
-        ProgramMethod oldMethod, ProgramMethod newMethod);
+    ArtProfileAdditionsBuilder addClassRule(DexType type);
+
+    ArtProfileAdditionsBuilder addMethodRule(DexMethod method);
+
+    default ArtProfileAdditionsBuilder removeMovedMethodRule(
+        ProgramMethod oldMethod, ProgramMethod newMethod) {
+      return removeMovedMethodRule(oldMethod.getReference(), newMethod);
+    }
+
+    ArtProfileAdditionsBuilder removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod);
   }
 
   private ArtProfile artProfile;
@@ -46,12 +68,15 @@
       new ConcurrentHashMap<>();
   private final Set<DexMethod> methodRuleRemovals = Sets.newConcurrentHashSet();
 
+  private final NestedMethodRuleAdditionsGraph nestedMethodRuleAdditionsGraph =
+      new NestedMethodRuleAdditionsGraph();
+
   ArtProfileAdditions(ArtProfile artProfile) {
     this.artProfile = artProfile;
   }
 
   void applyIfContextIsInProfile(DexType context, Consumer<ArtProfileAdditions> fn) {
-    if (artProfile.containsClassRule(context)) {
+    if (artProfile.containsClassRule(context) || classRuleAdditions.containsKey(context)) {
       fn.accept(this);
     }
   }
@@ -64,36 +89,54 @@
           new ArtProfileAdditionsBuilder() {
 
             @Override
-            public ArtProfileAdditionsBuilder addRule(ProgramDefinition definition) {
-              return addRule(definition.getReference());
+            public ArtProfileAdditionsBuilder addClassRule(DexType type) {
+              ArtProfileAdditions.this.addClassRule(type);
+              return this;
             }
 
             @Override
-            public ArtProfileAdditionsBuilder addRule(DexReference reference) {
-              addRuleFromContext(
-                  reference, contextMethodRule, MethodRuleAdditionConfig.getDefault());
+            public ArtProfileAdditionsBuilder addMethodRule(DexMethod method) {
+              ArtProfileAdditions.this.addMethodRuleFromContext(
+                  method,
+                  methodRuleInfoBuilder ->
+                      methodRuleInfoBuilder.joinFlags(contextMethodRule.getMethodRuleInfo()));
               return this;
             }
 
             @Override
             public ArtProfileAdditionsBuilder removeMovedMethodRule(
-                ProgramMethod oldMethod, ProgramMethod newMethod) {
+                DexMethod oldMethod, ProgramMethod newMethod) {
               ArtProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
               return this;
             }
           });
-    }
-  }
+    } else if (methodRuleAdditions.containsKey(context)) {
+      builderConsumer.accept(
+          new ArtProfileAdditionsBuilder() {
 
-  private void addRuleFromContext(
-      DexReference reference,
-      ArtProfileMethodRule contextMethodRule,
-      MethodRuleAdditionConfig config) {
-    if (reference.isDexType()) {
-      addClassRule(reference.asDexType());
-    } else {
-      assert reference.isDexMethod();
-      addMethodRuleFromContext(reference.asDexMethod(), contextMethodRule, config);
+            @Override
+            public ArtProfileAdditionsBuilder addClassRule(DexType type) {
+              ArtProfileAdditions.this.addClassRule(type);
+              return this;
+            }
+
+            @Override
+            public ArtProfileAdditionsBuilder addMethodRule(DexMethod method) {
+              ArtProfileMethodRule.Builder contextRuleBuilder = methodRuleAdditions.get(context);
+              ArtProfileAdditions.this.addMethodRuleFromContext(
+                  method,
+                  methodRuleInfoBuilder -> methodRuleInfoBuilder.joinFlags(contextRuleBuilder));
+              nestedMethodRuleAdditionsGraph.recordMethodRuleInfoFlagsLargerThan(method, context);
+              return this;
+            }
+
+            @Override
+            public ArtProfileAdditionsBuilder removeMovedMethodRule(
+                DexMethod oldMethod, ProgramMethod newMethod) {
+              ArtProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
+              return this;
+            }
+          });
     }
   }
 
@@ -112,11 +155,9 @@
   }
 
   private void addMethodRuleFromContext(
-      DexMethod method, ArtProfileMethodRule contextMethodRule, MethodRuleAdditionConfig config) {
-    addMethodRule(
-        method,
-        methodRuleInfoBuilder ->
-            config.configureMethodRuleInfo(methodRuleInfoBuilder, contextMethodRule));
+      DexMethod method,
+      Consumer<ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
+    addMethodRule(method, methodRuleInfoBuilderConsumer);
   }
 
   public ArtProfileAdditions addMethodRule(
@@ -141,10 +182,10 @@
     return this;
   }
 
-  void removeMovedMethodRule(ProgramMethod oldMethod, ProgramMethod newMethod) {
-    assert artProfile.containsMethodRule(oldMethod.getReference());
+  void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
+    assert artProfile.containsMethodRule(oldMethod) || methodRuleAdditions.containsKey(oldMethod);
     assert methodRuleAdditions.containsKey(newMethod.getReference());
-    methodRuleRemovals.add(oldMethod.getReference());
+    methodRuleRemovals.add(oldMethod);
   }
 
   ArtProfile createNewArtProfile() {
@@ -153,12 +194,27 @@
       return artProfile;
     }
 
+    nestedMethodRuleAdditionsGraph.propagateMethodRuleInfoFlags(methodRuleAdditions);
+
     // Add existing rules to new profile.
     ArtProfile.Builder artProfileBuilder = ArtProfile.builder();
     artProfile.forEachRule(
         artProfileBuilder::addRule,
         methodRule -> {
-          if (!methodRuleRemovals.contains(methodRule.getMethod())) {
+          if (methodRuleRemovals.contains(methodRule.getMethod())) {
+            return;
+          }
+          ArtProfileMethodRule.Builder methodRuleBuilder =
+              methodRuleAdditions.remove(methodRule.getReference());
+          if (methodRuleBuilder != null) {
+            ArtProfileMethodRule newMethodRule =
+                methodRuleBuilder
+                    .acceptMethodRuleInfoBuilder(
+                        methodRuleInfoBuilder ->
+                            methodRuleInfoBuilder.joinFlags(methodRule.getMethodRuleInfo()))
+                    .build();
+            artProfileBuilder.addRule(newMethodRule);
+          } else {
             artProfileBuilder.addRule(methodRule);
           }
         });
@@ -205,4 +261,49 @@
   void setArtProfile(ArtProfile artProfile) {
     this.artProfile = artProfile;
   }
+
+  private static class NestedMethodRuleAdditionsGraph {
+
+    private final Map<DexMethod, Set<DexMethod>> successors = new ConcurrentHashMap<>();
+    private final Map<DexMethod, Set<DexMethod>> predecessors = new ConcurrentHashMap<>();
+
+    void recordMethodRuleInfoFlagsLargerThan(DexMethod largerFlags, DexMethod smallerFlags) {
+      predecessors
+          .computeIfAbsent(largerFlags, ignoreKey(Sets::newConcurrentHashSet))
+          .add(smallerFlags);
+      successors
+          .computeIfAbsent(smallerFlags, ignoreKey(Sets::newConcurrentHashSet))
+          .add(largerFlags);
+    }
+
+    void propagateMethodRuleInfoFlags(
+        Map<DexMethod, ArtProfileMethodRule.Builder> methodRuleAdditions) {
+      List<DexMethod> leaves =
+          successors.keySet().stream()
+              .filter(method -> predecessors.getOrDefault(method, Collections.emptySet()).isEmpty())
+              .collect(Collectors.toList());
+      WorkList<DexMethod> worklist = WorkList.newIdentityWorkList(leaves);
+      while (worklist.hasNext()) {
+        DexMethod method = worklist.next();
+        ArtProfileMethodRule.Builder methodRuleBuilder = methodRuleAdditions.get(method);
+        for (DexMethod successor : successors.getOrDefault(method, Collections.emptySet())) {
+          methodRuleAdditions
+              .get(successor)
+              .acceptMethodRuleInfoBuilder(
+                  methodRuleInfoBuilder -> {
+                    int oldFlags = methodRuleInfoBuilder.getFlags();
+                    methodRuleInfoBuilder.joinFlags(methodRuleBuilder);
+                    // If this assertion fails, that means we have synthetics with multiple
+                    // synthesizing contexts, which are not guaranteed to be processed before the
+                    // synthetic itself. In that case this assertion should simply be removed.
+                    assert methodRuleInfoBuilder.getFlags() == oldFlags;
+                  });
+          // Note: no need to addIgnoringSeenSet() since the graph will not have cycles. Indeed, it
+          // should never be the case that a method m2(), which is synthesized from method context
+          // m1(), would itself be a synthesizing context for m1().
+          worklist.addIfNotSeen(successor);
+        }
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
index 36d235f..5198d05 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.profile.art.ArtProfileCollection;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileAdditions.ArtProfileAdditionsBuilder;
 import java.util.function.Consumer;
@@ -34,6 +35,8 @@
     return NopArtProfileCollectionAdditions.getInstance();
   }
 
+  public abstract void addMethodIfContextIsInProfile(ProgramMethod method, ProgramMethod context);
+
   public abstract void applyIfContextIsInProfile(
       DexMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer);
 
@@ -52,4 +55,6 @@
 
   public abstract ArtProfileCollectionAdditions setArtProfileCollection(
       ArtProfileCollection artProfileCollection);
+
+  public abstract boolean verifyIsCommitted();
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingApiReferenceStubberEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingApiReferenceStubberEventConsumer.java
index 683bb93..0b4dfde 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingApiReferenceStubberEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingApiReferenceStubberEventConsumer.java
@@ -24,11 +24,17 @@
     this.parent = parent;
   }
 
-  public static ArtProfileRewritingApiReferenceStubberEventConsumer attach(
-      ConcreteArtProfileCollectionAdditions collectionAdditions,
-      ApiReferenceStubberEventConsumer eventConsumer) {
-    return new ArtProfileRewritingApiReferenceStubberEventConsumer(
-        collectionAdditions, eventConsumer);
+  public static ApiReferenceStubberEventConsumer attach(
+      AppView<?> appView, ApiReferenceStubberEventConsumer eventConsumer) {
+    if (appView.options().getArtProfileOptions().isIncludingApiReferenceStubs()) {
+      ArtProfileCollectionAdditions artProfileCollectionAdditions =
+          ArtProfileCollectionAdditions.create(appView);
+      if (!artProfileCollectionAdditions.isNop()) {
+        return new ArtProfileRewritingApiReferenceStubberEventConsumer(
+            artProfileCollectionAdditions.asConcrete(), eventConsumer);
+      }
+    }
+    return eventConsumer;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer.java
new file mode 100644
index 0000000..1084d3a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer.java
@@ -0,0 +1,50 @@
+// 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.profile.art.rewriting;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorSyntheticEventConsumer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer
+    implements ArgumentPropagatorSyntheticEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final ArgumentPropagatorSyntheticEventConsumer parent;
+
+  private ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      ArgumentPropagatorSyntheticEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static ArgumentPropagatorSyntheticEventConsumer attach(
+      AppView<AppInfoWithLiveness> appView,
+      ArgumentPropagatorSyntheticEventConsumer eventConsumer) {
+    ArtProfileCollectionAdditions additionsCollection =
+        ArtProfileCollectionAdditions.create(appView);
+    if (additionsCollection.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingArgumentPropagatorSyntheticEventConsumer(
+        additionsCollection.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptInitializerArgumentClass(DexProgramClass clazz, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(clazz));
+    parent.acceptInitializerArgumentClass(clazz, context);
+  }
+
+  @Override
+  public void finished(AppView<AppInfoWithLiveness> appView) {
+    additionsCollection.commit(appView);
+    parent.finished(appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.java
index 3cc4631..ebc4799 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.java
@@ -4,37 +4,55 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
+import static com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingVarHandleDesugaringEventConsumerUtils.handleVarHandleDesugaringClassContext;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+import com.android.tools.r8.profile.art.ArtProfileOptions;
 import java.util.Set;
 
 public class ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer
     extends CfClassSynthesizerDesugaringEventConsumer {
 
   private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final ArtProfileOptions options;
   private final CfClassSynthesizerDesugaringEventConsumer parent;
 
   private ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer(
       ConcreteArtProfileCollectionAdditions additionsCollection,
+      ArtProfileOptions options,
       CfClassSynthesizerDesugaringEventConsumer parent) {
     this.additionsCollection = additionsCollection;
+    this.options = options;
     this.parent = parent;
   }
 
   public static CfClassSynthesizerDesugaringEventConsumer attach(
-      ArtProfileCollectionAdditions artProfileCollectionAdditions,
-      CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
+      AppView<?> appView, CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
+    return attach(appView, eventConsumer, ArtProfileCollectionAdditions.create(appView));
+  }
+
+  public static CfClassSynthesizerDesugaringEventConsumer attach(
+      AppView<?> appView,
+      CfClassSynthesizerDesugaringEventConsumer eventConsumer,
+      ArtProfileCollectionAdditions artProfileCollectionAdditions) {
     if (artProfileCollectionAdditions.isNop()) {
       return eventConsumer;
     }
     return new ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer(
-        artProfileCollectionAdditions.asConcrete(), eventConsumer);
+        artProfileCollectionAdditions.asConcrete(),
+        appView.options().getArtProfileOptions(),
+        eventConsumer);
   }
 
   @Override
-  public void acceptCollectionConversion(ProgramMethod arrayConversion) {
-    parent.acceptCollectionConversion(arrayConversion);
+  public void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(arrayConversion, context);
+    parent.acceptCollectionConversion(arrayConversion, context);
   }
 
   @Override
@@ -79,8 +97,21 @@
   }
 
   @Override
-  public void acceptVarHandleDesugaringClass(DexProgramClass varHandleClass) {
-    parent.acceptVarHandleDesugaringClass(varHandleClass);
+  public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+    parent.acceptVarHandleDesugaringClass(clazz);
+  }
+
+  @Override
+  public void acceptVarHandleDesugaringClassContext(
+      DexProgramClass clazz, ProgramDefinition context) {
+    handleVarHandleDesugaringClassContext(clazz, context, additionsCollection, options);
+    parent.acceptVarHandleDesugaringClassContext(clazz, context);
+  }
+
+  @Override
+  public void finished(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    additionsCollection.commit(appView);
+    parent.finished(appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java
index 811b1f5..3ac8a23 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
+import static com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingVarHandleDesugaringEventConsumerUtils.handleVarHandleDesugaringClassContext;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -15,6 +19,7 @@
 import com.android.tools.r8.ir.desugar.LambdaClass.Target;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
 import java.util.List;
 
 public class ArtProfileRewritingCfInstructionDesugaringEventConsumer
@@ -24,6 +29,8 @@
   private final ConcreteArtProfileCollectionAdditions additionsCollection;
   private final CfInstructionDesugaringEventConsumer parent;
 
+  private final NestBasedAccessDesugaringEventConsumer nestBasedAccessDesugaringEventConsumer;
+
   private ArtProfileRewritingCfInstructionDesugaringEventConsumer(
       AppView<?> appView,
       ConcreteArtProfileCollectionAdditions additionsCollection,
@@ -31,6 +38,9 @@
     this.appView = appView;
     this.additionsCollection = additionsCollection;
     this.parent = parent;
+    this.nestBasedAccessDesugaringEventConsumer =
+        ArtProfileRewritingNestBasedAccessDesugaringEventConsumer.attach(
+            additionsCollection, NestBasedAccessDesugaringEventConsumer.empty());
   }
 
   public static CfInstructionDesugaringEventConsumer attach(
@@ -45,21 +55,27 @@
   }
 
   @Override
-  public void acceptAPIConversion(ProgramMethod method) {
-    parent.acceptAPIConversion(method);
+  public void acceptAPIConversionOutline(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptAPIConversionOutline(method, context);
   }
 
   @Override
   public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
+    if (appView.options().getArtProfileOptions().isIncludingBackportedClasses()) {
+      additionsCollection.applyIfContextIsInProfile(
+          context,
+          additionsBuilder -> {
+            additionsBuilder.addRule(backportedClass);
+            backportedClass.forEachProgramMethod(additionsBuilder::addRule);
+          });
+    }
     parent.acceptBackportedClass(backportedClass, context);
   }
 
   @Override
   public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context,
-        additionsBuilder ->
-            additionsBuilder.addRule(backportedMethod).addRule(backportedMethod.getHolder()));
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(backportedMethod, context);
     parent.acceptBackportedMethod(backportedMethod, context);
   }
 
@@ -69,8 +85,9 @@
   }
 
   @Override
-  public void acceptCollectionConversion(ProgramMethod arrayConversion) {
-    parent.acceptCollectionConversion(arrayConversion);
+  public void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(arrayConversion, context);
+    parent.acceptCollectionConversion(arrayConversion, context);
   }
 
   @Override
@@ -79,21 +96,46 @@
   }
 
   @Override
-  public void acceptConstantDynamicClass(ConstantDynamicClass lambdaClass, ProgramMethod context) {
-    parent.acceptConstantDynamicClass(lambdaClass, context);
+  public void acceptConstantDynamicClass(
+      ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+    if (appView.options().getArtProfileOptions().isIncludingConstantDynamicClass()) {
+      additionsCollection.applyIfContextIsInProfile(
+          context,
+          additionsBuilder -> {
+            DexProgramClass clazz = constantDynamicClass.getConstantDynamicProgramClass();
+            additionsBuilder.addRule(clazz);
+            clazz.forEachProgramMethod(additionsBuilder::addRule);
+          });
+    }
+    parent.acceptConstantDynamicClass(constantDynamicClass, context);
   }
 
   @Override
-  public void acceptCovariantRetargetMethod(ProgramMethod method) {
-    parent.acceptCovariantRetargetMethod(method);
+  public void acceptConstantDynamicRewrittenBootstrapMethod(
+      ProgramMethod bootstrapMethod, DexMethod oldSignature) {
+    additionsCollection.applyIfContextIsInProfile(
+        oldSignature,
+        additionsBuilder ->
+            additionsBuilder
+                .addRule(bootstrapMethod)
+                .removeMovedMethodRule(oldSignature, bootstrapMethod));
+    parent.acceptConstantDynamicRewrittenBootstrapMethod(bootstrapMethod, oldSignature);
+  }
+
+  @Override
+  public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptCovariantRetargetMethod(method, context);
   }
 
   @Override
   public void acceptDefaultAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
     additionsCollection.applyIfContextIsInProfile(
         method,
-        additionsBuilder ->
-            additionsBuilder.addRule(companionMethod).addRule(companionMethod.getHolder()));
+        additionsBuilder -> {
+          additionsBuilder.addRule(companionMethod).addRule(companionMethod.getHolder());
+          companionMethod.getHolder().acceptProgramClassInitializer(additionsBuilder::addRule);
+        });
     parent.acceptDefaultAsCompanionMethod(method, companionMethod);
   }
 
@@ -114,15 +156,15 @@
 
   @Override
   public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
-    additionsCollection.applyIfContextIsInProfile(
-        info.getVirtualMethod(),
-        additionsBuilder -> additionsBuilder.addRule(info.getNewDirectMethod()));
+    additionsCollection.addMethodIfContextIsInProfile(
+        info.getNewDirectMethod(), info.getVirtualMethod());
     parent.acceptInvokeSpecialBridgeInfo(info);
   }
 
   @Override
   public void acceptInvokeStaticInterfaceOutliningMethod(
       ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptInvokeStaticInterfaceOutliningMethod(method, context);
   }
 
@@ -144,20 +186,27 @@
             additionsBuilder.addRule(lambdaProgramClass.getProgramClassInitializer());
           }
           lambdaProgramClass.forEachProgramInstanceInitializer(additionsBuilder::addRule);
+          if (appView.options().testing.alwaysGenerateLambdaFactoryMethods) {
+            lambdaProgramClass.forEachProgramStaticMethod(additionsBuilder::addRule);
+          }
         });
   }
 
   private void addLambdaVirtualMethodsIfLambdaImplementationIsInProfile(
       LambdaClass lambdaClass, ProgramMethod context) {
+    Target target = lambdaClass.getTarget();
     if (shouldConservativelyAddLambdaVirtualMethodsIfLambdaInstantiated(lambdaClass, context)) {
       additionsCollection.applyIfContextIsInProfile(
           context,
-          additionsBuilder ->
-              lambdaClass
-                  .getLambdaProgramClass()
-                  .forEachProgramVirtualMethod(additionsBuilder::addRule));
+          additionsBuilder -> {
+            lambdaClass
+                .getLambdaProgramClass()
+                .forEachProgramVirtualMethod(additionsBuilder::addRule);
+            if (target.getCallTarget() != target.getImplementationMethod()) {
+              additionsBuilder.addRule(target.getCallTarget());
+            }
+          });
     } else {
-      Target target = lambdaClass.getTarget();
       additionsCollection.applyIfContextIsInProfile(
           target.getImplementationMethod(),
           additionsBuilder -> {
@@ -187,7 +236,7 @@
               .appInfoWithClassHierarchy()
               .resolveMethod(target.getImplementationMethod(), target.isInterface())
               .getResolutionPair();
-      if (resolutionResult == null || resolutionResult.isProgramMethod()) {
+      if (resolutionResult != null && resolutionResult.isProgramMethod()) {
         // Direct call to other method in the app. Only add virtual methods if the callee is in the
         // profile.
         return false;
@@ -207,41 +256,35 @@
       ProgramMethod bridge,
       DexProgramClass argumentClass,
       DexClassAndMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(argumentClass).addRule(bridge));
+    nestBasedAccessDesugaringEventConsumer.acceptNestConstructorBridge(
+        target, bridge, argumentClass, context);
     parent.acceptNestConstructorBridge(target, bridge, argumentClass, context);
   }
 
   @Override
   public void acceptNestFieldGetBridge(
       ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(bridge));
+    nestBasedAccessDesugaringEventConsumer.acceptNestFieldGetBridge(target, bridge, context);
     parent.acceptNestFieldGetBridge(target, bridge, context);
   }
 
   @Override
   public void acceptNestFieldPutBridge(
       ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(bridge));
+    nestBasedAccessDesugaringEventConsumer.acceptNestFieldPutBridge(target, bridge, context);
     parent.acceptNestFieldPutBridge(target, bridge, context);
   }
 
   @Override
   public void acceptNestMethodBridge(
       ProgramMethod target, ProgramMethod bridge, DexClassAndMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(bridge));
+    nestBasedAccessDesugaringEventConsumer.acceptNestMethodBridge(target, bridge, context);
     parent.acceptNestMethodBridge(target, bridge, context);
   }
 
   @Override
   public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context,
-        additionsBuilder ->
-            additionsBuilder.addRule(outlinedMethod).addRule(outlinedMethod.getHolder()));
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(outlinedMethod, context);
     parent.acceptOutlinedMethod(outlinedMethod, context);
   }
 
@@ -249,11 +292,13 @@
   public void acceptPrivateAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
     additionsCollection.applyIfContextIsInProfile(
         method,
-        additionsBuilder ->
-            additionsBuilder
-                .addRule(companionMethod)
-                .addRule(companionMethod.getHolder())
-                .removeMovedMethodRule(method, companionMethod));
+        additionsBuilder -> {
+          additionsBuilder
+              .addRule(companionMethod)
+              .addRule(companionMethod.getHolder())
+              .removeMovedMethodRule(method, companionMethod);
+          companionMethod.getHolder().acceptProgramClassInitializer(additionsBuilder::addRule);
+        });
     parent.acceptPrivateAsCompanionMethod(method, companionMethod);
   }
 
@@ -269,30 +314,26 @@
 
   @Override
   public void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(method));
+    additionsCollection.addMethodIfContextIsInProfile(method, context);
     parent.acceptRecordEqualsHelperMethod(method, context);
   }
 
   @Override
   public void acceptRecordGetFieldsAsObjectsHelperMethod(
       ProgramMethod method, ProgramMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(method));
+    additionsCollection.addMethodIfContextIsInProfile(method, context);
     parent.acceptRecordGetFieldsAsObjectsHelperMethod(method, context);
   }
 
   @Override
   public void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptRecordHashCodeHelperMethod(method, context);
   }
 
   @Override
   public void acceptRecordToStringHelperMethod(ProgramMethod method, ProgramMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptRecordToStringHelperMethod(method, context);
   }
 
@@ -300,60 +341,74 @@
   public void acceptStaticAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
     additionsCollection.applyIfContextIsInProfile(
         method,
-        additionsBuilder ->
-            additionsBuilder
-                .addRule(companionMethod)
-                .addRule(companionMethod.getHolder())
-                .removeMovedMethodRule(method, companionMethod));
+        additionsBuilder -> {
+          additionsBuilder
+              .addRule(companionMethod)
+              .addRule(companionMethod.getHolder())
+              .removeMovedMethodRule(method, companionMethod);
+          companionMethod.getHolder().acceptProgramClassInitializer(additionsBuilder::addRule);
+        });
     parent.acceptStaticAsCompanionMethod(method, companionMethod);
   }
 
   @Override
   public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
-    additionsCollection.applyIfContextIsInProfile(
-        context,
-        additionsBuilder -> additionsBuilder.addRule(closeMethod).addRule(closeMethod.getHolder()));
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(closeMethod, context);
     parent.acceptTwrCloseResourceMethod(closeMethod, context);
   }
 
   @Override
   public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(context, method);
     parent.acceptUtilityToStringIfNotNullMethod(method, context);
   }
 
   @Override
   public void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
       ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptUtilityThrowClassCastExceptionIfNotNullMethod(method, context);
   }
 
   @Override
   public void acceptUtilityThrowIllegalAccessErrorMethod(
       ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptUtilityThrowIllegalAccessErrorMethod(method, context);
   }
 
   @Override
   public void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
       ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptUtilityThrowIncompatibleClassChangeErrorMethod(method, context);
   }
 
   @Override
   public void acceptUtilityThrowNoSuchMethodErrorMethod(
       ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptUtilityThrowNoSuchMethodErrorMethod(method, context);
   }
 
   @Override
   public void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
       ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
     parent.acceptUtilityThrowRuntimeExceptionWithMessageMethod(method, context);
   }
 
   @Override
-  public void acceptVarHandleDesugaringClass(DexProgramClass varHandleClass) {
-    parent.acceptVarHandleDesugaringClass(varHandleClass);
+  public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+    parent.acceptVarHandleDesugaringClass(clazz);
+  }
+
+  @Override
+  public void acceptVarHandleDesugaringClassContext(
+      DexProgramClass clazz, ProgramDefinition context) {
+    handleVarHandleDesugaringClassContext(
+        clazz, context, additionsCollection, appView.options().getArtProfileOptions());
+    parent.acceptVarHandleDesugaringClassContext(clazz, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java
index 6843852..568efcc 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java
@@ -4,15 +4,22 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
+import com.android.tools.r8.profile.art.ArtProfileOptions;
+import com.android.tools.r8.utils.BooleanBox;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
@@ -20,38 +27,48 @@
     extends CfPostProcessingDesugaringEventConsumer {
 
   private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final ArtProfileOptions options;
   private final CfPostProcessingDesugaringEventConsumer parent;
 
   private ArtProfileRewritingCfPostProcessingDesugaringEventConsumer(
       ConcreteArtProfileCollectionAdditions additionsCollection,
+      ArtProfileOptions options,
       CfPostProcessingDesugaringEventConsumer parent) {
     this.additionsCollection = additionsCollection;
+    this.options = options;
     this.parent = parent;
   }
 
   public static CfPostProcessingDesugaringEventConsumer attach(
+      AppView<?> appView,
       ArtProfileCollectionAdditions artProfileCollectionAdditions,
       CfPostProcessingDesugaringEventConsumer eventConsumer) {
     if (artProfileCollectionAdditions.isNop()) {
       return eventConsumer;
     }
     return new ArtProfileRewritingCfPostProcessingDesugaringEventConsumer(
-        artProfileCollectionAdditions.asConcrete(), eventConsumer);
+        artProfileCollectionAdditions.asConcrete(),
+        appView.options().getArtProfileOptions(),
+        eventConsumer);
   }
 
   @Override
-  public void acceptAPIConversionCallback(ProgramMethod method) {
-    parent.acceptAPIConversionCallback(method);
+  public void acceptAPIConversionCallback(
+      ProgramMethod callbackMethod, ProgramMethod convertedMethod) {
+    additionsCollection.addMethodIfContextIsInProfile(callbackMethod, convertedMethod);
+    parent.acceptAPIConversionCallback(callbackMethod, convertedMethod);
   }
 
   @Override
-  public void acceptCollectionConversion(ProgramMethod arrayConversion) {
-    parent.acceptCollectionConversion(arrayConversion);
+  public void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(arrayConversion, context);
+    parent.acceptCollectionConversion(arrayConversion, context);
   }
 
   @Override
-  public void acceptCovariantRetargetMethod(ProgramMethod method) {
-    parent.acceptCovariantRetargetMethod(method);
+  public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(context, method);
+    parent.acceptCovariantRetargetMethod(method, context);
   }
 
   @Override
@@ -60,8 +77,12 @@
   }
 
   @Override
-  public void acceptDesugaredLibraryRetargeterForwardingMethod(ProgramMethod method) {
-    parent.acceptDesugaredLibraryRetargeterForwardingMethod(method);
+  public void acceptDesugaredLibraryRetargeterForwardingMethod(
+      ProgramMethod method, EmulatedDispatchMethodDescriptor descriptor) {
+    if (options.isIncludingDesugaredLibraryRetargeterForwardingMethodsUnconditionally()) {
+      additionsCollection.apply(additions -> additions.addMethodRule(method, emptyConsumer()));
+    }
+    parent.acceptDesugaredLibraryRetargeterForwardingMethod(method, descriptor);
   }
 
   @Override
@@ -88,14 +109,29 @@
   @Override
   public void acceptInterfaceMethodDesugaringForwardingMethod(
       ProgramMethod method, DexClassAndMethod baseMethod) {
-    additionsCollection.applyIfContextIsInProfile(
-        baseMethod, additionsBuilder -> additionsBuilder.addRule(method));
+    additionsCollection.addMethodIfContextIsInProfile(method, baseMethod, emptyConsumer());
     parent.acceptInterfaceMethodDesugaringForwardingMethod(method, baseMethod);
   }
 
   @Override
-  public void acceptThrowingMethod(ProgramMethod method, DexType errorType) {
-    parent.acceptThrowingMethod(method, errorType);
+  public void acceptThrowingMethod(
+      ProgramMethod method, DexType errorType, FailedResolutionResult resolutionResult) {
+    if (options.isIncludingThrowingMethods()) {
+      BooleanBox seenMethodCausingError = new BooleanBox();
+      resolutionResult.forEachFailureDependency(
+          emptyConsumer(),
+          methodCausingError -> {
+            additionsCollection.applyIfContextIsInProfile(
+                methodCausingError.getReference(),
+                additionsBuilder -> additionsBuilder.addRule(method));
+            seenMethodCausingError.set();
+          });
+      if (seenMethodCausingError.isFalse()) {
+        additionsCollection.applyIfContextIsInProfile(
+            method.getHolder(), additions -> additions.addMethodRule(method, emptyConsumer()));
+      }
+    }
+    parent.acceptThrowingMethod(method, errorType, resolutionResult);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.java
new file mode 100644
index 0000000..9722924
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.java
@@ -0,0 +1,38 @@
+// 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.profile.art.rewriting;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformerEventConsumer;
+
+public class ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer
+    implements CovariantReturnTypeAnnotationTransformerEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final CovariantReturnTypeAnnotationTransformerEventConsumer parent;
+
+  private ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      CovariantReturnTypeAnnotationTransformerEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static CovariantReturnTypeAnnotationTransformerEventConsumer attach(
+      ArtProfileCollectionAdditions additionsCollection,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
+    if (additionsCollection.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer(
+        additionsCollection.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptCovariantReturnTypeBridgeMethod(ProgramMethod bridge, ProgramMethod target) {
+    additionsCollection.addMethodIfContextIsInProfile(bridge, target);
+    parent.acceptCovariantReturnTypeBridgeMethod(bridge, target);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMemberRebindingEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMemberRebindingEventConsumer.java
new file mode 100644
index 0000000..df87170
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMemberRebindingEventConsumer.java
@@ -0,0 +1,53 @@
+// 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.profile.art.rewriting;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.MemberRebindingEventConsumer;
+import com.android.tools.r8.optimize.MemberRebindingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ArtProfileRewritingMemberRebindingEventConsumer
+    implements MemberRebindingEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final MemberRebindingEventConsumer parent;
+
+  private ArtProfileRewritingMemberRebindingEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      MemberRebindingEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static MemberRebindingEventConsumer attach(
+      AppView<AppInfoWithLiveness> appView, MemberRebindingEventConsumer eventConsumer) {
+    ArtProfileCollectionAdditions additionsCollection =
+        ArtProfileCollectionAdditions.create(appView);
+    if (additionsCollection.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingMemberRebindingEventConsumer(
+        additionsCollection.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptMemberRebindingBridgeMethod(
+      ProgramMethod bridgeMethod, DexClassAndMethod targetMethod) {
+    additionsCollection.addMethodIfContextIsInProfile(bridgeMethod, targetMethod, emptyConsumer());
+    parent.acceptMemberRebindingBridgeMethod(bridgeMethod, targetMethod);
+  }
+
+  @Override
+  public void finished(
+      AppView<AppInfoWithLiveness> appView, MemberRebindingLens memberRebindingLens) {
+    additionsCollection.commit(appView);
+    parent.finished(appView, memberRebindingLens);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMethodProcessorEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMethodProcessorEventConsumer.java
index 325422e..dea3841 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMethodProcessorEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMethodProcessorEventConsumer.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class ArtProfileRewritingMethodProcessorEventConsumer extends MethodProcessorEventConsumer {
 
@@ -20,6 +22,17 @@
   }
 
   public static MethodProcessorEventConsumer attach(
+      AppView<?> appView, MethodProcessorEventConsumer eventConsumer) {
+    ArtProfileCollectionAdditions additionsCollection =
+        ArtProfileCollectionAdditions.create(appView);
+    if (additionsCollection.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingMethodProcessorEventConsumer(
+        additionsCollection.asConcrete(), eventConsumer);
+  }
+
+  public static MethodProcessorEventConsumer attach(
       ArtProfileCollectionAdditions artProfileCollectionAdditions,
       MethodProcessorEventConsumer eventConsumer) {
     if (artProfileCollectionAdditions.isNop()) {
@@ -30,9 +43,15 @@
   }
 
   @Override
+  public void acceptAssertionErrorCreateMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptAssertionErrorCreateMethod(method, context);
+  }
+
+  @Override
   public void acceptEnumUnboxerCheckNotZeroContext(ProgramMethod method, ProgramMethod context) {
     additionsCollection.applyIfContextIsInProfile(
-        context, additionsBuilder -> additionsBuilder.addRule(method));
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
     parent.acceptEnumUnboxerCheckNotZeroContext(method, context);
   }
 
@@ -64,6 +83,12 @@
   }
 
   @Override
+  public void acceptServiceLoaderLoadUtilityMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptServiceLoaderLoadUtilityMethod(method, context);
+  }
+
+  @Override
   public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
     additionsCollection.applyIfContextIsInProfile(
         context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
@@ -109,4 +134,10 @@
         context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
     parent.acceptUtilityThrowRuntimeExceptionWithMessageMethod(method, context);
   }
+
+  @Override
+  public void finished(AppView<AppInfoWithLiveness> appView) {
+    additionsCollection.commit(appView);
+    parent.finished(appView);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingNestBasedAccessDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingNestBasedAccessDesugaringEventConsumer.java
new file mode 100644
index 0000000..2988ce4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingNestBasedAccessDesugaringEventConsumer.java
@@ -0,0 +1,76 @@
+// 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.profile.art.rewriting;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
+
+public class ArtProfileRewritingNestBasedAccessDesugaringEventConsumer
+    implements NestBasedAccessDesugaringEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final NestBasedAccessDesugaringEventConsumer parent;
+
+  private ArtProfileRewritingNestBasedAccessDesugaringEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      NestBasedAccessDesugaringEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static NestBasedAccessDesugaringEventConsumer attach(
+      ArtProfileCollectionAdditions additionsCollection,
+      NestBasedAccessDesugaringEventConsumer eventConsumer) {
+    if (additionsCollection.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingNestBasedAccessDesugaringEventConsumer(
+        additionsCollection.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptNestConstructorBridge(
+      ProgramMethod target,
+      ProgramMethod bridge,
+      DexProgramClass argumentClass,
+      DexClassAndMethod context) {
+    if (context.isProgramMethod()) {
+      additionsCollection.applyIfContextIsInProfile(
+          context.asProgramMethod(),
+          additionsBuilder -> additionsBuilder.addRule(argumentClass).addRule(bridge));
+    } else {
+      additionsCollection.apply(
+          additions ->
+              additions.addClassRule(argumentClass).addMethodRule(bridge, emptyConsumer()));
+    }
+    parent.acceptNestConstructorBridge(target, bridge, argumentClass, context);
+  }
+
+  @Override
+  public void acceptNestFieldGetBridge(
+      ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+    additionsCollection.addMethodIfContextIsInProfile(bridge, context, emptyConsumer());
+    parent.acceptNestFieldGetBridge(target, bridge, context);
+  }
+
+  @Override
+  public void acceptNestFieldPutBridge(
+      ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+    additionsCollection.addMethodIfContextIsInProfile(bridge, context, emptyConsumer());
+    parent.acceptNestFieldPutBridge(target, bridge, context);
+  }
+
+  @Override
+  public void acceptNestMethodBridge(
+      ProgramMethod target, ProgramMethod bridge, DexClassAndMethod context) {
+    additionsCollection.addMethodIfContextIsInProfile(bridge, context, emptyConsumer());
+    parent.acceptNestMethodBridge(target, bridge, context);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingOutlineOptimizationEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingOutlineOptimizationEventConsumer.java
new file mode 100644
index 0000000..2184a87
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingOutlineOptimizationEventConsumer.java
@@ -0,0 +1,52 @@
+// 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.profile.art.rewriting;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.outliner.OutlineOptimizationEventConsumer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collection;
+
+public class ArtProfileRewritingOutlineOptimizationEventConsumer
+    implements OutlineOptimizationEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final OutlineOptimizationEventConsumer parent;
+
+  private ArtProfileRewritingOutlineOptimizationEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      OutlineOptimizationEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static OutlineOptimizationEventConsumer attach(
+      AppView<AppInfoWithLiveness> appView, OutlineOptimizationEventConsumer eventConsumer) {
+    ArtProfileCollectionAdditions additionsCollection =
+        ArtProfileCollectionAdditions.create(appView);
+    if (additionsCollection.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingOutlineOptimizationEventConsumer(
+        additionsCollection.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptOutlineMethod(ProgramMethod method, Collection<ProgramMethod> contexts) {
+    for (ProgramMethod context : contexts) {
+      additionsCollection.applyIfContextIsInProfile(
+          context,
+          additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    }
+    parent.acceptOutlineMethod(method, contexts);
+  }
+
+  @Override
+  public void finished(AppView<AppInfoWithLiveness> appView) {
+    additionsCollection.commit(appView);
+    parent.finished(appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingRootSetBuilderEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingRootSetBuilderEventConsumer.java
new file mode 100644
index 0000000..005c33c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingRootSetBuilderEventConsumer.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.profile.art.rewriting;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.RootSetBuilderEventConsumer;
+
+public class ArtProfileRewritingRootSetBuilderEventConsumer implements RootSetBuilderEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final RootSetBuilderEventConsumer parent;
+
+  private ArtProfileRewritingRootSetBuilderEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      RootSetBuilderEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static RootSetBuilderEventConsumer attach(
+      ArtProfileCollectionAdditions additionsCollection,
+      RootSetBuilderEventConsumer eventConsumer) {
+    if (additionsCollection.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingRootSetBuilderEventConsumer(
+        additionsCollection.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptCompanionClassClinit(ProgramMethod method) {
+    parent.acceptCompanionClassClinit(method);
+  }
+
+  @Override
+  public void acceptDefaultAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(companionMethod, method);
+    parent.acceptDefaultAsCompanionMethod(method, companionMethod);
+  }
+
+  @Override
+  public void acceptPrivateAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+    additionsCollection.applyIfContextIsInProfile(
+        method,
+        additionsBuilder ->
+            additionsBuilder
+                .addRule(companionMethod)
+                .addRule(companionMethod.getHolder())
+                .removeMovedMethodRule(method, companionMethod));
+    parent.acceptPrivateAsCompanionMethod(method, companionMethod);
+  }
+
+  @Override
+  public void acceptStaticAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+    additionsCollection.applyIfContextIsInProfile(
+        method,
+        additionsBuilder ->
+            additionsBuilder
+                .addRule(companionMethod)
+                .addRule(companionMethod.getHolder())
+                .removeMovedMethodRule(method, companionMethod));
+    parent.acceptStaticAsCompanionMethod(method, companionMethod);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingVarHandleDesugaringEventConsumerUtils.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingVarHandleDesugaringEventConsumerUtils.java
new file mode 100644
index 0000000..f53e9bc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingVarHandleDesugaringEventConsumerUtils.java
@@ -0,0 +1,34 @@
+// 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.profile.art.rewriting;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.profile.art.ArtProfileOptions;
+
+public class ArtProfileRewritingVarHandleDesugaringEventConsumerUtils {
+
+  static void handleVarHandleDesugaringClassContext(
+      DexProgramClass varHandleClass,
+      ProgramDefinition context,
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      ArtProfileOptions options) {
+    if (options.isIncludingVarHandleClasses()) {
+      additionsCollection.applyIfContextIsInProfile(
+          context,
+          additions -> {
+            additions.addClassRule(varHandleClass);
+            varHandleClass.forEachProgramMethod(
+                method -> additions.addMethodRule(method, emptyConsumer()));
+          },
+          additionsBuilder -> {
+            additionsBuilder.addRule(varHandleClass);
+            varHandleClass.forEachProgramMethod(additionsBuilder::addRule);
+          });
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
index 85c54e1..1d1a897 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
@@ -5,12 +5,15 @@
 package com.android.tools.r8.profile.art.rewriting;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.profile.art.ArtProfile;
 import com.android.tools.r8.profile.art.ArtProfileCollection;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
 import com.android.tools.r8.profile.art.NonEmptyArtProfileCollection;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileAdditions.ArtProfileAdditionsBuilder;
 import com.google.common.collect.Iterables;
@@ -24,6 +27,8 @@
 
   private final List<ArtProfileAdditions> additionsCollection;
 
+  private boolean committed = false;
+
   private ConcreteArtProfileCollectionAdditions(List<ArtProfileAdditions> additionsCollection) {
     this.additionsCollection = additionsCollection;
   }
@@ -36,8 +41,50 @@
     assert !additionsCollection.isEmpty();
   }
 
+  @Override
+  public void addMethodIfContextIsInProfile(ProgramMethod method, ProgramMethod context) {
+    applyIfContextIsInProfile(context, additionsBuilder -> additionsBuilder.addRule(method));
+  }
+
+  public void addMethodIfContextIsInProfile(
+      ProgramMethod method,
+      DexClassAndMethod context,
+      Consumer<ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
+    if (context.isProgramMethod()) {
+      applyIfContextIsInProfile(
+          context.asProgramMethod(), additionsBuilder -> additionsBuilder.addRule(method));
+    } else {
+      apply(
+          artProfileAdditions ->
+              artProfileAdditions.addMethodRule(method, methodRuleInfoBuilderConsumer));
+    }
+  }
+
+  public void addMethodAndHolderIfContextIsInProfile(ProgramMethod method, ProgramMethod context) {
+    applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+  }
+
+  void apply(Consumer<ArtProfileAdditions> additionsConsumer) {
+    for (ArtProfileAdditions artProfileAdditions : additionsCollection) {
+      additionsConsumer.accept(artProfileAdditions);
+    }
+  }
+
   void applyIfContextIsInProfile(
-      DexClass context, Consumer<ArtProfileAdditions> additionsConsumer) {
+      ProgramDefinition context,
+      Consumer<ArtProfileAdditions> additionsConsumer,
+      Consumer<ArtProfileAdditionsBuilder> additionsBuilderConsumer) {
+    if (context.isProgramClass()) {
+      applyIfContextIsInProfile(context.asProgramClass(), additionsConsumer);
+    } else {
+      assert context.isProgramMethod();
+      applyIfContextIsInProfile(context.asProgramMethod(), additionsBuilderConsumer);
+    }
+  }
+
+  void applyIfContextIsInProfile(
+      DexProgramClass context, Consumer<ArtProfileAdditions> additionsConsumer) {
     applyIfContextIsInProfile(context.getType(), additionsConsumer);
   }
 
@@ -48,7 +95,7 @@
   }
 
   public void applyIfContextIsInProfile(
-      DexClassAndMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
+      ProgramMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
     applyIfContextIsInProfile(context.getReference(), builderConsumer);
   }
 
@@ -67,9 +114,11 @@
 
   @Override
   public void commit(AppView<?> appView) {
+    assert !committed;
     if (hasAdditions()) {
       appView.setArtProfileCollection(createNewArtProfileCollection());
     }
+    committed = true;
   }
 
   private ArtProfileCollection createNewArtProfileCollection() {
@@ -106,4 +155,10 @@
     }
     return this;
   }
+
+  @Override
+  public boolean verifyIsCommitted() {
+    assert committed;
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/MethodRuleAdditionConfig.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/MethodRuleAdditionConfig.java
deleted file mode 100644
index 5f4f17f..0000000
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/MethodRuleAdditionConfig.java
+++ /dev/null
@@ -1,38 +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.profile.art.rewriting;
-
-import com.android.tools.r8.profile.art.ArtProfileMethodRule;
-import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
-
-public abstract class MethodRuleAdditionConfig {
-
-  public static MethodRuleAdditionConfig getDefault() {
-    return DefaultMethodRuleAdditionConfig.getInstance();
-  }
-
-  public abstract void configureMethodRuleInfo(
-      ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder,
-      ArtProfileMethodRule contextMethodRule);
-
-  private static class DefaultMethodRuleAdditionConfig extends MethodRuleAdditionConfig {
-
-    private static final DefaultMethodRuleAdditionConfig INSTANCE =
-        new DefaultMethodRuleAdditionConfig();
-
-    private DefaultMethodRuleAdditionConfig() {}
-
-    static DefaultMethodRuleAdditionConfig getInstance() {
-      return INSTANCE;
-    }
-
-    @Override
-    public void configureMethodRuleInfo(
-        ArtProfileMethodRuleInfoImpl.Builder methodRuleInfoBuilder,
-        ArtProfileMethodRule contextMethodRule) {
-      methodRuleInfoBuilder.joinFlags(contextMethodRule.getMethodRuleInfo());
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java
index a0c6925..8bfd4a6 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.profile.art.ArtProfileCollection;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileAdditions.ArtProfileAdditionsBuilder;
 import java.util.function.Consumer;
@@ -23,6 +24,11 @@
   }
 
   @Override
+  public void addMethodIfContextIsInProfile(ProgramMethod method, ProgramMethod context) {
+    // Intentionally empty.
+  }
+
+  @Override
   public void applyIfContextIsInProfile(
       DexMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
     // Intentionally empty.
@@ -51,4 +57,10 @@
     // Intentionally empty.
     return this;
   }
+
+  @Override
+  public boolean verifyIsCommitted() {
+    // Nothing to commit.
+    return true;
+  }
 }
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 bbdb059..1578174 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -473,6 +473,7 @@
 
   Enqueuer(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
@@ -481,7 +482,7 @@
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
     this.appView = appView.withClassHierarchy();
-    this.artProfileCollectionAdditions = ArtProfileCollectionAdditions.create(appView);
+    this.artProfileCollectionAdditions = artProfileCollectionAdditions;
     this.deferredTracing = EnqueuerDeferredTracing.create(appView, this, mode);
     this.executorService = executorService;
     this.subtypingInfo = subtypingInfo;
@@ -531,6 +532,10 @@
     return appView.appInfo();
   }
 
+  public ArtProfileCollectionAdditions getArtProfileCollectionAdditions() {
+    return artProfileCollectionAdditions;
+  }
+
   public Mode getMode() {
     return mode;
   }
@@ -4010,7 +4015,7 @@
     // registered first and no dependencies may exist among them.
     SyntheticAdditions additions = new SyntheticAdditions(appView.createProcessorContext());
     desugar(additions);
-    synthesizeInterfaceMethodBridges(additions);
+    synthesizeInterfaceMethodBridges();
     if (additions.isEmpty()) {
       return;
     }
@@ -4164,11 +4169,13 @@
     }
   }
 
-  private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
-    for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
+  private void synthesizeInterfaceMethodBridges() {
+    for (InterfaceMethodSyntheticBridgeAction action : syntheticInterfaceMethodBridges.values()) {
+      ProgramMethod bridge = action.getMethodToKeep();
       DexProgramClass holder = bridge.getHolder();
       DexEncodedMethod method = bridge.getDefinition();
       holder.addVirtualMethod(method);
+      artProfileCollectionAdditions.addMethodIfContextIsInProfile(bridge, action.getSingleTarget());
     }
     syntheticInterfaceMethodBridges.clear();
   }
@@ -4473,7 +4480,7 @@
             }
           }
           ConsequentRootSetBuilder consequentSetBuilder =
-              ConsequentRootSet.builder(appView, subtypingInfo, this);
+              ConsequentRootSet.builder(appView, this, subtypingInfo);
           IfRuleEvaluator ifRuleEvaluator =
               new IfRuleEvaluator(
                   appView,
@@ -4567,6 +4574,7 @@
 
     CfPostProcessingDesugaringEventConsumer eventConsumer =
         CfPostProcessingDesugaringEventConsumer.createForR8(
+            appView,
             syntheticAdditions,
             artProfileCollectionAdditions,
             desugaring,
@@ -4633,7 +4641,7 @@
   }
 
   private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
-    RootSetBuilder builder = RootSet.builder(appView, subtypingInfo);
+    RootSetBuilder builder = RootSet.builder(appView, this, subtypingInfo);
     for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
       if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) {
         handleInterfaceMethodSyntheticBridgeAction(
@@ -4643,8 +4651,8 @@
     return builder.buildConsequentRootSet();
   }
 
-  private final Map<DexMethod, ProgramMethod> syntheticInterfaceMethodBridges =
-      new LinkedHashMap<>();
+  private final Map<DexMethod, InterfaceMethodSyntheticBridgeAction>
+      syntheticInterfaceMethodBridges = new LinkedHashMap<>();
 
   private void identifySyntheticInterfaceMethodBridges(
       InterfaceMethodSyntheticBridgeAction action) {
@@ -4656,8 +4664,7 @@
     if (methodToKeep != singleTarget
         && !syntheticInterfaceMethodBridges.containsKey(
             methodToKeep.getDefinition().getReference())) {
-      syntheticInterfaceMethodBridges.put(
-          methodToKeep.getDefinition().getReference(), methodToKeep);
+      syntheticInterfaceMethodBridges.put(methodToKeep.getDefinition().getReference(), action);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index cfce371..e1d0124 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.Enqueuer.Mode;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -17,9 +18,16 @@
 
   public static Enqueuer createForInitialTreeShaking(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
       ExecutorService executorService,
       SubtypingInfo subtypingInfo) {
-    return new Enqueuer(appView, executorService, subtypingInfo, null, Mode.INITIAL_TREE_SHAKING);
+    return new Enqueuer(
+        appView,
+        artProfileCollectionAdditions,
+        executorService,
+        subtypingInfo,
+        null,
+        Mode.INITIAL_TREE_SHAKING);
   }
 
   public static Enqueuer createForFinalTreeShaking(
@@ -28,9 +36,16 @@
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
       Set<DexType> initialPrunedTypes) {
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.create(appView);
     Enqueuer enqueuer =
         new Enqueuer(
-            appView, executorService, subtypingInfo, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
+            appView,
+            artProfileCollectionAdditions,
+            executorService,
+            subtypingInfo,
+            keptGraphConsumer,
+            Mode.FINAL_TREE_SHAKING);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
     enqueuer.setInitialPrunedTypes(initialPrunedTypes);
@@ -41,8 +56,15 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ExecutorService executorService,
       SubtypingInfo subtypingInfo) {
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.create(appView);
     return new Enqueuer(
-        appView, executorService, subtypingInfo, null, Mode.INITIAL_MAIN_DEX_TRACING);
+        appView,
+        artProfileCollectionAdditions,
+        executorService,
+        subtypingInfo,
+        null,
+        Mode.INITIAL_MAIN_DEX_TRACING);
   }
 
   public static Enqueuer createForFinalMainDexTracing(
@@ -50,8 +72,15 @@
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer) {
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.create(appView);
     return new Enqueuer(
-        appView, executorService, subtypingInfo, keptGraphConsumer, Mode.FINAL_MAIN_DEX_TRACING);
+        appView,
+        artProfileCollectionAdditions,
+        executorService,
+        subtypingInfo,
+        keptGraphConsumer,
+        Mode.FINAL_MAIN_DEX_TRACING);
   }
 
   public static Enqueuer createForGenerateMainDexList(
@@ -59,8 +88,15 @@
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer) {
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.create(appView);
     return new Enqueuer(
-        appView, executorService, subtypingInfo, keptGraphConsumer, Mode.GENERATE_MAIN_DEX_LIST);
+        appView,
+        artProfileCollectionAdditions,
+        executorService,
+        subtypingInfo,
+        keptGraphConsumer,
+        Mode.GENERATE_MAIN_DEX_LIST);
   }
 
   public static Enqueuer createForWhyAreYouKeeping(
@@ -68,7 +104,14 @@
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer) {
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.create(appView);
     return new Enqueuer(
-        appView, executorService, subtypingInfo, keptGraphConsumer, Mode.WHY_ARE_YOU_KEEPING);
+        appView,
+        artProfileCollectionAdditions,
+        executorService,
+        subtypingInfo,
+        keptGraphConsumer,
+        Mode.WHY_ARE_YOU_KEEPING);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 4cc4154..8baf037 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -14,6 +15,7 @@
 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.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -22,6 +24,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.Box;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -41,6 +44,9 @@
   }
 
   public void run(Set<DexType> roots) {
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    DexItemFactory factory = appView.dexItemFactory();
+    AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
     for (DexType type : roots) {
       DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
       // Should only happen for library classes, which are filtered out.
@@ -48,7 +54,7 @@
       consumer.accept(type);
       // Super and interfaces are live, no need to add them.
       if (!DexAnnotation.hasSynthesizedClassAnnotation(
-          clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems())) {
+          clazz.annotations(), factory, syntheticItems, apiLevelCompute)) {
         traceAnnotationsDirectDependencies(clazz.annotations());
       }
       clazz.forEachField(field -> consumer.accept(field.getReference().type));
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilderEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilderEventConsumer.java
new file mode 100644
index 0000000..9378b76
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilderEventConsumer.java
@@ -0,0 +1,57 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringBaseEventConsumer;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingRootSetBuilderEventConsumer;
+
+public interface RootSetBuilderEventConsumer extends InterfaceMethodDesugaringBaseEventConsumer {
+
+  static RootSetBuilderEventConsumer create(
+      ArtProfileCollectionAdditions artProfileCollectionAdditions) {
+    return ArtProfileRewritingRootSetBuilderEventConsumer.attach(
+        artProfileCollectionAdditions, empty());
+  }
+
+  static EmptyRootSetBuilderEventConsumer empty() {
+    return EmptyRootSetBuilderEventConsumer.getInstance();
+  }
+
+  class EmptyRootSetBuilderEventConsumer implements RootSetBuilderEventConsumer {
+
+    private static final EmptyRootSetBuilderEventConsumer INSTANCE =
+        new EmptyRootSetBuilderEventConsumer();
+
+    private EmptyRootSetBuilderEventConsumer() {}
+
+    static EmptyRootSetBuilderEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptCompanionClassClinit(ProgramMethod method) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptDefaultAsCompanionMethod(
+        ProgramMethod method, ProgramMethod companionMethod) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptPrivateAsCompanionMethod(
+        ProgramMethod method, ProgramMethod companionMethod) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptStaticAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 149201d..b5810f2 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -50,10 +50,10 @@
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringBaseEventConsumer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.repackaging.RepackagingUtils;
 import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
 import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
@@ -113,6 +113,7 @@
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
     private AssumeInfoCollection.Builder assumeInfoCollectionBuilder;
+    private final RootSetBuilderEventConsumer eventConsumer;
     private final SubtypingInfo subtypingInfo;
     private final DirectMappedDexApplication application;
     private final Iterable<? extends ProguardConfigurationRule> rules;
@@ -156,9 +157,11 @@
 
     private RootSetBuilder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
+        RootSetBuilderEventConsumer eventConsumer,
         SubtypingInfo subtypingInfo,
         Iterable<? extends ProguardConfigurationRule> rules) {
       this.appView = appView;
+      this.eventConsumer = eventConsumer;
       this.subtypingInfo = subtypingInfo;
       this.application = appView.appInfo().app().asDirect();
       this.rules = rules;
@@ -170,8 +173,14 @@
     }
 
     private RootSetBuilder(
-        AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
-      this(appView, subtypingInfo, null);
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        Enqueuer enqueuer,
+        SubtypingInfo subtypingInfo) {
+      this(
+          appView,
+          RootSetBuilderEventConsumer.create(enqueuer.getArtProfileCollectionAdditions()),
+          subtypingInfo,
+          null);
     }
 
     boolean isMainDexRootSetBuilder() {
@@ -1582,32 +1591,7 @@
         ProgramMethod method = item.asMethod();
         ProgramMethod companion =
             interfaceDesugaringSyntheticHelper.ensureMethodOfProgramCompanionClassStub(
-                method,
-                new InterfaceMethodDesugaringBaseEventConsumer() {
-
-                  @Override
-                  public void acceptCompanionClassClinit(ProgramMethod method) {
-                    // No processing of synthesized CC.<clinit>. They will be picked up by tracing.
-                  }
-
-                  @Override
-                  public void acceptDefaultAsCompanionMethod(
-                      ProgramMethod method, ProgramMethod companionMethod) {
-                    // The move will be included in the pending-inverse map below.
-                  }
-
-                  @Override
-                  public void acceptPrivateAsCompanionMethod(
-                      ProgramMethod method, ProgramMethod companion) {
-                    // The move will be included in the pending-inverse map below.
-                  }
-
-                  @Override
-                  public void acceptStaticAsCompanionMethod(
-                      ProgramMethod method, ProgramMethod companion) {
-                    // The move will be included in the pending-inverse map below.
-                  }
-                });
+                method, eventConsumer);
         // Add the method to the inverse map as tracing will now directly target the CC method.
         pendingMethodMoveInverse.put(companion, method);
         // Only shrinking and optimization are transferred for interface companion methods.
@@ -2179,15 +2163,22 @@
     }
 
     public static RootSetBuilder builder(
-        AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
-      return new RootSetBuilder(appView, subtypingInfo);
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        Enqueuer enqueuer,
+        SubtypingInfo subtypingInfo) {
+      return new RootSetBuilder(appView, enqueuer, subtypingInfo);
     }
 
     public static RootSetBuilder builder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
+        ArtProfileCollectionAdditions artProfileCollectionAdditions,
         SubtypingInfo subtypingInfo,
         Iterable<? extends ProguardConfigurationRule> rules) {
-      return new RootSetBuilder(appView, subtypingInfo, rules);
+      return new RootSetBuilder(
+          appView,
+          RootSetBuilderEventConsumer.create(artProfileCollectionAdditions),
+          subtypingInfo,
+          rules);
     }
   }
 
@@ -2197,9 +2188,13 @@
 
     private ConsequentRootSetBuilder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
-        SubtypingInfo subtypingInfo,
-        Enqueuer enqueuer) {
-      super(appView, subtypingInfo, null);
+        Enqueuer enqueuer,
+        SubtypingInfo subtypingInfo) {
+      super(
+          appView,
+          RootSetBuilderEventConsumer.create(enqueuer.getArtProfileCollectionAdditions()),
+          subtypingInfo,
+          null);
       this.enqueuer = enqueuer;
     }
 
@@ -2235,9 +2230,9 @@
 
     static ConsequentRootSetBuilder builder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
-        SubtypingInfo subtypingInfo,
-        Enqueuer enqueuer) {
-      return new ConsequentRootSetBuilder(appView, subtypingInfo, enqueuer);
+        Enqueuer enqueuer,
+        SubtypingInfo subtypingInfo) {
+      return new ConsequentRootSetBuilder(appView, enqueuer, subtypingInfo);
     }
   }
 
@@ -2245,9 +2240,14 @@
 
     private MainDexRootSetBuilder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
+        ArtProfileCollectionAdditions artProfileCollectionAdditions,
         SubtypingInfo subtypingInfo,
         Iterable<? extends ProguardConfigurationRule> rules) {
-      super(appView, subtypingInfo, rules);
+      super(
+          appView,
+          RootSetBuilderEventConsumer.create(artProfileCollectionAdditions),
+          subtypingInfo,
+          rules);
     }
 
     @Override
@@ -2299,9 +2299,11 @@
 
     public static MainDexRootSetBuilder builder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
+        ArtProfileCollectionAdditions artProfileCollectionAdditions,
         SubtypingInfo subtypingInfo,
         Iterable<? extends ProguardConfigurationRule> rules) {
-      return new MainDexRootSetBuilder(appView, subtypingInfo, rules);
+      return new MainDexRootSetBuilder(
+          appView, artProfileCollectionAdditions, subtypingInfo, rules);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index da2b320..dc4c7ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -537,9 +537,9 @@
     // somewhat expensive.
     if (appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()) {
       ComputedApiLevel sourceApiLevel =
-          getApiReferenceLevelForMerging(appView, apiLevelCompute, sourceClass);
+          getApiReferenceLevelForMerging(apiLevelCompute, sourceClass);
       ComputedApiLevel targetApiLevel =
-          getApiReferenceLevelForMerging(appView, apiLevelCompute, targetClass);
+          getApiReferenceLevelForMerging(apiLevelCompute, targetClass);
       if (!sourceApiLevel.equals(targetApiLevel)) {
         if (Log.ENABLED) {
           AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index ebb3ba0..8cf1e77 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -197,15 +197,7 @@
     appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
     appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
     if (result.lens != null) {
-      appView.setGraphLens(result.lens);
-      appView.setAppInfo(
-          appView
-              .appInfo()
-              .rebuildWithMainDexInfo(
-                  appView
-                      .appInfo()
-                      .getMainDexInfo()
-                      .rewrittenWithLens(appView.getSyntheticItems(), result.lens)));
+      appView.rewriteWithLens(result.lens);
     }
     appView.pruneItems(result.prunedItems, executorService);
   }
@@ -572,7 +564,7 @@
       DexProgramClass externalSyntheticClass,
       AppView<?> appView) {
     if (shouldAnnotateSynthetics(appView.options())) {
-      SyntheticMarker.addMarkerToClass(externalSyntheticClass, kind, appView.options());
+      SyntheticMarker.addMarkerToClass(externalSyntheticClass, kind, appView);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index d8ae369..631231c 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -466,6 +466,16 @@
     return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
   }
 
+  public Iterable<SyntheticKind> getSyntheticKinds(DexType type) {
+    Iterable<SyntheticKind> references =
+        IterableUtils.transform(committed.getItems(type), SyntheticReference::getKind);
+    SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
+    if (definition != null) {
+      references = Iterables.concat(references, IterableUtils.singleton(definition.getKind()));
+    }
+    return references;
+  }
+
   boolean isSyntheticInput(DexProgramClass clazz) {
     return committed.containsSyntheticInput(clazz.getType());
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index 67dbe6a..aebc9ae 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -6,14 +6,15 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.SynthesizedAnnotationClassInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import java.nio.charset.StandardCharsets;
 import org.objectweb.asm.Attribute;
 import org.objectweb.asm.ByteVector;
@@ -108,14 +109,18 @@
   }
 
   public static void addMarkerToClass(
-      DexProgramClass clazz, SyntheticKind kind, InternalOptions options) {
+      DexProgramClass clazz, SyntheticKind kind, AppView<?> appView) {
     // TODO(b/158159959): Consider moving this to the dex writer similar to the CF case.
-    assert !options.isGeneratingClassFiles();
+    assert !appView.options().isGeneratingClassFiles();
     clazz.setAnnotations(
         clazz
             .annotations()
             .getWithAddedOrReplaced(
-                DexAnnotation.createAnnotationSynthesizedClass(kind, options.itemFactory)));
+                DexAnnotation.createAnnotationSynthesizedClass(
+                    kind,
+                    appView.options().itemFactory,
+                    AndroidApiLevelUtils.getApiReferenceLevelForMerging(
+                        appView.apiLevelCompute(), clazz))));
   }
 
   public static SyntheticMarker stripMarkerFromClass(DexProgramClass clazz, AppView<?> appView) {
@@ -134,7 +139,10 @@
     SyntheticMarker marker = internalStripMarkerFromClass(clazz, appView);
     assert marker != NO_MARKER
         || !DexAnnotation.hasSynthesizedClassAnnotation(
-            clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems());
+            clazz.annotations(),
+            appView.dexItemFactory(),
+            appView.getSyntheticItems(),
+            appView.apiLevelCompute());
     return marker;
   }
 
@@ -146,13 +154,17 @@
     if (isDefinitelyNotSyntheticProgramClass(clazz)) {
       return NO_MARKER;
     }
-    SyntheticKind kind =
+    SynthesizedAnnotationClassInfo synthesizedInfo =
         DexAnnotation.getSynthesizedClassAnnotationInfo(
-            clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems());
-    if (kind == null) {
+            clazz.annotations(),
+            appView.dexItemFactory(),
+            appView.getSyntheticItems(),
+            appView.apiLevelCompute());
+    if (synthesizedInfo == null) {
       return NO_MARKER;
     }
     assert clazz.annotations().size() == 1;
+    SyntheticKind kind = synthesizedInfo.getSyntheticKind();
     if (kind.isSingleSyntheticMethod()) {
       if (!clazz.interfaces.isEmpty()) {
         return NO_MARKER;
@@ -164,6 +176,7 @@
       }
     }
     clazz.setAnnotations(DexAnnotationSet.empty());
+    clazz.forEachMethod(method -> method.setApiLevelForCode(synthesizedInfo.getComputedApiLevel()));
     DexType context = getSyntheticContextType(clazz.type, kind, appView.dexItemFactory());
     return new SyntheticMarker(
         kind, SynthesizingContext.fromSyntheticInputClass(clazz, context, appView));
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 59aff72..5c81e4d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -68,21 +68,25 @@
 
   // Method synthetics.
   public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
-      generator.forSingleMethod("CheckNotZero");
-  public final SyntheticKind RECORD_HELPER = generator.forSingleMethod("Record");
-  public final SyntheticKind BACKPORT = generator.forSingleMethod("Backport");
+      generator.forSingleMethodWithGlobalMerging("CheckNotZero");
+  public final SyntheticKind RECORD_HELPER = generator.forSingleMethodWithGlobalMerging("Record");
+  public final SyntheticKind BACKPORT = generator.forSingleMethodWithGlobalMerging("Backport");
   public final SyntheticKind BACKPORT_WITH_FORWARDING =
       generator.forSingleMethod("BackportWithForwarding");
   public final SyntheticKind STATIC_INTERFACE_CALL =
       generator.forSingleMethod("StaticInterfaceCall");
-  public final SyntheticKind TO_STRING_IF_NOT_NULL = generator.forSingleMethod("ToStringIfNotNull");
-  public final SyntheticKind THROW_CCE_IF_NOT_NULL = generator.forSingleMethod("ThrowCCEIfNotNull");
-  public final SyntheticKind THROW_IAE = generator.forSingleMethod("ThrowIAE");
-  public final SyntheticKind THROW_ICCE = generator.forSingleMethod("ThrowICCE");
-  public final SyntheticKind THROW_NSME = generator.forSingleMethod("ThrowNSME");
-  public final SyntheticKind THROW_RTE = generator.forSingleMethod("ThrowRTE");
-  public final SyntheticKind TWR_CLOSE_RESOURCE = generator.forSingleMethod("TwrCloseResource");
-  public final SyntheticKind SERVICE_LOADER = generator.forSingleMethod("ServiceLoad");
+  public final SyntheticKind TO_STRING_IF_NOT_NULL =
+      generator.forSingleMethodWithGlobalMerging("ToStringIfNotNull");
+  public final SyntheticKind THROW_CCE_IF_NOT_NULL =
+      generator.forSingleMethodWithGlobalMerging("ThrowCCEIfNotNull");
+  public final SyntheticKind THROW_IAE = generator.forSingleMethodWithGlobalMerging("ThrowIAE");
+  public final SyntheticKind THROW_ICCE = generator.forSingleMethodWithGlobalMerging("ThrowICCE");
+  public final SyntheticKind THROW_NSME = generator.forSingleMethodWithGlobalMerging("ThrowNSME");
+  public final SyntheticKind THROW_RTE = generator.forSingleMethodWithGlobalMerging("ThrowRTE");
+  public final SyntheticKind TWR_CLOSE_RESOURCE =
+      generator.forSingleMethodWithGlobalMerging("TwrCloseResource");
+  public final SyntheticKind SERVICE_LOADER =
+      generator.forSingleMethodWithGlobalMerging("ServiceLoad");
   public final SyntheticKind OUTLINE = generator.forSingleMethod("Outline");
   public final SyntheticKind COVARIANT_OUTLINE = generator.forSingleMethod("CovariantOutline");
   public final SyntheticKind API_CONVERSION = generator.forSingleMethod("APIConversion");
@@ -90,7 +94,10 @@
       generator.forSingleMethod("APIConversionParameters");
   public final SyntheticKind COLLECTION_CONVERSION =
       generator.forSingleMethod("$CollectionConversion");
-  public final SyntheticKind API_MODEL_OUTLINE = generator.forSingleMethod("ApiModelOutline");
+  public final SyntheticKind API_MODEL_OUTLINE =
+      generator.forSingleMethodWithGlobalMerging("ApiModelOutline");
+  public final SyntheticKind API_MODEL_OUTLINE_WITHOUT_GLOBAL_MERGING =
+      generator.forSingleMethod("ApiModelOutline");
 
   private final List<SyntheticKind> ALL_KINDS;
   private String lazyVersionHash = null;
@@ -144,7 +151,11 @@
     }
 
     SyntheticKind forSingleMethod(String descriptor) {
-      return register(new SyntheticMethodKind(getNextId(), descriptor));
+      return register(new SyntheticMethodKind(getNextId(), descriptor, false));
+    }
+
+    SyntheticKind forSingleMethodWithGlobalMerging(String descriptor) {
+      return register(new SyntheticMethodKind(getNextId(), descriptor, true));
     }
 
     // TODO(b/214901256): Remove once fixed.
@@ -217,6 +228,14 @@
       return descriptor;
     }
 
+    public boolean isSyntheticMethodKind() {
+      return false;
+    }
+
+    public SyntheticMethodKind asSyntheticMethodKind() {
+      return null;
+    }
+
     public abstract boolean isShareable();
 
     public abstract boolean isSingleSyntheticMethod();
@@ -236,10 +255,13 @@
     public abstract void internalHash(Hasher hasher);
   }
 
-  private static class SyntheticMethodKind extends SyntheticKind {
+  public static class SyntheticMethodKind extends SyntheticKind {
 
-    public SyntheticMethodKind(int id, String descriptor) {
+    private final boolean allowGlobalMerging;
+
+    public SyntheticMethodKind(int id, String descriptor, boolean allowGlobalMerging) {
       super(id, descriptor);
+      this.allowGlobalMerging = allowGlobalMerging;
     }
 
     @Override
@@ -268,6 +290,20 @@
       return false;
     }
 
+    public boolean isAllowGlobalMerging() {
+      return allowGlobalMerging;
+    }
+
+    @Override
+    public boolean isSyntheticMethodKind() {
+      return true;
+    }
+
+    @Override
+    public SyntheticMethodKind asSyntheticMethodKind() {
+      return this;
+    }
+
     @Override
     public void internalHash(Hasher hasher) {
       hasher.putString("method", StandardCharsets.UTF_8);
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 5be322e..2e7e2e3 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -99,6 +99,8 @@
       case V39:
         return AndroidApiLevel.P;
       case V40:
+        return AndroidApiLevel.R;
+      case V41:
         return AndroidApiLevel.ANDROID_PLATFORM;
       default:
         throw new Unreachable();
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 841bf9a..3067de7 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -65,20 +65,24 @@
   }
 
   public static ComputedApiLevel getApiReferenceLevelForMerging(
-      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute, DexProgramClass clazz) {
+      AndroidApiLevelCompute apiLevelCompute, DexProgramClass clazz) {
     // The api level of a class is the max level of it's members, super class and interfaces.
     return getMembersApiReferenceLevelForMerging(
         clazz, apiLevelCompute.computeApiLevelForDefinition(clazz.allImmediateSupertypes()));
   }
 
-  private static ComputedApiLevel getMembersApiReferenceLevelForMerging(
+  public static ComputedApiLevel getMembersApiReferenceLevelForMerging(
       DexProgramClass clazz, ComputedApiLevel memberLevel) {
     // Based on b/138781768#comment57 there is almost no penalty for having an unknown reference
     // as long as we are not invoking or accessing a field on it. Therefore we can disregard static
     // types of fields and only consider method code api levels.
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.hasCode()) {
-        memberLevel = memberLevel.max(method.getApiLevelForCode());
+        ComputedApiLevel apiLevelForCode = method.getApiLevelForCode();
+        if (apiLevelForCode.isNotSetApiLevel()) {
+          return ComputedApiLevel.notSet();
+        }
+        memberLevel = memberLevel.max(apiLevelForCode);
       }
       if (memberLevel.isUnknownApiLevel()) {
         return memberLevel;
diff --git a/src/main/java/com/android/tools/r8/utils/BitUtils.java b/src/main/java/com/android/tools/r8/utils/BitUtils.java
index b264b4b..f951031 100644
--- a/src/main/java/com/android/tools/r8/utils/BitUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BitUtils.java
@@ -13,4 +13,9 @@
   public static boolean isBitInMaskSet(int value, int mask) {
     return (value & mask) != 0;
   }
+
+  public static boolean isAligned(int alignment, int value) {
+    assert (alignment & (alignment - 1)) == 0; // Check alignment is power of 2.
+    return (value & (alignment - 1)) == 0;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index d094cf7..e1b4588 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -13,7 +13,8 @@
   V37(37, new byte[] {'0', '3', '7'}),
   V38(38, new byte[] {'0', '3', '8'}),
   V39(39, new byte[] {'0', '3', '9'}),
-  V40(40, new byte[] {'0', '4', '0'});
+  V40(40, new byte[] {'0', '4', '0'}),
+  V41(41, new byte[] {'0', '4', '1'});
 
   private final int dexVersion;
 
@@ -47,6 +48,8 @@
       case Sv2:
       case S:
       case R:
+        // Dex version should have been V40 starting from API level 30, see b/269089718.
+        // return DexVersion.V40;
       case Q:
       case P:
         return DexVersion.V39;
@@ -97,6 +100,8 @@
         return Optional.of(V39);
       case 40:
         return Optional.of(V40);
+      case 41:
+        return Optional.of(V41);
       default:
         return Optional.empty();
     }
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 e7dfeee..6e39e8e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -154,7 +154,7 @@
 
   public static final int SUPPORTED_DEX_VERSION =
       AndroidApiLevel.LATEST.getDexVersion().getIntValue();
-  public static final int EXPERIMENTAL_DEX_VERSION = DexVersion.V40.getIntValue();
+  public static final int EXPERIMENTAL_DEX_VERSION = DexVersion.V41.getIntValue();
 
   public static final int ASM_VERSION = Opcodes.ASM9;
 
@@ -839,7 +839,7 @@
   private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions();
   private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
   private final MappingComposeOptions mappingComposeOptions = new MappingComposeOptions();
-  private final ArtProfileOptions artProfileOptions = new ArtProfileOptions();
+  private final ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
   private final StartupOptions startupOptions = new StartupOptions();
   private final StartupInstrumentationOptions startupInstrumentationOptions =
       new StartupInstrumentationOptions();
@@ -1909,18 +1909,6 @@
     public void disableStubbingOfClasses() {
       enableStubbingOfClasses = false;
     }
-
-    private boolean isThrowable(AppView<?> appView, DexLibraryClass libraryClass) {
-      DexClass current = libraryClass;
-      while (current.getSuperType() != null) {
-        DexType superType = current.getSuperType();
-        if (superType == appView.dexItemFactory().throwableType) {
-          return true;
-        }
-        current = appView.definitionFor(current.getSuperType());
-      }
-      return false;
-    }
   }
 
   public static class ProtoShrinkingOptions {
@@ -1972,12 +1960,14 @@
     // If false, use the desugared library implementation when desugared library is enabled.
     public boolean alwaysBackportListSetMapMethods = true;
     public boolean neverReuseCfLocalRegisters = false;
-    public boolean roundtripThroughLIR = false;
+    public boolean roundtripThroughLir = false;
     public boolean checkReceiverAlwaysNullInCallSiteOptimization = true;
     public boolean forceInlineAPIConversions = false;
     private boolean hasReadCheckDeterminism = false;
     private DeterminismChecker determinismChecker = null;
     public boolean usePcEncodingInCfForTesting = false;
+    public boolean dexVersion40FromApiLevel30 =
+        System.getProperty("com.android.tools.r8.dexVersion40ForApiLevel30") != null;
     public boolean dexContainerExperiment =
         System.getProperty("com.android.tools.r8.dexContainerExperiment") != null;
 
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ProgramConsumerUtils.java
new file mode 100644
index 0000000..e33f72e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ProgramConsumerUtils.java
@@ -0,0 +1,32 @@
+// 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.utils;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.dex.Marker.Backend;
+
+public class ProgramConsumerUtils {
+
+  public static Backend getBackend(ProgramConsumer programConsumer) {
+    if (isGeneratingClassFiles(programConsumer)) {
+      return Backend.CF;
+    } else {
+      assert isGeneratingDex(programConsumer);
+      return Backend.DEX;
+    }
+  }
+
+  public static boolean isGeneratingClassFiles(ProgramConsumer programConsumer) {
+    return programConsumer instanceof ClassFileConsumer;
+  }
+
+  public static boolean isGeneratingDex(ProgramConsumer programConsumer) {
+    return programConsumer instanceof DexIndexedConsumer
+        || programConsumer instanceof DexFilePerClassFileConsumer;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
index 4fda093..88e998c 100644
--- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.origin.Origin;
@@ -42,7 +43,7 @@
   @Test
   public void test() throws Exception {
     InternalOptions options = new InternalOptions();
-    options.dumpOptions = DumpOptions.builder(Tool.D8).build();
+    options.dumpOptions = DumpOptions.builder(Tool.D8).setBackend(Marker.Backend.DEX).build();
 
     String dataResourceName = "my-resource.bin";
     byte[] dataResourceData = new byte[] {1, 2, 3};
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 74620e9..d4123d3 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -856,14 +857,20 @@
         computeAppViewWithClassHierarchy(app, keepConfig, optionsConsumer);
     // Run the tree shaker to compute an instance of AppInfoWithLiveness.
     ExecutorService executor = Executors.newSingleThreadExecutor();
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.nop();
     SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
     RootSet rootSet =
         RootSet.builder(
-                appView, subtypingInfo, appView.options().getProguardConfiguration().getRules())
+                appView,
+                artProfileCollectionAdditions,
+                subtypingInfo,
+                appView.options().getProguardConfiguration().getRules())
             .build(executor);
     appView.setRootSet(rootSet);
     EnqueuerResult enqueuerResult =
-        EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo)
+        EnqueuerFactory.createForInitialTreeShaking(
+                appView, artProfileCollectionAdditions, executor, subtypingInfo)
             .traceApplication(rootSet, executor, Timing.empty());
     executor.shutdown();
     // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 68b6fc0..9b44892 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -57,6 +57,7 @@
         options.testing.allowUnnecessaryDontWarnWildcards = false;
         options.horizontalClassMergerOptions().enable();
         options.horizontalClassMergerOptions().setEnableInterfaceMerging();
+        options.getArtProfileOptions().setEnableCompletenessCheckForTesting(true);
         options
             .getCfCodeAnalysisOptions()
             .setAllowUnreachableCfBlocks(false)
@@ -481,6 +482,11 @@
     return super.addLibraryProvider(provider);
   }
 
+  public T setUseDefaultRuntimeLibrary(boolean useDefaultRuntimeLibrary) {
+    this.useDefaultRuntimeLibrary = useDefaultRuntimeLibrary;
+    return self();
+  }
+
   @Override
   public T allowStdoutMessages() {
     allowStdoutMessages = true;
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 9f80832..8978240 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -54,6 +54,7 @@
     Set<String> removedTypeNames = new HashSet<>();
     if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.U)) {
       removedTypeNames.add("com.android.internal.util.Predicate");
+      removedTypeNames.add("android.adservices.AdServicesVersion");
     }
     return removedTypeNames;
   }
@@ -72,10 +73,12 @@
       ClassSubject clazz = inspector.clazz(type);
       if (!clazz.isPresent()) {
         if (!clazz.getOriginalName().startsWith("android.test")
-            && !clazz.getOriginalName().startsWith("junit")
-            && node.getAttributes().getNamedItem("module") == null) {
+            && !clazz.getOriginalName().startsWith("junit")) {
           assert exemptionList.contains(type) || hasRemoved(node);
           assert exemptionList.contains(type) || getRemoved(node).isLessThanOrEqualTo(maxApiLevel);
+          if (!hasRemoved(node)) {
+            exemptionList.remove(type);
+          }
         }
         continue;
       }
@@ -110,6 +113,7 @@
         }
       }
     }
+    assert exemptionList.isEmpty();
   }
 
   private boolean isMethod(Node node) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
new file mode 100644
index 0000000..f0b5d28
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
@@ -0,0 +1,286 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.util.ArrayList;
+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;
+
+/***
+ * This is a replication of b/268596049.
+ */
+@RunWith(Parameterized.class)
+public class ApiModelD8GradleSetupTest extends TestBase {
+
+  private static final AndroidApiLevel mockApiLevelOne = AndroidApiLevel.M;
+  private static final AndroidApiLevel mockApiLevelTwo = AndroidApiLevel.O;
+  private static final AndroidApiLevel mockApiLevelThree = AndroidApiLevel.R;
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addLibraryClasses(LibraryClassOne.class, LibraryClassTwo.class, LibraryClassThree.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .apply(setMockApiLevelForClass(LibraryClassOne.class, mockApiLevelOne))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClassOne.class.getDeclaredMethod("foo"), mockApiLevelOne))
+        .apply(setMockApiLevelForClass(LibraryClassTwo.class, mockApiLevelTwo))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClassTwo.class.getDeclaredMethod("bar"), mockApiLevelTwo))
+        .apply(setMockApiLevelForClass(LibraryClassThree.class, mockApiLevelThree))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClassThree.class.getDeclaredMethod("baz"), mockApiLevelThree))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+  }
+
+  private boolean willHorizontallyMergeOutlines() {
+    // After api level mockApiLevelTwo we only have a single outline and therefore will not merge.
+    return parameters.getApiLevel().isLessThan(mockApiLevelTwo);
+  }
+
+  private boolean willStubLibraryClassThree() {
+    return parameters.getApiLevel().isLessThan(mockApiLevelThree);
+  }
+
+  public AndroidApiLevel getApiLevelForRuntime() {
+    return parameters.isCfRuntime()
+        ? AndroidApiLevel.B
+        : parameters.getRuntime().asDex().maxSupportedApiLevel();
+  }
+
+  public boolean addToBootClasspath(Class<?> clazz) {
+    if (clazz == LibraryClassOne.class) {
+      return getApiLevelForRuntime().isGreaterThanOrEqualTo(mockApiLevelOne);
+    }
+    if (clazz == LibraryClassTwo.class) {
+      return getApiLevelForRuntime().isGreaterThanOrEqualTo(mockApiLevelTwo);
+    }
+    assert clazz == LibraryClassThree.class;
+    return getApiLevelForRuntime().isGreaterThanOrEqualTo(mockApiLevelThree);
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(Main.class, ProgramClassOne.class, ProgramClassTwo.class)
+        .addAndroidBuildVersion(AndroidApiLevel.B)
+        .addLibraryClasses(LibraryClassOne.class, LibraryClassTwo.class, LibraryClassThree.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8DebugWithMerge() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testD8(
+        CompilationMode.DEBUG,
+        this::inspectNumberOfClassesFromOutput,
+        HorizontallyMergedClassesInspector::assertNoClassesMerged);
+  }
+
+  @Test
+  public void testD8ReleaseForApiLevelWithOutlining() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    assumeTrue(willHorizontallyMergeOutlines());
+    testD8(
+        CompilationMode.RELEASE,
+        this::inspectNumberOfClassesFromOutput,
+        HorizontallyMergedClassesInspector::assertNoClassesMerged);
+  }
+
+  @Test
+  public void testD8ReleaseForApiLevelWithNoOutlining() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    assumeFalse(willHorizontallyMergeOutlines());
+    testD8(
+        CompilationMode.RELEASE,
+        this::inspectNumberOfClassesFromOutput,
+        HorizontallyMergedClassesInspector::assertNoClassesMerged);
+  }
+
+  private void testD8(
+      CompilationMode mode,
+      ThrowingConsumer<CodeInspector, Exception> inspect,
+      ThrowableConsumer<HorizontallyMergedClassesInspector> horizontallyMergingConsumer)
+      throws Exception {
+    GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+    D8TestCompileResult compileResultProgramClass =
+        compileIntermediate(mode, globals, ProgramClassOne.class);
+    D8TestCompileResult compileResultProgramClassTwo =
+        compileIntermediate(mode, globals, ProgramClassTwo.class);
+    D8TestCompileResult compileResultMain =
+        compileIntermediate(
+            mode, globals, Main.class, ProgramClassOne.class, ProgramClassTwo.class);
+
+    if (willStubLibraryClassThree()) {
+      assertTrue(globals.isSingleGlobal());
+    } else {
+      assertFalse(globals.hasGlobals());
+    }
+
+    List<Class<?>> bootClassPath = new ArrayList<>();
+    if (addToBootClasspath(LibraryClassOne.class)) {
+      bootClassPath.add(LibraryClassOne.class);
+    }
+    if (addToBootClasspath(LibraryClassTwo.class)) {
+      bootClassPath.add(LibraryClassTwo.class);
+    }
+    if (addToBootClasspath(LibraryClassThree.class)) {
+      bootClassPath.add(LibraryClassThree.class);
+    }
+
+    testForD8()
+        .setMode(mode)
+        .setUseDefaultRuntimeLibrary(false)
+        .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals.getProviders()))
+        .addProgramFiles(
+            compileResultProgramClass.writeToZip(),
+            compileResultProgramClassTwo.writeToZip(),
+            compileResultMain.writeToZip())
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion(getApiLevelForRuntime())
+        .addHorizontallyMergedClassesInspector(horizontallyMergingConsumer)
+        .compile()
+        .inspect(inspect)
+        .addBootClasspathFiles(
+            buildOnDexRuntime(parameters, bootClassPath.toArray(new Class<?>[0])))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private D8TestCompileResult compileIntermediate(
+      CompilationMode mode,
+      GlobalSyntheticsTestingConsumer globals,
+      Class<?> programClass,
+      Class<?>... classpathClass)
+      throws Exception {
+    return testForD8(parameters.getBackend())
+        .setMode(mode)
+        .setIntermediate(true)
+        .addProgramClasses(programClass)
+        .addClasspathClasses(classpathClass)
+        .apply(this::setupTestBuilder)
+        .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals))
+        .compile();
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    runResult.assertSuccessWithOutputLines(
+        addToBootClasspath(LibraryClassOne.class) ? "LibraryClassOne::foo" : "Not calling foo()",
+        addToBootClasspath(LibraryClassTwo.class) ? "LibraryClassTwo::bar" : "Not calling bar()",
+        addToBootClasspath(LibraryClassThree.class)
+            ? "LibraryClassThree::baz"
+            : "Not calling baz()");
+  }
+
+  private void inspectNumberOfClassesFromOutput(CodeInspector inspector) {
+    // We always have Main, ProgramClassOne, ProgramClassTwo and AndroidBuildVersion as program
+    // classes. Depending on the api a number of synthetic classes.
+    int numberOfClasses =
+        4
+            + (willStubLibraryClassThree() ? 2 : 0)
+            + BooleanUtils.intValue(parameters.getApiLevel().isLessThan(mockApiLevelTwo))
+            + BooleanUtils.intValue(parameters.getApiLevel().isLessThan(mockApiLevelOne));
+    assertEquals(numberOfClasses, inspector.allClasses().size());
+    assertThat(inspector.clazz(Main.class), isPresent());
+    assertThat(inspector.clazz(ProgramClassOne.class), isPresent());
+  }
+
+  // Will be present from api level 23
+  public static class LibraryClassOne {
+
+    public static void foo() {
+      System.out.println("LibraryClassOne::foo");
+    }
+  }
+
+  // Will be present from api level 26
+  public static class LibraryClassTwo {
+
+    public static void bar() {
+      System.out.println("LibraryClassTwo::bar");
+    }
+  }
+
+  // Will be present form api level 30
+  public static class LibraryClassThree {
+
+    public void baz() {
+      System.out.println("LibraryClassThree::baz");
+    }
+  }
+
+  public static class ProgramClassOne {
+
+    public static void callOneAndTwo() {
+      if (AndroidBuildVersion.VERSION >= 23) {
+        LibraryClassOne.foo();
+      } else {
+        System.out.println("Not calling foo()");
+      }
+      if (AndroidBuildVersion.VERSION >= 26) {
+        LibraryClassTwo.bar();
+      } else {
+        System.out.println("Not calling bar()");
+      }
+    }
+  }
+
+  public static class ProgramClassTwo extends LibraryClassThree {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ProgramClassOne.callOneAndTwo();
+      if (AndroidBuildVersion.VERSION >= 30) {
+        new ProgramClassTwo().baz();
+      } else {
+        System.out.println("Not calling baz()");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoUnknownMergeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoUnknownMergeTest.java
new file mode 100644
index 0000000..67082dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoUnknownMergeTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.util.HashSet;
+import java.util.Set;
+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 ApiModelNoUnknownMergeTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Set<String> methodReferences = new HashSet<>();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, LibraryClassFooCaller.class, LibraryClassBarCaller.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .addOptionsModification(
+            options -> {
+              ClassReference fooCaller = Reference.classFromClass(LibraryClassFooCaller.class);
+              ClassReference barCaller = Reference.classFromClass(LibraryClassBarCaller.class);
+              options.apiModelingOptions().tracedMethodApiLevelCallback =
+                  (methodReference, computedApiLevel) -> {
+                    if ((methodReference.getHolderClass().equals(fooCaller)
+                            && methodReference.getMethodName().equals("callFoo"))
+                        || (methodReference.getHolderClass().equals(barCaller)
+                            && methodReference.getMethodName().equals("callBar"))) {
+                      methodReferences.add(methodReference.toSourceString());
+                      assertTrue(computedApiLevel.isUnknownApiLevel());
+                    }
+                  };
+            })
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .compile()
+        .addBootClasspathClasses(LibraryClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("LibraryClass::foo", "LibraryClass::bar");
+    Set<String> expected = new HashSet<>();
+    expected.add(
+        Reference.methodFromMethod(LibraryClassFooCaller.class.getDeclaredMethod("callFoo"))
+            .toSourceString());
+    expected.add(
+        Reference.methodFromMethod(LibraryClassBarCaller.class.getDeclaredMethod("callBar"))
+            .toSourceString());
+    // Ensure that the two caller methods has been visited.
+    assertEquals(expected, methodReferences);
+  }
+
+  public static class LibraryClass {
+
+    public static void foo() {
+      System.out.println("LibraryClass::foo");
+    }
+
+    public static void bar() {
+      System.out.println("LibraryClass::bar");
+    }
+  }
+
+  public static class LibraryClassFooCaller {
+
+    @NeverInline
+    public static void callFoo() {
+      LibraryClass.foo();
+    }
+  }
+
+  public static class LibraryClassBarCaller {
+
+    @NeverInline
+    public static void callBar() {
+      LibraryClass.bar();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      LibraryClassFooCaller.callFoo();
+      LibraryClassBarCaller.callBar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
index 12edd71..204afc7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
@@ -7,19 +7,36 @@
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
 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.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 
-public class MergedVirtualMethodStackTraceTest extends HorizontalClassMergingTestBase {
-  public MergedVirtualMethodStackTraceTest(TestParameters parameters) {
-    super(parameters);
+@RunWith(Parameterized.class)
+public class MergedVirtualMethodStackTraceTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean forceInlineOnly;
+
+  @Parameterized.Parameters(name = "{0}, forceInlineOnly={1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   public StackTrace expectedStackTrace;
@@ -30,7 +47,7 @@
     expectedStackTrace =
         testForJvm()
             .addTestClasspath()
-            .run(CfRuntime.getSystemRuntime(), Program.Main.class)
+            .run(CfRuntime.getSystemRuntime(), Main.class)
             .assertFailure()
             .map(StackTrace::extractFromJvm);
   }
@@ -38,63 +55,43 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(Program.class)
-        .addKeepMainRule(Program.Main.class)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
         .addKeepAttributeLineNumberTable()
         .addKeepAttributeSourceFile()
-        .addDontWarn(C.class)
-        .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
+        .applyIf(
+            forceInlineOnly, b -> b.addOptionsModification(InlinerOptions::setOnlyForceInlining))
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class))
-        .run(parameters.getRuntime(), Program.Main.class)
+            inspector -> inspector.assertMergedInto(B.class, A.class))
+        .run(parameters.getRuntime(), Main.class)
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
-              assertThat(codeInspector.clazz(Program.A.class), isPresent());
-              assertThat(codeInspector.clazz(Program.B.class), isAbsent());
+              assertThat(codeInspector.clazz(A.class), notIf(isPresent(), !forceInlineOnly));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
               assertThat(stackTrace, isSame(expectedStackTrace));
             });
   }
 
-  public static class C {
-    public static void foo() {
-      System.out.println("foo c");
+  @NeverClassInline
+  public static class A {
+    public void foo() {
+      System.out.println("foo a");
     }
   }
 
-  public static class Program {
-    @NeverClassInline
-    public static class A {
-      @NeverInline
-      public void foo() {
-        System.out.println("foo a");
-        try {
-          // Undefined reference, prevents inlining.
-          C.foo();
-        } catch (NoClassDefFoundError e) {
-        }
-      }
+  @NeverClassInline
+  public static class B {
+    public void foo() {
+      throw new RuntimeException();
     }
+  }
 
-    @NeverClassInline
-    public static class B {
-      @NeverInline
-      public void foo() {
-        try {
-          // Undefined reference, prevents inlining.
-          C.foo();
-        } catch (NoClassDefFoundError e) {
-        }
-        throw new RuntimeException();
-      }
-    }
-
-    public static class Main {
-      public static void main(String[] args) {
-        new A().foo();
-        new B().foo();
-      }
+  public static class Main {
+    public static void main(String[] args) {
+      new A().foo();
+      new B().foo();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index a29a6d5..28e63fa 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -29,6 +29,7 @@
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -219,4 +220,21 @@
       assertEquals(6, html.stream().filter(s -> s.contains("Flow")).count());
     }
   }
+
+  public static void main(String[] args) throws Exception {
+    // Generate all html docs.
+    Path folder = Paths.get("html");
+    Files.createDirectories(folder);
+    ImmutableList<LibraryDesugaringSpecification> specs =
+        ImmutableList.of(JDK8, JDK11_MINIMAL, JDK11, JDK11_PATH, JDK11_LEGACY);
+    for (LibraryDesugaringSpecification spec : specs) {
+      Path jdkLibJar =
+          spec == JDK8
+              ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
+              : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
+      new GenerateHtmlDoc(
+              spec.getSpecification().toString(), jdkLibJar.toString(), folder.toString())
+          .run(spec + ".html");
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
index 7240267..6e3ea69 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
@@ -12,9 +13,12 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsGenerator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MemberAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClassesGenerator;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
@@ -116,36 +120,66 @@
               + " java.time.format.DateTimeFormatterBuilder.appendGenericZoneText(java.time.format.TextStyle,"
               + " java.util.Set)");
 
+  private static final Set<String> FAILURES_JAPANESE_ERA =
+      ImmutableSet.of("Field java.time.chrono.JapaneseEra java.time.chrono.JapaneseEra.REIWA");
+
   @Test
   public void test() throws Exception {
     SupportedClasses supportedClasses =
-        new SupportedMethodsGenerator(new InternalOptions())
+        new SupportedClassesGenerator(new InternalOptions())
             .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification());
 
     for (AndroidApiLevel api : getRelevantApiLevels()) {
-      Set<DexMethod> localFailures = Sets.newIdentityHashSet();
+
+      Set<DexMethod> localMethodFailures = Sets.newIdentityHashSet();
+      Set<DexField> localFieldFailures = Sets.newIdentityHashSet();
+
       supportedClasses.forEachClass(
-          supportedClass ->
-              supportedClass.forEachMethodAndAnnotation(
-                  (method, annotation) -> {
-                    if (annotation != null && annotation.isUnsupportedInMinApiRange()) {
-                      if (api.getLevel() >= annotation.getMinRange()
-                          && api.getLevel() <= annotation.getMaxRange()) {
-                        localFailures.add(method.getReference());
-                      }
-                    }
-                  }));
-      Set<String> expectedFailures = getExpectedFailures(api);
-      Set<String> apiFailuresString =
-          localFailures.stream().map(DexMethod::toString).collect(Collectors.toSet());
-      if (!expectedFailures.equals(apiFailuresString)) {
-        System.out.println("Failure for api " + api);
-        assertEquals(expectedFailures, apiFailuresString);
-      }
+          supportedClass -> {
+            supportedClass.forEachMethodAndAnnotation(
+                (method, annotation) -> {
+                  if (missingFromRange(api, annotation)) {
+                    localMethodFailures.add(method.getReference());
+                  }
+                });
+            supportedClass.forEachFieldAndAnnotation(
+                (field, annotation) -> {
+                  if (missingFromRange(api, annotation)) {
+                    localFieldFailures.add(field.getReference());
+                  }
+                });
+          });
+
+      assertStringEqualsAtApi(localFieldFailures, getExpectedFieldFailures(api), api);
+      assertStringEqualsAtApi(localMethodFailures, getExpectedMethodFailures(api), api);
     }
   }
 
-  private Set<String> getExpectedFailures(AndroidApiLevel api) {
+  private void assertStringEqualsAtApi(
+      Set<? extends DexMember<?, ?>> found, Set<String> expected, AndroidApiLevel api) {
+    Set<String> apiFailuresString =
+        found.stream().map(DexMember::toString).collect(Collectors.toSet());
+    assertEquals("Failure for api " + api, expected, apiFailuresString);
+  }
+
+  private boolean missingFromRange(AndroidApiLevel api, MemberAnnotation annotation) {
+    if (annotation != null && annotation.isUnsupportedInMinApiRange()) {
+      return api.getLevel() >= annotation.getMinRange()
+          && api.getLevel() <= annotation.getMaxRange();
+    }
+    return false;
+  }
+
+  private Set<String> getExpectedFieldFailures(AndroidApiLevel api) {
+    if (librarySpecification == JDK11 || librarySpecification == JDK11_PATH) {
+      if (api.isGreaterThanOrEqualTo(AndroidApiLevel.O) && api.isLessThan(AndroidApiLevel.R)) {
+        return FAILURES_JAPANESE_ERA;
+      }
+    }
+    return ImmutableSet.of();
+  }
+
+  private Set<String> getExpectedMethodFailures(AndroidApiLevel api) {
     Set<String> expectedFailures = new HashSet<>();
     boolean jdk11NonMinimal = librarySpecification != JDK8 && librarySpecification != JDK11_MINIMAL;
     if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
diff --git a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
index d1f909d..41aa6d0 100644
--- a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
+++ b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
@@ -6,12 +6,14 @@
 import static com.android.tools.r8.dex.Constants.CHECKSUM_OFFSET;
 import static com.android.tools.r8.dex.Constants.DATA_OFF_OFFSET;
 import static com.android.tools.r8.dex.Constants.DATA_SIZE_OFFSET;
+import static com.android.tools.r8.dex.Constants.DEX_MAGIC_SIZE;
 import static com.android.tools.r8.dex.Constants.FILE_SIZE_OFFSET;
 import static com.android.tools.r8.dex.Constants.MAP_OFF_OFFSET;
 import static com.android.tools.r8.dex.Constants.SIGNATURE_OFFSET;
 import static com.android.tools.r8.dex.Constants.STRING_IDS_OFF_OFFSET;
 import static com.android.tools.r8.dex.Constants.STRING_IDS_SIZE_OFFSET;
 import static com.android.tools.r8.dex.Constants.TYPE_STRING_ID_ITEM;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -26,7 +28,10 @@
 import com.android.tools.r8.maindexlist.MainDexListTests;
 import com.android.tools.r8.transformers.ClassTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexVersion;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
@@ -80,7 +85,7 @@
             .setMinApi(AndroidApiLevel.L)
             .compile()
             .writeToZip();
-    validateDex(outputA, 2);
+    validateDex(outputA, 2, AndroidApiLevel.L.getDexVersion());
 
     Path outputB =
         testForD8(Backend.DEX)
@@ -88,7 +93,7 @@
             .setMinApi(AndroidApiLevel.L)
             .compile()
             .writeToZip();
-    validateDex(outputB, 2);
+    validateDex(outputB, 2, AndroidApiLevel.L.getDexVersion());
 
     Path outputMerged =
         testForD8(Backend.DEX)
@@ -96,7 +101,7 @@
             .setMinApi(AndroidApiLevel.L)
             .compile()
             .writeToZip();
-    validateDex(outputMerged, 4);
+    validateDex(outputMerged, 4, AndroidApiLevel.L.getDexVersion());
   }
 
   @Test
@@ -135,49 +140,79 @@
     validateSingleContainerDex(outputB);
   }
 
-  private void validateDex(Path output, int expectedDexes) throws Exception {
+  private void validateDex(Path output, int expectedDexes, DexVersion expectedVersion)
+      throws Exception {
     List<byte[]> dexes = unzipContent(output);
     assertEquals(expectedDexes, dexes.size());
     for (byte[] dex : dexes) {
-      validate(dex);
+      validate(dex, expectedVersion);
     }
   }
 
   private void validateSingleContainerDex(Path output) throws Exception {
     List<byte[]> dexes = unzipContent(output);
     assertEquals(1, dexes.size());
-    validate(dexes.get(0));
+    validate(dexes.get(0), DexVersion.V41);
   }
 
-  private void validate(byte[] dex) throws Exception {
+  private void validate(byte[] dex, DexVersion expectedVersion) throws Exception {
     CompatByteBuffer buffer = CompatByteBuffer.wrap(dex);
     setByteOrder(buffer);
 
     IntList sections = new IntArrayList();
     int offset = 0;
     while (offset < buffer.capacity()) {
+      assertTrue(BitUtils.isAligned(4, offset));
       sections.add(offset);
       int dataSize = buffer.getInt(offset + DATA_SIZE_OFFSET);
       int dataOffset = buffer.getInt(offset + DATA_OFF_OFFSET);
+      int file_size = buffer.getInt(offset + FILE_SIZE_OFFSET);
       offset = dataOffset + dataSize;
+      assertEquals(file_size, offset - ListUtils.last(sections));
     }
     assertEquals(buffer.capacity(), offset);
 
-    int lastOffset = sections.getInt(sections.size() - 1);
-    int stringIdsSize = buffer.getInt(lastOffset + STRING_IDS_SIZE_OFFSET);
-    int stringIdsOffset = buffer.getInt(lastOffset + STRING_IDS_OFF_OFFSET);
-
     for (Integer sectionOffset : sections) {
-      assertEquals(stringIdsSize, buffer.getInt(sectionOffset + STRING_IDS_SIZE_OFFSET));
-      assertEquals(stringIdsOffset, buffer.getInt(sectionOffset + STRING_IDS_OFF_OFFSET));
-      assertEquals(stringIdsSize, getSizeFromMap(TYPE_STRING_ID_ITEM, buffer, sectionOffset));
-      assertEquals(stringIdsOffset, getOffsetFromMap(TYPE_STRING_ID_ITEM, buffer, sectionOffset));
+      validateHeader(sections, buffer, sectionOffset, expectedVersion);
       validateMap(buffer, sectionOffset);
       validateSignature(buffer, sectionOffset);
       validateChecksum(buffer, sectionOffset);
     }
   }
 
+  private byte[] magicBytes(DexVersion version) {
+    byte[] magic = new byte[DEX_MAGIC_SIZE];
+    System.arraycopy(
+        Constants.DEX_FILE_MAGIC_PREFIX, 0, magic, 0, Constants.DEX_FILE_MAGIC_PREFIX.length);
+    System.arraycopy(
+        version.getBytes(),
+        0,
+        magic,
+        Constants.DEX_FILE_MAGIC_PREFIX.length,
+        version.getBytes().length);
+    magic[Constants.DEX_FILE_MAGIC_PREFIX.length + version.getBytes().length] =
+        Constants.DEX_FILE_MAGIC_SUFFIX;
+    assertEquals(
+        DEX_MAGIC_SIZE, Constants.DEX_FILE_MAGIC_PREFIX.length + version.getBytes().length + 1);
+    return magic;
+  }
+
+  private void validateHeader(
+      IntList sections, CompatByteBuffer buffer, int offset, DexVersion expectedVersion) {
+    int lastOffset = sections.getInt(sections.size() - 1);
+    int stringIdsSize = buffer.getInt(lastOffset + STRING_IDS_SIZE_OFFSET);
+    int stringIdsOffset = buffer.getInt(lastOffset + STRING_IDS_OFF_OFFSET);
+
+    byte[] magic = new byte[DEX_MAGIC_SIZE];
+    buffer.get(magic);
+    assertArrayEquals(magicBytes(expectedVersion), magic);
+
+    assertEquals(stringIdsSize, buffer.getInt(offset + STRING_IDS_SIZE_OFFSET));
+    assertEquals(stringIdsOffset, buffer.getInt(offset + STRING_IDS_OFF_OFFSET));
+    assertEquals(stringIdsSize, getSizeFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
+    assertEquals(stringIdsOffset, getOffsetFromMap(TYPE_STRING_ID_ITEM, buffer, offset));
+  }
+
   private void validateMap(CompatByteBuffer buffer, int offset) {
     int mapOffset = buffer.getInt(offset + MAP_OFF_OFFSET);
     buffer.position(mapOffset);
@@ -199,9 +234,7 @@
     int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
     MessageDigest md = MessageDigest.getInstance("SHA-1");
     md.update(
-        buffer.asByteBuffer().array(),
-        offset + FILE_SIZE_OFFSET,
-        sectionSize - offset - FILE_SIZE_OFFSET);
+        buffer.asByteBuffer().array(), offset + FILE_SIZE_OFFSET, sectionSize - FILE_SIZE_OFFSET);
     byte[] expectedSignature = new byte[20];
     md.digest(expectedSignature, 0, 20);
     for (int i = 0; i < expectedSignature.length; i++) {
@@ -213,9 +246,7 @@
     int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
     Adler32 adler = new Adler32();
     adler.update(
-        buffer.asByteBuffer().array(),
-        offset + SIGNATURE_OFFSET,
-        sectionSize - offset - SIGNATURE_OFFSET);
+        buffer.asByteBuffer().array(), offset + SIGNATURE_OFFSET, sectionSize - SIGNATURE_OFFSET);
     assertEquals((int) adler.getValue(), buffer.getInt(offset + CHECKSUM_OFFSET));
   }
 
diff --git a/src/test/java/com/android/tools/r8/dex/whitespaceinidentifiers/WhiteSpaceInIdentifiersTest.java b/src/test/java/com/android/tools/r8/dex/whitespaceinidentifiers/WhiteSpaceInIdentifiersTest.java
new file mode 100644
index 0000000..1fad1ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/whitespaceinidentifiers/WhiteSpaceInIdentifiersTest.java
@@ -0,0 +1,357 @@
+// 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.dex.whitespaceinidentifiers;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+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.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.ProguardMapReader.ParseException;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexVersion;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.List;
+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)
+public class WhiteSpaceInIdentifiersTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withAllRuntimes().withAllApiLevels().build());
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          "0x20", "0xa0", "0x1680", "0x2000", "0x2001", "0x2002", "0x2003", "0x2004", "0x2005",
+          "0x2006", "0x2007", "0x2008", "0x2009", "0x200a", "0x202f", "0x205f", "0x3000");
+
+  private void assumeParametersWithSupportForWhitespaceInIdentifiers() {
+    assumeTrue(
+        parameters.isCfRuntime()
+            || (parameters.isDexRuntime()
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.R)));
+  }
+
+  private void assumeDexRuntimeSupportingDexVersion039() {
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getApiLevel().getDexVersion().isGreaterThanOrEqualTo(DexVersion.V39));
+  }
+
+  public void configure(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClassFileData(getTransformed())
+        .applyIf(parameters.isDexRuntime(), b -> b.setMinApi(parameters.getApiLevel()))
+        .applyIf(
+            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.R),
+            b -> {
+              try {
+                b.compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorsMatch(
+                            diagnosticMessage(
+                                containsString("are not allowed prior to DEX version 040"))));
+                fail("Unexpected success");
+              } catch (CompilationFailedException e) {
+                // Expected.
+              }
+            },
+            b ->
+                b.run(parameters.getRuntime(), TestClass.class)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    configure(testForD8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeParametersWithSupportForWhitespaceInIdentifiers();
+    Exception e =
+        assertThrows(
+            RuntimeException.class,
+            () -> configure(testForR8(parameters.getBackend()).addKeepMainRule(TestClass.class)));
+    // TODO(b/141287396): Proguard maps with spaces in identifiers are not supported. Running
+    //  on R8 always creates an inspector to find the name of the potentially renamed main class.
+    assertTrue(e.getCause() instanceof ExecutionException);
+    assertTrue(e.getCause().getCause() instanceof ParseException);
+  }
+
+  @Test
+  public void testR8Mapping() throws Exception {
+    assumeParametersWithSupportForWhitespaceInIdentifiers();
+    String map =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(getTransformed())
+            .applyIf(parameters.isDexRuntime(), b -> b.setMinApi(parameters.getApiLevel()))
+            .addKeepMainRule(TestClass.class)
+            .compile()
+            .getProguardMap();
+    // R8 renames methods with white space, so they appear in the mapping file.
+    assertTrue(StringUtils.splitLines(map).size() > 40);
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    parameters.assumeCfRuntime();
+    configure(
+        testForProguard(ProguardVersion.V7_0_0)
+            .addKeepMainRule(TestClass.class)
+            .addDontWarn(TestClass.class.getTypeName()));
+  }
+
+  @Test
+  public void testProguardMapping() throws Exception {
+    parameters.assumeCfRuntime();
+    String map =
+        testForProguard(ProguardVersion.V7_0_0)
+            .addProgramClassFileData(getTransformed())
+            .addKeepMainRule(TestClass.class)
+            .addDontWarn(TestClass.class.getTypeName())
+            .compile()
+            .getProguardMap();
+    // Proguard leaves the methods with white space alone, so they don't need to appear in the
+    // mapping file.
+    assertEquals(
+        StringUtils.lines(
+            TestClass.class.getTypeName() + " -> " + TestClass.class.getTypeName() + ":",
+            "    void <init>() -> <init>",
+            "    void main(java.lang.String[]) -> main"),
+        map);
+  }
+
+  @Test
+  public void testD8MergeWithSpaces() throws Exception {
+    assumeDexRuntimeSupportingDexVersion039();
+    // Compile with API level 30 to allow white space in identifiers. This generates DEX
+    // version 039.
+    Path dex =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(getTransformed())
+            .setMinApi(AndroidApiLevel.R)
+            .compile()
+            .writeToZip();
+
+    // Run merge step with DEX with white space in input (not forcing min API level of R).
+    testForD8(parameters.getBackend())
+        .addProgramFiles(dex)
+        .setMinApi(parameters.getApiLevel())
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.R),
+            b -> {
+              try {
+                // TODO(b/269089718): This should not be an AssertionError but a compilation error.
+                b.compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)));
+                fail("Unexpected success");
+              } catch (CompilationFailedException e) {
+                // Expected.
+              }
+            },
+            b ->
+                b.run(parameters.getRuntime(), TestClass.class)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testD8RunWithSpaces() throws Exception {
+    assumeDexRuntimeSupportingDexVersion039();
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformed())
+        .setMinApi(AndroidApiLevel.R)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.R),
+            b -> b.assertFailureWithErrorThatMatches(containsString("Failure to verify dex file")),
+            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testD8RunWithSpacesUsingDexV40() throws Exception {
+    assumeDexRuntimeSupportingDexVersion039();
+    testForD8(parameters.getBackend())
+        .addOptionsModification(options -> options.testing.dexVersion40FromApiLevel30 = true)
+        .addProgramClassFileData(getTransformed())
+        .setMinApi(AndroidApiLevel.R)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.R),
+            b ->
+                b.assertFailureWithErrorThatMatches(
+                    allOf(containsString("Unrecognized version"), containsString("0 4 0"))),
+            b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testJvmStackTrace() throws Exception {
+    parameters.assumeCfRuntime();
+    testForJvm()
+        .addProgramClassFileData(getTransformed())
+        .run(parameters.asCfRuntime(), TestClass.class, "some-argument")
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectOriginalStackTrace(
+            stackTrace ->
+                assertThat(
+                    stackTrace,
+                    StackTrace.isSameExceptForLineNumbers(
+                        StackTrace.builder()
+                            .add(
+                                StackTraceLine.builder()
+                                    .setMethodName(" ")
+                                    .setClassName(TestClass.class.getTypeName())
+                                    .setFileName("WhiteSpaceInIdentifiersTest.java")
+                                    .build())
+                            .add(
+                                StackTraceLine.builder()
+                                    .setMethodName("main")
+                                    .setClassName(TestClass.class.getTypeName())
+                                    .setFileName("WhiteSpaceInIdentifiersTest.java")
+                                    .build())
+                            .build())));
+  }
+
+  private String rename(String name) {
+    return new String(Character.toChars(Integer.parseInt(name.substring(1), 16)));
+  }
+
+  private byte[] getTransformed() throws Exception {
+    return transformer(TestClass.class)
+        .renameMethod(MethodPredicate.onName(name -> name.startsWith("t")), this::rename)
+        .transformMethodInsnInMethod(
+            "main",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.visitMethodInsn(
+                  opcode,
+                  owner,
+                  name.startsWith("t") ? rename(name) : name,
+                  descriptor,
+                  isInterface);
+            }))
+        .transform();
+  }
+
+  // Test with white space characters added in https://r8-review.git.corp.google.com/c/r8/+/42269.
+  // Supported on Art in https://android-review.git.corp.google.com/c/platform/art/+/1106719.
+  static class TestClass {
+
+    private static void t20(boolean throwException) {
+      System.out.println("0x20");
+      if (throwException) {
+        throw new RuntimeException();
+      }
+    }
+
+    private static void ta0() {
+      System.out.println("0xa0");
+    }
+
+    private static void t1680() {
+      System.out.println("0x1680");
+    }
+
+    private static void t2000() {
+      System.out.println("0x2000");
+    }
+
+    private static void t2001() {
+      System.out.println("0x2001");
+    }
+
+    private static void t2002() {
+      System.out.println("0x2002");
+    }
+
+    private static void t2003() {
+      System.out.println("0x2003");
+    }
+
+    private static void t2004() {
+      System.out.println("0x2004");
+    }
+
+    private static void t2005() {
+      System.out.println("0x2005");
+    }
+
+    private static void t2006() {
+      System.out.println("0x2006");
+    }
+
+    private static void t2007() {
+      System.out.println("0x2007");
+    }
+
+    private static void t2008() {
+      System.out.println("0x2008");
+    }
+
+    private static void t2009() {
+      System.out.println("0x2009");
+    }
+
+    private static void t200a() {
+      System.out.println("0x200a");
+    }
+
+    private static void t202f() {
+      System.out.println("0x202f");
+    }
+
+    private static void t205f() {
+      System.out.println("0x205f");
+    }
+
+    private static void t3000() {
+      System.out.println("0x3000");
+    }
+
+    public static void main(String[] args) {
+      t20(args.length > 0);
+      ta0();
+      t1680();
+      t2000();
+      t2001();
+      t2002();
+      t2003();
+      t2004();
+      t2005();
+      t2006();
+      t2007();
+      t2008();
+      t2009();
+      t200a();
+      t202f();
+      t205f();
+      t3000();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
index c2d3a03..eef3dcf 100644
--- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
+++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -48,8 +48,6 @@
   public void testR8() throws Exception {
     testForR8(Backend.DEX)
         .addProgramFiles(outDirectory.resolve("program.jar"))
-        .addOptionsModification(
-            options -> options.getArtProfileOptions().setEnableCompletenessCheckForTesting(true))
         .apply(this::configure)
         .compile();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index be71b20..037bb82 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.EnqueuerResult;
@@ -73,17 +74,21 @@
       throws ExecutionException {
     AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application.asDirect());
     appView.setAppServices(AppServices.builder(appView).build());
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.nop();
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
     SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
     appView.setRootSet(
         RootSet.builder(
                 appView,
+                artProfileCollectionAdditions,
                 subtypingInfo,
                 ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
             .build(executorService));
     Timing timing = Timing.empty();
     Enqueuer enqueuer =
-        EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
+        EnqueuerFactory.createForInitialTreeShaking(
+            appView, artProfileCollectionAdditions, executorService, subtypingInfo);
     EnqueuerResult enqueuerResult =
         enqueuer.traceApplication(appView.rootSet(), executorService, timing);
     appView.setAppInfo(enqueuerResult.getAppInfo());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
index b1899a3..8967763 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -5,21 +5,30 @@
 package com.android.tools.r8.ir.desugar.annotations;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
 import java.util.Collections;
+import java.util.List;
 import org.junit.Assert;
 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 CovariantReturnTypeAnnotationTransformerTest extends AsmTestBase {
+
   public static final String PACKAGE_NAME = "com/android/tools/r8/ir/desugar/annotations";
   public static final String CRT_BINARY_NAME = "dalvik/annotation/codegen/CovariantReturnType";
   public static final String CRTS_INNER_NAME = "CovariantReturnTypes";
@@ -28,10 +37,18 @@
   public static final String CRT_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.');
   public static final String CRTS_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.');
 
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+  }
+
   @Test
   public void testVersion1WithClient1And2() throws Exception {
-    AndroidApp input =
-        buildAndroidApp(
+    List<byte[]> input =
+        ImmutableList.of(
             ToolHelper.getClassAsBytes(Client.class),
             ToolHelper.getClassAsBytes(A.class),
             ToolHelper.getClassAsBytes(B.class),
@@ -46,8 +63,8 @@
 
   @Test
   public void testVersion1WithClient3() throws Exception {
-    AndroidApp input =
-        buildAndroidApp(
+    List<byte[]> input =
+        ImmutableList.of(
             com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
             ToolHelper.getClassAsBytes(A.class),
             ToolHelper.getClassAsBytes(B.class),
@@ -63,8 +80,8 @@
 
   @Test
   public void testVersion2WithClient1And2() throws Exception {
-    AndroidApp input =
-        buildAndroidApp(
+    List<byte[]> input =
+        ImmutableList.of(
             ToolHelper.getClassAsBytes(Client.class),
             ToolHelper.getClassAsBytes(A.class),
             com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
@@ -79,8 +96,8 @@
 
   @Test
   public void testVersion2WithClient3() throws Exception {
-    AndroidApp input =
-        buildAndroidApp(
+    List<byte[]> input =
+        ImmutableList.of(
             com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
             ToolHelper.getClassAsBytes(A.class),
             com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
@@ -100,8 +117,8 @@
 
   @Test
   public void testVersion3WithClient3() throws Exception {
-    AndroidApp input =
-        buildAndroidApp(
+    List<byte[]> input =
+        ImmutableList.of(
             com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
             ToolHelper.getClassAsBytes(A.class),
             com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
@@ -116,8 +133,8 @@
 
   @Test
   public void testVersion3WithClient1And2() throws Exception {
-    AndroidApp input =
-        buildAndroidApp(
+    List<byte[]> input =
+        ImmutableList.of(
             ToolHelper.getClassAsBytes(Client.class),
             ToolHelper.getClassAsBytes(A.class),
             com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
@@ -132,8 +149,8 @@
 
   @Test
   public void testRepeatedCompilation() throws Exception {
-    AndroidApp input =
-        buildAndroidApp(
+    List<byte[]> input =
+        ImmutableList.of(
             ToolHelper.getClassAsBytes(Client.class),
             ToolHelper.getClassAsBytes(A.class),
             com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
@@ -142,51 +159,73 @@
     // Version 2 contains annotations.
     checkPresenceOfCovariantAnnotations(input, true);
 
-    AndroidApp output =
-        compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = true);
-
-    // Compilation output does not contain annotations.
-    checkPresenceOfCovariantAnnotations(output, false);
+    Path output =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(input)
+            .addOptionsModification(options -> options.processCovariantReturnTypeAnnotations = true)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            // Compilation output does not contain annotations.
+            .inspect(inspector -> checkPresenceOfCovariantAnnotations(inspector, false))
+            .writeToZip();
 
     // Compilation will fail with a compilation error the second time if the implementation does
     // not remove the CovariantReturnType annotations properly during the first compilation.
-    compileWithD8(output, options -> options.processCovariantReturnTypeAnnotations = true);
+    testForD8(parameters.getBackend())
+        .addProgramFiles(output)
+        .addOptionsModification(options -> options.processCovariantReturnTypeAnnotations = true)
+        .setMinApi(parameters.getApiLevel())
+        .compile();
   }
 
   private void succeedsWithOption(
-      AndroidApp input, boolean option, boolean checkPresenceOfSyntheticMethods) throws Exception {
-    AndroidApp output =
-        compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
-    String stdout = runOnArt(output, Client.class.getCanonicalName());
-    assertEquals(getExpectedOutput(), stdout);
-    checkPresenceOfCovariantAnnotations(output, false);
-    if (option && checkPresenceOfSyntheticMethods) {
-      checkPresenceOfSyntheticMethods(output);
-    }
-  }
-
-  private void failsWithOption(AndroidApp input, boolean option) throws Exception {
-    AndroidApp output =
-        compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
-    checkPresenceOfCovariantAnnotations(output, false);
-    ToolHelper.ProcessResult result = runOnArtRaw(output, Client.class.getCanonicalName());
-    assertThat(result.stderr, containsString("java.lang.NoSuchMethodError"));
-  }
-
-  private void succeedsIndependentOfFlag(AndroidApp input, boolean checkPresenceOfSyntheticMethods)
+      List<byte[]> input, boolean option, boolean checkPresenceOfSyntheticMethods)
       throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(input)
+        .addOptionsModification(options -> options.processCovariantReturnTypeAnnotations = option)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              checkPresenceOfCovariantAnnotations(inspector, false);
+              if (option && checkPresenceOfSyntheticMethods) {
+                checkPresenceOfSyntheticMethods(inspector);
+              }
+            })
+        .run(parameters.getRuntime(), Client.class.getCanonicalName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private void failsWithOption(List<byte[]> input, boolean option) throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(input)
+        .addOptionsModification(options -> options.processCovariantReturnTypeAnnotations = option)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> checkPresenceOfCovariantAnnotations(inspector, false))
+        .run(parameters.getRuntime(), Client.class.getCanonicalName())
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  private void succeedsIndependentOfFlag(
+      List<byte[]> input, boolean checkPresenceOfSyntheticMethods) throws Exception {
     succeedsWithOption(input, true, checkPresenceOfSyntheticMethods);
     succeedsWithOption(input, false, checkPresenceOfSyntheticMethods);
   }
 
-  private void failsIndependentOfFlag(AndroidApp input) throws Exception {
+  private void failsIndependentOfFlag(List<byte[]> input) throws Exception {
     failsWithOption(input, true);
     failsWithOption(input, false);
   }
 
-  private void checkPresenceOfCovariantAnnotations(AndroidApp app, boolean expected)
+  private void checkPresenceOfCovariantAnnotations(List<byte[]> input, boolean expected)
       throws Exception {
-    CodeInspector inspector = new CodeInspector(app);
+    CodeInspector inspector = new CodeInspector(buildAndroidApp(input));
+    checkPresenceOfCovariantAnnotations(inspector, expected);
+  }
+
+  private void checkPresenceOfCovariantAnnotations(CodeInspector inspector, boolean expected) {
     assertEquals(
         expected,
         inspector.allClasses().stream()
@@ -196,9 +235,7 @@
                         .anyMatch(method -> method.annotation(CRTS_TYPE_NAME).isPresent())));
   }
 
-  private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception {
-    CodeInspector inspector = new CodeInspector(output);
-
+  private void checkPresenceOfSyntheticMethods(CodeInspector inspector) throws Exception {
     // Get classes A, B, and C.
     ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
     assertThat(clazzA, isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/AccessFlagConfig.java b/src/test/java/com/android/tools/r8/keepanno/AccessFlagConfig.java
new file mode 100644
index 0000000..5ce7bb5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/AccessFlagConfig.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.MemberAccess;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class AccessFlagConfig {
+
+  public static List<AccessFlagConfig> MEMBER_CONFIGS =
+      ImmutableList.of(
+          // Member patterns.
+          new AccessFlagConfig(MemberAccess.PUBLIC, Opcodes.ACC_PUBLIC),
+          new AccessFlagConfig(MemberAccess.PROTECTED, Opcodes.ACC_PROTECTED),
+          new AccessFlagConfig(MemberAccess.PRIVATE, Opcodes.ACC_PRIVATE),
+          new AccessFlagConfig(MemberAccess.PACKAGE_PRIVATE, 0x0, Opcodes.ACC_PUBLIC),
+          new AccessFlagConfig(MemberAccess.STATIC, Opcodes.ACC_STATIC),
+          new AccessFlagConfig(MemberAccess.FINAL, Opcodes.ACC_FINAL),
+          new AccessFlagConfig(MemberAccess.SYNTHETIC, Opcodes.ACC_SYNTHETIC),
+          new AccessFlagConfig(MemberAccess.SYNTHETIC, Opcodes.ACC_SYNTHETIC));
+
+  final String enumValue;
+  final int positive;
+  final int negative;
+
+  public AccessFlagConfig(String enumValue, int access) {
+    this.enumValue = enumValue;
+    positive = access;
+    negative = 0x0;
+  }
+
+  public AccessFlagConfig(String enumValue, int positive, int negative) {
+    this.enumValue = enumValue;
+    this.positive = positive;
+    this.negative = negative;
+  }
+
+  @Override
+  public String toString() {
+    return enumValue;
+  }
+
+  public AccessFlagConfig invert() {
+    assertFalse(enumValue.startsWith(MemberAccess.NEGATION_PREFIX));
+    return new AccessFlagConfig(MemberAccess.NEGATION_PREFIX + enumValue, negative, positive);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnFieldsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnFieldsTest.java
new file mode 100644
index 0000000..b48b7c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnFieldsTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+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.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.FieldAccess;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+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.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class KeepAccessFlagsOnFieldsTest extends TestBase {
+
+  private static final List<AccessFlagConfig> CONFIGS =
+      ImmutableList.<AccessFlagConfig>builder()
+          .addAll(AccessFlagConfig.MEMBER_CONFIGS)
+          .add(new AccessFlagConfig(FieldAccess.VOLATILE, Opcodes.ACC_VOLATILE))
+          .add(new AccessFlagConfig(FieldAccess.TRANSIENT, Opcodes.ACC_TRANSIENT))
+          .build();
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public AccessFlagConfig config;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    List<AccessFlagConfig> configs = new ArrayList<>(CONFIGS.size() * 2);
+    CONFIGS.forEach(
+        c -> {
+          configs.add(c);
+          configs.add(c.invert());
+        });
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build(),
+        configs);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClassFileData(getTargetClass())
+        .addProgramClassFileData(getMainClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .inspect(this::checkOutput);
+  }
+
+  public byte[] getTargetClass() throws Exception {
+    return transformer(A.class)
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public FieldVisitor visitField(
+                  int access, String name, String descriptor, String signature, Object value) {
+                if (name.equals("x")) {
+                  access = config.positive;
+                }
+                if (name.equals("y")) {
+                  access = config.negative;
+                }
+                return super.visitField(access, name, descriptor, signature, value);
+              }
+            })
+        .transform();
+  }
+
+  public byte[] getMainClass() throws Exception {
+    return transformer(TestClass.class)
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                assertEquals(AnnotationConstants.UsesReflection.DESCRIPTOR, descriptor);
+                return new AnnotationVisitor(
+                    ASM_VERSION, super.visitAnnotation(descriptor, visible)) {
+                  @Override
+                  public AnnotationVisitor visitArray(String name) {
+                    assertEquals(AnnotationConstants.UsesReflection.value, name);
+                    return new AnnotationVisitor(ASM_VERSION, super.visitArray(name)) {
+                      @Override
+                      public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+                        assertEquals(AnnotationConstants.Target.DESCRIPTOR, descriptor);
+                        return new AnnotationVisitor(
+                            ASM_VERSION, super.visitAnnotation(name, descriptor)) {
+                          @Override
+                          public AnnotationVisitor visitArray(String name) {
+                            assertEquals(AnnotationConstants.Item.fieldAccess, name);
+                            AnnotationVisitor visitor = super.visitArray(name);
+                            visitor.visitEnum(null, FieldAccess.DESCRIPTOR, config.enumValue);
+                            visitor.visitEnd();
+                            return null;
+                          }
+                        };
+                      }
+                    };
+                  }
+                };
+              }
+            })
+        .transform();
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("x"), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("y"), isAbsent());
+  }
+
+  static class A {
+    int x;
+    int y;
+  }
+
+  static class TestClass {
+
+    @UsesReflection({
+      @KeepTarget(
+          classConstant = A.class,
+          fieldAccess = {FieldAccessFlags.PUBLIC})
+    })
+    public static void main(String[] args) throws Exception {
+      Object o = System.nanoTime() > 0 ? new A() : null;
+      for (Field f : o.getClass().getDeclaredFields()) {
+        System.out.println(f.getName());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnMembersTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnMembersTest.java
new file mode 100644
index 0000000..d6eb3f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnMembersTest.java
@@ -0,0 +1,180 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+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.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+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.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class KeepAccessFlagsOnMembersTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public AccessFlagConfig config;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    List<AccessFlagConfig> configs = new ArrayList<>(AccessFlagConfig.MEMBER_CONFIGS.size() * 2);
+    AccessFlagConfig.MEMBER_CONFIGS.forEach(
+        c -> {
+          configs.add(c);
+          configs.add(c.invert());
+        });
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build(),
+        configs);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClassFileData(getTargetClass())
+        .addProgramClassFileData(getMainClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .inspect(this::checkOutput);
+  }
+
+  public byte[] getTargetClass() throws Exception {
+    return transformer(A.class)
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public FieldVisitor visitField(
+                  int access, String name, String descriptor, String signature, Object value) {
+                if (name.equals("x")) {
+                  access = config.positive;
+                }
+                if (name.equals("y")) {
+                  access = config.negative;
+                }
+                return super.visitField(access, name, descriptor, signature, value);
+              }
+
+              @Override
+              public MethodVisitor visitMethod(
+                  int access,
+                  String name,
+                  String descriptor,
+                  String signature,
+                  String[] exceptions) {
+                if (name.equals("foo")) {
+                  access = config.positive;
+                }
+                if (name.equals("bar")) {
+                  access = config.negative;
+                }
+                return super.visitMethod(access, name, descriptor, signature, exceptions);
+              }
+            })
+        .transform();
+  }
+
+  public byte[] getMainClass() throws Exception {
+    return transformer(TestClass.class)
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                assertEquals(AnnotationConstants.UsesReflection.DESCRIPTOR, descriptor);
+                return new AnnotationVisitor(
+                    ASM_VERSION, super.visitAnnotation(descriptor, visible)) {
+                  @Override
+                  public AnnotationVisitor visitArray(String name) {
+                    assertEquals(AnnotationConstants.UsesReflection.value, name);
+                    return new AnnotationVisitor(ASM_VERSION, super.visitArray(name)) {
+                      @Override
+                      public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+                        assertEquals(AnnotationConstants.Target.DESCRIPTOR, descriptor);
+                        return new AnnotationVisitor(
+                            ASM_VERSION, super.visitAnnotation(name, descriptor)) {
+                          @Override
+                          public AnnotationVisitor visitArray(String name) {
+                            assertEquals(AnnotationConstants.Item.memberAccess, name);
+                            AnnotationVisitor visitor = super.visitArray(name);
+                            visitor.visitEnum(
+                                null,
+                                AnnotationConstants.MemberAccess.DESCRIPTOR,
+                                config.enumValue);
+                            visitor.visitEnd();
+                            return null;
+                          }
+                        };
+                      }
+                    };
+                  }
+                };
+              }
+            })
+        .transform();
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("x"), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("y"), isAbsent());
+    assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("foo"), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("bar"), isAbsent());
+  }
+
+  static class A {
+    int x;
+
+    int y;
+
+    int foo() {
+      return 1;
+    }
+
+    int bar() {
+      return 2;
+    }
+  }
+
+  static class TestClass {
+
+    @UsesReflection({
+      @KeepTarget(
+          classConstant = A.class,
+          memberAccess = {MemberAccessFlags.PUBLIC})
+    })
+    public static void main(String[] args) throws Exception {
+      Object o = System.nanoTime() > 0 ? new A() : null;
+      for (Field f : o.getClass().getDeclaredFields()) {
+        System.out.println(f.getName());
+      }
+      for (Method m : o.getClass().getDeclaredMethods()) {
+        System.out.println(m.getName());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnMethodsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnMethodsTest.java
new file mode 100644
index 0000000..de33e6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAccessFlagsOnMethodsTest.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+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.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.MethodAccessFlags;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+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.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class KeepAccessFlagsOnMethodsTest extends TestBase {
+
+  private static final List<AccessFlagConfig> CONFIGS =
+      ImmutableList.<AccessFlagConfig>builder()
+          .addAll(AccessFlagConfig.MEMBER_CONFIGS)
+          .add(new AccessFlagConfig(MethodAccess.NATIVE, Opcodes.ACC_NATIVE))
+          .add(new AccessFlagConfig(MethodAccess.ABSTRACT, Opcodes.ACC_ABSTRACT))
+          .add(new AccessFlagConfig(MethodAccess.STRICT_FP, Opcodes.ACC_STRICT))
+          .build();
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public AccessFlagConfig config;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    List<AccessFlagConfig> configs = new ArrayList<>(CONFIGS.size() * 2);
+    CONFIGS.forEach(
+        c -> {
+          configs.add(c);
+          configs.add(c.invert());
+        });
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build(),
+        configs);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClassFileData(getTargetClass())
+        .addProgramClassFileData(getMainClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .inspect(this::checkOutput);
+  }
+
+  public byte[] getTargetClass() throws Exception {
+    return transformer(A.class)
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public MethodVisitor visitMethod(
+                  int access,
+                  String name,
+                  String descriptor,
+                  String signature,
+                  String[] exceptions) {
+                if (name.equals("x")) {
+                  access = config.positive;
+                }
+                if (name.equals("y")) {
+                  access = config.negative;
+                }
+                return super.visitMethod(access, name, descriptor, signature, exceptions);
+              }
+            })
+        .transform();
+  }
+
+  public byte[] getMainClass() throws Exception {
+    return transformer(TestClass.class)
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                assertEquals(AnnotationConstants.UsesReflection.DESCRIPTOR, descriptor);
+                return new AnnotationVisitor(
+                    ASM_VERSION, super.visitAnnotation(descriptor, visible)) {
+                  @Override
+                  public AnnotationVisitor visitArray(String name) {
+                    assertEquals(AnnotationConstants.UsesReflection.value, name);
+                    return new AnnotationVisitor(ASM_VERSION, super.visitArray(name)) {
+                      @Override
+                      public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+                        assertEquals(AnnotationConstants.Target.DESCRIPTOR, descriptor);
+                        return new AnnotationVisitor(
+                            ASM_VERSION, super.visitAnnotation(name, descriptor)) {
+                          @Override
+                          public AnnotationVisitor visitArray(String name) {
+                            assertEquals(AnnotationConstants.Item.methodAccess, name);
+                            AnnotationVisitor visitor = super.visitArray(name);
+                            visitor.visitEnum(
+                                null,
+                                AnnotationConstants.MethodAccess.DESCRIPTOR,
+                                config.enumValue);
+                            visitor.visitEnd();
+                            return null;
+                          }
+                        };
+                      }
+                    };
+                  }
+                };
+              }
+            })
+        .transform();
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("x"), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("y"), isAbsent());
+  }
+
+  static class A {
+    int x() {
+      return 1;
+    }
+
+    int y() {
+      return 2;
+    }
+  }
+
+  static class TestClass {
+
+    @UsesReflection({
+      @KeepTarget(
+          classConstant = A.class,
+          methodAccess = {MethodAccessFlags.PUBLIC})
+    })
+    public static void main(String[] args) throws Exception {
+      Object o = System.nanoTime() > 0 ? new A() : null;
+      for (Method m : o.getClass().getDeclaredMethods()) {
+        System.out.println(m.getName());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
new file mode 100644
index 0000000..c2d5570
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAccessVisibilityFlagsTest.java
@@ -0,0 +1,261 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+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.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepAccessVisibilityFlagsTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          // Field targets.
+          "packagePrivateField",
+          "protectedField",
+          "publicField",
+          // Method targets.
+          "privateMethod",
+          "protectedMethod",
+          "publicMethod",
+          // Member field targets.
+          "packagePrivateField",
+          "privateField",
+          // Member method targets.
+          "packagePrivateMethod",
+          "privateMethod");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public KeepAccessVisibilityFlagsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(
+        TestClass.class,
+        A.class,
+        FieldRuleTarget.class,
+        MethodRuleTarget.class,
+        MemberRuleTarget.class);
+  }
+
+  private static List<String> allMembers =
+      ImmutableList.of(
+          "publicField",
+          "protectedField",
+          "packagePrivateField",
+          "privateField",
+          "publicMethod",
+          "protectedMethod",
+          "packagePrivateMethod",
+          "privateMethod");
+
+  private void checkOutput(CodeInspector inspector) {
+    assertPresent(
+        inspector, FieldRuleTarget.class, "publicField", "protectedField", "packagePrivateField");
+    assertPresent(
+        inspector, MethodRuleTarget.class, "publicMethod", "protectedMethod", "privateMethod");
+    assertPresent(
+        inspector,
+        MemberRuleTarget.class,
+        "packagePrivateField",
+        "privateField",
+        "packagePrivateMethod",
+        "privateMethod");
+  }
+
+  private void assertPresent(CodeInspector inspector, Class<?> clazz, String... members) {
+    ClassSubject subject = inspector.clazz(clazz);
+    assertThat(subject, isPresent());
+    Set<String> expectedPresent = ImmutableSet.copyOf(members);
+    for (String member : allMembers) {
+      if (member.endsWith("Field")) {
+        assertThat(
+            subject.uniqueFieldWithOriginalName(member),
+            expectedPresent.contains(member) ? isPresent() : isAbsent());
+      } else {
+        assertThat(
+            subject.uniqueMethodWithOriginalName(member),
+            expectedPresent.contains(member) ? isPresent() : isAbsent());
+      }
+    }
+  }
+
+  abstract static class FieldRuleTarget {
+    public String publicField = "public";
+    protected String protectedField = "protected";
+    private String privateField = "private";
+    String packagePrivateField = "package-private";
+
+    public void publicMethod() {}
+
+    protected void protectedMethod() {}
+
+    private void privateMethod() {}
+
+    void packagePrivateMethod() {}
+  }
+
+  abstract static class MethodRuleTarget {
+    public String publicField = "public";
+    protected String protectedField = "protected";
+    private String privateField = "private";
+    String packagePrivateField = "package-private";
+
+    public void publicMethod() {}
+
+    protected void protectedMethod() {}
+
+    private void privateMethod() {}
+
+    void packagePrivateMethod() {}
+  }
+
+  abstract static class MemberRuleTarget {
+    public String publicField = "public";
+    protected String protectedField = "protected";
+    private String privateField = "private";
+    String packagePrivateField = "package-private";
+
+    public void publicMethod() {}
+
+    protected void protectedMethod() {}
+
+    private void privateMethod() {}
+
+    void packagePrivateMethod() {}
+  }
+
+  static class A {
+
+    @UsesReflection({
+      @KeepTarget(
+          kind = KeepItemKind.CLASS_AND_MEMBERS,
+          classConstant = FieldRuleTarget.class,
+          fieldAccess = {FieldAccessFlags.NON_PRIVATE}),
+      @KeepTarget(
+          kind = KeepItemKind.CLASS_AND_MEMBERS,
+          classConstant = MethodRuleTarget.class,
+          methodAccess = {MethodAccessFlags.NON_PACKAGE_PRIVATE}),
+      @KeepTarget(
+          kind = KeepItemKind.CLASS_AND_MEMBERS,
+          classConstant = MemberRuleTarget.class,
+          memberAccess = {MemberAccessFlags.PACKAGE_PRIVATE, MemberAccessFlags.PRIVATE}),
+    })
+    void foo() throws Exception {
+      // Print all non-private fields.
+      {
+        List<String> nonPrivateFields = new ArrayList<>();
+        for (Field field : FieldRuleTarget.class.getDeclaredFields()) {
+          int mod = field.getModifiers();
+          if (!Modifier.isPrivate(mod)) {
+            nonPrivateFields.add(field.getName());
+          }
+        }
+        printSorted(nonPrivateFields);
+      }
+      // Print all non-package-private methods.
+      {
+        List<String> nonPackagePrivateMethods = new ArrayList<>();
+        for (Method method : MethodRuleTarget.class.getDeclaredMethods()) {
+          int mod = method.getModifiers();
+          if (Modifier.isPublic(mod) || Modifier.isProtected(mod) || Modifier.isPrivate(mod)) {
+            nonPackagePrivateMethods.add(method.getName());
+          }
+        }
+        printSorted(nonPackagePrivateMethods);
+      }
+      // Print all private and package-private members.
+      {
+        List<String> privateOrPackagePrivateFields = new ArrayList<>();
+        for (Field field : MemberRuleTarget.class.getDeclaredFields()) {
+          int mod = field.getModifiers();
+          if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
+            privateOrPackagePrivateFields.add(field.getName());
+          }
+        }
+        printSorted(privateOrPackagePrivateFields);
+      }
+      {
+        List<String> privateOrPackagePrivateMethods = new ArrayList<>();
+        for (Method method : MemberRuleTarget.class.getDeclaredMethods()) {
+          int mod = method.getModifiers();
+          if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
+            privateOrPackagePrivateMethods.add(method.getName());
+          }
+        }
+        printSorted(privateOrPackagePrivateMethods);
+      }
+    }
+
+    // The order of methods and fields is different on stock JDKs depending on linux or windows
+    // hosts. It is also different once compiled to DEX where the pools are split. Sort the
+    // names lexicographically to avoid differences in output.
+    private static void printSorted(List<String> strings) {
+      strings.sort(String::compareTo);
+      for (String string : strings) {
+        System.out.println(string);
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepClassApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepClassApiTest.java
new file mode 100644
index 0000000..7c1b0f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepClassApiTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepClassApiTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar", "B::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public KeepClassApiTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getLibraryClasses())
+        .addProgramClasses(getClientClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    Path lib =
+        testForR8(parameters.getBackend())
+            .enableExperimentalKeepAnnotations()
+            .addProgramClasses(getLibraryClasses())
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::checkLibraryOutput)
+            .writeToZip();
+
+    testForD8(parameters.getBackend())
+        .addProgramClasses(getClientClasses())
+        .addProgramFiles(lib)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public List<Class<?>> getLibraryClasses() {
+    return ImmutableList.of(A.class, B.class);
+  }
+
+  public List<Class<?>> getClientClasses() {
+    return ImmutableList.of(TestClass.class);
+  }
+
+  private void checkLibraryOutput(CodeInspector inspector) {
+    ClassSubject aClass = inspector.clazz(A.class);
+    assertThat(aClass, isPresent());
+    assertThat(aClass.uniqueMethodWithOriginalName("foo"), isPresent());
+    assertThat(aClass.uniqueMethodWithOriginalName("bar"), isPresent());
+    assertThat(aClass.uniqueMethodWithOriginalName("baz"), isAbsent());
+    ClassSubject bClass = inspector.clazz(B.class);
+    assertThat(bClass, isPresent());
+    assertThat(bClass.uniqueMethodWithOriginalName("foo"), isPresent());
+    assertThat(bClass.uniqueMethodWithOriginalName("bar"), isAbsent());
+    assertThat(bClass.uniqueMethodWithOriginalName("baz"), isAbsent());
+  }
+
+  @KeepForApi /* if members are unspecified then default is any public or protected member. */
+  public static class A {
+
+    public void foo() {
+      System.out.println("A::foo");
+    }
+
+    protected void bar() {
+      System.out.println("A::bar");
+    }
+
+    void baz() {
+      System.out.println("A::baz");
+    }
+  }
+
+  @KeepForApi(memberAccess = {MemberAccessFlags.PUBLIC})
+  public static class B {
+
+    public void foo() {
+      System.out.println("B::foo");
+    }
+
+    protected void bar() {
+      System.out.println("B::bar");
+    }
+
+    void baz() {
+      System.out.println("B::baz");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().bar();
+      new B().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
new file mode 100644
index 0000000..00d7d28
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepFieldValueApiTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepFieldValueApiTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("B::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public KeepFieldValueApiTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getLibraryClasses())
+        .addProgramClasses(getClientClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    Path lib =
+        testForR8(parameters.getBackend())
+            .enableExperimentalKeepAnnotations()
+            .addProgramClasses(getLibraryClasses())
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::checkLibraryOutput)
+            .writeToZip();
+
+    testForD8(parameters.getBackend())
+        .addProgramClasses(getClientClasses())
+        .addProgramFiles(lib)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public List<Class<?>> getLibraryClasses() {
+    return ImmutableList.of(A.class, B.class);
+  }
+
+  public List<Class<?>> getClientClasses() {
+    return ImmutableList.of(TestClass.class);
+  }
+
+  private void checkLibraryOutput(CodeInspector inspector) {
+    ClassSubject aClass = inspector.clazz(A.class);
+    assertThat(aClass, isPresent());
+    assertThat(aClass.uniqueFieldWithFinalName("CLASS"), isPresent());
+    ClassSubject bClass = inspector.clazz(B.class);
+    assertThat(bClass, isPresent());
+    assertThat(bClass.uniqueMethodWithOriginalName("foo"), isPresent());
+    assertThat(bClass.uniqueMethodWithOriginalName("bar"), isPresent());
+    assertThat(bClass.uniqueMethodWithOriginalName("baz"), isPresent());
+  }
+
+  public static class A {
+
+    @KeepForApi(
+        additionalTargets = {
+          @KeepTarget(classConstant = B.class, kind = KeepItemKind.CLASS_AND_MEMBERS)
+        })
+    public static final String CLASS = "com.android.tools.r8.keepanno.KeepFieldValueApiTest$B";
+  }
+
+  public static class B {
+
+    public void foo() {
+      System.out.println("B::foo");
+    }
+
+    protected void bar() {
+      System.out.println("B::bar");
+    }
+
+    void baz() {
+      System.out.println("B::baz");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      B b = (B) Class.forName(A.CLASS).getConstructor().newInstance();
+      b.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
new file mode 100644
index 0000000..6cb2ddd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+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.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
+import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepInvalidForApiTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public KeepInvalidForApiTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  private static List<String> extractRuleForClass(Class<?> clazz) throws IOException {
+    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
+    List<String> rules = new ArrayList<>();
+    KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
+    keepEdges.forEach(extractor::extract);
+    return rules;
+  }
+
+  private void assertThrowsWith(ThrowingRunnable fn, Matcher<String> matcher) {
+    try {
+      fn.run();
+    } catch (KeepEdgeException e) {
+      assertThat(e.getMessage(), matcher);
+      return;
+    } catch (Throwable e) {
+      fail("Expected run to fail with KeepEdgeException, but failed with: " + e);
+    }
+    fail("Expected run to fail");
+  }
+
+  @Test
+  public void testInvalidMemberAccess() throws Exception {
+    assertThrowsWith(
+        () -> extractRuleForClass(RefineMemberAccess.class),
+        allOf(
+            containsString("Unexpected array"),
+            containsString("@KeepForApi"),
+            containsString("memberAccess")));
+  }
+
+  static class RefineMemberAccess {
+
+    @KeepForApi(memberAccess = {MemberAccessFlags.PUBLIC})
+    public static void main(String[] args) throws Exception {
+      System.out.println("Hello, world");
+    }
+  }
+
+  @Test
+  public void testInvalidMethodName() throws Exception {
+    assertThrowsWith(
+        () -> extractRuleForClass(RefineMethodName.class),
+        allOf(
+            containsString("Unexpected value"),
+            containsString("@KeepForApi"),
+            containsString("methodName")));
+  }
+
+  static class RefineMethodName {
+
+    @KeepForApi(methodName = "foo")
+    public static void main(String[] args) throws Exception {
+      System.out.println("Hello, world");
+    }
+  }
+
+  @Test
+  public void testInvalidFieldName() throws Exception {
+    assertThrowsWith(
+        () -> extractRuleForClass(RefineFieldName.class),
+        allOf(
+            containsString("Unexpected value"),
+            containsString("@KeepForApi"),
+            containsString("fieldName")));
+  }
+
+  static class RefineFieldName {
+
+    @KeepForApi(fieldName = "foo")
+    public static void main(String[] args) throws Exception {
+      System.out.println("Hello, world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMembersAccessFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMembersAccessFlagsTest.java
new file mode 100644
index 0000000..c071aab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMembersAccessFlagsTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepMembersAccessFlagsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world", "bar");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public KeepMembersAccessFlagsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class);
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(A.class);
+    assertThat(clazz, isPresent());
+    assertThat(clazz.uniqueFieldWithOriginalName("staticField"), isAbsent());
+    assertThat(clazz.uniqueFieldWithOriginalName("fieldA"), isPresent());
+    assertThat(clazz.uniqueFieldWithOriginalName("fieldB"), isAbsent());
+    assertThat(clazz.uniqueMethodWithOriginalName("bar"), isPresent());
+    assertThat(clazz.uniqueMethodWithOriginalName("baz"), isAbsent());
+    assertThat(clazz.uniqueMethodWithOriginalName("foobar"), isAbsent());
+  }
+
+  static class A {
+
+    public static String staticField = "the static";
+
+    public String fieldA = "Hello, world";
+
+    private Integer fieldB = 42;
+
+    @UsesReflection({
+      @KeepTarget(
+          classConstant = A.class,
+          memberAccess = {MemberAccessFlags.PUBLIC, MemberAccessFlags.NON_STATIC})
+    })
+    void foo() throws Exception {
+      for (Field field : getClass().getDeclaredFields()) {
+        int modifiers = field.getModifiers();
+        if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
+          System.out.println(field.get(this));
+        }
+      }
+      for (Method method : getClass().getDeclaredMethods()) {
+        int modifiers = method.getModifiers();
+        if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
+          System.out.println(method.getName());
+        }
+      }
+    }
+
+    public void bar() {}
+
+    private void baz() {}
+
+    public static void foobar() {}
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
new file mode 100644
index 0000000..b8169ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMembersApiTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepMembersApiTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar", "B::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public KeepMembersApiTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getLibraryClasses())
+        .addProgramClasses(getClientClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    Path lib =
+        testForR8(parameters.getBackend())
+            .enableExperimentalKeepAnnotations()
+            .addProgramClasses(getLibraryClasses())
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::checkLibraryOutput)
+            .writeToZip();
+
+    testForD8(parameters.getBackend())
+        .addProgramClasses(getClientClasses())
+        .addProgramFiles(lib)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public List<Class<?>> getLibraryClasses() {
+    return ImmutableList.of(A.class, B.class);
+  }
+
+  public List<Class<?>> getClientClasses() {
+    return ImmutableList.of(TestClass.class);
+  }
+
+  private void checkLibraryOutput(CodeInspector inspector) {
+    ClassSubject aClass = inspector.clazz(A.class);
+    assertThat(aClass, isPresent());
+    assertThat(aClass.uniqueMethodWithOriginalName("foo"), isAbsent());
+    assertThat(aClass.uniqueMethodWithOriginalName("bar"), isPresent());
+    assertThat(aClass.uniqueMethodWithOriginalName("baz"), isAbsent());
+    ClassSubject bClass = inspector.clazz(B.class);
+    assertThat(bClass, isPresent());
+    assertThat(bClass.uniqueMethodWithOriginalName("foo"), isPresent());
+    assertThat(bClass.uniqueMethodWithOriginalName("bar"), isAbsent());
+    assertThat(bClass.uniqueMethodWithOriginalName("baz"), isAbsent());
+  }
+
+  @KeepForApi(kind = KeepItemKind.ONLY_CLASS)
+  public static class A {
+
+    @KeepForApi
+    public A() {}
+
+    public void foo() {
+      System.out.println("A::foo");
+    }
+
+    @KeepForApi
+    protected void bar() {
+      System.out.println("A::bar");
+    }
+
+    void baz() {
+      System.out.println("A::baz");
+    }
+  }
+
+  public static class B {
+
+    @KeepForApi
+    public B() {}
+
+    @KeepForApi
+    public void foo() {
+      System.out.println("B::foo");
+    }
+
+    protected void bar() {
+      System.out.println("B::bar");
+    }
+
+    void baz() {
+      System.out.println("B::baz");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().bar();
+      new B().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMethodsAccessFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsAccessFlagsTest.java
new file mode 100644
index 0000000..929eb99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsAccessFlagsTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.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.MethodAccessFlags;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepMethodsAccessFlagsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("hello", "world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public KeepMethodsAccessFlagsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class, Abs.class);
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Abs.class);
+    assertThat(clazz, isPresent());
+    assertThat(clazz.uniqueMethodWithOriginalName("hello"), isPresent());
+    assertThat(clazz.uniqueMethodWithOriginalName("my"), isAbsent());
+    assertThat(clazz.uniqueMethodWithOriginalName("old"), isAbsent());
+    assertThat(clazz.uniqueMethodWithOriginalName("world"), isPresent());
+  }
+
+  abstract static class Abs {
+    public abstract void hello();
+
+    public void my() {}
+
+    abstract void old();
+
+    public abstract void world();
+  }
+
+  static class A {
+
+    @UsesReflection({
+      @KeepTarget(
+          kind = KeepItemKind.CLASS_AND_MEMBERS,
+          classConstant = Abs.class,
+          methodAccess = {MethodAccessFlags.PUBLIC, MethodAccessFlags.ABSTRACT})
+    })
+    void foo() throws Exception {
+      List<String> sorted = new ArrayList<>();
+      for (Method method : Abs.class.getDeclaredMethods()) {
+        int modifiers = method.getModifiers();
+        if (Modifier.isPublic(modifiers) && Modifier.isAbstract(modifiers)) {
+          sorted.add(method.getName());
+        }
+      }
+      sorted.sort(String::compareTo);
+      for (String string : sorted) {
+        System.out.println(string);
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMethodsEmptyAccessFlagsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsEmptyAccessFlagsTest.java
new file mode 100644
index 0000000..ac720ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMethodsEmptyAccessFlagsTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepMethodsEmptyAccessFlagsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("hello", "world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public KeepMethodsEmptyAccessFlagsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithRuleExtraction() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class, Abs.class);
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Abs.class);
+    assertThat(clazz, isPresent());
+    assertThat(clazz.uniqueMethodWithOriginalName("hello"), isPresent());
+    assertThat(clazz.uniqueMethodWithOriginalName("my"), isPresent());
+    assertThat(clazz.uniqueMethodWithOriginalName("old"), isPresent());
+    assertThat(clazz.uniqueMethodWithOriginalName("world"), isPresent());
+  }
+
+  abstract static class Abs {
+    public abstract void hello();
+
+    public void my() {}
+
+    abstract void old();
+
+    public abstract void world();
+  }
+
+  static class A {
+
+    @UsesReflection({
+      @KeepTarget(
+          kind = KeepItemKind.CLASS_AND_MEMBERS,
+          classConstant = Abs.class,
+          methodAccess = {
+            /* the explicit empty set matches all access */
+          })
+    })
+    void foo() throws Exception {
+      List<String> sorted = new ArrayList<>();
+      for (Method method : Abs.class.getDeclaredMethods()) {
+        int modifiers = method.getModifiers();
+        if (Modifier.isPublic(modifiers) && Modifier.isAbstract(modifiers)) {
+          sorted.add(method.getName());
+        }
+      }
+      sorted.sort(String::compareTo);
+      for (String string : sorted) {
+        System.out.println(string);
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
index b7c8c16..2d90a08 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
@@ -68,13 +68,17 @@
   public void testKotlincFailsRenamed() throws Exception {
     R8TestCompileResult r8libResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
+            .addProgramFiles(
+                jarMap.getForConfiguration(kotlinc, targetVersion),
+                kotlinc.getKotlinAnnotationJar())
             .addClasspathFiles(kotlinc.getKotlinStdlibJar())
             .addKeepKotlinMetadata()
             .addKeepEnumsRule()
             .addKeepClassRules(DIRECTION_TYPE_NAME)
             .addKeepClassAndMembersRulesWithAllowObfuscation(DIRECTION_TYPE_NAME)
+            .allowDiagnosticWarningMessages()
             .compile()
+            .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
             .inspect(
                 inspector -> {
                   ClassSubject direction = inspector.clazz(DIRECTION_TYPE_NAME);
@@ -96,13 +100,17 @@
   public void testR8() throws Exception {
     R8TestCompileResult r8libResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
+            .addProgramFiles(
+                jarMap.getForConfiguration(kotlinc, targetVersion),
+                kotlinc.getKotlinAnnotationJar())
             .addClasspathFiles(kotlinc.getKotlinStdlibJar())
             .addKeepKotlinMetadata()
             .addKeepEnumsRule()
             .addKeepClassRules(DIRECTION_TYPE_NAME)
             .addKeepClassAndMembersRulesWithAllowObfuscation(DIRECTION_TYPE_NAME)
+            .allowDiagnosticWarningMessages()
             .compile()
+            .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
             .inspect(
                 inspector -> {
                   ClassSubject direction = inspector.clazz(DIRECTION_TYPE_NAME);
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index ab2f10d..89c268e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -106,10 +106,11 @@
         .compile()
         .assertNoErrorMessages()
         // -keepattributes Signature is added in kotlin-reflect from version 1.4.20.
+        .applyIf(kotlinParameters.is(KOTLINC_1_3_72), TestCompileResult::assertNoInfoMessages)
+        // TODO(b/269794485): Figure out why generic signatures fail using kotlin-dev.
         .applyIf(
-            kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72),
-            TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation,
-            TestCompileResult::assertNoInfoMessages)
+            kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72) && !kotlinParameters.isKotlinDev(),
+            TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
         .writeToZip(foo.toPath())
         .run(parameters.getRuntime(), PKG + ".SimpleReflectKt")
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
index 6273755..9eecfaf 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
@@ -102,7 +102,7 @@
         .addKeepClassAndMembersRules(PKG + ".Data")
         // TODO(b/242158616): Figure out why this is necessary.
         .applyIf(
-            kotlinc.getCompilerVersion().isGreaterThan(KotlinCompilerVersion.KOTLINC_1_7_0),
+            kotlinc.is(KotlinCompilerVersion.KOTLINC_1_8_0),
             b ->
                 b.addKeepClassAndMembersRules(
                     "kotlin.reflect.jvm.internal.ClassValueCache$initClassValue$1"))
@@ -119,7 +119,11 @@
         .assertNoErrorMessages()
         .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
         .run(parameters.getRuntime(), MAIN_CLASS)
-        .assertFailureWithErrorThatThrows(IllegalArgumentException.class);
+        // TODO(b/269792580): Figure out why this is throwing an abstract method error.
+        .assertFailureWithErrorThatThrows(
+            kotlinParameters.isKotlinDev() && parameters.isCfRuntime()
+                ? AbstractMethodError.class
+                : IllegalArgumentException.class);
   }
 
   @Test
@@ -130,6 +134,9 @@
         .assertNoErrorMessages()
         .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
         .run(parameters.getRuntime(), MAIN_CLASS)
-        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+        // TODO(b/269792580): Figure out why this is throwing an abstract method error.
+        .assertFailureWithErrorThatThrowsIf(
+            kotlinParameters.isKotlinDev() && parameters.isCfRuntime(), AbstractMethodError.class)
+        .assertSuccessWithOutputLinesIf(!kotlinParameters.isKotlinDev(), EXPECTED_OUTPUT);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java
similarity index 81%
rename from src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
rename to src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java
index c8e9e8a..59dc2b0 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRBasicCallbackTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java
@@ -21,14 +21,14 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class LIRBasicCallbackTest extends TestBase {
+public class LirBasicCallbackTest extends TestBase {
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withNoneRuntime().build();
   }
 
-  public LIRBasicCallbackTest(TestParameters parameters) {
+  public LirBasicCallbackTest(TestParameters parameters) {
     parameters.assertNoneRuntime();
   }
 
@@ -36,8 +36,8 @@
   public void test() throws Exception {
     DexItemFactory factory = new DexItemFactory();
     DexMethod method = factory.createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V"));
-    LIRCode code =
-        LIRCode.builder(
+    LirCode code =
+        LirCode.builder(
                 method,
                 v -> {
                   throw new Unreachable();
@@ -51,16 +51,16 @@
             .addConstInt(42)
             .build();
 
-    LIRIterator it = code.iterator();
+    LirIterator it = code.iterator();
 
     // The iterator and the elements are the same object providing a view on the byte stream.
     assertTrue(it.hasNext());
-    LIRInstructionView next = it.next();
+    LirInstructionView next = it.next();
     assertSame(it, next);
 
     it.accept(
         insn -> {
-          assertEquals(LIROpcodes.ACONST_NULL, insn.getOpcode());
+          assertEquals(LirOpcodes.ACONST_NULL, insn.getOpcode());
           assertEquals(0, insn.getRemainingOperandSizeInBytes());
         });
 
@@ -68,21 +68,21 @@
     it.next();
     it.accept(
         insn -> {
-          assertEquals(LIROpcodes.ICONST, insn.getOpcode());
+          assertEquals(LirOpcodes.ICONST, insn.getOpcode());
           assertEquals(4, insn.getRemainingOperandSizeInBytes());
         });
     assertFalse(it.hasNext());
 
     // The iterator can also be use in a normal java for-each loop.
     // However, the item is not an actual item just a current view, so it can't be cached!
-    LIRInstructionView oldView = null;
-    for (LIRInstructionView view : code) {
+    LirInstructionView oldView = null;
+    for (LirInstructionView view : code) {
       if (oldView == null) {
         oldView = view;
-        view.accept(insn -> assertEquals(LIROpcodes.ACONST_NULL, insn.getOpcode()));
+        view.accept(insn -> assertEquals(LirOpcodes.ACONST_NULL, insn.getOpcode()));
       } else {
         assertSame(oldView, view);
-        view.accept(insn -> assertEquals(LIROpcodes.ICONST, insn.getOpcode()));
+        view.accept(insn -> assertEquals(LirOpcodes.ICONST, insn.getOpcode()));
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java b/src/test/java/com/android/tools/r8/lightir/LirRoundtripTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
rename to src/test/java/com/android/tools/r8/lightir/LirRoundtripTest.java
index 2188ba6..a796311 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LirRoundtripTest.java
@@ -15,7 +15,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class LIRRoundtripTest extends DebugTestBase {
+public class LirRoundtripTest extends DebugTestBase {
 
   static class TestClass {
     public static void main(String[] args) {
@@ -36,7 +36,7 @@
 
   private final TestParameters parameters;
 
-  public LIRRoundtripTest(TestParameters parameters) {
+  public LirRoundtripTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -58,7 +58,7 @@
         .addOptionsModification(
             o -> {
               o.testing.forceIRForCfToCfDesugar = true;
-              o.testing.roundtripThroughLIR = true;
+              o.testing.roundtripThroughLir = true;
             })
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world!");
@@ -73,7 +73,7 @@
         .addOptionsModification(
             o -> {
               o.testing.forceIRForCfToCfDesugar = true;
-              o.testing.roundtripThroughLIR = true;
+              o.testing.roundtripThroughLir = true;
             })
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world!")
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/CompletenessTestingEnabledTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/CompletenessTestingEnabledTest.java
new file mode 100644
index 0000000..d5dc8fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/CompletenessTestingEnabledTest.java
@@ -0,0 +1,50 @@
+// 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.profile.art.completeness;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.profile.art.ArtProfileOptions;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CompletenessTestingEnabledTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public CompletenessTestingEnabledTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  /**
+   * Verifies that -Dcom.android.tools.r8.artprofilerewritingcompletenesscheck=true when running
+   * from test.py. If running this locally in IntelliJ make sure to set the system property in the
+   * run configuration.
+   */
+  @Test
+  public void test() {
+    // Verify that completeness testing is enabled for testing.
+    assertEquals("true", System.getProperty(ArtProfileOptions.COMPLETENESS_PROPERTY_KEY, "false"));
+    assertTrue(new InternalOptions().getArtProfileOptions().isCompletenessCheckForTestingEnabled());
+
+    // Verify that completeness testing is disabled by default.
+    System.clearProperty(ArtProfileOptions.COMPLETENESS_PROPERTY_KEY);
+    assertFalse(
+        new InternalOptions().getArtProfileOptions().isCompletenessCheckForTestingEnabled());
+    System.setProperty(ArtProfileOptions.COMPLETENESS_PROPERTY_KEY, "true");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
index e9058d4..e74465b 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
@@ -98,7 +98,8 @@
     assertThat(sharedValuesMethodSubject, isPresent());
 
     profileInspector
-        .assertContainsClassRule(enumUnboxingSharedUtilityClassSubject)
+        .assertContainsClassRules(
+            enumUnboxingLocalUtilityClassSubject, enumUnboxingSharedUtilityClassSubject)
         .assertContainsMethodRules(
             mainClassSubject.mainMethod(),
             localGreetMethodSubject,
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java
index 6e7ab5b..009966f 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.profile.art.completeness;
 
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticCompanionClass;
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticStaticInterfaceMethodAsCompanionMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -14,7 +16,6 @@
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
 import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -48,6 +49,20 @@
   }
 
   @Test
+  public void testD8FromProfileAfterDesugaring() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addArtProfileForRewriting(
+            getArtProfileAfterDesugaring(
+                parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectResidualArtProfile(this::inspectD8)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     testForR8(parameters.getBackend())
@@ -62,12 +77,42 @@
         .assertSuccessWithOutputLines("Hello, world!");
   }
 
+  @Test
+  public void testR8FromProfileAfterDesugaring() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addArtProfileForRewriting(
+            getArtProfileAfterDesugaring(
+                parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods()))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectResidualArtProfile(this::inspectR8)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
   private ExternalArtProfile getArtProfile() throws Exception {
     return ExternalArtProfile.builder()
         .addMethodRule(Reference.methodFromMethod(I.class.getDeclaredMethod("m")))
         .build();
   }
 
+  private ExternalArtProfile getArtProfileAfterDesugaring(
+      boolean canUseDefaultAndStaticInterfaceMethods) throws Exception {
+    if (canUseDefaultAndStaticInterfaceMethods) {
+      return getArtProfile();
+    } else {
+      return ExternalArtProfile.builder()
+          .addClassRule(syntheticCompanionClass(I.class))
+          .addMethodRule(
+              syntheticStaticInterfaceMethodAsCompanionMethod(I.class.getDeclaredMethod("m")))
+          .build();
+    }
+  }
+
   private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector)
       throws Exception {
     inspect(
@@ -100,8 +145,7 @@
           .assertContainsMethodRule(staticInterfaceMethodSubject)
           .assertContainsNoOtherRules();
     } else {
-      ClassSubject companionClassSubject =
-          inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(I.class));
+      ClassSubject companionClassSubject = inspector.clazz(syntheticCompanionClass(I.class));
       assertThat(companionClassSubject, isPresent());
 
       MethodSubject staticInterfaceMethodSubject =
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
index c3fd851..419ebfc 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
@@ -226,13 +226,16 @@
                         .filter(
                             allOf(
                                     diagnosticMessage(containsString("Running R8 version main")),
-                                    diagnosticMessage(containsString("Using version 8.1.0 for")))
+                                    diagnosticMessage(
+                                        containsString(
+                                            "Using an artificial version newer than any known"
+                                                + " version")))
                                 ::matches)
                         .count()))
         .inspectProguardConfiguration(
             configuration ->
                 assertEquals(
-                    StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim()),
+                    StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_E.trim()),
                     configuration.toString()));
   }
 
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 478af32..e69bb0f 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -228,6 +228,14 @@
         originalMethod.getMethodDescriptor());
   }
 
+  public static MethodReference syntheticStaticInterfaceMethodAsCompanionMethod(Method method) {
+    MethodReference originalMethod = Reference.methodFromMethod(method);
+    ClassReference companionClassReference =
+        syntheticCompanionClass(originalMethod.getHolderClass());
+    return Reference.methodFromDescriptor(
+        companionClassReference, method.getName(), originalMethod.getMethodDescriptor());
+  }
+
   public static ClassReference syntheticEnumUnboxingLocalUtilityClass(Class<?> clazz) {
     return Reference.classFromTypeName(
         clazz.getTypeName() + naming.ENUM_UNBOXING_LOCAL_UTILITY_CLASS.getDescriptor());
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
index 5b00775..fd37907 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
@@ -68,7 +68,7 @@
             DexAnnotation[] annotations = clazz.getDexProgramClass().annotations().annotations;
             assertEquals(1, annotations.length);
             DexEncodedAnnotation annotation = annotations[0].annotation;
-            assertEquals(2, annotation.elements.length);
+            assertEquals(3, annotation.elements.length);
             assertEquals(
                 "com.android.tools.r8.annotations.SynthesizedClassV2",
                 annotation.type.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java
index 963c2e7..cbb5100 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.tracereferences;
 
+import static com.android.tools.r8.dump.DumpOptions.SYSTEM_PROPERTY_PREFIX;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -12,6 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DumpInputFlags;
@@ -21,25 +23,27 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class TraceReferenceDumpInputsTest extends TestBase {
 
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withSystemRuntime().build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
   public TraceReferenceDumpInputsTest(TestParameters parameters) {
-    this.parameters = parameters;
+    parameters.assertNoneRuntime();
   }
 
   @Test
@@ -92,12 +96,20 @@
 
   private void checkProperties(Path properties) throws IOException {
     List<String> lines = Files.readAllLines(properties);
-    assertEquals(4, lines.size());
+    Map<String, String> systemProperties = DumpOptions.Builder.getCurrentSystemProperties();
+    assertEquals(4 + systemProperties.size(), lines.size());
     assertEquals("tool=TraceReferences", lines.get(0));
     assertEquals(
         "trace_references_consumer=com.android.tools.r8.tracereferences.TraceReferencesKeepRules",
         lines.get(2));
     assertEquals("minification=true", lines.get(3));
+    Iterator<Entry<String, String>> systemPropertiesIterator =
+        systemProperties.entrySet().iterator();
+    for (int i = 4; i < lines.size(); i++) {
+      assertTrue(systemPropertiesIterator.hasNext());
+      Entry<String, String> entry = systemPropertiesIterator.next();
+      assertEquals(SYSTEM_PROPERTY_PREFIX + entry.getKey() + "=" + entry.getValue(), lines.get(i));
+    }
   }
 
   private void contains(Path unzipped, String jar, Class<?> clazz) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index f9f8f13..0ee68d2 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -39,6 +39,7 @@
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
@@ -610,11 +611,15 @@
     }
 
     static MethodPredicate onName(String name) {
-      return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName);
+      return onName(name::equals);
+    }
+
+    static MethodPredicate onName(Predicate<String> predicate) {
+      return (access, otherName, descriptor, signature, exceptions) -> predicate.test(otherName);
     }
 
     static MethodPredicate onNames(Collection<String> names) {
-      return (access, otherName, descriptor, signature, exceptions) -> names.contains(otherName);
+      return onName(names::contains);
     }
 
     static MethodPredicate onNames(String... names) {
@@ -758,13 +763,19 @@
   }
 
   public ClassFileTransformer renameMethod(MethodPredicate predicate, String newName) {
+    return renameMethod(predicate, name -> newName);
+  }
+
+  public ClassFileTransformer renameMethod(
+      MethodPredicate predicate, Function<String, String> newName) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
           public MethodVisitor visitMethod(
               int access, String name, String descriptor, String signature, String[] exceptions) {
             if (predicate.test(access, name, descriptor, signature, exceptions)) {
-              return super.visitMethod(access, newName, descriptor, signature, exceptions);
+              return super.visitMethod(
+                  access, newName.apply(name), descriptor, signature, exceptions);
             }
             return super.visitMethod(access, name, descriptor, signature, exceptions);
           }
diff --git a/tools/archive_smali.py b/tools/archive_smali.py
index ca503dc..db7cb89 100755
--- a/tools/archive_smali.py
+++ b/tools/archive_smali.py
@@ -25,12 +25,8 @@
   subprocess.check_call(['git', 'clone', REPO, temp])
   return temp
 
-
 def parse_options():
   result = argparse.ArgumentParser(description='Release Smali')
-  result.add_argument('--archive-hash',
-                      metavar=('<main hash>'),
-                      help='The hash to use for archiving a smali build')
   result.add_argument('--version',
                       metavar=('<version>'),
                       help='The version of smali to archive.')
@@ -57,9 +53,8 @@
       + 'Use --dry-run to test locally')
   if options.checkout and not options.dry_run:
     raise Exception('Using local checkout is only allowed with --dry-run')
-  if not options.checkout:
-    if not options.archive_hash or not options.version:
-      raise Exception('Both --archive-hash and --version are required')
+  if not options.checkout and not options.version:
+    raise Exception('Option --version is required (when not using local checkout)')
 
   if utils.is_bot() and not utils.IsWindows():
     set_rlimit_to_max()
@@ -74,8 +69,12 @@
 
     checkout_dir = options.checkout if options.checkout else checkout(temp)
     with utils.ChangedWorkingDirectory(checkout_dir):
-      if options.archive_hash:
-        subprocess.check_call(['git', 'checkout', options.archive_hash])
+      if options.version:
+        output = subprocess.check_output(['git', 'tag', '-l', options.version])
+        if len(output) == 0:
+          raise Exception(
+            'Repository does not have a release tag for version %s' % options.version)
+        subprocess.check_call(['git', 'checkout', options.version])
 
       # Find version from `build.gradle`.
       for line in open(os.path.join('build.gradle'), 'r'):
@@ -89,7 +88,8 @@
         if (options.checkout):
           raise Exception('Checkout %s has %s' % (options.checkout, message))
         else:
-          raise Exception('Commit % has %s' % (options.archive_hash, message))
+          raise Exception('Tag % has %s' % (options.version, message))
+
       print('Building version: %s' % version)
 
       # Build release to local Maven repository.
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 691bdeb..52805f9 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -289,11 +289,8 @@
   return dump.program_jar()
 
 def determine_class_file(args, build_properties):
-  if args.classfile:
-    return args.classfile
-  if 'classfile' in build_properties:
-    return True
-  return None
+  return args.classfile \
+      or build_properties.get('backend', 'dex').lower() == 'cf'
 
 def determine_android_platform_build(args, build_properties):
   if args.android_platform_build:
diff --git a/tools/r8_release.py b/tools/r8_release.py
index f98e24f..c799d90 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -325,17 +325,6 @@
       sources.write(re.sub(pattern, replace, line))
 
 
-def replace_startswith(prefix, replacement, path):
-  with open(path, "r") as source:
-    lines = source.readlines()
-  with open(path, "w") as source:
-    for line in lines:
-      if line.startswith(prefix):
-        source.write(replacement)
-      else:
-        source.write(line)
-
-
 def download_file(version, file, dst):
   dir = 'raw' if len(version) != 40 else 'raw/main'
   urllib.request.urlretrieve(
@@ -860,12 +849,6 @@
           "R8_DEV_BRANCH = '%s.%s" % (str(semver.major), str(semver.minor)),
           THIS_FILE_RELATIVE)
 
-        # Update main version file with the new dev branch.
-        replace_startswith(
-          '  public static final String ACTIVE_DEV_VERSION = ',
-          '  public static final String ACTIVE_DEV_VERSION = "' + branch_version + '.0"',
-          R8_VERSION_FILE)
-
         message = \
             'Prepare %s for branch %s' % (THIS_FILE_RELATIVE, branch_version)
         subprocess.check_call(['git', 'commit', '-a', '-m', message])
diff --git a/tools/test.py b/tools/test.py
index e7e2b99..9cfaa8b 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -78,10 +78,6 @@
   result.add_option('--all-tests', '--all_tests',
       help='Run tests in all configurations.',
       default=False, action='store_true')
-  result.add_option('--art-profile-rewriting-completeness-check',
-       '--art_profile_rewriting_completeness_check',
-      help='Enable completeness check for ART profile rewriting.',
-      default=False, action='store_true')
   result.add_option('--slow-tests', '--slow_tests',
       help='Also run slow tests.',
       default=False, action='store_true')
@@ -291,8 +287,6 @@
     gradle_args.append('-Ponly_internal')
   if options.all_tests:
     gradle_args.append('-Pall_tests')
-  if options.art_profile_rewriting_completeness_check:
-    gradle_args.append('-Part_profile_rewriting_completeness_check=1')
   if options.slow_tests:
     gradle_args.append('-Pslow_tests=1')
   if options.tool:
@@ -364,6 +358,9 @@
   if options.testing_state_name:
     gradle_args.append('-Ptesting-state-name=' + options.testing_state_name)
 
+  # Enable completeness testing of ART profile rewriting.
+  gradle_args.append('-Part_profile_rewriting_completeness_check=true')
+
   # Build an R8 with dependencies for bootstrapping tests before adding test sources.
   gradle_args.append('r8WithRelocatedDeps')
   gradle_args.append('r8WithRelocatedDeps17')