Merge commit '444568354ab3b1892fcab337b0ae6ba86fa97739' into dev-release

Change-Id: Id49b3356a82ec694bbfda7dbfb23af2bfbf6cc4f
diff --git a/.gitignore b/.gitignore
index 27d89f9..ad943a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,6 +160,8 @@
 third_party/kotlin/kotlin-compiler-1.8.0
 third_party/kotlin/kotlin-compiler-1.9.21.tar.gz
 third_party/kotlin/kotlin-compiler-1.9.21
+third_party/kotlin/kotlin-compiler-2.0.20.tar.gz
+third_party/kotlin/kotlin-compiler-2.0.20
 third_party/kotlin/kotlin-compiler-dev.tar.gz
 third_party/kotlin/kotlin-compiler-dev
 third_party/kotlinx-coroutines-1.3.6.tar.gz
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index b54edc3..1154694 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -870,6 +870,7 @@
     "kotlin-compiler-1.7.0",
     "kotlin-compiler-1.8.0",
     "kotlin-compiler-1.9.21",
+    "kotlin-compiler-2.0.20",
     "kotlin-compiler-dev")
     .map { ThirdPartyDependency(
       it,
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index fafce61..81b904c 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -140,38 +140,30 @@
     }
   }
 
-  public static Map<Integer, SegmentInfo> run(Command command)
+  public static Map<Integer, SegmentInfo> runForTesting(Command command)
+      throws IOException, ResourceException {
+    return run(command);
+  }
+
+  public static Int2ReferenceMap<SegmentInfo> run(Command command)
       throws IOException, ResourceException {
     if (command.isPrintHelp()) {
       System.out.println(Command.USAGE_MESSAGE);
       return null;
     }
-    AndroidApp app = command.getInputApp();
+    return run(command.getInputApp());
+  }
 
+  public static Map<Integer, SegmentInfo> runForTesting(AndroidApp app)
+      throws IOException, ResourceException {
+    return run(app);
+  }
+
+  public static Int2ReferenceMap<SegmentInfo> run(AndroidApp app)
+      throws IOException, ResourceException {
     Int2ReferenceMap<SegmentInfo> result = new Int2ReferenceLinkedOpenHashMap<>();
-    // Fill the results with all benchmark items otherwise golem may report missing benchmarks.
-    int[] benchmarks =
-        new int[] {
-          Constants.TYPE_ENCODED_ARRAY_ITEM,
-          Constants.TYPE_HEADER_ITEM,
-          Constants.TYPE_DEBUG_INFO_ITEM,
-          Constants.TYPE_FIELD_ID_ITEM,
-          Constants.TYPE_ANNOTATION_SET_REF_LIST,
-          Constants.TYPE_STRING_ID_ITEM,
-          Constants.TYPE_MAP_LIST,
-          Constants.TYPE_PROTO_ID_ITEM,
-          Constants.TYPE_METHOD_ID_ITEM,
-          Constants.TYPE_TYPE_ID_ITEM,
-          Constants.TYPE_STRING_DATA_ITEM,
-          Constants.TYPE_CLASS_DATA_ITEM,
-          Constants.TYPE_TYPE_LIST,
-          Constants.TYPE_ANNOTATIONS_DIRECTORY_ITEM,
-          Constants.TYPE_ANNOTATION_ITEM,
-          Constants.TYPE_ANNOTATION_SET_ITEM,
-          Constants.TYPE_CLASS_DEF_ITEM
-        };
-    for (int benchmark : benchmarks) {
-      result.computeIfAbsent(benchmark, (key) -> new SegmentInfo());
+    for (int benchmark : DexSection.getConstants()) {
+      result.put(benchmark, new SegmentInfo());
     }
     try (Closer closer = Closer.create()) {
       for (ProgramResource resource : app.computeAllProgramResources()) {
@@ -179,7 +171,8 @@
           for (DexSection dexSection :
               DexParser.parseMapFrom(
                   closer.register(resource.getByteStream()), resource.getOrigin())) {
-            SegmentInfo info = result.computeIfAbsent(dexSection.type, (key) -> new SegmentInfo());
+            assert result.containsKey(dexSection.type) : dexSection.typeName();
+            SegmentInfo info = result.get(dexSection.type);
             info.increment(dexSection.length, dexSection.size());
           }
         }
diff --git a/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformer.java
new file mode 100644
index 0000000..47357cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformer.java
@@ -0,0 +1,295 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.covariantreturntype;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.DexEncodedAnnotation;
+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.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.utils.ForEachable;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+
+// Responsible for processing the annotations dalvik.annotation.codegen.CovariantReturnType and
+// dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
+//
+// Consider the following class:
+//   public class B extends A {
+//     @CovariantReturnType(returnType = B.class, presentAfter = 25)
+//     @Override
+//     public A m(...) { ... return new B(); }
+//   }
+//
+// The annotation is used to indicate that the compiler should insert a synthetic method that is
+// equivalent to method m, but has return type B instead of A. Thus, for this example, this
+// component is responsible for inserting the following method in class B (in addition to the
+// existing method m):
+//   public B m(...) { A result = "invoke B.m(...)A;"; return (B) result; }
+//
+// Note that a method may be annotated with more than one CovariantReturnType annotation. In this
+// case there will be a CovariantReturnType$CovariantReturnTypes annotation on the method that wraps
+// several CovariantReturnType annotations. In this case, a new method is synthesized for each of
+// the contained CovariantReturnType annotations.
+public final class CovariantReturnTypeAnnotationTransformer {
+
+  private final AppView<?> appView;
+  private final IRConverter converter;
+  private final DexItemFactory factory;
+  private final CovariantReturnTypeReferences references;
+
+  public CovariantReturnTypeAnnotationTransformer(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this(appView, null);
+  }
+
+  private CovariantReturnTypeAnnotationTransformer(AppView<?> appView, IRConverter converter) {
+    this.appView = appView;
+    this.converter = converter;
+    this.factory = appView.dexItemFactory();
+    this.references = new CovariantReturnTypeReferences(factory);
+  }
+
+  public CovariantReturnTypeReferences getReferences() {
+    return references;
+  }
+
+  public static void runIfNecessary(
+      AppView<?> appView,
+      IRConverter converter,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer,
+      ExecutorService executorService)
+      throws ExecutionException {
+    if (shouldRun(appView)) {
+      new CovariantReturnTypeAnnotationTransformer(appView, converter)
+          .run(eventConsumer, executorService);
+    }
+  }
+
+  public static boolean shouldRun(AppView<?> appView) {
+    if (!appView.options().processCovariantReturnTypeAnnotations) {
+      return false;
+    }
+    assert !appView.options().isDesugaredLibraryCompilation();
+    DexItemFactory factory = appView.dexItemFactory();
+    DexString covariantReturnTypeDescriptor =
+        factory.createString(CovariantReturnTypeReferences.COVARIANT_RETURN_TYPE_DESCRIPTOR);
+    return factory.lookupType(covariantReturnTypeDescriptor) != null;
+  }
+
+  private void run(
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer,
+      ExecutorService executorService)
+      throws ExecutionException {
+    List<ProgramMethod> covariantReturnTypeMethods = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      List<ProgramMethod> newCovariantReturnTypeMethods =
+          processClass(clazz, clazz::forEachProgramVirtualMethod, eventConsumer);
+      covariantReturnTypeMethods.addAll(newCovariantReturnTypeMethods);
+    }
+    // Convert methods to DEX.
+    converter.optimizeSynthesizedMethods(
+        covariantReturnTypeMethods,
+        MethodProcessorEventConsumer.empty(),
+        MethodConversionOptions.forD8(appView),
+        executorService);
+  }
+
+  public void processMethods(
+      Map<DexProgramClass, List<ProgramMethod>> methodsToProcess,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer,
+      ExecutorService executorService)
+      throws ExecutionException {
+    ThreadUtils.processMap(
+        methodsToProcess,
+        (clazz, methods) -> {
+          List<ProgramMethod> sortedMethods =
+              ListUtils.destructiveSort(methods, Comparator.comparing(ProgramMethod::getReference));
+          processClass(clazz, sortedMethods::forEach, eventConsumer);
+        },
+        appView.options().getThreadingModule(),
+        executorService);
+  }
+
+  // Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.codegen.
+  // CovariantReturnTypes annotations in the given DexClass. Adds the newly constructed, synthetic
+  // methods to the list covariantReturnTypeMethods.
+  private List<ProgramMethod> processClass(
+      DexProgramClass clazz,
+      ForEachable<ProgramMethod> methodsToProcess,
+      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
+    List<ProgramMethod> covariantReturnTypeMethods = new ArrayList<>();
+    methodsToProcess.forEach(
+        method ->
+            processMethod(
+                method,
+                covariantReturnTypeMethod -> {
+                  covariantReturnTypeMethods.add(covariantReturnTypeMethod);
+                  eventConsumer.acceptCovariantReturnTypeBridgeMethod(
+                      covariantReturnTypeMethod, method);
+                }));
+    clazz.getMethodCollection().addVirtualClassMethods(covariantReturnTypeMethods);
+    return covariantReturnTypeMethods;
+  }
+
+  // Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.Co-
+  // variantReturnTypes annotations on the given method. Adds the newly constructed, synthetic
+  // methods to the list covariantReturnTypeMethods.
+  private void processMethod(ProgramMethod method, Consumer<ProgramMethod> consumer) {
+    for (DexType covariantReturnType : clearCovariantReturnTypeAnnotations(method)) {
+      consumer.accept(buildCovariantReturnTypeMethod(method, covariantReturnType));
+    }
+  }
+
+  // Builds a synthetic method that invokes the given method, casts the result to
+  // covariantReturnType, and then returns the result. The newly created method will have return
+  // type covariantReturnType.
+  //
+  // Note: any "synchronized" or "strictfp" modifier could be dropped safely.
+  private ProgramMethod buildCovariantReturnTypeMethod(
+      ProgramMethod method, DexType covariantReturnType) {
+    DexMethod covariantReturnTypeMethodReference =
+        method.getReference().withReturnType(covariantReturnType, factory);
+    failIfPresent(method.getHolder(), covariantReturnTypeMethodReference);
+    DexEncodedMethod definition =
+        DexEncodedMethod.syntheticBuilder()
+            .setMethod(covariantReturnTypeMethodReference)
+            .setAccessFlags(
+                method.getAccessFlags().copy().setBridge().setSynthetic().unsetAbstract())
+            .setGenericSignature(method.getDefinition().getGenericSignature())
+            .setAnnotations(method.getAnnotations())
+            .setParameterAnnotations(method.getParameterAnnotations())
+            .setCode(
+                ForwardMethodBuilder.builder(factory)
+                    .setNonStaticSource(covariantReturnTypeMethodReference)
+                    .setVirtualTarget(method.getReference(), method.getHolder().isInterface())
+                    .setCastResult()
+                    .buildCf())
+            .setApiLevelForDefinition(method.getDefinition().getApiLevelForDefinition())
+            .setApiLevelForCode(method.getDefinition().getApiLevelForCode())
+            .build();
+    return new ProgramMethod(method.getHolder(), definition);
+  }
+
+  private void failIfPresent(DexProgramClass clazz, DexMethod covariantReturnTypeMethodReference) {
+    // It is a compilation error if the class already has a method with a signature similar to one
+    // of the methods in covariantReturnTypeMethods.
+    if (clazz.lookupMethod(covariantReturnTypeMethodReference) != null) {
+      throw appView
+          .reporter()
+          .fatalError(
+              String.format(
+                  "Cannot process CovariantReturnType annotation: Class %s already "
+                      + "has a method \"%s\"",
+                  clazz.getTypeName(), covariantReturnTypeMethodReference.toSourceString()));
+    }
+  }
+
+  // Returns the set of covariant return types for method.
+  //
+  // If the method is:
+  //   @dalvik.annotation.codegen.CovariantReturnType(returnType=SubOfFoo, presentAfter=25)
+  //   @dalvik.annotation.codegen.CovariantReturnType(returnType=SubOfSubOfFoo, presentAfter=28)
+  //   @Override
+  //   public Foo foo() { ... return new SubOfSubOfFoo(); }
+  // then this method returns the set { SubOfFoo, SubOfSubOfFoo }.
+  private Set<DexType> clearCovariantReturnTypeAnnotations(ProgramMethod method) {
+    Set<DexType> covariantReturnTypes = new LinkedHashSet<>();
+    for (DexAnnotation annotation : method.getAnnotations().getAnnotations()) {
+      if (references.isOneOfCovariantReturnTypeAnnotations(annotation.getAnnotationType())) {
+        getCovariantReturnTypesFromAnnotation(
+            method, annotation.getAnnotation(), covariantReturnTypes);
+      }
+    }
+    if (!covariantReturnTypes.isEmpty()) {
+      method
+          .getDefinition()
+          .setAnnotations(
+              method
+                  .getAnnotations()
+                  .removeIf(
+                      annotation ->
+                          references.isOneOfCovariantReturnTypeAnnotations(
+                              annotation.getAnnotationType())));
+    }
+    return covariantReturnTypes;
+  }
+
+  private void getCovariantReturnTypesFromAnnotation(
+      ProgramMethod method, DexEncodedAnnotation annotation, Set<DexType> covariantReturnTypes) {
+    boolean hasPresentAfterElement = false;
+    for (DexAnnotationElement element : annotation.elements) {
+      DexString name = element.getName();
+      if (references.isCovariantReturnTypeAnnotation(annotation.getType())) {
+        if (name.isIdenticalTo(references.returnTypeName)) {
+          DexValueType dexValueType = element.getValue().asDexValueType();
+          if (dexValueType == null) {
+            throw new CompilationError(
+                String.format(
+                    "Expected element \"returnType\" of CovariantReturnType annotation to "
+                        + "reference a type (method: \"%s\", was: %s)",
+                    method.toSourceString(), element.value.getClass().getCanonicalName()));
+          }
+          covariantReturnTypes.add(dexValueType.getValue());
+        } else if (name.isIdenticalTo(references.presentAfterName)) {
+          hasPresentAfterElement = true;
+        }
+      } else {
+        assert references.isCovariantReturnTypesAnnotation(annotation.getType());
+        if (name.isIdenticalTo(references.valueName)) {
+          DexValueArray array = element.getValue().asDexValueArray();
+          if (array == null) {
+            throw new CompilationError(
+                String.format(
+                    "Expected element \"value\" of CovariantReturnTypes annotation to "
+                        + "be an array (method: \"%s\", was: %s)",
+                    method.toSourceString(), element.getValue().getClass().getCanonicalName()));
+          }
+
+          // Handle the inner dalvik.annotation.codegen.CovariantReturnType annotations recursively.
+          for (DexValue value : array.getValues()) {
+            assert value.isDexValueAnnotation();
+            DexValueAnnotation innerAnnotation = value.asDexValueAnnotation();
+            getCovariantReturnTypesFromAnnotation(
+                method, innerAnnotation.getValue(), covariantReturnTypes);
+          }
+        }
+      }
+    }
+
+    if (references.isCovariantReturnTypeAnnotation(annotation.getType())
+        && !hasPresentAfterElement) {
+      throw new CompilationError(
+          String.format(
+              "CovariantReturnType annotation for method \"%s\" is missing mandatory element "
+                  + "\"presentAfter\" (class %s)",
+              method.toSourceString(), method.getHolder().getType()));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformerEventConsumer.java b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformerEventConsumer.java
similarity index 93%
rename from src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformerEventConsumer.java
rename to src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformerEventConsumer.java
index f5c97fe..a0ff741 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformerEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformerEventConsumer.java
@@ -1,8 +1,7 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
+package com.android.tools.r8.desugar.covariantreturntype;
 
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
diff --git a/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeEnqueuerExtension.java b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeEnqueuerExtension.java
new file mode 100644
index 0000000..4e8573f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeEnqueuerExtension.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.covariantreturntype;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.errors.NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.shaking.KeepInfo;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.shaking.MinimumKeepInfoCollection;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class CovariantReturnTypeEnqueuerExtension extends EnqueuerAnalysis {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final CovariantReturnTypeAnnotationTransformer transformer;
+
+  private final Map<DexProgramClass, List<ProgramMethod>> pendingCovariantReturnTypeDesugaring =
+      new IdentityHashMap<>();
+
+  public CovariantReturnTypeEnqueuerExtension(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.appView = appView;
+    this.transformer = new CovariantReturnTypeAnnotationTransformer(appView);
+  }
+
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+    if (enqueuer.getMode().isInitialTreeShaking()
+        && CovariantReturnTypeAnnotationTransformer.shouldRun(appView)) {
+      enqueuer.registerAnalysis(new CovariantReturnTypeEnqueuerExtension(appView));
+    }
+  }
+
+  @Override
+  public void processNewlyLiveMethod(
+      ProgramMethod method,
+      ProgramDefinition context,
+      Enqueuer enqueuer,
+      EnqueuerWorklist worklist) {
+    if (hasCovariantReturnTypeAnnotation(method)) {
+      pendingCovariantReturnTypeDesugaring
+          .computeIfAbsent(method.getHolder(), ignoreKey(ArrayList::new))
+          .add(method);
+    }
+  }
+
+  private boolean hasCovariantReturnTypeAnnotation(ProgramMethod method) {
+    CovariantReturnTypeReferences references = transformer.getReferences();
+    DexAnnotationSet annotations = method.getAnnotations();
+    return annotations.hasAnnotation(
+        annotation ->
+            references.isOneOfCovariantReturnTypeAnnotations(annotation.getAnnotationType()));
+  }
+
+  @Override
+  public void notifyFixpoint(
+      Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    if (pendingCovariantReturnTypeDesugaring.isEmpty()) {
+      return;
+    }
+    ProgramMethodMap<Diagnostic> errors = ProgramMethodMap.createConcurrent();
+    transformer.processMethods(
+        pendingCovariantReturnTypeDesugaring,
+        (bridge, target) -> {
+          KeepMethodInfo.Joiner bridgeKeepInfo =
+              getKeepInfoForCovariantReturnTypeBridge(target, errors);
+          enqueuer.getKeepInfo().registerCompilerSynthesizedMethod(bridge);
+          enqueuer.applyMinimumKeepInfoWhenLiveOrTargeted(bridge, bridgeKeepInfo);
+          enqueuer.getProfileCollectionAdditions().addMethodIfContextIsInProfile(bridge, target);
+        },
+        executorService);
+    errors.forEachValue(appView.reporter()::error);
+    pendingCovariantReturnTypeDesugaring.clear();
+  }
+
+  private KeepMethodInfo.Joiner getKeepInfoForCovariantReturnTypeBridge(
+      ProgramMethod target, ProgramMethodMap<Diagnostic> errors) {
+    KeepInfo.Joiner<?, ?, ?> targetKeepInfo =
+        appView
+            .rootSet()
+            .getDependentMinimumKeepInfo()
+            .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty())
+            .getOrDefault(target.getReference(), null);
+    if (targetKeepInfo == null) {
+      targetKeepInfo = KeepMethodInfo.newEmptyJoiner();
+    }
+    InternalOptions options = appView.options();
+    if ((options.isMinifying() && targetKeepInfo.isMinificationAllowed())
+        || (options.isOptimizing() && targetKeepInfo.isOptimizationAllowed())
+        || (options.isShrinking() && targetKeepInfo.isShrinkingAllowed())) {
+      errors.computeIfAbsent(target, NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic::new);
+    }
+    return targetKeepInfo.asMethodJoiner();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeReferences.java b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeReferences.java
new file mode 100644
index 0000000..fa26148
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeReferences.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.covariantreturntype;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+
+public class CovariantReturnTypeReferences {
+
+  static String COVARIANT_RETURN_TYPE_DESCRIPTOR =
+      "Ldalvik/annotation/codegen/CovariantReturnType;";
+
+  final DexType annotationCovariantReturnType;
+  final DexType annotationCovariantReturnTypes;
+
+  final DexString presentAfterName;
+  final DexString returnTypeName;
+  final DexString valueName;
+
+  CovariantReturnTypeReferences(DexItemFactory factory) {
+    this.annotationCovariantReturnType = factory.createType(COVARIANT_RETURN_TYPE_DESCRIPTOR);
+    this.annotationCovariantReturnTypes =
+        factory.createType("Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;");
+    this.presentAfterName = factory.createString("presentAfter");
+    this.returnTypeName = factory.createString("returnType");
+    this.valueName = factory.createString("value");
+  }
+
+  boolean isCovariantReturnTypeAnnotation(DexType type) {
+    return type.isIdenticalTo(annotationCovariantReturnType);
+  }
+
+  boolean isCovariantReturnTypesAnnotation(DexType type) {
+    return type.isIdenticalTo(annotationCovariantReturnTypes);
+  }
+
+  boolean isOneOfCovariantReturnTypeAnnotations(DexType type) {
+    return isCovariantReturnTypeAnnotation(type) || isCovariantReturnTypesAnnotation(type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/DexSection.java b/src/main/java/com/android/tools/r8/dex/DexSection.java
index 39a01b2..79b20bc 100644
--- a/src/main/java/com/android/tools/r8/dex/DexSection.java
+++ b/src/main/java/com/android/tools/r8/dex/DexSection.java
@@ -20,6 +20,29 @@
     this.end = -1;
   }
 
+  public static int[] getConstants() {
+    return new int[] {
+      Constants.TYPE_ENCODED_ARRAY_ITEM,
+      Constants.TYPE_HEADER_ITEM,
+      Constants.TYPE_DEBUG_INFO_ITEM,
+      Constants.TYPE_FIELD_ID_ITEM,
+      Constants.TYPE_ANNOTATION_SET_REF_LIST,
+      Constants.TYPE_STRING_ID_ITEM,
+      Constants.TYPE_MAP_LIST,
+      Constants.TYPE_PROTO_ID_ITEM,
+      Constants.TYPE_METHOD_ID_ITEM,
+      Constants.TYPE_TYPE_ID_ITEM,
+      Constants.TYPE_STRING_DATA_ITEM,
+      Constants.TYPE_CLASS_DATA_ITEM,
+      Constants.TYPE_TYPE_LIST,
+      Constants.TYPE_ANNOTATIONS_DIRECTORY_ITEM,
+      Constants.TYPE_ANNOTATION_ITEM,
+      Constants.TYPE_ANNOTATION_SET_ITEM,
+      Constants.TYPE_CLASS_DEF_ITEM,
+      Constants.TYPE_CODE_ITEM
+    };
+  }
+
   void setEnd(int end) {
     this.end = end;
   }
diff --git a/src/main/java/com/android/tools/r8/errors/NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.java b/src/main/java/com/android/tools/r8/errors/NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.java
new file mode 100644
index 0000000..a737aa4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+
+@KeepForApi
+public class NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic implements Diagnostic {
+
+  private final Origin origin;
+  private final MethodReference method;
+  private final MethodPosition position;
+
+  public NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic(ProgramMethod method) {
+    this.origin = method.getOrigin();
+    this.method = method.getMethodReference();
+    this.position = MethodPosition.create(method);
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Methods with @CovariantReturnType annotations should be kept, but was not: "
+        + MethodReferenceUtils.toSourceString(method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 4151107..58011d6 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -226,8 +226,9 @@
     return isSet(Constants.ACC_SYNTHETIC);
   }
 
-  public void setSynthetic() {
+  public T setSynthetic() {
     set(Constants.ACC_SYNTHETIC);
+    return self();
   }
 
   public T unsetSynthetic() {
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 b742d92..9f3483e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -534,7 +534,6 @@
   }
 
   public ComposeReferences getComposeReferences() {
-    assert options().getJetpackComposeOptions().isAnyOptimizationsEnabled();
     if (composeReferences == null) {
       composeReferences = new ComposeReferences(dexItemFactory());
     }
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 ec6b70a..cee4f28 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
@@ -79,6 +78,10 @@
     this.annotation = annotation;
   }
 
+  public DexEncodedAnnotation getAnnotation() {
+    return annotation;
+  }
+
   public boolean isTypeAnnotation() {
     return false;
   }
@@ -136,22 +139,21 @@
     mixedItems.add(this);
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  public static boolean retainCompileTimeAnnotation(DexType annotation, InternalOptions options) {
+  public static boolean retainCompileTimeAnnotation(
+      DexType annotationType, InternalOptions options) {
     if (options.retainCompileTimeAnnotations) {
       return true;
     }
-    if (annotation == options.itemFactory.annotationSynthesizedClass
-        || annotation
-            .getDescriptor()
-            .startsWith(options.itemFactory.dalvikAnnotationOptimizationPrefix)) {
+    DexItemFactory factory = options.itemFactory;
+    if (annotationType.isIdenticalTo(factory.annotationSynthesizedClass)) {
       return true;
     }
-    if (options.processCovariantReturnTypeAnnotations) {
-      // @CovariantReturnType annotations are processed by CovariantReturnTypeAnnotationTransformer,
-      // they thus need to be read here and will then be removed as part of the processing.
-      return CovariantReturnTypeAnnotationTransformer.isCovariantReturnTypeAnnotation(
-          annotation, options.itemFactory);
+    DexString descriptor = annotationType.getDescriptor();
+    if (descriptor.startsWith(factory.dalvikAnnotationPrefix)) {
+      if (descriptor.startsWith(factory.dalvikAnnotationCodegenCovariantReturnTypePrefix)) {
+        return options.processCovariantReturnTypeAnnotations;
+      }
+      return descriptor.startsWith(factory.dalvikAnnotationOptimizationPrefix);
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index 6dab6e8..c76fb95 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -156,9 +156,17 @@
     return getFirstMatching(type) != null;
   }
 
+  public boolean hasAnnotation(Predicate<DexAnnotation> predicate) {
+    return getFirstMatching(predicate) != null;
+  }
+
   public DexAnnotation getFirstMatching(DexType type) {
+    return getFirstMatching(annotation -> annotation.getAnnotationType().isIdenticalTo(type));
+  }
+
+  public DexAnnotation getFirstMatching(Predicate<DexAnnotation> predicate) {
     for (DexAnnotation annotation : annotations) {
-      if (annotation.getAnnotationType().isIdenticalTo(type)) {
+      if (predicate.test(annotation)) {
         return annotation;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index 143dbe4..884b892 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -41,6 +41,10 @@
     return DexEncodedAnnotation::specify;
   }
 
+  public DexType getType() {
+    return type;
+  }
+
   public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
     type.collectIndexedItems(appView, indexedItems);
     for (DexAnnotationElement element : elements) {
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 3c056f8..8f6be1b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -81,8 +81,6 @@
       "Lcom/android/tools/r8/DesugarMethodHandlesLookup;";
   public static final String methodHandlesLookupDescriptorString =
       "Ljava/lang/invoke/MethodHandles$Lookup;";
-  public static final String dalvikAnnotationOptimizationPrefixString =
-      "Ldalvik/annotation/optimization/";
   public static final String androidUtilSparseArrayDescriptorString = "Landroid/util/SparseArray;";
   public static final String androidContentResTypedArrayDescriptorString =
       "Landroid/content/res/TypedArray;";
@@ -380,8 +378,11 @@
   public final DexString apiLevelString = createString("apiLevel");
 
   // Prefix for runtime affecting yet potential class-retained annotations.
+  public final DexString dalvikAnnotationPrefix = createString("Ldalvik/annotation/");
+  public final DexString dalvikAnnotationCodegenCovariantReturnTypePrefix =
+      createString("Ldalvik/annotation/codegen/CovariantReturnType");
   public final DexString dalvikAnnotationOptimizationPrefix =
-      createString(dalvikAnnotationOptimizationPrefixString);
+      createString("Ldalvik/annotation/optimization/");
 
   // Method names used on VarHandle.
   public final DexString getString = createString("get");
@@ -811,11 +812,6 @@
   public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
   public final DexType annotationSynthesizedClass =
       createStaticallyKnownType("Lcom/android/tools/r8/annotations/SynthesizedClassV2;");
-  public final DexType annotationCovariantReturnType =
-      createStaticallyKnownType("Ldalvik/annotation/codegen/CovariantReturnType;");
-  public final DexType annotationCovariantReturnTypes =
-      createStaticallyKnownType(
-          "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;");
 
   public final String annotationReachabilitySensitiveDesc =
       "Ldalvik/annotation/optimization/ReachabilitySensitive;";
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index ea4be31..4abdb68 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -360,4 +360,8 @@
   public DexMethod withProto(DexProto proto, DexItemFactory dexItemFactory) {
     return dexItemFactory.createMethod(holder, proto, name);
   }
+
+  public DexMethod withReturnType(DexType returnType, DexItemFactory dexItemFactory) {
+    return withProto(dexItemFactory.createProto(returnType, getParameters()), dexItemFactory);
+  }
 }
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 5b0aec4..e890a6a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -755,11 +755,6 @@
     methodCollection.addMethod(method);
   }
 
-  public void replaceVirtualMethod(
-      DexMethod virtualMethod, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    methodCollection.replaceVirtualMethod(virtualMethod, replacement);
-  }
-
   public void addExtraInterfaces(List<ClassTypeSignature> extraInterfaces, DexItemFactory factory) {
     if (extraInterfaces.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 8ede6f7..742b628 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -152,8 +152,9 @@
     return isSet(Constants.ACC_BRIDGE);
   }
 
-  public void setBridge() {
+  public MethodAccessFlags setBridge() {
     set(Constants.ACC_BRIDGE);
+    return this;
   }
 
   public void unsetBridge() {
@@ -204,8 +205,9 @@
     promote(Constants.ACC_ABSTRACT);
   }
 
-  public void unsetAbstract() {
+  public MethodAccessFlags unsetAbstract() {
     unset(Constants.ACC_ABSTRACT);
+    return this;
   }
 
   public boolean isStrict() {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index ab17919..7daef60 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -184,12 +184,12 @@
   }
 
   @Override
-  void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+  <T> void addVirtualMethods(Collection<T> methods, Function<? super T, DexEncodedMethod> fn) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
     System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
     int i = virtualMethods.length;
-    for (DexEncodedMethod method : methods) {
-      newMethods[i] = method;
+    for (T method : methods) {
+      newMethods[i] = fn.apply(method);
       i++;
     }
     virtualMethods = newMethods;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 0f10f5a..17b7e8b 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -5,6 +5,9 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -12,7 +15,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class MethodCollection {
@@ -354,9 +356,21 @@
   }
 
   public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
-    assert verifyCorrectnessOfMethodHolders(methods);
+    internalAddVirtualMethods(methods, Functions.identity());
+  }
+
+  public void addVirtualClassMethods(Collection<? extends DexClassAndMethod> methods) {
+    internalAddVirtualMethods(methods, DexClassAndMethod::getDefinition);
+  }
+
+  private <T> void internalAddVirtualMethods(
+      Collection<T> methods, Function<? super T, DexEncodedMethod> fn) {
+    if (methods.isEmpty()) {
+      return;
+    }
+    assert verifyCorrectnessOfMethodHolders(Iterables.transform(methods, fn));
     resetVirtualMethodCaches();
-    backing.addVirtualMethods(methods);
+    backing.addVirtualMethods(methods, fn);
   }
 
   public void clearVirtualMethods() {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 18a72a3..9de91a2 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -6,6 +6,7 @@
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.base.Functions;
 import java.util.Collection;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -99,7 +100,12 @@
 
   abstract void addDirectMethods(Collection<DexEncodedMethod> methods);
 
-  abstract void addVirtualMethods(Collection<DexEncodedMethod> methods);
+  final void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+    addVirtualMethods(methods, Functions.identity());
+  }
+
+  abstract <T> void addVirtualMethods(
+      Collection<T> methods, Function<? super T, DexEncodedMethod> fn);
 
   // Removal methods.
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
index 78cfebe8..fa3a0a9 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
@@ -4,11 +4,11 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.base.Function;
 import java.util.Collection;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class MethodCollectionConcurrencyChecked extends MethodCollection {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index fa553d9..f878e46 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -198,9 +198,9 @@
   }
 
   @Override
-  void addVirtualMethods(Collection<DexEncodedMethod> methods) {
-    for (DexEncodedMethod method : methods) {
-      addVirtualMethod(method);
+  <T> void addVirtualMethods(Collection<T> methods, Function<? super T, DexEncodedMethod> fn) {
+    for (T method : methods) {
+      addVirtualMethod(fn.apply(method));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index d587b61..791b3b4 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.utils.Timing;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public abstract class EnqueuerAnalysis {
 
@@ -57,7 +59,9 @@
    * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
    * analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
    */
-  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {}
+  public void notifyFixpoint(
+      Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
+      throws ExecutionException {}
 
   /**
    * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
index 3d61f0e..097cdc8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
 import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -41,12 +42,13 @@
   public PathConstraintAnalysis(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
+      FieldValueFactory fieldValueFactory,
       MethodParameterFactory methodParameterFactory) {
     super(
         appView,
         PathConstraintAnalysisState.bottom(),
         code,
         new PathConstraintAnalysisTransferFunction(
-            appView.abstractValueFactory(), code.context(), methodParameterFactory));
+            appView, code, code.context(), fieldValueFactory, methodParameterFactory));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisTransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisTransferFunction.java
index c61df2a..ed423d7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisTransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisTransferFunction.java
@@ -3,31 +3,37 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.path;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
 import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
 import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
-import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeBuilder;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.DefaultComputationTreeBuilder;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class PathConstraintAnalysisTransferFunction
     implements AbstractTransferFunction<BasicBlock, Instruction, PathConstraintAnalysisState> {
 
-  private final ComputationTreeBuilder computationTreeBuilder;
+  private final DefaultComputationTreeBuilder computationTreeBuilder;
 
   PathConstraintAnalysisTransferFunction(
-      AbstractValueFactory abstractValueFactory,
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
       ProgramMethod method,
+      FieldValueFactory fieldValueFactory,
       MethodParameterFactory methodParameterFactory) {
     computationTreeBuilder =
-        new ComputationTreeBuilder(abstractValueFactory, method, methodParameterFactory);
+        new DefaultComputationTreeBuilder(
+            appView, code, method, fieldValueFactory, methodParameterFactory);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintSupplier.java b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintSupplier.java
index cb81605..649b2bb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintSupplier.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintSupplier.java
@@ -5,16 +5,21 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
 import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class PathConstraintSupplier {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final IRCode code;
+  private final FieldValueFactory fieldValueFactory;
   private final MethodParameterFactory methodParameterFactory;
 
   private SuccessfulDataflowAnalysisResult<BasicBlock, PathConstraintAnalysisState>
@@ -23,16 +28,32 @@
   public PathConstraintSupplier(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
+      FieldValueFactory fieldValueFactory,
       MethodParameterFactory methodParameterFactory) {
     this.appView = appView;
     this.code = code;
+    this.fieldValueFactory = fieldValueFactory;
     this.methodParameterFactory = methodParameterFactory;
   }
 
+  public ComputationTreeNode getDifferentiatingPathConstraint(
+      BasicBlock block, BasicBlock otherBlock) {
+    ConcretePathConstraintAnalysisState state = getPathConstraint(block).asConcreteState();
+    if (state == null) {
+      return AbstractValue.unknown();
+    }
+    ConcretePathConstraintAnalysisState otherState =
+        getPathConstraint(otherBlock).asConcreteState();
+    if (otherState == null) {
+      return AbstractValue.unknown();
+    }
+    return state.getDifferentiatingPathConstraint(otherState);
+  }
+
   public PathConstraintAnalysisState getPathConstraint(BasicBlock block) {
     if (pathConstraintAnalysisResult == null) {
       PathConstraintAnalysis analysis =
-          new PathConstraintAnalysis(appView, code, methodParameterFactory);
+          new PathConstraintAnalysis(appView, code, fieldValueFactory, methodParameterFactory);
       pathConstraintAnalysisResult = analysis.run(code.entryBlock()).asSuccessfulAnalysisResult();
       assert pathConstraintAnalysisResult != null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
index 7bcb650..28bdcf1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.analysis.path.state;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import java.util.Collections;
 import java.util.HashMap;
@@ -62,26 +63,22 @@
   @Override
   public PathConstraintAnalysisState add(ComputationTreeNode pathConstraint, boolean negate) {
     PathConstraintKind previousKind = pathConstraints.get(pathConstraint);
+    PathConstraintKind newKind;
     if (previousKind != null) {
-      if (previousKind == PathConstraintKind.DISABLED) {
-        // There is a loop.
+      // There is a loop.
+      newKind = PathConstraintKind.DISABLED;
+      if (previousKind == newKind) {
         return this;
       }
-      if (previousKind == PathConstraintKind.get(negate)) {
-        // This branch is dominated by a previous if-condition that has the same branch condition,
-        // e.g., if (x) { if (x) { ...
-        return this;
-      }
-      // This branch is dominated by a previous if-condition that has the negated branch condition,
-      // e.g., if (x) { if (!x) { ...
-      return bottom();
+    } else {
+      newKind = PathConstraintKind.get(negate);
     }
     // No jumps can dominate the entry of their own block, so when adding the condition of a jump
     // this cannot currently be active.
     Map<ComputationTreeNode, PathConstraintKind> newPathConstraints =
         new HashMap<>(pathConstraints.size() + 1);
     newPathConstraints.putAll(pathConstraints);
-    newPathConstraints.put(pathConstraint, PathConstraintKind.get(negate));
+    newPathConstraints.put(pathConstraint, newKind);
     return new ConcretePathConstraintAnalysisState(newPathConstraints);
   }
 
@@ -153,7 +150,7 @@
         return pathConstraint;
       }
     }
-    return null;
+    return AbstractValue.unknown();
   }
 
   public ConcretePathConstraintAnalysisState join(ConcretePathConstraintAnalysisState other) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintKind.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintKind.java
index 4e97d27..6891163 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintKind.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintKind.java
@@ -24,6 +24,9 @@
   }
 
   PathConstraintKind join(PathConstraintKind other) {
+    if (other == null) {
+      return this;
+    }
     return this == other ? this : DISABLED;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 04c491e..f3299a3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -135,7 +135,11 @@
     return new EnqueuerAnalysis() {
       @Override
       @SuppressWarnings("ReferenceEquality")
-      public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {
+      public void notifyFixpoint(
+          Enqueuer enqueuer,
+          EnqueuerWorklist worklist,
+          ExecutorService executorService,
+          Timing timing) {
         builders.forEach(
             (builder, dynamicMethod) -> {
               if (seen.add(builder)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index c9a6021..0ce0317 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -54,6 +54,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Predicate;
 
 // TODO(b/112437944): Handle cycles in the graph + add a test that fails with the current
@@ -179,7 +180,11 @@
   }
 
   @Override
-  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {
+  public void notifyFixpoint(
+      Enqueuer enqueuer,
+      EnqueuerWorklist worklist,
+      ExecutorService executorService,
+      Timing timing) {
     timing.begin("[Proto] Extend fixpoint");
     populateExtensionGraph(enqueuer);
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index 6350b1e..9fa7cf7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -9,10 +9,15 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowKind;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.function.IntFunction;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Function;
 
 public abstract class AbstractValue implements ComputationTreeNode {
 
@@ -26,15 +31,37 @@
 
   @Override
   public AbstractValue evaluate(
-      IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
     return this;
   }
 
   @Override
-  public MethodParameter getSingleOpenVariable() {
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    // Abstract values do not contain any open variables.
+    return TraversalContinuation.doContinue();
+  }
+
+  @Override
+  public InFlowKind getKind() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public BaseInFlow getSingleOpenVariable() {
     return null;
   }
 
+  @Override
+  public int internalCompareToSameKind(InFlow inFlow, InFlowComparator comparator) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public final boolean isComputationLeaf() {
+    return true;
+  }
+
   public abstract boolean isNonTrivial();
 
   public boolean isSingleBoolean() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index b55931c..59fde05 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -370,6 +370,10 @@
     return predecessors.get(0);
   }
 
+  public BasicBlock getPredecessor(int i) {
+    return getPredecessors().get(i);
+  }
+
   public List<BasicBlock> getPredecessors() {
     return ListUtils.unmodifiableForTesting(predecessors);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IfType.java b/src/main/java/com/android/tools/r8/ir/code/IfType.java
index 5babdae..8a0d96b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IfType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IfType.java
@@ -4,63 +4,88 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public enum IfType {
   EQ {
     @Override
-    public AbstractValue evaluate(
-        SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
-      return abstractValueFactory.createSingleBooleanValue(operand.getValue() == 0);
+    public boolean evaluate(int operand) {
+      return operand == 0;
+    }
+
+    @Override
+    public String getSymbol() {
+      return "==";
     }
   },
   GE {
     @Override
-    public AbstractValue evaluate(
-        SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
-      return abstractValueFactory.createSingleBooleanValue(operand.getValue() >= 0);
+    public boolean evaluate(int operand) {
+      return operand >= 0;
+    }
+
+    @Override
+    public String getSymbol() {
+      return ">=";
     }
   },
   GT {
     @Override
-    public AbstractValue evaluate(
-        SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
-      return abstractValueFactory.createSingleBooleanValue(operand.getValue() > 0);
+    public boolean evaluate(int operand) {
+      return operand > 0;
+    }
+
+    @Override
+    public String getSymbol() {
+      return ">";
     }
   },
   LE {
     @Override
-    public AbstractValue evaluate(
-        SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
-      return abstractValueFactory.createSingleBooleanValue(operand.getValue() <= 0);
+    public boolean evaluate(int operand) {
+      return operand <= 0;
+    }
+
+    @Override
+    public String getSymbol() {
+      return "<=";
     }
   },
   LT {
     @Override
-    public AbstractValue evaluate(
-        SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
-      return abstractValueFactory.createSingleBooleanValue(operand.getValue() < 0);
+    public boolean evaluate(int operand) {
+      return operand < 0;
+    }
+
+    @Override
+    public String getSymbol() {
+      return "<";
     }
   },
   NE {
     @Override
-    public AbstractValue evaluate(
-        SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
-      return abstractValueFactory.createSingleBooleanValue(operand.getValue() != 0);
+    public boolean evaluate(int operand) {
+      return operand != 0;
+    }
+
+    @Override
+    public String getSymbol() {
+      return "!=";
     }
   };
 
-  public AbstractValue evaluate(AbstractValue operand, AbstractValueFactory abstractValueFactory) {
+  public AbstractValue evaluate(AbstractValue operand, AppView<AppInfoWithLiveness> appView) {
     if (operand.isSingleNumberValue()) {
-      return evaluate(operand.asSingleNumberValue(), abstractValueFactory);
+      int operandValue = operand.asSingleNumberValue().getIntValue();
+      boolean result = evaluate(operandValue);
+      return appView.abstractValueFactory().createSingleBooleanValue(result);
     }
     return AbstractValue.unknown();
   }
 
-  public abstract AbstractValue evaluate(
-      SingleNumberValue operand, AbstractValueFactory abstractValueFactory);
+  public abstract boolean evaluate(int operand);
 
   public boolean isEqualsOrNotEquals() {
     return this == EQ || this == NE;
@@ -103,4 +128,6 @@
         throw new Unreachable("Unknown if condition type.");
     }
   }
+
+  public abstract String getSymbol();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
index 0ae5aaf..34d9483 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
@@ -4,27 +4,4 @@
 
 package com.android.tools.r8.ir.code;
 
-public interface InstructionOrPhi {
-
-  default boolean isInstruction() {
-    return false;
-  }
-
-  default Instruction asInstruction() {
-    return null;
-  }
-
-  default boolean isPhi() {
-    return false;
-  }
-
-  default boolean isStackMapPhi() {
-    return false;
-  }
-
-  default Phi asPhi() {
-    return null;
-  }
-
-  BasicBlock getBlock();
-}
+public interface InstructionOrPhi extends InstructionOrValue {}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java b/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java
new file mode 100644
index 0000000..b45fd92
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.code;
+
+public interface InstructionOrValue {
+
+  default boolean isInstruction() {
+    return false;
+  }
+
+  default Instruction asInstruction() {
+    return null;
+  }
+
+  default boolean isPhi() {
+    return false;
+  }
+
+  default boolean isStackMapPhi() {
+    return false;
+  }
+
+  default Phi asPhi() {
+    return null;
+  }
+
+  default Value asValue() {
+    return null;
+  }
+
+  BasicBlock getBlock();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index fbccb7c..d08946f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -51,7 +51,7 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
-public class Value implements Comparable<Value> {
+public class Value implements Comparable<Value>, InstructionOrValue {
 
   public void constrainType(
       ValueTypeConstraint constraint, ProgramMethod method, Reporter reporter) {
@@ -371,8 +371,17 @@
   }
 
   public Instruction singleUniqueUser() {
-    assert ImmutableSet.copyOf(users).size() == 1;
-    return users.getFirst();
+    assert hasSingleUniqueUser();
+    return firstUser();
+  }
+
+  public boolean hasSingleUniquePhiUser() {
+    return uniquePhiUsers().size() == 1;
+  }
+
+  public Phi singleUniquePhiUser() {
+    assert hasSingleUniquePhiUser();
+    return firstPhiUser();
   }
 
   public Set<Instruction> aliasedUsers() {
@@ -397,6 +406,11 @@
     }
   }
 
+  public Instruction firstUser() {
+    assert !users.isEmpty();
+    return users.getFirst();
+  }
+
   public Phi firstPhiUser() {
     assert !phiUsers.isEmpty();
     return phiUsers.getFirst();
@@ -775,6 +789,11 @@
   }
 
   @Override
+  public Value asValue() {
+    return this;
+  }
+
+  @Override
   public int compareTo(Value value) {
     return Integer.compare(this.number, value.number);
   }
@@ -929,14 +948,6 @@
     return predicate.test(definition);
   }
 
-  public boolean isPhi() {
-    return false;
-  }
-
-  public Phi asPhi() {
-    return null;
-  }
-
   /**
    * Returns whether this value is known to never be <code>null</code>.
    */
@@ -1106,6 +1117,7 @@
     return definition.hasBlock();
   }
 
+  @Override
   public BasicBlock getBlock() {
     return definition.getBlock();
   }
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 13b059d..fd87cbc 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
@@ -41,7 +41,6 @@
 import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer;
 import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
-import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
@@ -120,7 +119,6 @@
   protected final Inliner inliner;
   protected final IdentifierNameStringMarker identifierNameStringMarker;
   private final Devirtualizer devirtualizer;
-  protected final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
   private final TypeChecker typeChecker;
   protected EnumUnboxer enumUnboxer;
   protected final NumberUnboxer numberUnboxer;
@@ -190,7 +188,6 @@
       assert options.desugarState.isOn();
       this.instructionDesugaring =
           CfInstructionDesugaringCollection.create(appView, appView.apiLevelCompute());
-      this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
       this.fieldAccessAnalysis = null;
@@ -213,10 +210,6 @@
         appView.enableWholeProgramOptimizations()
             ? CfInstructionDesugaringCollection.empty()
             : CfInstructionDesugaringCollection.create(appView, appView.apiLevelCompute());
-    this.covariantReturnTypeAnnotationTransformer =
-        options.processCovariantReturnTypeAnnotations
-            ? new CovariantReturnTypeAnnotationTransformer(appView, this)
-            : null;
     removeVerificationErrorForUnknownReturnedValues =
         (appView.options().apiModelingOptions().enableLibraryApiModeling
                 && appView.options().canHaveVerifyErrorForUnknownUnusedReturnValue())
@@ -871,10 +864,6 @@
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor, timing));
 
-    if (methodProcessor.isComposeMethodProcessor()) {
-      methodProcessor.asComposeMethodProcessor().scan(method, code, timing);
-    }
-
     if (methodProcessor.isPrimaryMethodProcessor()) {
       enumUnboxer.analyzeEnums(code, methodProcessor);
       numberUnboxer.analyze(code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 72e0051..e8b022d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -35,7 +35,6 @@
 import static com.android.tools.r8.utils.ObjectUtils.getBooleanOrElse;
 
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -75,6 +74,7 @@
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstMethodHandle;
 import com.android.tools.r8.ir.code.ConstMethodType;
+import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.FieldGet;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -874,7 +874,7 @@
       }
     }
     if (mayHaveUnreachableBlocks) {
-      code.removeUnreachableBlocks(affectedValues, prunedValue -> affectedPhis.remove(prunedValue));
+      code.removeUnreachableBlocks(affectedValues, affectedPhis::remove);
     }
     affectedValues.narrowingWithAssumeRemoval(appView, code);
     if (!affectedPhis.isEmpty()) {
@@ -884,7 +884,7 @@
     nullCheckInserter.processWorklist();
     code.removeAllDeadAndTrivialPhis();
     code.removeRedundantBlocks();
-    removeUnusedArguments(method, code, unusedArguments);
+    removeUnusedArguments(code, unusedArguments);
 
     // Finalize cast and null check insertion.
     interfaceTypeToClassTypeRewriterHelper.processWorklist();
@@ -1075,16 +1075,31 @@
     return replacement;
   }
 
-  private void removeUnusedArguments(
-      ProgramMethod method, IRCode code, Set<UnusedArgument> unusedArguments) {
+  private void removeUnusedArguments(IRCode code, Set<UnusedArgument> unusedArguments) {
+    AffectedValues affectedValues = new AffectedValues();
     for (UnusedArgument unusedArgument : unusedArguments) {
+      InstructionListIterator instructionIterator =
+          unusedArgument.getBlock().listIterator(code, unusedArgument);
       if (unusedArgument.outValue().hasAnyUsers()) {
-        throw new Unreachable("Unused argument with users in " + method.toSourceString());
+        // This is an unused argument with a default value. The unused argument is an operand of the
+        // phi. This use is eliminated after constant propagation + branch pruning. We eliminate the
+        // UnusedArgument instruction early by replacing it with a const 0.
+        assert unusedArgument.outValue().hasPhiUsers();
+        assert !unusedArgument.outValue().hasUsers();
+        assert !unusedArgument.outValue().hasDebugUsers();
+        TypeElement type = unusedArgument.outValue().getType();
+        instructionIterator.replaceCurrentInstruction(
+            ConstNumber.builder()
+                .setFreshOutValue(code, type.isReferenceType() ? TypeElement.getNull() : type)
+                .setPosition(Position.none())
+                .setValue(0)
+                .build(),
+            affectedValues);
+      } else {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
       }
-      InstructionListIterator instructionIterator = unusedArgument.getBlock().listIterator(code);
-      instructionIterator.nextUntil(instruction -> instruction == unusedArgument);
-      instructionIterator.removeOrReplaceByDebugLocalRead();
     }
+    affectedValues.narrowingWithAssumeRemoval(appView, code);
   }
 
   private Deque<GraphLensInterval> getUnappliedLenses(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 9eb24e1..9c8b286 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -5,18 +5,9 @@
 
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
-import com.android.tools.r8.optimize.compose.ComposeMethodProcessor;
 
 public abstract class MethodProcessor {
 
-  public boolean isComposeMethodProcessor() {
-    return false;
-  }
-
-  public ComposeMethodProcessor asComposeMethodProcessor() {
-    return null;
-  }
-
   public boolean isD8MethodProcessor() {
     return false;
   }
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 eb16aef..93b681a 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
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.ir.desugar.lambda.D8LambdaDesugaring.rewriteEnclosingLambdaMethodAttributes;
 
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.desugar.covariantreturntype.CovariantReturnTypeAnnotationTransformer;
+import com.android.tools.r8.desugar.covariantreturntype.CovariantReturnTypeAnnotationTransformerEventConsumer;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
@@ -22,7 +24,6 @@
 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;
@@ -77,6 +78,8 @@
 
     application = commitPendingSyntheticItems(appView, application);
 
+    processCovariantReturnTypeAnnotations(profileCollectionAdditions, executorService);
+
     // Build a new application with jumbo string info,
     Builder<?> builder = application.builder();
 
@@ -85,8 +88,6 @@
       new L8InnerOuterAttributeEraser(appView).run();
     }
 
-    processCovariantReturnTypeAnnotations(builder, profileCollectionAdditions);
-
     timing.end();
 
     application = builder.build();
@@ -272,6 +273,7 @@
               appView.appInfo().getMainDexInfo()));
       application = appView.appInfo().app();
     }
+    assert application == appView.appInfo().app();
     return application;
   }
 
@@ -338,14 +340,13 @@
     programAdditions.apply(threadingModule, executorService);
   }
 
-  @SuppressWarnings("BadImport")
   private void processCovariantReturnTypeAnnotations(
-      Builder<?> builder, ProfileCollectionAdditions profileCollectionAdditions) {
-    if (covariantReturnTypeAnnotationTransformer != null) {
-      covariantReturnTypeAnnotationTransformer.process(
-          builder,
-          CovariantReturnTypeAnnotationTransformerEventConsumer.create(profileCollectionAdditions));
-    }
+      ProfileCollectionAdditions profileCollectionAdditions, ExecutorService executorService)
+      throws ExecutionException {
+    CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer =
+        CovariantReturnTypeAnnotationTransformerEventConsumer.create(profileCollectionAdditions);
+    CovariantReturnTypeAnnotationTransformer.runIfNecessary(
+        appView, this, eventConsumer, executorService);
   }
 
   Timing rewriteNonDesugaredCode(
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 3b3ba7a..d25c1b5 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
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.naming.IdentifierMinifier;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
-import com.android.tools.r8.optimize.compose.ComposableOptimizationPass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -224,8 +223,6 @@
       identifierNameStringMarker.decoupleIdentifierNameStringsInFields(executorService);
     }
 
-    ComposableOptimizationPass.run(appView, this, executorService, timing);
-
     // Assure that no more optimization feedback left after post processing.
     assert feedback.noUpdatesLeft();
     return appView.appInfo().app();
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
deleted file mode 100644
index f84950a..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.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;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedAnnotation;
-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.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
-import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
-import com.google.common.base.Predicates;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-// Responsible for processing the annotations dalvik.annotation.codegen.CovariantReturnType and
-// dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
-//
-// Consider the following class:
-//   public class B extends A {
-//     @CovariantReturnType(returnType = B.class, presentAfter = 25)
-//     @Override
-//     public A m(...) { ... return new B(); }
-//   }
-//
-// The annotation is used to indicate that the compiler should insert a synthetic method that is
-// equivalent to method m, but has return type B instead of A. Thus, for this example, this
-// component is responsible for inserting the following method in class B (in addition to the
-// existing method m):
-//   public B m(...) { A result = "invoke B.m(...)A;"; return (B) result; }
-//
-// Note that a method may be annotated with more than one CovariantReturnType annotation. In this
-// case there will be a CovariantReturnType$CovariantReturnTypes annotation on the method that wraps
-// several CovariantReturnType annotations. In this case, a new method is synthesized for each of
-// the contained CovariantReturnType annotations.
-public final class CovariantReturnTypeAnnotationTransformer {
-
-  private final IRConverter converter;
-  private final MethodProcessorEventConsumer methodProcessorEventConsumer =
-      MethodProcessorEventConsumer.empty();
-  private final DexItemFactory factory;
-
-  public CovariantReturnTypeAnnotationTransformer(AppView<?> appView, IRConverter converter) {
-    this.converter = converter;
-    this.factory = appView.dexItemFactory();
-  }
-
-  // TODO(b/270398965): Replace LinkedList.
-  @SuppressWarnings("JdkObsolete")
-  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,
-          eventConsumer);
-      if (covariantReturnTypeMethods.isEmpty()) {
-        continue;
-      }
-      updateClass(clazz, methodsWithCovariantReturnTypeAnnotation, covariantReturnTypeMethods);
-      // Reset lists for the next class that will have a CovariantReturnType or
-      // CovariantReturnType$CovariantReturnTypes annotation.
-      methodsWithCovariantReturnTypeAnnotation.clear();
-      covariantReturnTypeMethods.clear();
-    }
-  }
-
-  private void updateClass(
-      DexClass clazz,
-      List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation,
-      List<DexEncodedMethod> covariantReturnTypeMethods) {
-    // It is a compilation error if the class already has a method with a signature similar to one
-    // of the methods in covariantReturnTypeMethods.
-    for (DexEncodedMethod syntheticMethod : covariantReturnTypeMethods) {
-      if (hasVirtualMethodWithSignature(clazz, syntheticMethod)) {
-        throw new CompilationError(
-            String.format(
-                "Cannot process CovariantReturnType annotation: Class %s already "
-                    + "has a method \"%s\"",
-                clazz.getType(), syntheticMethod.toSourceString()));
-      }
-    }
-    // Remove the CovariantReturnType annotations.
-    for (DexEncodedMethod method : methodsWithCovariantReturnTypeAnnotation) {
-      method.setAnnotations(
-          method.annotations().keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)));
-    }
-    // Add the newly constructed methods to the class.
-    clazz.addVirtualMethods(covariantReturnTypeMethods);
-  }
-
-  // Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.codegen.
-  // CovariantReturnTypes annotations in the given DexClass. Adds the newly constructed, synthetic
-  // methods to the list covariantReturnTypeMethods.
-  private void buildCovariantReturnTypeMethodsForClass(
-      DexProgramClass clazz,
-      List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation,
-      List<DexEncodedMethod> covariantReturnTypeMethods,
-      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
-    clazz.forEachProgramVirtualMethod(
-        method -> {
-          if (methodHasCovariantReturnTypeAnnotation(method.getDefinition())) {
-            methodsWithCovariantReturnTypeAnnotation.add(method.getDefinition());
-            buildCovariantReturnTypeMethodsForMethod(
-                method, covariantReturnTypeMethods, eventConsumer);
-          }
-        });
-  }
-
-  private boolean methodHasCovariantReturnTypeAnnotation(DexEncodedMethod method) {
-    for (DexAnnotation annotation : method.annotations().annotations) {
-      if (isCovariantReturnTypeAnnotation(annotation.annotation)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  // Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.Co-
-  // variantReturnTypes annotations on the given method. Adds the newly constructed, synthetic
-  // methods to the list covariantReturnTypeMethods.
-  private void buildCovariantReturnTypeMethodsForMethod(
-      ProgramMethod method,
-      List<DexEncodedMethod> covariantReturnTypeMethods,
-      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
-    assert methodHasCovariantReturnTypeAnnotation(method.getDefinition());
-    for (DexType covariantReturnType : getCovariantReturnTypes(method)) {
-      DexEncodedMethod covariantReturnTypeMethod =
-          buildCovariantReturnTypeMethod(method, covariantReturnType, eventConsumer);
-      covariantReturnTypeMethods.add(covariantReturnTypeMethod);
-    }
-  }
-
-  // Builds a synthetic method that invokes the given method, casts the result to
-  // covariantReturnType, and then returns the result. The newly created method will have return
-  // type covariantReturnType.
-  //
-  // Note: any "synchronized" or "strictfp" modifier could be dropped safely.
-  private DexEncodedMethod buildCovariantReturnTypeMethod(
-      ProgramMethod method,
-      DexType covariantReturnType,
-      CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer) {
-    DexProgramClass methodHolder = method.getHolder();
-    DexMethod methodReference = method.getReference();
-    DexEncodedMethod methodDefinition = method.getDefinition();
-    DexProto newProto = factory.createProto(covariantReturnType, methodReference.proto.parameters);
-    MethodAccessFlags newAccessFlags = methodDefinition.accessFlags.copy();
-    newAccessFlags.setBridge();
-    newAccessFlags.setSynthetic();
-    newAccessFlags.unsetAbstract(); // Synthetic bridge has code, so never abstract.
-    DexMethod newMethod =
-        factory.createMethod(methodHolder.getType(), newProto, methodReference.getName());
-    ForwardMethodBuilder forwardMethodBuilder =
-        ForwardMethodBuilder.builder(factory)
-            .setNonStaticSource(newMethod)
-            .setVirtualTarget(methodReference, methodHolder.isInterface())
-            .setCastResult();
-    DexEncodedMethod newVirtualMethod =
-        DexEncodedMethod.syntheticBuilder()
-            .setMethod(newMethod)
-            .setAccessFlags(newAccessFlags)
-            .setGenericSignature(methodDefinition.getGenericSignature())
-            .setAnnotations(
-                methodDefinition
-                    .annotations()
-                    .keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)))
-            .setParameterAnnotations(
-                methodDefinition.parameterAnnotationsList.keepIf(Predicates.alwaysTrue()))
-            .setCode(forwardMethodBuilder.buildCf())
-            .setApiLevelForDefinition(methodDefinition.getApiLevelForDefinition())
-            .setApiLevelForCode(methodDefinition.getApiLevelForCode())
-            .build();
-    // Optimize to generate DexCode instead of CfCode.
-    ProgramMethod programMethod = new ProgramMethod(methodHolder, newVirtualMethod);
-    converter.optimizeSynthesizedMethod(
-        programMethod,
-        methodProcessorEventConsumer,
-        MethodConversionOptions.forD8(converter.appView));
-    eventConsumer.acceptCovariantReturnTypeBridgeMethod(programMethod, method);
-    return newVirtualMethod;
-  }
-
-  // Returns the set of covariant return types for method.
-  //
-  // If the method is:
-  //   @dalvik.annotation.codegen.CovariantReturnType(returnType=SubOfFoo, presentAfter=25)
-  //   @dalvik.annotation.codegen.CovariantReturnType(returnType=SubOfSubOfFoo, presentAfter=28)
-  //   @Override
-  //   public Foo foo() { ... return new SubOfSubOfFoo(); }
-  // then this method returns the set { SubOfFoo, SubOfSubOfFoo }.
-  private Set<DexType> getCovariantReturnTypes(ProgramMethod method) {
-    Set<DexType> covariantReturnTypes = new HashSet<>();
-    for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
-      if (isCovariantReturnTypeAnnotation(annotation.annotation)) {
-        getCovariantReturnTypesFromAnnotation(
-            method.getHolder(),
-            method.getDefinition(),
-            annotation.annotation,
-            covariantReturnTypes);
-      }
-    }
-    return covariantReturnTypes;
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private void getCovariantReturnTypesFromAnnotation(
-      DexClass clazz,
-      DexEncodedMethod method,
-      DexEncodedAnnotation annotation,
-      Set<DexType> covariantReturnTypes) {
-    assert isCovariantReturnTypeAnnotation(annotation);
-    boolean hasPresentAfterElement = false;
-    for (DexAnnotationElement element : annotation.elements) {
-      String name = element.name.toString();
-      if (annotation.type == factory.annotationCovariantReturnType) {
-        if (name.equals("returnType")) {
-          DexValueType dexValueType = element.value.asDexValueType();
-          if (dexValueType == null) {
-            throw new CompilationError(
-                String.format(
-                    "Expected element \"returnType\" of CovariantReturnType annotation to "
-                        + "reference a type (method: \"%s\", was: %s)",
-                    method.toSourceString(), element.value.getClass().getCanonicalName()));
-          }
-          covariantReturnTypes.add(dexValueType.value);
-        } else if (name.equals("presentAfter")) {
-          hasPresentAfterElement = true;
-        }
-      } else {
-        if (name.equals("value")) {
-          DexValueArray array = element.value.asDexValueArray();
-          if (array == null) {
-            throw new CompilationError(
-                String.format(
-                    "Expected element \"value\" of CovariantReturnTypes annotation to "
-                        + "be an array (method: \"%s\", was: %s)",
-                    method.toSourceString(), element.value.getClass().getCanonicalName()));
-          }
-
-          // Handle the inner dalvik.annotation.codegen.CovariantReturnType annotations recursively.
-          for (DexValue value : array.getValues()) {
-            assert value.isDexValueAnnotation();
-            DexValueAnnotation innerAnnotation = value.asDexValueAnnotation();
-            getCovariantReturnTypesFromAnnotation(
-                clazz, method, innerAnnotation.value, covariantReturnTypes);
-          }
-        }
-      }
-    }
-
-    if (annotation.type == factory.annotationCovariantReturnType && !hasPresentAfterElement) {
-      throw new CompilationError(
-          String.format(
-              "CovariantReturnType annotation for method \"%s\" is missing mandatory element "
-                  + "\"presentAfter\" (class %s)",
-              clazz.getType(), method.toSourceString()));
-    }
-  }
-
-  public boolean isCovariantReturnTypeAnnotation(DexEncodedAnnotation annotation) {
-    return isCovariantReturnTypeAnnotation(annotation, factory);
-  }
-
-  public static boolean isCovariantReturnTypeAnnotation(
-      DexEncodedAnnotation annotation, DexItemFactory factory) {
-    return isCovariantReturnTypeAnnotation(annotation.type, factory);
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  public static boolean isCovariantReturnTypeAnnotation(DexType type, DexItemFactory factory) {
-    return type == factory.annotationCovariantReturnType
-        || type == factory.annotationCovariantReturnTypes;
-  }
-
-  private static boolean hasVirtualMethodWithSignature(DexClass clazz, DexEncodedMethod method) {
-    for (DexEncodedMethod existingMethod : clazz.virtualMethods()) {
-      if (existingMethod.getReference().equals(method.getReference())) {
-        return true;
-      }
-    }
-    return false;
-  }
-}
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 520e2d8..85215e5 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
@@ -324,7 +324,8 @@
     }
 
     // Check that the service is not kept.
-    if (appView().appInfo().isPinnedWithDefinitionLookup(serviceType)) {
+    if (!options.allowServiceLoaderRewritingPinnedTypes
+        && appView().appInfo().isPinnedWithDefinitionLookup(serviceType)) {
       report(code.context(), serviceType, "The service loader type is kept");
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index b651b1f..41a3bc2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 
 // A flat lattice structure: TOP and a lattice element that holds accumulated argument info.
 public abstract class CallSiteOptimizationInfo {
@@ -32,5 +33,9 @@
     return UnknownValue.getInstance();
   }
 
+  public final AbstractValue getAbstractArgumentValue(MethodParameter methodParameter) {
+    return getAbstractArgumentValue(methodParameter.getIndex());
+  }
+
   // TODO(b/139249918): propagate classes that are guaranteed to be initialized.
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 8b2f63d..b348230 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
@@ -33,7 +32,6 @@
   static final Set<DexType> UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT = ImmutableSet.of();
   static final int UNKNOWN_RETURNED_ARGUMENT = -1;
   static final boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
-  static final AbstractValue UNKNOWN_ABSTRACT_RETURN_VALUE = UnknownValue.getInstance();
   static final boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
   static final boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
   static final boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false;
@@ -138,12 +136,12 @@
 
   @Override
   public AbstractFunction getAbstractFunction() {
-    return AbstractFunction.unknown();
+    return AbstractValue.unknown();
   }
 
   @Override
   public AbstractValue getAbstractReturnValue() {
-    return UNKNOWN_ABSTRACT_RETURN_VALUE;
+    return AbstractValue.unknown();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index fbf3767..ab2fc60 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -104,7 +104,7 @@
 import com.android.tools.r8.kotlin.Kotlin.Intrinsics;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
 import com.android.tools.r8.optimize.compose.ComposeUtils;
-import com.android.tools.r8.optimize.compose.UpdateChangedFlagsAbstractFunction;
+import com.android.tools.r8.optimize.compose.ComputationTreeUnopUpdateChangedFlagsNode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -755,10 +755,9 @@
   private void computeAbstractFunction(IRCode code, OptimizationFeedback feedback) {
     if (ComposeUtils.isUpdateChangedFlags(code, appView.dexItemFactory())) {
       MethodParameter methodParameter = new MethodParameter(code.context(), 0);
-      UpdateChangedFlagsAbstractFunction updateChangedFlagsAbstractFunction =
-          new UpdateChangedFlagsAbstractFunction(methodParameter);
-      feedback.setAbstractFunction(
-          code.context().getDefinition(), updateChangedFlagsAbstractFunction);
+      ComputationTreeUnopUpdateChangedFlagsNode node =
+          new ComputationTreeUnopUpdateChangedFlagsNode(methodParameter);
+      feedback.setAbstractFunction(code.context().getDefinition(), node);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
index 6c59889..727328a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
@@ -77,7 +77,7 @@
 
   @Override
   public AbstractFunction getAbstractFunction() {
-    return AbstractFunction.unknown();
+    return AbstractValue.unknown();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index efe5cb6..f8c1f07 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -44,9 +44,8 @@
   private Set<DexType> initializedClassesOnNormalExit =
       DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
   private int returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT;
-  private AbstractFunction abstractFunction = AbstractFunction.unknown();
-  private AbstractValue abstractReturnValue =
-      DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
+  private AbstractFunction abstractFunction = AbstractValue.unknown();
+  private AbstractValue abstractReturnValue = AbstractValue.unknown();
   private ClassInlinerMethodConstraint classInlinerConstraint =
       ClassInlinerMethodConstraint.alwaysFalse();
   private boolean convertCheckNotNull = false;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 2273dbb..c059f0a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -130,11 +130,15 @@
       AbstractValueSupplier abstractValueSupplier =
           value -> value.getAbstractValue(appView, method);
       PathConstraintSupplier pathConstraintSupplier =
-          new PathConstraintSupplier(appView, code, codeScanner.getMethodParameterFactory());
+          new PathConstraintSupplier(
+              appView,
+              code,
+              codeScanner.getFieldValueFactory(),
+              codeScanner.getMethodParameterFactory());
       codeScanner.scan(method, code, abstractValueSupplier, pathConstraintSupplier, timing);
 
       assert effectivelyUnusedArgumentsAnalysis != null;
-      effectivelyUnusedArgumentsAnalysis.scan(method, code);
+      effectivelyUnusedArgumentsAnalysis.scan(method, code, pathConstraintSupplier);
 
       assert reprocessingCriteriaCollection != null;
       reprocessingCriteriaCollection.analyzeArgumentUses(method, code);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index ba31341..99759a9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.path.PathConstraintSupplier;
 import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
@@ -26,7 +27,6 @@
 import com.android.tools.r8.ir.code.AbstractValueSupplier;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
-import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.FieldGet;
 import com.android.tools.r8.ir.code.FieldPut;
@@ -38,7 +38,6 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.CastAbstractFunction;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
@@ -66,6 +65,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComposableComputationTreeBuilder;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
 import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.MethodReprocessingCriteria;
@@ -77,10 +77,9 @@
 import com.android.tools.r8.utils.DeterminismChecker.LineCallback;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalUtils;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -105,8 +104,6 @@
 
   private final AppView<AppInfoWithLiveness> appView;
 
-  private final ArgumentPropagatorCodeScannerModeling modeling;
-
   private final FieldValueFactory fieldValueFactory = new FieldValueFactory();
 
   final MethodParameterFactory methodParameterFactory = new MethodParameterFactory();
@@ -145,7 +142,6 @@
       AppView<AppInfoWithLiveness> appView,
       ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection) {
     this.appView = appView;
-    this.modeling = new ArgumentPropagatorCodeScannerModeling(appView);
     this.reprocessingCriteriaCollection = reprocessingCriteriaCollection;
   }
 
@@ -161,6 +157,10 @@
     return fieldStates;
   }
 
+  public FieldValueFactory getFieldValueFactory() {
+    return fieldValueFactory;
+  }
+
   public MethodParameterFactory getMethodParameterFactory() {
     return methodParameterFactory;
   }
@@ -242,8 +242,6 @@
     protected final ProgramMethod context;
     private final PathConstraintSupplier pathConstraintSupplier;
 
-    private Object2IntMap<Phi> phiNumbering = null;
-
     protected CodeScanner(
         AbstractValueSupplier abstractValueSupplier,
         IRCode code,
@@ -320,6 +318,7 @@
       NonEmptyValueState inFlowState =
           computeInFlowState(
               field.getType(),
+              field,
               value,
               initialValue,
               context,
@@ -356,7 +355,7 @@
         MethodParameter inParameter =
             methodParameterFactory.create(
                 context, valueRoot.getDefinition().asArgument().getIndex());
-        if (!widenBaseInFlow(staticType, inParameter, context).isUnknownAbstractFunction()) {
+        if (!widenBaseInFlow(staticType, inParameter, context).isUnknown()) {
           return inParameter;
         }
       } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
@@ -364,7 +363,7 @@
         ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
         if (field != null) {
           FieldValue fieldValue = fieldValueFactory.create(field);
-          if (!widenBaseInFlow(staticType, fieldValue, context).isUnknownAbstractFunction()) {
+          if (!widenBaseInFlow(staticType, fieldValue, context).isUnknown()) {
             return fieldValue;
           }
         }
@@ -380,6 +379,7 @@
     // TODO(b/302281503): Canonicalize computed in flow.
     private InFlow computeInFlow(
         DexType staticType,
+        ProgramMember<?, ?> target,
         Value value,
         Value initialValue,
         ProgramMethod context,
@@ -411,16 +411,28 @@
         return castBaseInFlow(
             widenBaseInFlow(staticType, fieldValueFactory.create(field), context), value);
       } else if (value.isPhi()) {
+        // TODO(b/302281503): Replace IfThenElseAbstractFunction by ComputationTreeNode (?).
         return computeIfThenElseAbstractFunction(value.asPhi(), valueStateSupplier);
+      } else if (target != null && appView.getComposeReferences().isComposable(target)) {
+        ComputationTreeNode node =
+            new ComposableComputationTreeBuilder(
+                    appView,
+                    code,
+                    code.context(),
+                    fieldValueFactory,
+                    methodParameterFactory,
+                    pathConstraintSupplier)
+                .getOrBuildComputationTree(value);
+        if (!node.isComputationLeaf() && TraversalUtils.hasNext(node::traverseBaseInFlow)) {
+          recordComputationTreePosition(node, value);
+          return node;
+        }
       }
       return null;
     }
 
     private IfThenElseAbstractFunction computeIfThenElseAbstractFunction(
         Phi phi, Function<Value, NonEmptyValueState> valueStateSupplier) {
-      if (!appView.testing().enableIfThenElseAbstractFunction) {
-        return null;
-      }
       if (phi.getOperands().size() != 2 || !phi.hasOperandThatMatches(Value::isArgument)) {
         return null;
       }
@@ -438,11 +450,14 @@
       ComputationTreeNode condition =
           leftPredecessorPathConstraint.getDifferentiatingPathConstraint(
               rightPredecessorPathConstraint);
-      if (condition == null || condition.getSingleOpenVariable() == null) {
+      if (condition.getSingleOpenVariable() == null) {
         return null;
       }
       NonEmptyValueState leftValue = valueStateSupplier.apply(phi.getOperand(0));
       NonEmptyValueState rightValue = valueStateSupplier.apply(phi.getOperand(1));
+      if (leftValue.isUnknown() && rightValue.isUnknown()) {
+        return null;
+      }
       IfThenElseAbstractFunction result =
           leftPredecessorPathConstraint.isNegated(condition)
               ? new IfThenElseAbstractFunction(condition, rightValue, leftValue)
@@ -451,30 +466,27 @@
       return result;
     }
 
+    private void recordComputationTreePosition(ComputationTreeNode computation, Value value) {
+      inFlowComparatorBuilder.addComputationTreePosition(
+          computation,
+          SourcePosition.builder()
+              .setMethod(code.context().getReference())
+              .setLine(value.getNumber())
+              .build());
+    }
+
     private void recordIfThenElsePosition(
         IfThenElseAbstractFunction ifThenElseAbstractFunction, Phi phi) {
       inFlowComparatorBuilder.addIfThenElsePosition(
           ifThenElseAbstractFunction,
           SourcePosition.builder()
               .setMethod(code.context().getReference())
-              .setLine(getOrCreatePhiNumbering().getInt(phi))
+              .setLine(phi.getNumber())
               .build());
     }
 
-    private Object2IntMap<Phi> getOrCreatePhiNumbering() {
-      if (phiNumbering == null) {
-        phiNumbering = new Object2IntOpenHashMap<>();
-        for (BasicBlock block : code.getBlocks()) {
-          for (Phi phi : block.getPhis()) {
-            phiNumbering.put(phi, phiNumbering.size());
-          }
-        }
-      }
-      return phiNumbering;
-    }
-
     private InFlow castBaseInFlow(InFlow inFlow, Value value) {
-      if (inFlow.isUnknownAbstractFunction()) {
+      if (inFlow.isUnknown()) {
         return inFlow;
       }
       assert inFlow.isBaseInFlow();
@@ -489,12 +501,12 @@
     private InFlow widenBaseInFlow(DexType staticType, BaseInFlow inFlow, ProgramMethod context) {
       if (inFlow.isFieldValue()) {
         if (isFieldValueAlreadyUnknown(staticType, inFlow.asFieldValue().getField())) {
-          return AbstractFunction.unknown();
+          return AbstractValue.unknown();
         }
       } else {
         assert inFlow.isMethodParameter();
         if (isMethodParameterAlreadyUnknown(staticType, inFlow.asMethodParameter(), context)) {
-          return AbstractFunction.unknown();
+          return AbstractValue.unknown();
         }
       }
       return inFlow;
@@ -502,19 +514,22 @@
 
     private NonEmptyValueState computeInFlowState(
         DexType staticType,
+        ProgramMember<?, ?> target,
         Value value,
         Value initialValue,
         ProgramMethod context,
         Function<Value, NonEmptyValueState> valueStateSupplier) {
       assert value == initialValue || initialValue.getAliasedValue().isPhi();
-      InFlow inFlow = computeInFlow(staticType, value, initialValue, context, valueStateSupplier);
+      InFlow inFlow =
+          computeInFlow(staticType, target, value, initialValue, context, valueStateSupplier);
       if (inFlow == null) {
         return null;
       }
-      if (inFlow.isUnknownAbstractFunction()) {
+      if (inFlow.isUnknown()) {
         return ValueState.unknown();
       }
       assert inFlow.isBaseInFlow()
+          || inFlow.isAbstractComputation()
           || inFlow.isCastAbstractFunction()
           || inFlow.isIfThenElseAbstractFunction()
           || inFlow.isInstanceFieldReadAbstractFunction();
@@ -860,13 +875,8 @@
         Value value,
         Value initialValue,
         ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
+      assert invoke.isInvokeStatic() || argumentIndex > 0;
       assert value == initialValue || initialValue.getAliasedValue().isPhi();
-      NonEmptyValueState modeledState =
-          modeling.modelParameterStateForArgumentToFunction(
-              invoke, singleTarget, argumentIndex, value, context);
-      if (modeledState != null) {
-        return modeledState;
-      }
 
       // Don't compute a state for this parameter if the stored state is already unknown.
       if (existingMethodState.isMonomorphic()
@@ -885,6 +895,7 @@
       NonEmptyValueState inFlowState =
           computeInFlowState(
               parameterType,
+              singleTarget,
               value,
               initialValue,
               context,
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
deleted file mode 100644
index 0a021d2..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
+++ /dev/null
@@ -1,40 +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.optimize.argumentpropagation;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
-import com.android.tools.r8.optimize.compose.ArgumentPropagatorComposeModeling;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public class ArgumentPropagatorCodeScannerModeling {
-
-  private final ArgumentPropagatorComposeModeling composeModeling;
-
-  ArgumentPropagatorCodeScannerModeling(AppView<AppInfoWithLiveness> appView) {
-    this.composeModeling =
-        appView
-                .options()
-                .getJetpackComposeOptions()
-                .isModelingChangedArgumentsToComposableFunctions()
-            ? new ArgumentPropagatorComposeModeling(appView)
-            : null;
-  }
-
-  NonEmptyValueState modelParameterStateForArgumentToFunction(
-      InvokeMethod invoke,
-      ProgramMethod singleTarget,
-      int argumentIndex,
-      Value argument,
-      ProgramMethod context) {
-    if (composeModeling != null) {
-      return composeModeling.modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
-          invoke, singleTarget, argumentIndex, argument, context);
-    }
-    return null;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java
index 6830918..7ffaddd 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
 
 public interface AbstractFunction extends InFlow {
 
@@ -12,17 +14,14 @@
     return IdentityAbstractFunction.get();
   }
 
-  static UnknownAbstractFunction unknown() {
-    return UnknownAbstractFunction.get();
-  }
-
   /**
-   * Applies the current abstract function to its declared inputs (in {@link #getBaseInFlow()}).
+   * Applies the current abstract function to its declared inputs (from {@link
+   * #traverseBaseInFlow}).
    *
    * <p>It is guaranteed by the caller that the given {@param state} is the abstract state for the
    * field or parameter that caused this function to be reevaluated. If this abstract function takes
    * a single input, then {@param state} is guaranteed to be the state for the node returned by
-   * {@link #getBaseInFlow()}, and {@param flowGraphStateProvider} should never be used.
+   * {@link #traverseBaseInFlow}, and {@param flowGraphStateProvider} should never be used.
    *
    * <p>Abstract functions that depend on multiple inputs can lookup the state for each input in
    * {@param flowGraphStateProvider}. Attempting to lookup the state of a non-declared input is an
@@ -31,16 +30,8 @@
   ValueState apply(
       AppView<AppInfoWithLiveness> appView,
       FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState inState);
-
-  /** Returns true if the given {@param inFlow} is a declared input of this abstract function. */
-  boolean verifyContainsBaseInFlow(BaseInFlow inFlow);
-
-  /**
-   * Returns the program field or parameter graph nodes that this function depends on. Upon any
-   * change to the abstract state of any of these nodes this abstract function must be re-evaluated.
-   */
-  Iterable<BaseInFlow> getBaseInFlow();
+      ConcreteValueState inState,
+      DexType outStaticType);
 
   default boolean usesFlowGraphStateProvider() {
     return false;
@@ -59,4 +50,16 @@
   default boolean isIdentity() {
     return false;
   }
+
+  default boolean isUpdateChangedFlags() {
+    return false;
+  }
+
+  /** Verifies that {@param stoppingCriterion} is a declared input of this abstract function. */
+  default boolean verifyContainsBaseInFlow(BaseInFlow stoppingCriterion) {
+    assert traverseBaseInFlow(
+            inFlow -> TraversalContinuation.breakIf(inFlow.equals(stoppingCriterion)))
+        .shouldBreak();
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
index 19f1a3d..645d3bc 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
@@ -34,6 +34,10 @@
     if (inState.isUnknown()) {
       return inState;
     }
+    if (inState.isUnused()) {
+      assert inState.identical(unusedArrayTypeState());
+      return inState;
+    }
     assert inState.isConcrete();
     assert inState.asConcrete().isReferenceState();
     ConcreteReferenceTypeValueState concreteState = inState.asConcrete().asReferenceState();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
index 6eca904..1019811 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
@@ -36,6 +36,10 @@
     if (inState.isUnknown()) {
       return inState;
     }
+    if (inState.isUnused()) {
+      assert inState.identical(unusedClassTypeState());
+      return inState;
+    }
     assert inState.isConcrete();
     assert inState.asConcrete().isReferenceState();
     ConcreteReferenceTypeValueState concreteState = inState.asConcrete().asReferenceState();
@@ -60,7 +64,6 @@
     if (outStaticType != null) {
       return WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, outStaticType);
     } else {
-      assert !joinedDynamicType.isUnknown();
       return joinedDynamicType;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
index b429da0..08f7a1d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
@@ -34,6 +34,10 @@
     if (inState.isUnknown()) {
       return inState;
     }
+    if (inState.isUnused()) {
+      assert inState.identical(unusedPrimitiveTypeState());
+      return inState;
+    }
     assert inState.isPrimitiveState();
     return cloner.mutableCopy(inState);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java
index 2586f8d..c6d7ba6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/CastAbstractFunction.java
@@ -6,8 +6,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collections;
+import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.Objects;
+import java.util.function.Function;
 
 public class CastAbstractFunction implements AbstractFunction {
 
@@ -23,19 +24,15 @@
   public ValueState apply(
       AppView<AppInfoWithLiveness> appView,
       FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState predecessorState) {
+      ConcreteValueState predecessorState,
+      DexType outStaticType) {
     return predecessorState.asReferenceState().cast(appView, type);
   }
 
   @Override
-  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
-    assert inFlow.equals(this.inFlow);
-    return true;
-  }
-
-  @Override
-  public Iterable<BaseInFlow> getBaseInFlow() {
-    return Collections.singleton(inFlow);
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    return inFlow.traverseBaseInFlow(fn);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
index df2c3b8..065159e 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
@@ -10,9 +10,11 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 public abstract class ConcreteValueState extends NonEmptyValueState {
@@ -84,6 +86,18 @@
     return inFlow;
   }
 
+  public final <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    TraversalContinuation<TB, TC> traversalContinuation = TraversalContinuation.doContinue();
+    for (InFlow inFlow : getInFlow()) {
+      traversalContinuation = inFlow.traverseBaseInFlow(fn);
+      if (traversalContinuation.shouldBreak()) {
+        break;
+      }
+    }
+    return traversalContinuation;
+  }
+
   public abstract BottomValueState getCorrespondingBottom();
 
   public abstract ConcreteParameterStateKind getKind();
@@ -131,7 +145,7 @@
       DexType outStaticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (inState.isBottom()) {
+    if (inState.isBottom() || inState.isUnused()) {
       return this;
     }
     if (inState.isUnknown()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
index 99fcf7b..c066906 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
@@ -3,11 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Function;
 
 // TODO(b/296030319): Change DexField to implement InFlow and use DexField in all places instead of
 //  FieldValue to avoid wrappers? This would also remove the need for the FieldValueFactory.
-public class FieldValue implements BaseInFlow {
+public class FieldValue implements BaseInFlow, ComputationTreeNode {
 
   private final DexField field;
 
@@ -15,6 +21,12 @@
     this.field = field;
   }
 
+  @Override
+  public AbstractValue evaluate(
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
+    return flowGraphStateProvider.getState(field).getAbstractValue(appView);
+  }
+
   public DexField getField() {
     return field;
   }
@@ -25,11 +37,21 @@
   }
 
   @Override
+  public BaseInFlow getSingleOpenVariable() {
+    return this;
+  }
+
+  @Override
   public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
     return field.compareTo(other.asFieldValue().getField());
   }
 
   @Override
+  public boolean isComputationLeaf() {
+    return true;
+  }
+
+  @Override
   public boolean isFieldValue() {
     return true;
   }
@@ -45,6 +67,12 @@
   }
 
   @Override
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    return fn.apply(this);
+  }
+
+  @Override
   @SuppressWarnings("EqualsGetClass")
   public boolean equals(Object obj) {
     if (this == obj) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
index 385c957..639765b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.FlowGraph;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Supplier;
@@ -18,7 +21,8 @@
     // If the abstract function needs to perform state lookups, we restrict state lookups to the
     // declared base in flow. This is important for arriving at the correct fix point.
     if (abstractFunction.usesFlowGraphStateProvider()) {
-      assert abstractFunction.isIfThenElseAbstractFunction()
+      assert abstractFunction.isAbstractComputation()
+          || abstractFunction.isIfThenElseAbstractFunction()
           || abstractFunction.isInstanceFieldReadAbstractFunction();
       return new FlowGraphStateProvider() {
 
@@ -38,10 +42,7 @@
     }
     // Otherwise, the abstract function is a canonical function, or the abstract function has a
     // single declared input, meaning we should never perform any state lookups.
-    assert abstractFunction.isIdentity()
-        || abstractFunction.isCastAbstractFunction()
-        || abstractFunction.isUnknownAbstractFunction()
-        || abstractFunction.isUpdateChangedFlagsAbstractFunction();
+    assert abstractFunction.isIdentity() || abstractFunction.isCastAbstractFunction();
     return new FlowGraphStateProvider() {
 
       @Override
@@ -57,6 +58,31 @@
     };
   }
 
+  static FlowGraphStateProvider createFromMethodOptimizationInfo(ProgramMethod method) {
+    return new FlowGraphStateProvider() {
+
+      @Override
+      public ValueState getState(DexField field) {
+        return ValueState.unknown();
+      }
+
+      @Override
+      public ValueState getState(
+          MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider) {
+        if (methodParameter.getMethod().isNotIdenticalTo(method.getReference())) {
+          return ValueState.unknown();
+        }
+        MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
+        AbstractValue abstractValue =
+            optimizationInfo.getArgumentInfos().getAbstractArgumentValue(methodParameter);
+        if (abstractValue.isUnknown()) {
+          return ValueState.unknown();
+        }
+        return ConcreteValueState.create(methodParameter.getType(), abstractValue);
+      }
+    };
+  }
+
   ValueState getState(DexField field);
 
   ValueState getState(MethodParameter methodParameter, Supplier<ValueState> defaultStateProvider);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
index f31b821..8d37ae7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Function;
 
 public class IdentityAbstractFunction implements AbstractFunction {
 
@@ -21,17 +24,14 @@
   public ValueState apply(
       AppView<AppInfoWithLiveness> appView,
       FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState inState) {
+      ConcreteValueState inState,
+      DexType outStaticType) {
     return inState;
   }
 
   @Override
-  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
-    throw new Unreachable();
-  }
-
-  @Override
-  public Iterable<BaseInFlow> getBaseInFlow() {
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
index 4d21db9..a9b7dfa 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IfThenElseAbstractFunction.java
@@ -9,9 +9,9 @@
 import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
-import java.util.List;
+import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.Objects;
+import java.util.function.Function;
 
 /**
  * Represents a ternary expression (exp ? u : v). The {@link #condition} is an expression containing
@@ -19,6 +19,7 @@
  * true, then `u` is chosen. If the abstract value is false, then `v` is chosen. Otherwise, the
  * result is unknown.
  */
+// TODO(b/302281503): Replace this by a ComputationTreeNode.
 // TODO(b/302281503): Evaluate the impact of using the join of `u` and `v` instead of unknown when
 //  the condition does not evaluate to true or false.
 public class IfThenElseAbstractFunction implements AbstractFunction {
@@ -42,8 +43,9 @@
   public ValueState apply(
       AppView<AppInfoWithLiveness> appView,
       FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState inState) {
-    AbstractValue conditionValue = evaluateCondition(appView, flowGraphStateProvider);
+      ConcreteValueState inState,
+      DexType outStaticType) {
+    AbstractValue conditionValue = condition.evaluate(appView, flowGraphStateProvider);
     NonEmptyValueState resultState;
     if (conditionValue.isTrue()) {
       resultState = thenState;
@@ -60,30 +62,14 @@
     if (!concreteResultState.hasInFlow()) {
       return concreteResultState;
     }
-    return resolveInFlow(appView, flowGraphStateProvider, concreteResultState);
-  }
-
-  private AbstractValue evaluateCondition(
-      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
-    MethodParameter openVariable = condition.getSingleOpenVariable();
-    assert openVariable != null;
-    ValueState variableState = flowGraphStateProvider.getState(openVariable, () -> null);
-    if (variableState == null) {
-      // TODO(b/302281503): Conservatively return unknown for now. Investigate exactly when this
-      //  happens and whether we can return something more precise instead of unknown.
-      assert false;
-      return AbstractValue.unknown();
-    }
-    AbstractValue variableValue = variableState.getAbstractValue(appView);
-    // Since the condition is guaranteed to have a single open variable we simply return the
-    // `variableValue` for any given argument index.
-    return condition.evaluate(i -> variableValue, appView.abstractValueFactory());
+    return resolveInFlow(appView, flowGraphStateProvider, concreteResultState, outStaticType);
   }
 
   private ValueState resolveInFlow(
       AppView<AppInfoWithLiveness> appView,
       FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState resultStateWithInFlow) {
+      ConcreteValueState resultStateWithInFlow,
+      DexType outStaticType) {
     ValueState resultStateWithoutInFlow = resultStateWithInFlow.mutableCopyWithoutInFlow();
     for (InFlow inFlow : resultStateWithInFlow.getInFlow()) {
       // We currently only allow the primitive kinds of in flow (fields and method parameters) to
@@ -91,13 +77,11 @@
       assert inFlow.isBaseInFlow();
       ValueState inFlowState = flowGraphStateProvider.getState(inFlow.asBaseInFlow(), () -> null);
       if (inFlowState == null) {
-        assert false;
         return ValueState.unknown();
       }
       // TODO(b/302281503): The IfThenElseAbstractFunction is only used on input to base in flow.
       //  We should set  the `outStaticType` to the static type of the current field/parameter.
       DexType inStaticType = null;
-      DexType outStaticType = null;
       resultStateWithoutInFlow =
           resultStateWithoutInFlow.mutableJoin(
               appView, inFlowState, inStaticType, outStaticType, StateCloner.getCloner());
@@ -106,27 +90,25 @@
   }
 
   @Override
-  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
-    // TODO(b/302281503): Implement this.
-    return true;
-  }
-
-  @Override
-  public Iterable<BaseInFlow> getBaseInFlow() {
-    List<BaseInFlow> baseInFlow = ListUtils.newArrayList(condition.getSingleOpenVariable());
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    TraversalContinuation<TB, TC> traversalContinuation = condition.traverseBaseInFlow(fn);
+    if (traversalContinuation.shouldBreak()) {
+      return traversalContinuation;
+    }
     if (thenState.isConcrete()) {
-      for (InFlow inFlow : thenState.asConcrete().getInFlow()) {
-        assert inFlow.isBaseInFlow();
-        baseInFlow.add(inFlow.asBaseInFlow());
+      traversalContinuation = thenState.asConcrete().traverseBaseInFlow(fn);
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
       }
     }
     if (elseState.isConcrete()) {
-      for (InFlow inFlow : elseState.asConcrete().getInFlow()) {
-        assert inFlow.isBaseInFlow();
-        baseInFlow.add(inFlow.asBaseInFlow());
+      traversalContinuation = elseState.asConcrete().traverseBaseInFlow(fn);
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
       }
     }
-    return baseInFlow;
+    return traversalContinuation;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
index 3c28c20..2eb6d54 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
@@ -5,7 +5,9 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.optimize.compose.UpdateChangedFlagsAbstractFunction;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Function;
 
 public interface InFlow {
 
@@ -20,6 +22,14 @@
 
   InFlowKind getKind();
 
+  default boolean isAbstractComputation() {
+    return false;
+  }
+
+  default ComputationTreeNode asAbstractComputation() {
+    return null;
+  }
+
   default boolean isAbstractFunction() {
     return false;
   }
@@ -84,19 +94,10 @@
     return null;
   }
 
-  default OrAbstractFunction asOrAbstractFunction() {
-    return null;
-  }
-
-  default boolean isUnknownAbstractFunction() {
+  default boolean isUnknown() {
     return false;
   }
 
-  default boolean isUpdateChangedFlagsAbstractFunction() {
-    return false;
-  }
-
-  default UpdateChangedFlagsAbstractFunction asUpdateChangedFlagsAbstractFunction() {
-    return null;
-  }
+  <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn);
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowComparator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowComparator.java
index 82a1cc8..1327a44 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowComparator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowComparator.java
@@ -4,18 +4,29 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
 
 public class InFlowComparator implements Comparator<InFlow> {
 
+  private final Map<ComputationTreeNode, SourcePosition> computationTreePositions;
   private final Map<IfThenElseAbstractFunction, SourcePosition> ifThenElsePositions;
 
-  private InFlowComparator(Map<IfThenElseAbstractFunction, SourcePosition> ifThenElsePositions) {
+  private InFlowComparator(
+      Map<ComputationTreeNode, SourcePosition> computationTreePositions,
+      Map<IfThenElseAbstractFunction, SourcePosition> ifThenElsePositions) {
+    this.computationTreePositions = computationTreePositions;
     this.ifThenElsePositions = ifThenElsePositions;
   }
 
+  public SourcePosition getComputationTreePosition(ComputationTreeNode computation) {
+    SourcePosition position = computationTreePositions.get(computation);
+    assert position != null;
+    return position;
+  }
+
   public SourcePosition getIfThenElsePosition(IfThenElseAbstractFunction fn) {
     SourcePosition position = ifThenElsePositions.get(fn);
     assert position != null;
@@ -37,9 +48,16 @@
 
   public static class Builder {
 
+    private final Map<ComputationTreeNode, SourcePosition> computationTreePositions =
+        new HashMap<>();
     private final Map<IfThenElseAbstractFunction, SourcePosition> ifThenElsePositions =
         new HashMap<>();
 
+    public void addComputationTreePosition(
+        ComputationTreeNode computation, SourcePosition position) {
+      computationTreePositions.put(computation, position);
+    }
+
     public void addIfThenElsePosition(IfThenElseAbstractFunction fn, SourcePosition position) {
       synchronized (ifThenElsePositions) {
         ifThenElsePositions.put(fn, position);
@@ -47,7 +65,7 @@
     }
 
     public InFlowComparator build() {
-      return new InFlowComparator(ifThenElsePositions);
+      return new InFlowComparator(computationTreePositions, ifThenElsePositions);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java
index 0714caf..6844aa2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlowKind.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
 public enum InFlowKind {
+  ABSTRACT_COMPUTATION,
   ABSTRACT_FUNCTION_CAST,
   ABSTRACT_FUNCTION_IDENTITY,
   ABSTRACT_FUNCTION_IF_THEN_ELSE,
   ABSTRACT_FUNCTION_INSTANCE_FIELD_READ,
-  ABSTRACT_FUNCTION_OR,
-  ABSTRACT_FUNCTION_UNKNOWN,
-  ABSTRACT_FUNCTION_UPDATE_CHANGED_FLAGS,
   FIELD,
   METHOD_PARAMETER
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
index 382706b..022c2b4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
@@ -5,9 +5,11 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.google.common.collect.Lists;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Function;
 
 public class InstanceFieldReadAbstractFunction implements AbstractFunction {
 
@@ -23,7 +25,8 @@
   public ValueState apply(
       AppView<AppInfoWithLiveness> appView,
       FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState predecessorState) {
+      ConcreteValueState predecessorState,
+      DexType outStaticType) {
     ValueState state = flowGraphStateProvider.getState(receiver, () -> ValueState.bottom(field));
     if (state.isBottom()) {
       return ValueState.bottom(field);
@@ -47,14 +50,13 @@
   }
 
   @Override
-  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
-    assert inFlow.equals(receiver) || inFlow.isFieldValue(field);
-    return true;
-  }
-
-  @Override
-  public Iterable<BaseInFlow> getBaseInFlow() {
-    return Lists.newArrayList(receiver, new FieldValue(field));
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    TraversalContinuation<TB, TC> traversalContinuation = fn.apply(receiver);
+    if (traversalContinuation.shouldContinue()) {
+      traversalContinuation = fn.apply(new FieldValue(field));
+    }
+    return traversalContinuation;
   }
 
   private ValueState getFallbackState(FlowGraphStateProvider flowGraphStateProvider) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
index ed49cea..4b6340b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
@@ -4,15 +4,17 @@
 
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.Objects;
-import java.util.function.IntFunction;
+import java.util.function.Function;
 
 public class MethodParameter implements BaseInFlow, ComputationTreeNode {
 
@@ -30,8 +32,10 @@
     this.isMethodStatic = isMethodStatic;
   }
 
-  public static MethodParameter createStatic(DexMethod method, int index) {
-    return new MethodParameter(method, index, true);
+  @Override
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    return fn.apply(this);
   }
 
   @Override
@@ -48,7 +52,7 @@
   }
 
   @Override
-  public MethodParameter getSingleOpenVariable() {
+  public BaseInFlow getSingleOpenVariable() {
     return this;
   }
 
@@ -58,8 +62,9 @@
 
   @Override
   public AbstractValue evaluate(
-      IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
-    return argumentAssignment.apply(index);
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
+    ValueState state = flowGraphStateProvider.getState(this, () -> ValueState.bottom(getType()));
+    return state.getAbstractValue(appView);
   }
 
   @Override
@@ -78,6 +83,11 @@
   }
 
   @Override
+  public boolean isComputationLeaf() {
+    return true;
+  }
+
+  @Override
   public boolean isMethodParameter() {
     return true;
   }
@@ -93,13 +103,16 @@
   }
 
   @Override
-  @SuppressWarnings({"EqualsGetClass", "ReferenceEquality"})
+  @SuppressWarnings("EqualsGetClass")
   public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
     if (obj == null || getClass() != obj.getClass()) {
       return false;
     }
     MethodParameter methodParameter = (MethodParameter) obj;
-    return method == methodParameter.method && index == methodParameter.index;
+    return method.isIdenticalTo(methodParameter.method) && index == methodParameter.index;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java
deleted file mode 100644
index c8294a4..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.optimize.argumentpropagation.codescanner;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
-import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IterableUtils;
-import java.util.Objects;
-
-/**
- * Encodes the `x | const` abstract function. This is currently used as part of the modeling of
- * updateChangedFlags, since the updateChangedFlags function is invoked with `changedFlags | 1` as
- * an argument.
- */
-public class OrAbstractFunction implements AbstractFunction {
-
-  public final BaseInFlow inFlow;
-  public final SingleNumberValue constant;
-
-  public OrAbstractFunction(BaseInFlow inFlow, SingleNumberValue constant) {
-    this.inFlow = inFlow;
-    this.constant = constant;
-  }
-
-  @Override
-  public OrAbstractFunction asOrAbstractFunction() {
-    return this;
-  }
-
-  @Override
-  public ValueState apply(
-      AppView<AppInfoWithLiveness> appView,
-      FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState inState) {
-    ConcretePrimitiveTypeValueState inPrimitiveState = inState.asPrimitiveState();
-    AbstractValue result =
-        AbstractCalculator.orIntegers(appView, inPrimitiveState.getAbstractValue(), constant);
-    return ConcretePrimitiveTypeValueState.create(result, inPrimitiveState.copyInFlow());
-  }
-
-  @Override
-  public boolean verifyContainsBaseInFlow(BaseInFlow otherInFlow) {
-    if (inFlow.isAbstractFunction()) {
-      assert inFlow.asAbstractFunction().verifyContainsBaseInFlow(otherInFlow);
-    } else {
-      assert inFlow.isBaseInFlow();
-      assert inFlow.equals(otherInFlow);
-    }
-    return true;
-  }
-
-  @Override
-  public Iterable<BaseInFlow> getBaseInFlow() {
-    if (inFlow.isAbstractFunction()) {
-      return inFlow.asAbstractFunction().getBaseInFlow();
-    }
-    return IterableUtils.singleton(inFlow);
-  }
-
-  @Override
-  public InFlowKind getKind() {
-    return InFlowKind.ABSTRACT_FUNCTION_OR;
-  }
-
-  @Override
-  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
-    OrAbstractFunction fn = other.asOrAbstractFunction();
-    int result = inFlow.compareTo(fn.inFlow, comparator);
-    if (result == 0) {
-      result = constant.getIntValue() - fn.constant.getIntValue();
-    }
-    return result;
-  }
-
-  @Override
-  @SuppressWarnings("EqualsGetClass")
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (obj == null || getClass() != obj.getClass()) {
-      return false;
-    }
-    OrAbstractFunction fn = (OrAbstractFunction) obj;
-    return inFlow.equals(fn.inFlow) && constant.equals(fn.constant);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(getClass(), inFlow, constant);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
deleted file mode 100644
index 726ade5..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.optimize.argumentpropagation.codescanner;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public class UnknownAbstractFunction implements AbstractFunction {
-
-  private static final UnknownAbstractFunction INSTANCE = new UnknownAbstractFunction();
-
-  private UnknownAbstractFunction() {}
-
-  static UnknownAbstractFunction get() {
-    return INSTANCE;
-  }
-
-  @Override
-  public ValueState apply(
-      AppView<AppInfoWithLiveness> appView,
-      FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState inState) {
-    return ValueState.unknown();
-  }
-
-  @Override
-  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
-    throw new Unreachable();
-  }
-
-  @Override
-  public Iterable<BaseInFlow> getBaseInFlow() {
-    throw new Unreachable();
-  }
-
-  @Override
-  public InFlowKind getKind() {
-    return InFlowKind.ABSTRACT_FUNCTION_UNKNOWN;
-  }
-
-  @Override
-  public int internalCompareToSameKind(InFlow inFlow, InFlowComparator comparator) {
-    assert this == inFlow;
-    return 0;
-  }
-
-  @Override
-  public boolean isUnknownAbstractFunction() {
-    return true;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedArrayTypeValueState.java
new file mode 100644
index 0000000..e912bca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedArrayTypeValueState.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public class UnusedArrayTypeValueState extends UnusedValueState {
+
+  private static final UnusedArrayTypeValueState INSTANCE = new UnusedArrayTypeValueState();
+
+  private UnusedArrayTypeValueState() {}
+
+  public static UnusedArrayTypeValueState get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public ValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
+      StateCloner cloner,
+      Action onChangedAction) {
+    if (inState.isBottom() || inState.isUnused()) {
+      return this;
+    }
+    if (inState.isUnknown()) {
+      return inState;
+    }
+    assert inState.isConcrete();
+    assert inState.asConcrete().isReferenceState();
+    return ValueState.bottomArrayTypeState()
+        .mutableJoin(appView, inState, inStaticType, outStaticType, cloner, onChangedAction);
+  }
+
+  @Override
+  public String toString() {
+    return "UNUSED(ARRAY)";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedClassTypeValueState.java
new file mode 100644
index 0000000..9e55e43
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedClassTypeValueState.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public class UnusedClassTypeValueState extends UnusedValueState {
+
+  private static final UnusedClassTypeValueState INSTANCE = new UnusedClassTypeValueState();
+
+  private UnusedClassTypeValueState() {}
+
+  public static UnusedClassTypeValueState get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public ValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
+      StateCloner cloner,
+      Action onChangedAction) {
+    if (inState.isBottom() || inState.isUnused()) {
+      return this;
+    }
+    if (inState.isUnknown()) {
+      return inState;
+    }
+    assert inState.isConcrete();
+    assert inState.asConcrete().isReferenceState();
+    return ValueState.bottomClassTypeState()
+        .mutableJoin(appView, inState, inStaticType, outStaticType, cloner, onChangedAction);
+  }
+
+  @Override
+  public String toString() {
+    return "UNUSED(CLASS)";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedPrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedPrimitiveTypeValueState.java
new file mode 100644
index 0000000..1ea268a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedPrimitiveTypeValueState.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public class UnusedPrimitiveTypeValueState extends UnusedValueState {
+
+  private static final UnusedPrimitiveTypeValueState INSTANCE = new UnusedPrimitiveTypeValueState();
+
+  private UnusedPrimitiveTypeValueState() {}
+
+  public static UnusedPrimitiveTypeValueState get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public ValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      ValueState inState,
+      DexType inStaticType,
+      DexType outStaticType,
+      StateCloner cloner,
+      Action onChangedAction) {
+    if (inState.isBottom() || inState.isUnused()) {
+      return this;
+    }
+    if (inState.isUnknown()) {
+      return inState;
+    }
+    assert inState.isConcrete();
+    assert inState.asConcrete().isPrimitiveState();
+    return ValueState.bottomPrimitiveTypeState()
+        .mutableJoin(appView, inState, inStaticType, outStaticType, cloner, onChangedAction);
+  }
+
+  @Override
+  public String toString() {
+    return "UNUSED(PRIMITIVE)";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedValueState.java
new file mode 100644
index 0000000..e4d7a81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnusedValueState.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public abstract class UnusedValueState extends NonEmptyValueState {
+
+  UnusedValueState() {}
+
+  @Override
+  public final AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
+    return AbstractValue.bottom();
+  }
+
+  @Override
+  public final boolean isUnused() {
+    return true;
+  }
+
+  @Override
+  public final ValueState mutableCopy() {
+    return this;
+  }
+
+  @Override
+  public ValueState mutableCopyWithoutInFlow() {
+    return this;
+  }
+
+  @Override
+  public final boolean equals(Object obj) {
+    return this == obj;
+  }
+
+  @Override
+  public final int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
index a0ed391..9876db3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
@@ -16,17 +16,20 @@
 public abstract class ValueState {
 
   public static BottomValueState bottom(ProgramField field) {
-    return bottom(field.getReference());
+    return bottom(field.getType());
   }
 
   public static BottomValueState bottom(DexField field) {
-    DexType fieldType = field.getType();
-    if (fieldType.isArrayType()) {
+    return bottom(field.getType());
+  }
+
+  public static BottomValueState bottom(DexType type) {
+    if (type.isArrayType()) {
       return bottomArrayTypeState();
-    } else if (fieldType.isClassType()) {
+    } else if (type.isClassType()) {
       return bottomClassTypeState();
     } else {
-      assert fieldType.isPrimitiveType();
+      assert type.isPrimitiveType();
       return bottomPrimitiveTypeState();
     }
   }
@@ -51,6 +54,29 @@
     return UnknownValueState.get();
   }
 
+  public static UnusedValueState unused(DexType type) {
+    if (type.isArrayType()) {
+      return unusedArrayTypeState();
+    } else if (type.isClassType()) {
+      return unusedClassTypeState();
+    } else {
+      assert type.isPrimitiveType();
+      return unusedPrimitiveTypeState();
+    }
+  }
+
+  public static UnusedArrayTypeValueState unusedArrayTypeState() {
+    return UnusedArrayTypeValueState.get();
+  }
+
+  public static UnusedClassTypeValueState unusedClassTypeState() {
+    return UnusedClassTypeValueState.get();
+  }
+
+  public static UnusedPrimitiveTypeValueState unusedPrimitiveTypeState() {
+    return UnusedPrimitiveTypeValueState.get();
+  }
+
   public abstract AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView);
 
   public boolean isArrayState() {
@@ -117,6 +143,10 @@
     return false;
   }
 
+  public boolean isUnused() {
+    return false;
+  }
+
   public abstract ValueState mutableCopy();
 
   public abstract ValueState mutableCopyWithoutInFlow();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComposableComputationTreeBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComposableComputationTreeBuilder.java
new file mode 100644
index 0000000..0e5f0f1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComposableComputationTreeBuilder.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.computation;
+
+import static com.android.tools.r8.ir.code.Opcodes.AND;
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.OR;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.path.PathConstraintSupplier;
+import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Or;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
+import com.android.tools.r8.optimize.compose.ComputationTreeUnopUpdateChangedFlagsNode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * Similar to {@link DefaultComputationTreeBuilder} except that this also has support for
+ * int-valued, non-cyclic phis and logical OR instructions.
+ */
+public class ComposableComputationTreeBuilder extends ComputationTreeBuilder {
+
+  private final PathConstraintSupplier pathConstraintSupplier;
+
+  private final Set<Phi> seenPhis = Sets.newIdentityHashSet();
+
+  public ComposableComputationTreeBuilder(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      ProgramMethod method,
+      FieldValueFactory fieldValueFactory,
+      MethodParameterFactory methodParameterFactory,
+      PathConstraintSupplier pathConstraintSupplier) {
+    super(appView, code, method, fieldValueFactory, methodParameterFactory);
+    this.pathConstraintSupplier = pathConstraintSupplier;
+  }
+
+  @Override
+  ComputationTreeNode buildComputationTree(Instruction instruction) {
+    switch (instruction.opcode()) {
+      case AND:
+        {
+          And and = instruction.asAnd();
+          ComputationTreeNode left = getOrBuildComputationTree(and.leftValue());
+          ComputationTreeNode right = getOrBuildComputationTree(and.rightValue());
+          return ComputationTreeLogicalBinopAndNode.create(left, right);
+        }
+      case ARGUMENT:
+        {
+          Argument argument = instruction.asArgument();
+          if (argument.getOutType().isInt()) {
+            return methodParameterFactory.create(method, argument.getIndex());
+          }
+          break;
+        }
+      case CONST_NUMBER:
+        {
+          ConstNumber constNumber = instruction.asConstNumber();
+          if (constNumber.getOutType().isInt()) {
+            return constNumber.getAbstractValue(factory());
+          }
+          break;
+        }
+      case IF:
+        {
+          If theIf = instruction.asIf();
+          if (theIf.isZeroTest()) {
+            ComputationTreeNode operand = getOrBuildComputationTree(theIf.lhs());
+            return ComputationTreeUnopCompareNode.create(operand, theIf.getType());
+          }
+          break;
+        }
+      case INSTANCE_GET:
+        {
+          InstanceGet instanceGet = instruction.asInstanceGet();
+          DexField reference = instanceGet.getField();
+          if (instanceGet.object().isThis() && reference.getType().isIntType()) {
+            ProgramField field = instanceGet.resolveField(appView, method).getProgramField();
+            if (field != null) {
+              return fieldValueFactory.create(field);
+            }
+          }
+          break;
+        }
+      case INVOKE_STATIC:
+        {
+          InvokeStatic invoke = instruction.asInvokeStatic();
+          DexMethod reference = invoke.getInvokedMethod();
+          if (reference.getArity() != 1
+              || !reference.getParameter(0).isIntType()
+              || !reference.getReturnType().isIntType()) {
+            break;
+          }
+          SingleResolutionResult<?> resolutionResult =
+              invoke.resolveMethod(appView, method).asSingleResolution();
+          if (resolutionResult == null) {
+            break;
+          }
+          DexClassAndMethod singleTarget =
+              resolutionResult
+                  .lookupDispatchTarget(appView, invoke, method)
+                  .getSingleDispatchTarget();
+          if (singleTarget == null) {
+            break;
+          }
+          if (!singleTarget.getOptimizationInfo().getAbstractFunction().isUpdateChangedFlags()) {
+            break;
+          }
+          ComputationTreeNode operand = getOrBuildComputationTree(invoke.getFirstArgument());
+          return ComputationTreeUnopUpdateChangedFlagsNode.create(operand);
+        }
+      case OR:
+        {
+          Or or = instruction.asOr();
+          ComputationTreeNode left = getOrBuildComputationTree(or.leftValue());
+          ComputationTreeNode right = getOrBuildComputationTree(or.rightValue());
+          return ComputationTreeLogicalBinopOrNode.create(left, right);
+        }
+      default:
+        break;
+    }
+    return unknown();
+  }
+
+  @Override
+  ComputationTreeNode buildComputationTree(Phi phi) {
+    if (!seenPhis.add(phi) || phi.getOperands().size() != 2 || !phi.getType().isInt()) {
+      return unknown();
+    }
+    ComputationTreeNode left = getOrBuildComputationTree(phi.getOperand(0));
+    ComputationTreeNode right = getOrBuildComputationTree(phi.getOperand(1));
+    if (left.isUnknown() && right.isUnknown()) {
+      return unknown();
+    }
+    BasicBlock block = phi.getBlock();
+    ComputationTreeNode condition =
+        pathConstraintSupplier.getDifferentiatingPathConstraint(
+            block.getPredecessor(0), block.getPredecessor(1));
+    return ComputationTreeLogicalBinopIntPhiNode.create(condition, left, right);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBaseNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBaseNode.java
index 2228c3e..f5c5870 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBaseNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBaseNode.java
@@ -3,14 +3,40 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.computation;
 
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowKind;
+
 /**
  * Represents a computation tree with no open variables other than the arguments of a given method.
  */
 abstract class ComputationTreeBaseNode implements ComputationTreeNode {
 
   @Override
+  public InFlowKind getKind() {
+    return InFlowKind.ABSTRACT_COMPUTATION;
+  }
+
+  @Override
+  public int internalCompareToSameKind(InFlow inFlow, InFlowComparator comparator) {
+    SourcePosition position = comparator.getComputationTreePosition(this);
+    SourcePosition otherPosition =
+        comparator.getComputationTreePosition(inFlow.asAbstractComputation());
+    return position.compareTo(otherPosition);
+  }
+
+  @Override
+  public final boolean isComputationLeaf() {
+    return false;
+  }
+
+  @Override
   public abstract boolean equals(Object obj);
 
   @Override
   public abstract int hashCode();
+
+  @Override
+  public abstract String toString();
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBuilder.java
index 74804a2..b56c7c0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBuilder.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBuilder.java
@@ -3,102 +3,80 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.computation;
 
-import static com.android.tools.r8.ir.code.Opcodes.AND;
-import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
-import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
-import static com.android.tools.r8.ir.code.Opcodes.IF;
-
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.ir.code.And;
-import com.android.tools.r8.ir.code.Argument;
-import com.android.tools.r8.ir.code.ConstNumber;
-import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionOrValue;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-public class ComputationTreeBuilder {
+public abstract class ComputationTreeBuilder {
 
-  private final AbstractValueFactory abstractValueFactory;
-  private final ProgramMethod method;
-  private final MethodParameterFactory methodParameterFactory;
+  final AppView<AppInfoWithLiveness> appView;
+  final IRCode code;
+  final ProgramMethod method;
+  final FieldValueFactory fieldValueFactory;
+  final MethodParameterFactory methodParameterFactory;
 
-  private final Map<Instruction, ComputationTreeNode> cache = new IdentityHashMap<>();
+  private final Map<InstructionOrValue, ComputationTreeNode> cache = new IdentityHashMap<>();
 
   public ComputationTreeBuilder(
-      AbstractValueFactory abstractValueFactory,
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
       ProgramMethod method,
+      FieldValueFactory fieldValueFactory,
       MethodParameterFactory methodParameterFactory) {
-    this.abstractValueFactory = abstractValueFactory;
+    this.appView = appView;
+    this.code = code;
     this.method = method;
+    this.fieldValueFactory = fieldValueFactory;
     this.methodParameterFactory = methodParameterFactory;
   }
 
+  AbstractValueFactory factory() {
+    return appView.abstractValueFactory();
+  }
+
   // TODO(b/302281503): "Long lived" computation trees (i.e., the ones that survive past the IR
   //  conversion of the current method) should be canonicalized.
-  public ComputationTreeNode getOrBuildComputationTree(Instruction instruction) {
-    ComputationTreeNode existing = cache.get(instruction);
+  public final ComputationTreeNode getOrBuildComputationTree(
+      InstructionOrValue instructionOrValue) {
+    ComputationTreeNode existing = cache.get(instructionOrValue);
     if (existing != null) {
       return existing;
     }
-    ComputationTreeNode result = buildComputationTree(instruction);
-    cache.put(instruction, result);
+    ComputationTreeNode result = buildComputationTree(instructionOrValue);
+    cache.put(instructionOrValue, result);
     return result;
   }
 
-  private ComputationTreeNode buildComputationTree(Instruction instruction) {
-    switch (instruction.opcode()) {
-      case AND:
-        {
-          And and = instruction.asAnd();
-          ComputationTreeNode left = buildComputationTreeFromValue(and.leftValue());
-          ComputationTreeNode right = buildComputationTreeFromValue(and.rightValue());
-          return ComputationTreeLogicalBinopAndNode.create(left, right);
-        }
-      case ARGUMENT:
-        {
-          Argument argument = instruction.asArgument();
-          if (argument.getOutType().isInt()) {
-            return methodParameterFactory.create(method, argument.getIndex());
-          }
-          break;
-        }
-      case CONST_NUMBER:
-        {
-          ConstNumber constNumber = instruction.asConstNumber();
-          if (constNumber.getOutType().isInt()) {
-            return constNumber.getAbstractValue(abstractValueFactory);
-          }
-          break;
-        }
-      case IF:
-        {
-          If theIf = instruction.asIf();
-          if (theIf.isZeroTest()) {
-            ComputationTreeNode operand = buildComputationTreeFromValue(theIf.lhs());
-            return ComputationTreeUnopCompareNode.create(operand, theIf.getType());
-          }
-          break;
-        }
-      default:
-        break;
+  private ComputationTreeNode buildComputationTree(InstructionOrValue instructionOrValue) {
+    if (instructionOrValue.isInstruction()) {
+      return buildComputationTree(instructionOrValue.asInstruction());
+    } else {
+      Value value = instructionOrValue.asValue();
+      if (value.isPhi()) {
+        return buildComputationTree(value.asPhi());
+      } else {
+        return buildComputationTree(value.getDefinition());
+      }
     }
-    return AbstractValue.unknown();
   }
 
-  private ComputationTreeNode buildComputationTreeFromValue(Value value) {
-    if (value.isPhi()) {
-      return unknown();
-    }
-    return getOrBuildComputationTree(value.getDefinition());
-  }
+  abstract ComputationTreeNode buildComputationTree(Instruction instruction);
 
-  private static UnknownValue unknown() {
+  abstract ComputationTreeNode buildComputationTree(Phi phi);
+
+  static UnknownValue unknown() {
     return AbstractValue.unknown();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopAndNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopAndNode.java
index f18f081..9c60201 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopAndNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopAndNode.java
@@ -3,11 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.computation;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Objects;
-import java.util.function.IntFunction;
 
 public class ComputationTreeLogicalBinopAndNode extends ComputationTreeLogicalBinopNode {
 
@@ -24,11 +25,17 @@
 
   @Override
   public AbstractValue evaluate(
-      IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
     assert getNumericType().isInt();
-    AbstractValue leftValue = left.evaluate(argumentAssignment, abstractValueFactory);
-    AbstractValue rightValue = right.evaluate(argumentAssignment, abstractValueFactory);
-    return AbstractCalculator.andIntegers(abstractValueFactory, leftValue, rightValue);
+    AbstractValue leftValue = left.evaluate(appView, flowGraphStateProvider);
+    if (leftValue.isBottom()) {
+      return leftValue;
+    }
+    AbstractValue rightValue = right.evaluate(appView, flowGraphStateProvider);
+    if (rightValue.isBottom()) {
+      return rightValue;
+    }
+    return AbstractCalculator.andIntegers(appView, leftValue, rightValue);
   }
 
   @Override
@@ -47,4 +54,9 @@
   public int hashCode() {
     return Objects.hash(getClass(), left, right);
   }
+
+  @Override
+  public String toString() {
+    return left.toStringWithParenthesis() + " & " + right.toStringWithParenthesis();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopIntPhiNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopIntPhiNode.java
new file mode 100644
index 0000000..fb42934
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopIntPhiNode.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.computation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueConstantPropagationJoiner;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.Objects;
+import java.util.function.Function;
+
+public class ComputationTreeLogicalBinopIntPhiNode extends ComputationTreeLogicalBinopNode {
+
+  private final ComputationTreeNode condition;
+
+  private ComputationTreeLogicalBinopIntPhiNode(
+      ComputationTreeNode condition, ComputationTreeNode left, ComputationTreeNode right) {
+    super(left, right);
+    this.condition = condition;
+  }
+
+  public static ComputationTreeNode create(
+      ComputationTreeNode condition, ComputationTreeNode left, ComputationTreeNode right) {
+    if (left.isUnknown() && right.isUnknown()) {
+      return AbstractValue.unknown();
+    }
+    return new ComputationTreeLogicalBinopIntPhiNode(condition, left, right);
+  }
+
+  @Override
+  public AbstractValue evaluate(
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
+    AbstractValue result = condition.evaluate(appView, flowGraphStateProvider);
+    if (result.isBottom()) {
+      return AbstractValue.bottom();
+    } else if (result.isTrue()) {
+      return left.evaluate(appView, flowGraphStateProvider);
+    } else if (result.isFalse()) {
+      return right.evaluate(appView, flowGraphStateProvider);
+    } else {
+      AbstractValueConstantPropagationJoiner joiner =
+          appView.getAbstractValueConstantPropagationJoiner();
+      AbstractValue leftValue = left.evaluate(appView, flowGraphStateProvider);
+      AbstractValue rightValue = right.evaluate(appView, flowGraphStateProvider);
+      return joiner.join(leftValue, rightValue, TypeElement.getInt());
+    }
+  }
+
+  @Override
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    TraversalContinuation<TB, TC> traversalContinuation = condition.traverseBaseInFlow(fn);
+    if (traversalContinuation.shouldContinue()) {
+      traversalContinuation = super.traverseBaseInFlow(fn);
+    }
+    return traversalContinuation;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ComputationTreeLogicalBinopIntPhiNode)) {
+      return false;
+    }
+    ComputationTreeLogicalBinopIntPhiNode node = (ComputationTreeLogicalBinopIntPhiNode) obj;
+    return condition.equals(node.condition) && internalIsEqualTo(node);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), condition, left, right);
+  }
+
+  @Override
+  public String toString() {
+    return condition.toStringWithParenthesis()
+        + " ? "
+        + left.toStringWithParenthesis()
+        + " : "
+        + right.toStringWithParenthesis();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopNode.java
index d7cf46b..a2504cb 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopNode.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.optimize.argumentpropagation.computation;
 
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Function;
 
 public abstract class ComputationTreeLogicalBinopNode extends ComputationTreeBaseNode {
 
@@ -17,13 +19,23 @@
     this.right = right;
   }
 
+  @Override
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    TraversalContinuation<TB, TC> traversalContinuation = left.traverseBaseInFlow(fn);
+    if (traversalContinuation.shouldContinue()) {
+      traversalContinuation = right.traverseBaseInFlow(fn);
+    }
+    return traversalContinuation;
+  }
+
   public NumericType getNumericType() {
     return NumericType.INT;
   }
 
   @Override
-  public final MethodParameter getSingleOpenVariable() {
-    MethodParameter openVariable = left.getSingleOpenVariable();
+  public final BaseInFlow getSingleOpenVariable() {
+    BaseInFlow openVariable = left.getSingleOpenVariable();
     if (openVariable != null) {
       return right.getSingleOpenVariable() == null ? openVariable : null;
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopOrNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopOrNode.java
new file mode 100644
index 0000000..3d7c3fe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopOrNode.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.computation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Objects;
+
+public class ComputationTreeLogicalBinopOrNode extends ComputationTreeLogicalBinopNode {
+
+  private ComputationTreeLogicalBinopOrNode(ComputationTreeNode left, ComputationTreeNode right) {
+    super(left, right);
+  }
+
+  public static ComputationTreeNode create(ComputationTreeNode left, ComputationTreeNode right) {
+    if (left.isUnknown() && right.isUnknown()) {
+      return AbstractValue.unknown();
+    }
+    return new ComputationTreeLogicalBinopOrNode(left, right);
+  }
+
+  @Override
+  public AbstractValue evaluate(
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
+    assert getNumericType().isInt();
+    AbstractValue leftValue = left.evaluate(appView, flowGraphStateProvider);
+    if (leftValue.isBottom()) {
+      return leftValue;
+    }
+    AbstractValue rightValue = right.evaluate(appView, flowGraphStateProvider);
+    if (rightValue.isBottom()) {
+      return rightValue;
+    }
+    return AbstractCalculator.orIntegers(appView, leftValue, rightValue);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ComputationTreeLogicalBinopOrNode)) {
+      return false;
+    }
+    ComputationTreeLogicalBinopOrNode node = (ComputationTreeLogicalBinopOrNode) obj;
+    return internalIsEqualTo(node);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), left, right);
+  }
+
+  @Override
+  public String toString() {
+    return left.toStringWithParenthesis() + " | " + right.toStringWithParenthesis();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
index 1231d76..a22b633 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
@@ -3,23 +3,80 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.computation;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
-import java.util.function.IntFunction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 /**
  * Represents a computation tree with no open variables other than the arguments of a given method.
  */
-public interface ComputationTreeNode {
+public interface ComputationTreeNode extends AbstractFunction {
+
+  @Override
+  default ValueState apply(
+      AppView<AppInfoWithLiveness> appView,
+      FlowGraphStateProvider flowGraphStateProvider,
+      ConcreteValueState inState,
+      DexType outStaticType) {
+    AbstractValue abstractValue = evaluate(appView, flowGraphStateProvider);
+    if (abstractValue.isBottom()) {
+      return ValueState.bottom(outStaticType);
+    } else if (abstractValue.isUnknown()) {
+      return ValueState.unknown();
+    } else {
+      if (outStaticType.isArrayType()) {
+        // We currently do not track abstract values for array types.
+        return ValueState.unknown();
+      } else if (outStaticType.isClassType()) {
+        return ConcreteClassTypeValueState.create(abstractValue, DynamicType.unknown());
+      } else {
+        assert outStaticType.isPrimitiveType();
+        return ConcretePrimitiveTypeValueState.create(abstractValue);
+      }
+    }
+  }
 
   /** Evaluates the current computation tree on the given argument assignment. */
   AbstractValue evaluate(
-      IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory);
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider);
 
-  MethodParameter getSingleOpenVariable();
+  BaseInFlow getSingleOpenVariable();
 
-  default boolean isUnknown() {
+  @Override
+  default boolean isAbstractComputation() {
+    return true;
+  }
+
+  @Override
+  default ComputationTreeNode asAbstractComputation() {
+    return this;
+  }
+
+  default boolean isArgumentBitSetCompareNode() {
     return false;
   }
+
+  boolean isComputationLeaf();
+
+  @Override
+  default boolean usesFlowGraphStateProvider() {
+    return true;
+  }
+
+  default String toStringWithParenthesis() {
+    if (isComputationLeaf()) {
+      return toString();
+    } else {
+      return "(" + this + ")";
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
index fb4c465..7b4705f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
@@ -3,11 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.computation;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
 import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Objects;
-import java.util.function.IntFunction;
 
 public class ComputationTreeUnopCompareNode extends ComputationTreeUnopNode {
 
@@ -27,9 +30,26 @@
 
   @Override
   public AbstractValue evaluate(
-      IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
-    AbstractValue operandValue = operand.evaluate(argumentAssignment, abstractValueFactory);
-    return type.evaluate(operandValue, abstractValueFactory);
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
+    AbstractValue operandValue = operand.evaluate(appView, flowGraphStateProvider);
+    if (operandValue.isBottom()) {
+      return operandValue;
+    }
+    return type.evaluate(operandValue, appView);
+  }
+
+  @Override
+  public boolean isArgumentBitSetCompareNode() {
+    if (!type.isEqualsOrNotEquals() || !(operand instanceof ComputationTreeLogicalBinopAndNode)) {
+      return false;
+    }
+    ComputationTreeLogicalBinopAndNode andOperand = (ComputationTreeLogicalBinopAndNode) operand;
+    return andOperand.left instanceof MethodParameter
+        && andOperand.right instanceof SingleNumberValue;
+  }
+
+  public ComputationTreeUnopCompareNode negate() {
+    return new ComputationTreeUnopCompareNode(operand, type.inverted());
   }
 
   @Override
@@ -48,4 +68,9 @@
   public int hashCode() {
     return Objects.hash(getClass(), operand, type);
   }
+
+  @Override
+  public String toString() {
+    return operand.toStringWithParenthesis() + " " + type.getSymbol() + " 0";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopNode.java
index 8ac1ca9..71bd020 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopNode.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopNode.java
@@ -3,19 +3,27 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation.computation;
 
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Function;
 
 public abstract class ComputationTreeUnopNode extends ComputationTreeBaseNode {
 
-  final ComputationTreeNode operand;
+  protected final ComputationTreeNode operand;
 
-  ComputationTreeUnopNode(ComputationTreeNode operand) {
+  protected ComputationTreeUnopNode(ComputationTreeNode operand) {
     assert !operand.isUnknown();
     this.operand = operand;
   }
 
   @Override
-  public MethodParameter getSingleOpenVariable() {
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    return operand.traverseBaseInFlow(fn);
+  }
+
+  @Override
+  public BaseInFlow getSingleOpenVariable() {
     return operand.getSingleOpenVariable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/DefaultComputationTreeBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/DefaultComputationTreeBuilder.java
new file mode 100644
index 0000000..e34cc2c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/DefaultComputationTreeBuilder.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.argumentpropagation.computation;
+
+import static com.android.tools.r8.ir.code.Opcodes.AND;
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class DefaultComputationTreeBuilder extends ComputationTreeBuilder {
+
+  public DefaultComputationTreeBuilder(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      ProgramMethod method,
+      FieldValueFactory fieldValueFactory,
+      MethodParameterFactory methodParameterFactory) {
+    super(appView, code, method, fieldValueFactory, methodParameterFactory);
+  }
+
+  @Override
+  ComputationTreeNode buildComputationTree(Instruction instruction) {
+    switch (instruction.opcode()) {
+      case AND:
+        {
+          And and = instruction.asAnd();
+          ComputationTreeNode left = getOrBuildComputationTree(and.leftValue());
+          ComputationTreeNode right = getOrBuildComputationTree(and.rightValue());
+          return ComputationTreeLogicalBinopAndNode.create(left, right);
+        }
+      case ARGUMENT:
+        {
+          Argument argument = instruction.asArgument();
+          if (argument.getOutType().isInt()) {
+            return methodParameterFactory.create(method, argument.getIndex());
+          }
+          break;
+        }
+      case CONST_NUMBER:
+        {
+          ConstNumber constNumber = instruction.asConstNumber();
+          if (constNumber.getOutType().isInt()) {
+            return constNumber.getAbstractValue(factory());
+          }
+          break;
+        }
+      case IF:
+        {
+          If theIf = instruction.asIf();
+          if (theIf.isZeroTest()) {
+            ComputationTreeNode operand = getOrBuildComputationTree(theIf.lhs());
+            return ComputationTreeUnopCompareNode.create(operand, theIf.getType());
+          }
+          break;
+        }
+      default:
+        break;
+    }
+    return unknown();
+  }
+
+  @Override
+  ComputationTreeNode buildComputationTree(Phi phi) {
+    return unknown();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
index 5830d3f..c8389a1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
@@ -159,38 +158,34 @@
 
   // Returns BREAK if the current node has been set to unknown.
   private TraversalContinuation<?, ?> addInFlow(InFlow inFlow, FlowGraphNode node) {
-    if (inFlow.isAbstractFunction()) {
-      return addInFlow(inFlow.asAbstractFunction(), node);
-    } else if (inFlow.isFieldValue()) {
+    if (inFlow.isFieldValue()) {
       return addInFlow(inFlow.asFieldValue(), node);
     } else if (inFlow.isMethodParameter()) {
       return addInFlow(inFlow.asMethodParameter(), node);
+    } else if (inFlow.isAbstractFunction()) {
+      return addInFlow(inFlow.asAbstractFunction(), node);
     } else {
       throw new Unreachable(inFlow.getClass().getTypeName());
     }
   }
 
   private TraversalContinuation<?, ?> addInFlow(AbstractFunction inFlow, FlowGraphNode node) {
-    for (BaseInFlow baseInFlow : inFlow.getBaseInFlow()) {
-      TraversalContinuation<?, ?> traversalContinuation;
-      if (baseInFlow.isFieldValue()) {
-        traversalContinuation = addInFlow(baseInFlow.asFieldValue(), node, inFlow);
-      } else {
-        assert baseInFlow.isMethodParameter();
-        traversalContinuation = addInFlow(baseInFlow.asMethodParameter(), node, inFlow);
-      }
-      if (traversalContinuation.shouldBreak()) {
-        return traversalContinuation;
-      }
-    }
-    return TraversalContinuation.doContinue();
+    return inFlow.traverseBaseInFlow(
+        baseInFlow -> {
+          if (baseInFlow.isFieldValue()) {
+            return addInFlow(baseInFlow.asFieldValue(), node, inFlow);
+          } else {
+            assert baseInFlow.isMethodParameter();
+            return addInFlow(baseInFlow.asMethodParameter(), node, inFlow);
+          }
+        });
   }
 
-  private TraversalContinuation<?, ?> addInFlow(FieldValue inFlow, FlowGraphNode node) {
+  private <TB, TC> TraversalContinuation<TB, TC> addInFlow(FieldValue inFlow, FlowGraphNode node) {
     return addInFlow(inFlow, node, AbstractFunction.identity());
   }
 
-  private TraversalContinuation<?, ?> addInFlow(
+  private <TB, TC> TraversalContinuation<TB, TC> addInFlow(
       FieldValue inFlow, FlowGraphNode node, AbstractFunction transferFunction) {
     assert !node.isUnknown();
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index 82f2350..fef91ce 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -253,7 +253,8 @@
       FlowGraphStateProvider flowGraphStateProvider =
           FlowGraphStateProvider.create(flowGraph, transferFunction);
       ValueState transferState =
-          transferFunction.apply(appView, flowGraphStateProvider, stateToPropagate);
+          transferFunction.apply(
+              appView, flowGraphStateProvider, stateToPropagate, successorNode.getStaticType());
       ValueState oldSuccessorStateForDebugging =
           successorNode.getDebug() ? successorNode.getState().mutableCopy() : null;
       if (transferState.isBottom()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagatorDebugUtils.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagatorDebugUtils.java
index a39b534..7687b99 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagatorDebugUtils.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagatorDebugUtils.java
@@ -6,11 +6,11 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.WorkList;
 import java.util.ArrayList;
 import java.util.List;
@@ -86,14 +86,16 @@
       List<String> transferFunctionDependencies = new ArrayList<>();
       transferFunctionDependencies.add("");
       transferFunctionDependencies.add("TRANSFER FN INPUTS:");
-      for (BaseInFlow transferFunctionDependency : transferFunction.getBaseInFlow()) {
-        if (!node.equalsBaseInFlow(transferFunctionDependency)) {
-          ValueState transferFunctionDependencyState =
-              flowGraphStateProvider.getState(transferFunctionDependency, null);
-          transferFunctionDependencies.add("  DEP: " + transferFunctionDependency);
-          transferFunctionDependencies.add("  DEP STATE: " + transferFunctionDependencyState);
-        }
-      }
+      transferFunction.traverseBaseInFlow(
+          transferFunctionDependency -> {
+            if (!node.equalsBaseInFlow(transferFunctionDependency)) {
+              ValueState transferFunctionDependencyState =
+                  flowGraphStateProvider.getState(transferFunctionDependency, null);
+              transferFunctionDependencies.add("  DEP: " + transferFunctionDependency);
+              transferFunctionDependencies.add("  DEP STATE: " + transferFunctionDependencyState);
+            }
+            return TraversalContinuation.doContinue();
+          });
       ValueState newSuccessorState = successorNode.getState();
       log(
           "PROPAGATE CONCRETE",
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
index 1eec104..564ef5f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
@@ -11,12 +11,18 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.path.PathConstraintSupplier;
+import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
 import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeUnopCompareNode;
 import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
@@ -28,6 +34,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 
 /**
  * Analysis to find arguments that are effectively unused. The analysis first computes the
@@ -68,6 +75,14 @@
 
   private final AppView<AppInfoWithLiveness> appView;
 
+  // If the condition for a given method parameter evaluates to true then the parameter is unused,
+  // regardless (!) of the constraints for the method parameter in `constraints`. If the condition
+  // evaluates to false, the method parameter can still be unused if all the constraints for the
+  // method parameter are unused.
+  //
+  // All constraints in this map are currently on the form `(arg & const) != 0`.
+  private final Map<MethodParameter, ComputationTreeNode> conditions = new ConcurrentHashMap<>();
+
   // Maps each method parameter p to the method parameters that must be effectively unused in order
   // for the method parameter p to be effectively unused.
   private final Map<MethodParameter, Set<MethodParameter>> constraints = new ConcurrentHashMap<>();
@@ -107,72 +122,16 @@
         });
   }
 
-  public void scan(ProgramMethod method, IRCode code) {
-    // If this method is not subject to optimization, then don't compute effectively unused
-    // constraints for the method parameters.
-    if (isUnoptimizable(method)) {
-      return;
-    }
-    Iterator<Argument> argumentIterator = code.argumentIterator();
-    while (argumentIterator.hasNext()) {
-      Argument argument = argumentIterator.next();
-      Value argumentValue = argument.outValue();
-      Set<MethodParameter> effectivelyUnusedConstraints =
-          computeEffectivelyUnusedConstraints(method, argument, argumentValue);
-      if (effectivelyUnusedConstraints != null && !effectivelyUnusedConstraints.isEmpty()) {
-        MethodParameter methodParameter = new MethodParameter(method, argument.getIndex());
-        assert !constraints.containsKey(methodParameter);
-        constraints.put(methodParameter, effectivelyUnusedConstraints);
-      }
-    }
-  }
-
-  private Set<MethodParameter> computeEffectivelyUnusedConstraints(
-      ProgramMethod method, Argument argument, Value argumentValue) {
-    if (method.getDefinition().isInstanceInitializer() && argumentValue.isThis()) {
-      return null;
-    }
-    if (!ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, argument.getIndex())) {
-      return null;
-    }
-    if (!argumentValue.getType().isClassType()
-        || argumentValue.hasDebugUsers()
-        || argumentValue.hasPhiUsers()) {
-      return null;
-    }
-    Set<MethodParameter> effectivelyUnusedConstraints = new HashSet<>();
-    for (Instruction user : argumentValue.uniqueUsers()) {
-      if (user.isInvokeMethod()) {
-        InvokeMethod invoke = user.asInvokeMethod();
-        ProgramMethod resolvedMethod =
-            appView
-                .appInfo()
-                .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
-                .getResolvedProgramMethod();
-        if (resolvedMethod == null || isUnoptimizable(resolvedMethod)) {
-          return null;
-        }
-        int dependentArgumentIndex =
-            ListUtils.uniqueIndexMatching(invoke.arguments(), value -> value == argumentValue);
-        if (dependentArgumentIndex < 0
-            || !ParameterRemovalUtils.canRemoveUnusedParameter(
-                appView, resolvedMethod, dependentArgumentIndex)) {
-          return null;
-        }
-        effectivelyUnusedConstraints.add(
-            new MethodParameter(resolvedMethod, dependentArgumentIndex));
-      } else {
-        return null;
-      }
-    }
-    return effectivelyUnusedConstraints;
+  public void scan(
+      ProgramMethod method, IRCode code, PathConstraintSupplier pathConstraintSupplier) {
+    new Analyzer(method, code, pathConstraintSupplier).analyze();
   }
 
   public void computeEffectivelyUnusedArguments(PrunedItems prunedItems) {
     // Build a graph where nodes are method parameters and there is an edge from method parameter p0
     // to method parameter p1 if the removal of p0 depends on the removal of p1.
     EffectivelyUnusedArgumentsGraph dependenceGraph =
-        EffectivelyUnusedArgumentsGraph.create(appView, constraints, prunedItems);
+        EffectivelyUnusedArgumentsGraph.create(appView, conditions, constraints, prunedItems);
 
     // Remove all unoptimizable method parameters from the graph, as well as all nodes that depend
     // on a node that is unoptimable.
@@ -202,17 +161,6 @@
     }
   }
 
-  private boolean isUnoptimizable(ProgramMethod method) {
-    if (method.getDefinition().belongsToDirectPool()) {
-      return !ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method);
-    }
-    if (optimizableVirtualMethods.contains(method)) {
-      assert ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method);
-      return false;
-    }
-    return true;
-  }
-
   public void onMethodPruned(ProgramMethod method) {
     onMethodCodePruned(method);
   }
@@ -221,7 +169,154 @@
     for (int argumentIndex = 0;
         argumentIndex < method.getDefinition().getNumberOfArguments();
         argumentIndex++) {
-      constraints.remove(new MethodParameter(method, argumentIndex));
+      MethodParameter methodParameter = new MethodParameter(method, argumentIndex);
+      conditions.remove(methodParameter);
+      constraints.remove(methodParameter);
+    }
+  }
+
+  private class Analyzer {
+
+    private final ProgramMethod method;
+    private final IRCode code;
+    private final PathConstraintSupplier pathConstraintSupplier;
+
+    private Analyzer(
+        ProgramMethod method, IRCode code, PathConstraintSupplier pathConstraintSupplier) {
+      this.method = method;
+      this.code = code;
+      this.pathConstraintSupplier = pathConstraintSupplier;
+    }
+
+    void analyze() {
+      // If this method is not subject to optimization, then don't compute effectively unused
+      // constraints for the method parameters.
+      if (isUnoptimizable(method)) {
+        return;
+      }
+      Iterator<Argument> argumentIterator = code.argumentIterator();
+      while (argumentIterator.hasNext()) {
+        Argument argument = argumentIterator.next();
+        Value argumentValue = argument.outValue();
+        computeEffectivelyUnusedConstraints(
+            argument,
+            argumentValue,
+            effectivelyUnusedCondition -> {
+              MethodParameter methodParameter = new MethodParameter(method, argument.getIndex());
+              assert !conditions.containsKey(methodParameter);
+              conditions.put(methodParameter, effectivelyUnusedCondition);
+            },
+            effectivelyUnusedConstraints -> {
+              MethodParameter methodParameter = new MethodParameter(method, argument.getIndex());
+              assert !constraints.containsKey(methodParameter);
+              constraints.put(methodParameter, effectivelyUnusedConstraints);
+            });
+      }
+    }
+
+    private void computeEffectivelyUnusedConstraints(
+        Argument argument,
+        Value argumentValue,
+        Consumer<ComputationTreeNode> effectivelyUnusedConditionsConsumer,
+        Consumer<Set<MethodParameter>> effectivelyUnusedConstraintsConsumer) {
+      if (!ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, argument.getIndex())) {
+        return;
+      }
+      if (!argumentValue.getType().isClassType() || argumentValue.hasDebugUsers()) {
+        return;
+      }
+      Value usedValue;
+      if (argumentValue.hasPhiUsers()) {
+        // If the argument has one or more phi users, we check if there is a single phi due to a
+        // default value for the argument. If so, we record this condition and mark the users of the
+        // phi as effectively unused argument constraints for the current argument.
+        if (!computeEffectivelyUnusedCondition(
+            argumentValue, effectivelyUnusedConditionsConsumer)) {
+          return;
+        }
+        assert argumentValue.hasSingleUniquePhiUser();
+        Phi user = argumentValue.singleUniquePhiUser();
+        if (user.hasDebugUsers() || user.hasPhiUsers()) {
+          return;
+        }
+        usedValue = user;
+      } else {
+        usedValue = argumentValue;
+      }
+      Set<MethodParameter> effectivelyUnusedConstraints = new HashSet<>();
+      for (Instruction user : usedValue.uniqueUsers()) {
+        if (user.isInvokeMethod()) {
+          InvokeMethod invoke = user.asInvokeMethod();
+          ProgramMethod resolvedMethod =
+              appView
+                  .appInfo()
+                  .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
+                  .getResolvedProgramMethod();
+          if (resolvedMethod == null || isUnoptimizable(resolvedMethod)) {
+            return;
+          }
+          int dependentArgumentIndex =
+              ListUtils.uniqueIndexMatching(invoke.arguments(), value -> value == argumentValue);
+          if (dependentArgumentIndex < 0
+              || !ParameterRemovalUtils.canRemoveUnusedParameter(
+                  appView, resolvedMethod, dependentArgumentIndex)) {
+            return;
+          }
+          effectivelyUnusedConstraints.add(
+              new MethodParameter(resolvedMethod, dependentArgumentIndex));
+        } else {
+          return;
+        }
+      }
+      if (!effectivelyUnusedConstraints.isEmpty()) {
+        effectivelyUnusedConstraintsConsumer.accept(effectivelyUnusedConstraints);
+      }
+    }
+
+    private boolean computeEffectivelyUnusedCondition(
+        Value argumentValue, Consumer<ComputationTreeNode> effectivelyUnusedConditionsConsumer) {
+      assert argumentValue.hasPhiUsers();
+      if (argumentValue.hasUsers() || !argumentValue.hasSingleUniquePhiUser()) {
+        return false;
+      }
+      Phi phi = argumentValue.singleUniquePhiUser();
+      if (phi.getOperands().size() != 2) {
+        return false;
+      }
+      BasicBlock block = phi.getBlock();
+      ConcretePathConstraintAnalysisState leftState =
+          pathConstraintSupplier.getPathConstraint(block.getPredecessor(0)).asConcreteState();
+      ConcretePathConstraintAnalysisState rightState =
+          pathConstraintSupplier.getPathConstraint(block.getPredecessor(1)).asConcreteState();
+      if (leftState == null || rightState == null) {
+        return false;
+      }
+      // Find a condition that can be used to distinguish program paths coming from the two
+      // predecessors.
+      ComputationTreeNode condition = leftState.getDifferentiatingPathConstraint(rightState);
+      if (!condition.isArgumentBitSetCompareNode()) {
+        return false;
+      }
+      // Extract the state corresponding to the program path where the argument is unused. If the
+      // condition evaluates to false on this program path then negate the condition.
+      ConcretePathConstraintAnalysisState unusedState =
+          phi.getOperand(0) == argumentValue ? rightState : leftState;
+      if (unusedState.isNegated(condition)) {
+        condition = ((ComputationTreeUnopCompareNode) condition).negate();
+      }
+      effectivelyUnusedConditionsConsumer.accept(condition);
+      return true;
+    }
+
+    private boolean isUnoptimizable(ProgramMethod method) {
+      if (method.getDefinition().belongsToDirectPool()) {
+        return !ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method);
+      }
+      if (optimizableVirtualMethods.contains(method)) {
+        assert ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method);
+        return false;
+      }
+      return true;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
index 1f795ad..c2fa1f0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsGraph.java
@@ -9,8 +9,11 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.dfs.DFSStack;
@@ -38,9 +41,34 @@
 
   public static EffectivelyUnusedArgumentsGraph create(
       AppView<AppInfoWithLiveness> appView,
+      Map<MethodParameter, ComputationTreeNode> conditions,
       Map<MethodParameter, Set<MethodParameter>> constraints,
       PrunedItems prunedItems) {
     EffectivelyUnusedArgumentsGraph graph = new EffectivelyUnusedArgumentsGraph(appView);
+    conditions.forEach(
+        (methodParameter, condition) -> {
+          ProgramMethod method =
+              asProgramMethodOrNull(appView.definitionFor(methodParameter.getMethod()));
+          if (method == null) {
+            assert false;
+            return;
+          }
+          // Evaluate the condition. If the condition evaluates to true, then create a graph node
+          // for the method parameter and delete all constraints. As a result, the node will not
+          // have any successors, meaning it is effectively unused.
+          if (method
+              .getOptimizationInfo()
+              .getArgumentInfos()
+              .isConcreteCallSiteOptimizationInfo()) {
+            FlowGraphStateProvider flowGraphStateProvider =
+                FlowGraphStateProvider.createFromMethodOptimizationInfo(method);
+            AbstractValue result = condition.evaluate(appView, flowGraphStateProvider);
+            if (result.isTrue()) {
+              graph.getOrCreateNode(methodParameter);
+              constraints.remove(methodParameter);
+            }
+          }
+        });
     constraints.forEach(
         (methodParameter, constraintsForMethodParameter) -> {
           EffectivelyUnusedArgumentsGraphNode node = graph.getOrCreateNode(methodParameter);
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
deleted file mode 100644
index 371af3d..0000000
--- a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
+++ /dev/null
@@ -1,65 +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.optimize.compose;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.path.PathConstraintSupplier;
-import com.android.tools.r8.ir.code.AbstractValueSupplier;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorCodeScanner;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Timing;
-
-public class ArgumentPropagatorCodeScannerForComposableFunctions
-    extends ArgumentPropagatorCodeScanner {
-
-  private final ComposableCallGraph callGraph;
-
-  public ArgumentPropagatorCodeScannerForComposableFunctions(
-      AppView<AppInfoWithLiveness> appView, ComposableCallGraph callGraph) {
-    super(appView);
-    this.callGraph = callGraph;
-  }
-
-  @Override
-  public void scan(
-      ProgramMethod method,
-      IRCode code,
-      AbstractValueSupplier abstractValueSupplier,
-      PathConstraintSupplier pathConstraintSupplier,
-      Timing timing) {
-    new CodeScanner(abstractValueSupplier, code, method, pathConstraintSupplier).scan(timing);
-  }
-
-  @Override
-  protected boolean isMethodParameterAlreadyUnknown(
-      DexType staticType, MethodParameter methodParameter, ProgramMethod method) {
-    // We haven't defined the virtual root mapping, so we can't tell.
-    return false;
-  }
-
-  private class CodeScanner extends ArgumentPropagatorCodeScanner.CodeScanner {
-
-    protected CodeScanner(
-        AbstractValueSupplier abstractValueSupplier,
-        IRCode code,
-        ProgramMethod method,
-        PathConstraintSupplier pathConstraintSupplier) {
-      super(abstractValueSupplier, code, method, pathConstraintSupplier);
-    }
-
-    @Override
-    protected void addTemporaryMethodState(
-        InvokeMethod invoke, ProgramMethod resolvedMethod, Timing timing) {
-      ComposableCallGraphNode node = callGraph.getNodes().get(resolvedMethod);
-      if (node != null && node.isComposable()) {
-        super.addTemporaryMethodState(invoke, resolvedMethod, timing);
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
deleted file mode 100644
index 69a0331..0000000
--- a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
+++ /dev/null
@@ -1,230 +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.optimize.compose;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Or;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValue;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.OrAbstractFunction;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.google.common.collect.Iterables;
-
-public class ArgumentPropagatorComposeModeling {
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final ComposeReferences rewrittenComposeReferences;
-
-  private final DexType rewrittenFunction2Type;
-  private final DexString invokeName;
-
-  public ArgumentPropagatorComposeModeling(AppView<AppInfoWithLiveness> appView) {
-    assert appView
-        .options()
-        .getJetpackComposeOptions()
-        .isModelingChangedArgumentsToComposableFunctions();
-    this.appView = appView;
-    this.rewrittenComposeReferences =
-        appView
-            .getComposeReferences()
-            .rewrittenWithLens(appView.graphLens(), GraphLens.getIdentityLens());
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    this.rewrittenFunction2Type =
-        appView
-            .graphLens()
-            .lookupType(
-                dexItemFactory.createType("Lkotlin/jvm/functions/Function2;"),
-                GraphLens.getIdentityLens());
-    this.invokeName = dexItemFactory.createString("invoke");
-  }
-
-  /**
-   * Models calls to @Composable functions from Compose restart lambdas.
-   *
-   * <p>The @Composable functions are static and should have one of the following two parameter
-   * lists:
-   *
-   * <ol>
-   *   <li>(..., Composable, int)
-   *   <li>(..., Composable, int, int)
-   * </ol>
-   *
-   * <p>The int argument after the Composable parameter is the $$changed parameter. The second int
-   * argument after the Composable parameter (if present) is the $$default parameter.
-   *
-   * <p>The call to a @Composable function from its restart lambda follows the following code
-   * pattern:
-   *
-   * <pre>
-   *   MyComposableFunction(
-   *       ..., this.composer, updateChangedFlags(this.$$changed) || 1, this.$$default)
-   * </pre>
-   *
-   * <p>The modeling performed by this method assumes that updateChangedFlags() does not have any
-   * impact on $$changed (see the current implementation below). The modeling also assumes that
-   * this.$$changed and this.$$default are captures of the $$changed and $$default parameters of
-   * the @Composable function.
-   *
-   * <pre>
-   *   internal fun updateChangedFlags(flags: Int): Int {
-   *     val lowBits = flags and changedLowBitMask
-   *     val highBits = flags and changedHighBitMask
-   *     return ((flags and changedMask) or
-   *         (lowBits or (highBits shr 1)) or ((lowBits shl 1) and highBits))
-   *   }
-   * </pre>
-   */
-  public NonEmptyValueState modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
-      InvokeMethod invoke,
-      ProgramMethod singleTarget,
-      int argumentIndex,
-      Value argument,
-      ProgramMethod context) {
-    // TODO(b/302483644): Add some robust way of detecting restart lambda contexts.
-    if (!context.getHolder().getInterfaces().contains(rewrittenFunction2Type)
-        || !invoke.getPosition().getOutermostCaller().getMethod().getName().isEqualTo(invokeName)
-        || Iterables.isEmpty(
-            context
-                .getHolder()
-                .instanceFields(
-                    f -> f.getName().isIdenticalTo(rewrittenComposeReferences.changedFieldName)))) {
-      return null;
-    }
-
-    // First check if this is an invoke to a @Composable function.
-    if (singleTarget == null
-        || !singleTarget
-            .getDefinition()
-            .annotations()
-            .hasAnnotation(rewrittenComposeReferences.composableType)) {
-      return null;
-    }
-
-    // The @Composable function is expected to be static and have >= 2 parameters.
-    if (!invoke.isInvokeStatic()) {
-      return null;
-    }
-
-    DexMethod invokedMethod = invoke.getInvokedMethod();
-    if (invokedMethod.getArity() < 2) {
-      return null;
-    }
-
-    // Check if the parameters list is one of (..., Composer, int) or (..., Composer, int, int).
-    if (!invokedMethod.getParameter(invokedMethod.getArity() - 1).isIntType()) {
-      return null;
-    }
-
-    boolean hasDefaultParameter =
-        invokedMethod.getParameter(invokedMethod.getArity() - 2).isIntType();
-    if (hasDefaultParameter && invokedMethod.getArity() < 3) {
-      return null;
-    }
-
-    int composerParameterIndex =
-        invokedMethod.getArity() - 2 - BooleanUtils.intValue(hasDefaultParameter);
-    if (!invokedMethod
-        .getParameter(composerParameterIndex)
-        .isIdenticalTo(rewrittenComposeReferences.composerType)) {
-      return null;
-    }
-
-    // We only model the $$changed argument to the @Composable function.
-    if (argumentIndex != composerParameterIndex + 1) {
-      return null;
-    }
-
-    assert argument.getType().isInt();
-
-    DexField changedField =
-        appView
-            .dexItemFactory()
-            .createField(
-                context.getHolderType(),
-                appView.dexItemFactory().intType,
-                rewrittenComposeReferences.changedFieldName);
-
-    UpdateChangedFlagsAbstractFunction inFlow = null;
-    // We are looking at an argument to the $$changed parameter of the @Composable function.
-    // We generally expect this argument to be defined by a call to updateChangedFlags().
-    if (argument.isDefinedByInstructionSatisfying(Instruction::isInvokeStatic)) {
-      InvokeStatic invokeStatic = argument.getDefinition().asInvokeStatic();
-      SingleResolutionResult<?> resolutionResult =
-          invokeStatic.resolveMethod(appView, context).asSingleResolution();
-      if (resolutionResult == null) {
-        return null;
-      }
-      DexClassAndMethod invokeSingleTarget =
-          resolutionResult
-              .lookupDispatchTarget(appView, invokeStatic, context)
-              .getSingleDispatchTarget();
-      if (invokeSingleTarget == null) {
-        return null;
-      }
-      inFlow =
-          invokeSingleTarget
-              .getOptimizationInfo()
-              .getAbstractFunction()
-              .asUpdateChangedFlagsAbstractFunction();
-      if (inFlow == null) {
-        return null;
-      }
-      // By accounting for the abstract function we can safely strip the call.
-      argument = invokeStatic.getFirstArgument();
-    }
-    // Allow the argument to be defined by `this.$$changed | 1`.
-    if (argument.isDefinedByInstructionSatisfying(Instruction::isOr)) {
-      Or or = argument.getDefinition().asOr();
-      Value maybeNumberOperand = or.leftValue().isConstNumber() ? or.leftValue() : or.rightValue();
-      Value otherOperand = or.getOperand(1 - or.inValues().indexOf(maybeNumberOperand));
-      if (!maybeNumberOperand.isConstNumber(1)) {
-        return null;
-      }
-      // Strip the OR instruction.
-      argument = otherOperand;
-      // Update the model from bottom to a special value that effectively throws away any known
-      // information about the lowermost bit of $$changed.
-      SingleNumberValue one =
-          appView.abstractValueFactory().createSingleNumberValue(1, TypeElement.getInt());
-      inFlow =
-          new UpdateChangedFlagsAbstractFunction(
-              new OrAbstractFunction(new FieldValue(changedField), one));
-    } else {
-      inFlow = new UpdateChangedFlagsAbstractFunction(new FieldValue(changedField));
-    }
-
-    // At this point we expect that the restart lambda is reading this.$$changed using an
-    // instance-get.
-    if (!argument.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) {
-      return null;
-    }
-
-    // Check that the instance-get is reading the capture field that we expect it to.
-    InstanceGet instanceGet = argument.getDefinition().asInstanceGet();
-    if (!instanceGet.getField().isIdenticalTo(changedField)) {
-      return null;
-    }
-
-    // Return the argument model.
-    return new ConcretePrimitiveTypeValueState(inFlow);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java
deleted file mode 100644
index 8364488..0000000
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java
+++ /dev/null
@@ -1,171 +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.optimize.compose;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.collections.ProgramMethodMap;
-import java.util.function.Consumer;
-
-/**
- * A partial call graph that stores call edges to @Composable functions. By processing all the call
- * sites of a given @Composable function we can reapply arguent propagation for the @Composable
- * function.
- */
-public class ComposableCallGraph {
-
-  private final ProgramMethodMap<ComposableCallGraphNode> nodes;
-
-  public ComposableCallGraph(ProgramMethodMap<ComposableCallGraphNode> nodes) {
-    this.nodes = nodes;
-  }
-
-  public static Builder builder(AppView<AppInfoWithLiveness> appView) {
-    return new Builder(appView);
-  }
-
-  public static ComposableCallGraph empty() {
-    return new ComposableCallGraph(ProgramMethodMap.empty());
-  }
-
-  public void forEachNode(Consumer<ComposableCallGraphNode> consumer) {
-    nodes.forEachValue(consumer);
-  }
-
-  public ProgramMethodMap<ComposableCallGraphNode> getNodes() {
-    return nodes;
-  }
-
-  public boolean isEmpty() {
-    return nodes.isEmpty();
-  }
-
-  public static class Builder {
-
-    private final AppView<AppInfoWithLiveness> appView;
-    private final ProgramMethodMap<ComposableCallGraphNode> nodes = ProgramMethodMap.create();
-
-    Builder(AppView<AppInfoWithLiveness> appView) {
-      this.appView = appView;
-    }
-
-    public ComposableCallGraph build() {
-      createCallGraphNodesForComposableFunctions();
-      if (!nodes.isEmpty()) {
-        addCallEdgesToComposableFunctions();
-      }
-      return new ComposableCallGraph(nodes);
-    }
-
-    private void createCallGraphNodesForComposableFunctions() {
-      ComposeReferences rewrittenComposeReferences =
-          appView
-              .getComposeReferences()
-              .rewrittenWithLens(appView.graphLens(), GraphLens.getIdentityLens());
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
-        clazz.forEachProgramDirectMethodMatching(
-            method -> method.annotations().hasAnnotation(rewrittenComposeReferences.composableType),
-            method -> {
-              // TODO(b/302483644): Don't include kept @Composable functions, since we can't
-              //  optimize them anyway.
-              assert method.getAccessFlags().isStatic();
-              nodes.put(method, new ComposableCallGraphNode(method, true));
-            });
-      }
-    }
-
-    // TODO(b/302483644): Parallelize identification of @Composable call sites.
-    private void addCallEdgesToComposableFunctions() {
-      // Code is fully rewritten so no need to lens rewrite in registry.
-      assert appView.graphLens().isMemberRebindingIdentityLens();
-      assert appView.codeLens() == appView.graphLens().asNonIdentityLens().getPrevious();
-
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
-        clazz.forEachProgramMethodMatching(
-            DexEncodedMethod::hasCode,
-            method -> {
-              Code code = method.getDefinition().getCode();
-
-              // TODO(b/302483644): Leverage LIR code constant pool for efficient checking.
-              // TODO(b/302483644): Maybe remove the possibility of CF/DEX at this point.
-              assert code.isLirCode()
-                  || code.isCfCode()
-                  || code.isDexCode()
-                  || code.isDefaultInstanceInitializerCode()
-                  || code.isThrowNullCode();
-
-              code.registerCodeReferences(
-                  method,
-                  new UseRegistry<>(appView, method) {
-
-                    private final AppView<AppInfoWithLiveness> appViewWithLiveness =
-                        appView.withLiveness();
-
-                    @Override
-                    public void registerInvokeStatic(DexMethod method) {
-                      ProgramMethod resolvedMethod =
-                          appViewWithLiveness
-                              .appInfo()
-                              .unsafeResolveMethodDueToDexFormat(method)
-                              .getResolvedProgramMethod();
-                      if (resolvedMethod == null) {
-                        return;
-                      }
-
-                      ComposableCallGraphNode callee = nodes.get(resolvedMethod);
-                      if (callee == null || !callee.isComposable()) {
-                        // Only record calls to Composable functions.
-                        return;
-                      }
-
-                      ComposableCallGraphNode caller =
-                          nodes.computeIfAbsent(
-                              getContext(), context -> new ComposableCallGraphNode(context, false));
-                      callee.addCaller(caller);
-                    }
-
-                    @Override
-                    public void registerInitClass(DexType type) {}
-
-                    @Override
-                    public void registerInvokeDirect(DexMethod method) {}
-
-                    @Override
-                    public void registerInvokeInterface(DexMethod method) {}
-
-                    @Override
-                    public void registerInvokeSuper(DexMethod method) {}
-
-                    @Override
-                    public void registerInvokeVirtual(DexMethod method) {}
-
-                    @Override
-                    public void registerInstanceFieldRead(DexField field) {}
-
-                    @Override
-                    public void registerInstanceFieldWrite(DexField field) {}
-
-                    @Override
-                    public void registerStaticFieldRead(DexField field) {}
-
-                    @Override
-                    public void registerStaticFieldWrite(DexField field) {}
-
-                    @Override
-                    public void registerTypeReference(DexType type) {}
-                  });
-            });
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraphNode.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraphNode.java
deleted file mode 100644
index 13ec14db..0000000
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraphNode.java
+++ /dev/null
@@ -1,53 +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.optimize.compose;
-
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.SetUtils;
-import java.util.Set;
-import java.util.function.Consumer;
-
-public class ComposableCallGraphNode {
-
-  private final ProgramMethod method;
-  private final boolean isComposable;
-
-  private final Set<ComposableCallGraphNode> callers = SetUtils.newIdentityHashSet();
-  private final Set<ComposableCallGraphNode> callees = SetUtils.newIdentityHashSet();
-
-  ComposableCallGraphNode(ProgramMethod method, boolean isComposable) {
-    this.method = method;
-    this.isComposable = isComposable;
-  }
-
-  public void addCaller(ComposableCallGraphNode caller) {
-    callers.add(caller);
-    caller.callees.add(this);
-  }
-
-  public void forEachComposableCallee(Consumer<ComposableCallGraphNode> consumer) {
-    for (ComposableCallGraphNode callee : callees) {
-      if (callee.isComposable()) {
-        consumer.accept(callee);
-      }
-    }
-  }
-
-  public Set<ComposableCallGraphNode> getCallers() {
-    return callers;
-  }
-
-  public ProgramMethod getMethod() {
-    return method;
-  }
-
-  public boolean isComposable() {
-    return isComposable;
-  }
-
-  @Override
-  public String toString() {
-    return method.toString();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java
deleted file mode 100644
index c2890d8..0000000
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java
+++ /dev/null
@@ -1,114 +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.optimize.compose;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-public class ComposableOptimizationPass {
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final PrimaryR8IRConverter converter;
-
-  private ComposableOptimizationPass(
-      AppView<AppInfoWithLiveness> appView, PrimaryR8IRConverter converter) {
-    this.appView = appView;
-    this.converter = converter;
-  }
-
-  public static void run(
-      AppView<AppInfoWithLiveness> appView,
-      PrimaryR8IRConverter converter,
-      ExecutorService executorService,
-      Timing timing)
-      throws ExecutionException {
-    if (appView.options().getJetpackComposeOptions().isComposableOptimizationPassEnabled()) {
-      timing.time(
-          "ComposableOptimizationPass",
-          () -> new ComposableOptimizationPass(appView, converter).processWaves(executorService));
-    }
-  }
-
-  void processWaves(ExecutorService executorService) throws ExecutionException {
-    ComposableCallGraph callGraph = ComposableCallGraph.builder(appView).build();
-    ComposeMethodProcessor methodProcessor =
-        new ComposeMethodProcessor(appView, callGraph, converter);
-    Set<ComposableCallGraphNode> wave = createInitialWave(callGraph);
-    while (!wave.isEmpty()) {
-      Set<ComposableCallGraphNode> optimizedComposableFunctions =
-          methodProcessor.processWave(wave, executorService);
-      wave = createNextWave(methodProcessor, optimizedComposableFunctions);
-    }
-  }
-
-  // TODO(b/302483644): Should we skip root @Composable functions that don't have any nested
-  //  @Composable functions (?).
-  private Set<ComposableCallGraphNode> computeComposableRoots(ComposableCallGraph callGraph) {
-    Set<ComposableCallGraphNode> composableRoots = Sets.newIdentityHashSet();
-    callGraph.forEachNode(
-        node -> {
-          if (!node.isComposable()
-              || Iterables.any(node.getCallers(), ComposableCallGraphNode::isComposable)) {
-            // This is not a @Composable root.
-            return;
-          }
-          if (node.getCallers().isEmpty()) {
-            // Don't include root @Composable functions that are never called. These are either kept
-            // or will be removed in tree shaking.
-            return;
-          }
-          composableRoots.add(node);
-        });
-    return composableRoots;
-  }
-
-  private Set<ComposableCallGraphNode> createInitialWave(ComposableCallGraph callGraph) {
-    Set<ComposableCallGraphNode> wave = Sets.newIdentityHashSet();
-    Set<ComposableCallGraphNode> composableRoots = computeComposableRoots(callGraph);
-    composableRoots.forEach(composableRoot -> wave.addAll(composableRoot.getCallers()));
-    return wave;
-  }
-
-  // TODO(b/302483644): Consider repeatedly extracting the roots from the graph similar to the way
-  //  we extract leaves in the primary optimization pass.
-  private static Set<ComposableCallGraphNode> createNextWave(
-      ComposeMethodProcessor methodProcessor,
-      Set<ComposableCallGraphNode> optimizedComposableFunctions) {
-    Set<ComposableCallGraphNode> nextWave =
-        SetUtils.newIdentityHashSet(optimizedComposableFunctions);
-
-    // If the new wave contains two @Composable functions where one calls the other, then defer the
-    // processing of the callee to a later wave, to ensure that we have seen all of its callers
-    // before processing the callee.
-    List<ComposableCallGraphNode> deferredComposableFunctions = new ArrayList<>();
-    nextWave.forEach(
-        node -> {
-          if (SetUtils.containsAnyOf(nextWave, node.getCallers())) {
-            deferredComposableFunctions.add(node);
-          }
-        });
-    deferredComposableFunctions.forEach(nextWave::remove);
-
-    // To optimize the @Composable functions that are called from the @Composable functions of the
-    // next wave in the wave after that, we need to include their callers in the next wave as well.
-    Set<ComposableCallGraphNode> callersOfCalledComposableFunctions = Sets.newIdentityHashSet();
-    nextWave.forEach(
-        node ->
-            node.forEachComposableCallee(
-                callee -> callersOfCalledComposableFunctions.addAll(callee.getCallers())));
-    nextWave.addAll(callersOfCalledComposableFunctions);
-    nextWave.removeIf(methodProcessor::isProcessed);
-    return nextWave;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
deleted file mode 100644
index 3b51e7c..0000000
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
+++ /dev/null
@@ -1,280 +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.optimize.compose;
-
-import static com.android.tools.r8.graph.ProgramField.asProgramFieldOrNull;
-
-import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexMethod;
-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.analysis.constant.SparseConditionalConstantPropagation;
-import com.android.tools.r8.ir.analysis.path.PathConstraintSupplier;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.code.AbstractValueSupplier;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
-import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
-import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorCodeScanner;
-import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorOptimizationInfoPopulator;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.DefaultFieldValueJoiner;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.FlowGraph;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.InFlowPropagator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IterableUtils;
-import com.android.tools.r8.utils.LazyBox;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-public class ComposeMethodProcessor extends MethodProcessor {
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final ArgumentPropagatorCodeScanner codeScanner;
-  private final PrimaryR8IRConverter converter;
-
-  private final Set<ComposableCallGraphNode> processed = Sets.newIdentityHashSet();
-
-  public ComposeMethodProcessor(
-      AppView<AppInfoWithLiveness> appView,
-      ComposableCallGraph callGraph,
-      PrimaryR8IRConverter converter) {
-    this.appView = appView;
-    this.codeScanner = new ArgumentPropagatorCodeScannerForComposableFunctions(appView, callGraph);
-    this.converter = converter;
-  }
-
-  public Set<ComposableCallGraphNode> processWave(
-      Set<ComposableCallGraphNode> wave, ExecutorService executorService)
-      throws ExecutionException {
-    ProcessorContext processorContext = appView.createProcessorContext();
-    ThreadUtils.processItems(
-        wave,
-        node -> {
-          assert !processed.contains(node);
-          converter.processDesugaredMethod(
-              node.getMethod(),
-              OptimizationFeedback.getIgnoreFeedback(),
-              this,
-              processorContext.createMethodProcessingContext(node.getMethod()),
-              MethodConversionOptions.forLirPhase(appView));
-        },
-        appView.options().getThreadingModule(),
-        executorService);
-    processed.addAll(wave);
-    return optimizeComposableFunctionsCalledFromWave(wave, executorService);
-  }
-
-  private Set<ComposableCallGraphNode> optimizeComposableFunctionsCalledFromWave(
-      Set<ComposableCallGraphNode> wave, ExecutorService executorService)
-      throws ExecutionException {
-    prepareForInFlowPropagator();
-
-    InFlowComparator emptyComparator = InFlowComparator.builder().build();
-    InFlowPropagator inFlowPropagator =
-        new InFlowPropagator(
-            appView,
-            null,
-            converter,
-            codeScanner.getFieldStates(),
-            codeScanner.getMethodStates(),
-            emptyComparator) {
-
-          @Override
-          protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(
-              List<FlowGraph> flowGraphs) {
-            return new DefaultFieldValueJoiner(appView, null, fieldStates, flowGraphs) {
-
-              @Override
-              protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
-                // We do not rely on the optimization of any fields in the Composable optimization
-                // pass.
-                return Collections.emptyMap();
-              }
-            };
-          }
-        };
-    inFlowPropagator.run(executorService);
-
-    ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
-        new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null, null);
-    Set<ComposableCallGraphNode> optimizedComposableFunctions = Sets.newIdentityHashSet();
-    wave.forEach(
-        node ->
-            node.forEachComposableCallee(
-                callee -> {
-                  if (Iterables.all(callee.getCallers(), this::isProcessed)) {
-                    optimizationInfoPopulator.setOptimizationInfo(
-                        callee.getMethod(), ProgramMethodSet.empty(), getMethodState(callee));
-                    // TODO(b/302483644): Only enqueue this callee if its optimization info changed.
-                    optimizedComposableFunctions.add(callee);
-                  }
-                }));
-    return optimizedComposableFunctions;
-  }
-
-  private void prepareForInFlowPropagator() {
-    FieldStateCollection fieldStates = codeScanner.getFieldStates();
-
-    // Set all field states to unknown since we are not guaranteed to have processes all field
-    // writes.
-    fieldStates.forEach(
-        (field, fieldState) ->
-            fieldStates.addTemporaryFieldState(
-                appView, field, ValueState::unknown, Timing.empty()));
-
-    // Widen all parameter states that have in-flow to unknown, except when the in-flow is an
-    // update-changed-flags abstract function.
-    MethodStateCollectionByReference methodStates = codeScanner.getMethodStates();
-    methodStates.forEach(
-        (method, methodState) -> {
-          if (!methodState.isMonomorphic()) {
-            assert methodState.isUnknown();
-            return;
-          }
-          ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
-          for (int parameterIndex = 0;
-              parameterIndex < monomorphicMethodState.size();
-              parameterIndex++) {
-            ValueState parameterState = monomorphicMethodState.getParameterState(parameterIndex);
-            if (parameterState.isConcrete()) {
-              ConcreteValueState concreteParameterState = parameterState.asConcrete();
-              prepareParameterStateForInFlowPropagator(
-                  method, monomorphicMethodState, parameterIndex, concreteParameterState);
-            }
-          }
-        });
-  }
-
-  private void prepareParameterStateForInFlowPropagator(
-      DexMethod method,
-      ConcreteMonomorphicMethodState methodState,
-      int parameterIndex,
-      ConcreteValueState parameterState) {
-    if (!parameterState.hasInFlow()) {
-      return;
-    }
-
-    UpdateChangedFlagsAbstractFunction transferFunction = null;
-    if (parameterState.getInFlow().size() == 1) {
-      transferFunction =
-          Iterables.getOnlyElement(parameterState.getInFlow())
-              .asUpdateChangedFlagsAbstractFunction();
-    }
-    if (transferFunction == null) {
-      methodState.setParameterState(parameterIndex, ValueState.unknown());
-      return;
-    }
-
-    // This is a call to a composable function from a restart function.
-    Iterable<BaseInFlow> baseInFlow = transferFunction.getBaseInFlow();
-    assert Iterables.size(baseInFlow) == 1;
-    BaseInFlow singleBaseInFlow = IterableUtils.first(baseInFlow);
-    assert singleBaseInFlow.isFieldValue();
-
-    ProgramField field =
-        asProgramFieldOrNull(appView.definitionFor(singleBaseInFlow.asFieldValue().getField()));
-    assert field != null;
-
-    // If the only input to the $$changed parameter of the Composable function is in-flow then skip.
-    if (methodState.getParameterState(parameterIndex).getAbstractValue(appView).isBottom()) {
-      methodState.setParameterState(parameterIndex, ValueState.unknown());
-      return;
-    }
-
-    codeScanner
-        .getFieldStates()
-        .addTemporaryFieldState(
-            appView,
-            field,
-            () ->
-                new ConcretePrimitiveTypeValueState(
-                    MethodParameter.createStatic(method, parameterIndex)),
-            Timing.empty());
-  }
-
-  private MethodState getMethodState(ComposableCallGraphNode node) {
-    assert processed.containsAll(node.getCallers());
-    return codeScanner.getMethodStates().get(node.getMethod());
-  }
-
-  public void scan(ProgramMethod method, IRCode code, Timing timing) {
-    LazyBox<Map<Value, AbstractValue>> abstractValues =
-        new LazyBox<>(() -> new SparseConditionalConstantPropagation(appView).analyze(code));
-    AbstractValueSupplier abstractValueSupplier =
-        value -> {
-          AbstractValue abstractValue = abstractValues.computeIfAbsent().get(value);
-          assert abstractValue != null;
-          return abstractValue;
-        };
-    PathConstraintSupplier pathConstraintSupplier =
-        new PathConstraintSupplier(appView, code, codeScanner.getMethodParameterFactory());
-    codeScanner.scan(method, code, abstractValueSupplier, pathConstraintSupplier, timing);
-  }
-
-  public boolean isProcessed(ComposableCallGraphNode node) {
-    return processed.contains(node);
-  }
-
-  @Override
-  public CallSiteInformation getCallSiteInformation() {
-    return CallSiteInformation.empty();
-  }
-
-  @Override
-  public MethodProcessorEventConsumer getEventConsumer() {
-    throw new Unreachable();
-  }
-
-  @Override
-  public boolean isComposeMethodProcessor() {
-    return true;
-  }
-
-  @Override
-  public ComposeMethodProcessor asComposeMethodProcessor() {
-    return this;
-  }
-
-  @Override
-  public boolean isProcessedConcurrently(ProgramMethod method) {
-    return false;
-  }
-
-  @Override
-  public void scheduleDesugaredMethodForProcessing(ProgramMethod method) {
-    throw new Unreachable();
-  }
-
-  @Override
-  public boolean shouldApplyCodeRewritings(ProgramMethod method) {
-    return false;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
index e38f9a4..633008d 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.lens.GraphLens;
 
 public class ComposeReferences {
@@ -42,6 +43,11 @@
     this.updatedChangedFlagsMethod = updatedChangedFlagsMethod;
   }
 
+  public boolean isComposable(ProgramDefinition definition) {
+    return definition.isProgramMethod()
+        && definition.asProgramMethod().getAnnotations().hasAnnotation(composableType);
+  }
+
   public ComposeReferences rewrittenWithLens(GraphLens graphLens, GraphLens codeLens) {
     return new ComposeReferences(
         changedFieldName,
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComputationTreeUnopUpdateChangedFlagsNode.java b/src/main/java/com/android/tools/r8/optimize/compose/ComputationTreeUnopUpdateChangedFlagsNode.java
new file mode 100644
index 0000000..15ca10f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComputationTreeUnopUpdateChangedFlagsNode.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.optimize.compose;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.DefiniteBitsNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeUnopNode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.Objects;
+import java.util.function.Function;
+
+public class ComputationTreeUnopUpdateChangedFlagsNode extends ComputationTreeUnopNode {
+
+  private static final int changedLowBitMask = 0b001_001_001_001_001_001_001_001_001_001_0;
+  private static final int changedHighBitMask = changedLowBitMask << 1;
+  private static final int changedMask = ~(changedLowBitMask | changedHighBitMask);
+
+  public ComputationTreeUnopUpdateChangedFlagsNode(ComputationTreeNode operand) {
+    super(operand);
+  }
+
+  public static ComputationTreeNode create(ComputationTreeNode operand) {
+    if (operand.isUnknown()) {
+      return AbstractValue.unknown();
+    }
+    return new ComputationTreeUnopUpdateChangedFlagsNode(operand);
+  }
+
+  @Override
+  public AbstractValue evaluate(
+      AppView<AppInfoWithLiveness> appView, FlowGraphStateProvider flowGraphStateProvider) {
+    AbstractValue operandValue = operand.evaluate(appView, flowGraphStateProvider);
+    if (operandValue.isBottom()) {
+      return operandValue;
+    } else if (operandValue.isSingleNumberValue()) {
+      return evaluateConcrete(appView, operandValue.asSingleNumberValue().getIntValue());
+    } else if (operandValue.isDefiniteBitsNumberValue()) {
+      return evaluateAbstract(appView, operandValue.asDefiniteBitsNumberValue());
+    } else {
+      assert !operandValue.hasDefinitelySetAndUnsetBitsInformation();
+      return AbstractValue.unknown();
+    }
+  }
+
+  /**
+   * Applies the following function to the given {@param abstractValue}.
+   *
+   * <pre>
+   * private const val changedLowBitMask = 0b001_001_001_001_001_001_001_001_001_001_0
+   * private const val changedHighBitMask = changedLowBitMask shl 1
+   * private const val changedMask = (changedLowBitMask or changedHighBitMask).inv()
+   *
+   * internal fun updateChangedFlags(flags: Int): Int {
+   *     val lowBits = flags and changedLowBitMask
+   *     val highBits = flags and changedHighBitMask
+   *     return ((flags and changedMask) or
+   *         (lowBits or (highBits shr 1)) or ((lowBits shl 1) and highBits))
+   * }
+   * </pre>
+   */
+  private AbstractValue evaluateAbstract(
+      AppView<AppInfoWithLiveness> appView, DefiniteBitsNumberValue flags) {
+    AbstractValueFactory factory = appView.abstractValueFactory();
+    // Load constants.
+    AbstractValue changedLowBitMaskValue =
+        factory.createUncheckedSingleNumberValue(changedLowBitMask);
+    AbstractValue changedHighBitMaskValue =
+        factory.createUncheckedSingleNumberValue(changedHighBitMask);
+    AbstractValue changedMaskValue = factory.createUncheckedSingleNumberValue(changedMask);
+    // Evaluate expression.
+    AbstractValue lowBitsValue =
+        AbstractCalculator.andIntegers(appView, flags, changedLowBitMaskValue);
+    AbstractValue highBitsValue =
+        AbstractCalculator.andIntegers(appView, flags, changedHighBitMaskValue);
+    AbstractValue changedBitsValue =
+        AbstractCalculator.andIntegers(appView, flags, changedMaskValue);
+    return AbstractCalculator.orIntegers(
+        appView,
+        changedBitsValue,
+        lowBitsValue,
+        AbstractCalculator.shrIntegers(appView, highBitsValue, 1),
+        AbstractCalculator.andIntegers(
+            appView, AbstractCalculator.shlIntegers(appView, lowBitsValue, 1), highBitsValue));
+  }
+
+  private SingleNumberValue evaluateConcrete(AppView<AppInfoWithLiveness> appView, int flags) {
+    int lowBits = flags & changedLowBitMask;
+    int highBits = flags & changedHighBitMask;
+    int changedBits = flags & changedMask;
+    int result = changedBits | lowBits | (highBits >> 1) | ((lowBits << 1) & highBits);
+    return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+  }
+
+  @Override
+  public boolean isUpdateChangedFlags() {
+    return true;
+  }
+
+  @Override
+  public <TB, TC> TraversalContinuation<TB, TC> traverseBaseInFlow(
+      Function<? super BaseInFlow, TraversalContinuation<TB, TC>> fn) {
+    return operand.traverseBaseInFlow(fn);
+  }
+
+  @Override
+  @SuppressWarnings("EqualsGetClass")
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    ComputationTreeUnopUpdateChangedFlagsNode fn = (ComputationTreeUnopUpdateChangedFlagsNode) obj;
+    return operand.equals(fn.operand);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getClass(), operand);
+  }
+
+  @Override
+  public String toString() {
+    return "UpdateChangedFlags(" + operand + ")";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/JetpackComposeOptions.java b/src/main/java/com/android/tools/r8/optimize/compose/JetpackComposeOptions.java
index 69ec5e6..65a8199 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/JetpackComposeOptions.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/JetpackComposeOptions.java
@@ -10,10 +10,6 @@
 
   private final InternalOptions options;
 
-  public boolean enableComposableOptimizationPass =
-      SystemPropertyUtils.parseSystemPropertyOrDefault(
-          "com.android.tools.r8.jetpackcompose.enableComposableOptimizationPass", false);
-
   public boolean enableModelingOfChangedArguments =
       SystemPropertyUtils.parseSystemPropertyOrDefault(
           "com.android.tools.r8.jetpackcompose.enableModelingOfChangedArguments", false);
@@ -22,20 +18,6 @@
     this.options = options;
   }
 
-  public void enableAllOptimizations(boolean enable) {
-    enableComposableOptimizationPass = enable;
-    enableModelingOfChangedArguments = enable;
-  }
-
-  public boolean isAnyOptimizationsEnabled() {
-    return isComposableOptimizationPassEnabled()
-        || isModelingChangedArgumentsToComposableFunctions();
-  }
-
-  public boolean isComposableOptimizationPassEnabled() {
-    return isModelingChangedArgumentsToComposableFunctions() && enableComposableOptimizationPass;
-  }
-
   public boolean isModelingChangedArgumentsToComposableFunctions() {
     return options.isOptimizing() && options.isShrinking() && enableModelingOfChangedArguments;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
deleted file mode 100644
index 91476d3..0000000
--- a/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.optimize.compose;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
-import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowComparator;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlowKind;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.OrAbstractFunction;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IterableUtils;
-import java.util.Objects;
-
-public class UpdateChangedFlagsAbstractFunction implements AbstractFunction {
-
-  private static final int changedLowBitMask = 0b001_001_001_001_001_001_001_001_001_001_0;
-  private static final int changedHighBitMask = changedLowBitMask << 1;
-  private static final int changedMask = ~(changedLowBitMask | changedHighBitMask);
-
-  private final InFlow inFlow;
-
-  public UpdateChangedFlagsAbstractFunction(InFlow inFlow) {
-    this.inFlow = inFlow;
-  }
-
-  @Override
-  public ValueState apply(
-      AppView<AppInfoWithLiveness> appView,
-      FlowGraphStateProvider flowGraphStateProvider,
-      ConcreteValueState baseInState) {
-    ValueState inState;
-    if (inFlow.isAbstractFunction()) {
-      AbstractFunction orFunction = inFlow.asAbstractFunction();
-      assert orFunction instanceof OrAbstractFunction;
-      inState = orFunction.apply(appView, flowGraphStateProvider, baseInState);
-    } else {
-      inState = baseInState;
-    }
-    if (!inState.isPrimitiveState()) {
-      assert inState.isBottom() || inState.isUnknown();
-      return inState;
-    }
-    AbstractValue result = apply(appView, inState.asPrimitiveState().getAbstractValue());
-    return ConcretePrimitiveTypeValueState.create(result);
-  }
-
-  /**
-   * Applies the following function to the given {@param abstractValue}.
-   *
-   * <pre>
-   * private const val changedLowBitMask = 0b001_001_001_001_001_001_001_001_001_001_0
-   * private const val changedHighBitMask = changedLowBitMask shl 1
-   * private const val changedMask = (changedLowBitMask or changedHighBitMask).inv()
-   *
-   * internal fun updateChangedFlags(flags: Int): Int {
-   *     val lowBits = flags and changedLowBitMask
-   *     val highBits = flags and changedHighBitMask
-   *     return ((flags and changedMask) or
-   *         (lowBits or (highBits shr 1)) or ((lowBits shl 1) and highBits))
-   * }
-   * </pre>
-   */
-  private AbstractValue apply(AppView<AppInfoWithLiveness> appView, AbstractValue flagsValue) {
-    if (flagsValue.isSingleNumberValue()) {
-      return apply(appView, flagsValue.asSingleNumberValue().getIntValue());
-    }
-    AbstractValueFactory factory = appView.abstractValueFactory();
-    // Load constants.
-    AbstractValue changedLowBitMaskValue =
-        factory.createUncheckedSingleNumberValue(changedLowBitMask);
-    AbstractValue changedHighBitMaskValue =
-        factory.createUncheckedSingleNumberValue(changedHighBitMask);
-    AbstractValue changedMaskValue = factory.createUncheckedSingleNumberValue(changedMask);
-    // Evaluate expression.
-    AbstractValue lowBitsValue =
-        AbstractCalculator.andIntegers(appView, flagsValue, changedLowBitMaskValue);
-    AbstractValue highBitsValue =
-        AbstractCalculator.andIntegers(appView, flagsValue, changedHighBitMaskValue);
-    AbstractValue changedBitsValue =
-        AbstractCalculator.andIntegers(appView, flagsValue, changedMaskValue);
-    return AbstractCalculator.orIntegers(
-        appView,
-        changedBitsValue,
-        lowBitsValue,
-        AbstractCalculator.shrIntegers(appView, highBitsValue, 1),
-        AbstractCalculator.andIntegers(
-            appView, AbstractCalculator.shlIntegers(appView, lowBitsValue, 1), highBitsValue));
-  }
-
-  private SingleNumberValue apply(AppView<AppInfoWithLiveness> appView, int flags) {
-    int lowBits = flags & changedLowBitMask;
-    int highBits = flags & changedHighBitMask;
-    int changedBits = flags & changedMask;
-    int result = changedBits | lowBits | (highBits >> 1) | ((lowBits << 1) & highBits);
-    return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
-  }
-
-  @Override
-  public boolean verifyContainsBaseInFlow(BaseInFlow otherInFlow) {
-    if (inFlow.isAbstractFunction()) {
-      assert inFlow.asAbstractFunction().verifyContainsBaseInFlow(otherInFlow);
-    } else {
-      assert inFlow.isBaseInFlow();
-      assert inFlow.equals(otherInFlow);
-    }
-    return true;
-  }
-
-  @Override
-  public Iterable<BaseInFlow> getBaseInFlow() {
-    if (inFlow.isAbstractFunction()) {
-      return inFlow.asAbstractFunction().getBaseInFlow();
-    }
-    assert inFlow.isBaseInFlow();
-    return IterableUtils.singleton(inFlow.asBaseInFlow());
-  }
-
-  @Override
-  public InFlowKind getKind() {
-    return InFlowKind.ABSTRACT_FUNCTION_UPDATE_CHANGED_FLAGS;
-  }
-
-  @Override
-  public int internalCompareToSameKind(InFlow other, InFlowComparator comparator) {
-    return inFlow.compareTo(other.asUpdateChangedFlagsAbstractFunction().inFlow, comparator);
-  }
-
-  @Override
-  public boolean isUpdateChangedFlagsAbstractFunction() {
-    return true;
-  }
-
-  @Override
-  public UpdateChangedFlagsAbstractFunction asUpdateChangedFlagsAbstractFunction() {
-    return this;
-  }
-
-  @Override
-  @SuppressWarnings("EqualsGetClass")
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (obj == null || getClass() != obj.getClass()) {
-      return false;
-    }
-    UpdateChangedFlagsAbstractFunction fn = (UpdateChangedFlagsAbstractFunction) obj;
-    return inFlow.equals(fn.inFlow);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(getClass(), inFlow);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.java
index ef07715..9186c46 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.profile.rewriting;
 
+import com.android.tools.r8.desugar.covariantreturntype.CovariantReturnTypeAnnotationTransformerEventConsumer;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformerEventConsumer;
 
 public class ProfileRewritingCovariantReturnTypeAnnotationTransformerEventConsumer
     implements CovariantReturnTypeAnnotationTransformerEventConsumer {
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index bcfa888..0e2508f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -142,7 +142,7 @@
             .startsWith(options.itemFactory.dalvikAnnotationOptimizationPrefix)) {
           return true;
         }
-        if (isComposableAnnotationToRetain(appView, annotation, kind, mode, options)) {
+        if (isComposableAnnotationToRetain(appView, annotation, kind, mode)) {
           return true;
         }
         return shouldKeepNormalAnnotation(
@@ -309,13 +309,8 @@
   }
 
   private static boolean isComposableAnnotationToRetain(
-      AppView<?> appView,
-      DexAnnotation annotation,
-      AnnotatedKind kind,
-      Mode mode,
-      InternalOptions options) {
-    return options.getJetpackComposeOptions().isAnyOptimizationsEnabled()
-        && mode.isInitialTreeShaking()
+      AppView<?> appView, DexAnnotation annotation, AnnotatedKind kind, Mode mode) {
+    return mode.isInitialTreeShaking()
         && kind.isMethod()
         && annotation
             .getAnnotationType()
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 5ca4dbe..1781300 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
+import com.android.tools.r8.desugar.covariantreturntype.CovariantReturnTypeEnqueuerExtension;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
@@ -537,6 +538,7 @@
           shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
       IsolatedFeatureSplitsChecker.register(appView, this);
       ResourceAccessAnalysis.register(appView, this);
+      CovariantReturnTypeEnqueuerExtension.register(appView, this);
     }
 
     targetedMethods = new LiveMethodsSet(graphReporter::registerMethod);
@@ -4807,7 +4809,9 @@
 
         // Notify each analysis that a fixpoint has been reached, and give each analysis an
         // opportunity to add items to the worklist.
-        analyses.forEach(analysis -> analysis.notifyFixpoint(this, worklist, timing));
+        for (EnqueuerAnalysis analysis : analyses) {
+          analysis.notifyFixpoint(this, worklist, executorService, timing);
+        }
         if (!worklist.isEmpty()) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 23be490..d37185a 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -544,6 +544,10 @@
       return builder.isCheckDiscardedEnabled();
     }
 
+    public boolean isMinificationAllowed() {
+      return builder.isMinificationAllowed();
+    }
+
     public boolean isOptimizationAllowed() {
       return builder.isOptimizationAllowed();
     }
diff --git a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
index 2f19c5c..f7da18f 100644
--- a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
@@ -41,4 +41,8 @@
   public static <T, R> Function<T, R> ignoreArgument(Supplier<R> supplier) {
     return ignore -> supplier.get();
   }
+
+  public static <T, R> Function<T, R> supplyValue(R value) {
+    return ignore -> value;
+  }
 }
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 dac665d..44c289f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -372,7 +372,8 @@
     if (isAndroidPlatformBuild) {
       apiModelingOptions().disableApiModeling();
       disableBackportsAndReportIfTriggered = true;
-      addAndroidPlatformBuildToMarker = isAndroidPlatformBuild;
+      addAndroidPlatformBuildToMarker = true;
+      processCovariantReturnTypeAnnotations = true;
     }
   }
 
@@ -436,6 +437,8 @@
   public boolean enableSideEffectAnalysis = true;
   public boolean enableDeterminismAnalysis = true;
   public boolean enableServiceLoaderRewriting = true;
+  public boolean allowServiceLoaderRewritingPinnedTypes =
+      System.getProperty("com.android.tools.r8.allowServiceLoaderRewritingPinnedTypes") != null;
   public boolean enableNameReflectionOptimization = true;
   public boolean enableStringConcatenationOptimization = true;
   // Enabled only for R8 (not D8).
@@ -778,7 +781,7 @@
   public OffOrAuto tryWithResourcesDesugaring = OffOrAuto.Auto;
   // Flag to turn on/off processing of @dalvik.annotation.codegen.CovariantReturnType and
   // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
-  public boolean processCovariantReturnTypeAnnotations = true;
+  public boolean processCovariantReturnTypeAnnotations = false;
 
   public boolean loadAllClassDefinitions = false;
 
@@ -2460,7 +2463,6 @@
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableEnqueuerDeferredTracingForReferenceFields =
         System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null;
-    public boolean enableIfThenElseAbstractFunction = false;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableLegacyClassDefOrdering =
         System.getProperty("com.android.tools.r8.enableLegacyClassDefOrdering") != null;
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index a7c3366..ea46d64 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Collections;
@@ -149,6 +150,12 @@
     return set;
   }
 
+  public static <T> Set<T> unionHashSet(Iterable<? extends T> one, Iterable<? extends T> other) {
+    Set<T> union = Sets.newHashSet(one);
+    Iterables.addAll(union, other);
+    return union;
+  }
+
   public static <T> Set<T> unionIdentityHashSet(Set<T> one, Set<T> other) {
     Set<T> union = Sets.newIdentityHashSet();
     union.addAll(one);
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
index a06547e..76c0cde 100644
--- a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.androidapi;
 
 import static com.android.tools.r8.apimodel.JavaSourceCodePrinter.Type.fromType;
-import static com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer.isCovariantReturnTypeAnnotation;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
@@ -23,7 +22,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -63,6 +61,11 @@
 @RunWith(Parameterized.class)
 public class GenerateCovariantReturnTypeMethodsTest extends TestBase {
 
+  private static final String COVARIANT_RETURN_TYPE_ANNOTATION_NAME =
+      "dalvik.annotation.codegen.CovariantReturnType";
+  private static final String COVARIANT_RETURN_TYPES_ANNOTATION_NAME =
+      "dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes";
+
   private static final String CLASS_NAME = "CovariantReturnTypeMethods";
   private static final String PACKAGE_NAME = "com.android.tools.r8.androidapi";
   // When updating to support a new api level build libcore in aosp and update the cloud dependency.
@@ -200,20 +203,22 @@
     public static CovariantMethodsInJarResult create() throws Exception {
       Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap = new HashMap<>();
       CodeInspector inspector = new CodeInspector(PATH_TO_CORE_JAR);
-      DexItemFactory factory = inspector.getFactory();
       for (FoundClassSubject clazz : inspector.allClasses()) {
         clazz.forAllMethods(
             method -> {
               List<DexAnnotation> covariantAnnotations =
                   inspector.findAnnotations(
                       method.getMethod().annotations(),
-                      annotation ->
-                          isCovariantReturnTypeAnnotation(annotation.annotation, factory));
+                      annotation -> {
+                        String typeName = annotation.getAnnotationType().getTypeName();
+                        return typeName.equals(COVARIANT_RETURN_TYPE_ANNOTATION_NAME)
+                            || typeName.equals(COVARIANT_RETURN_TYPES_ANNOTATION_NAME);
+                      });
               if (!covariantAnnotations.isEmpty()) {
                 MethodReference methodReference = method.asMethodReference();
                 for (DexAnnotation covariantAnnotation : covariantAnnotations) {
                   createCovariantMethodReference(
-                      factory, methodReference, covariantAnnotation.annotation, methodReferenceMap);
+                      methodReference, covariantAnnotation.annotation, methodReferenceMap);
                 }
               }
             });
@@ -222,19 +227,21 @@
     }
 
     private static void createCovariantMethodReference(
-        DexItemFactory factory,
         MethodReference methodReference,
         DexEncodedAnnotation covariantAnnotation,
         Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap) {
-      if (covariantAnnotation.type == factory.annotationCovariantReturnType) {
+      if (covariantAnnotation
+          .getType()
+          .getTypeName()
+          .equals(COVARIANT_RETURN_TYPE_ANNOTATION_NAME)) {
         DexAnnotationElement returnTypeElement = covariantAnnotation.getElement(0);
-        assert returnTypeElement.name.toString().equals("returnType");
+        assert returnTypeElement.getName().toString().equals("returnType");
         DexValueType newReturnType = returnTypeElement.getValue().asDexValueType();
         DexAnnotationElement presentAfterElement = covariantAnnotation.getElement(1);
-        assert presentAfterElement.name.toString().equals("presentAfter");
+        assert presentAfterElement.getName().toString().equals("presentAfter");
         AndroidApiLevel apiLevel =
             AndroidApiLevel.getAndroidApiLevel(
-                presentAfterElement.getValue().asDexValueInt().value);
+                presentAfterElement.getValue().asDexValueInt().getValue());
         methodReferenceMap
             .computeIfAbsent(methodReference.getHolderClass(), ignoreKey(ArrayList::new))
             .add(
@@ -243,20 +250,23 @@
                         methodReference.getHolderClass(),
                         methodReference.getMethodName(),
                         methodReference.getFormalTypes(),
-                        newReturnType.value.asClassReference()),
+                        newReturnType.getValue().asClassReference()),
                     apiLevel));
       } else {
-        assert covariantAnnotation.type == factory.annotationCovariantReturnTypes;
+        assert covariantAnnotation
+            .getType()
+            .getTypeName()
+            .equals(COVARIANT_RETURN_TYPES_ANNOTATION_NAME);
         DexAnnotationElement valuesElement = covariantAnnotation.getElement(0);
-        assert valuesElement.name.toString().equals("value");
-        DexValueArray array = valuesElement.value.asDexValueArray();
+        assertEquals("value", valuesElement.getName().toString());
+        DexValueArray array = valuesElement.getValue().asDexValueArray();
         if (array == null) {
           fail(
               String.format(
                   "Expected element \"value\" of CovariantReturnTypes annotation to "
                       + "be an array (method: \"%s\", was: %s)",
                   methodReference.toSourceString(),
-                  valuesElement.value.getClass().getCanonicalName()));
+                  valuesElement.getValue().getClass().getCanonicalName()));
         }
 
         // Handle the inner dalvik.annotation.codegen.CovariantReturnType annotations recursively.
@@ -264,7 +274,7 @@
           assert value.isDexValueAnnotation();
           DexValueAnnotation innerAnnotation = value.asDexValueAnnotation();
           createCovariantMethodReference(
-              factory, methodReference, innerAnnotation.value, methodReferenceMap);
+              methodReference, innerAnnotation.value, methodReferenceMap);
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/annotations/DalvikAnnotationOptimizationTest.java b/src/test/java/com/android/tools/r8/annotations/DalvikAnnotationOptimizationTest.java
index f14c979..c6c128f 100644
--- a/src/test/java/com/android/tools/r8/annotations/DalvikAnnotationOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/DalvikAnnotationOptimizationTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.transformers.MethodTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -56,8 +55,7 @@
         BooleanUtils.values());
   }
 
-  private static final String dalvikOptimizationPrefix =
-      DexItemFactory.dalvikAnnotationOptimizationPrefixString;
+  private static final String dalvikOptimizationPrefix = "Ldalvik/annotation/optimization/";
   private static final String dalvikCodegenPrefix = "Ldalvik/annotation/codegen/";
   private static final String ourClassName =
       DescriptorUtils.javaTypeToDescriptor(DalvikAnnotationOptimizationTest.class.getTypeName());
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
index 0eab29b..bff9585 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
@@ -168,8 +168,23 @@
       return this;
     }
 
-    public Builder measureComposableCodeSize() {
-      metrics.add(BenchmarkMetric.ComposableCodeSize);
+    public Builder measureInstructionCodeSize() {
+      metrics.add(BenchmarkMetric.InstructionCodeSize);
+      return this;
+    }
+
+    public Builder measureComposableInstructionCodeSize() {
+      metrics.add(BenchmarkMetric.ComposableInstructionCodeSize);
+      return this;
+    }
+
+    public Builder measureDexSegmentsCodeSize() {
+      metrics.add(BenchmarkMetric.DexSegmentsCodeSize);
+      return this;
+    }
+
+    public Builder measureDex2OatCodeSize() {
+      metrics.add(BenchmarkMetric.Dex2OatCodeSize);
       return this;
     }
 
@@ -248,7 +263,7 @@
   }
 
   public boolean containsComposableCodeSizeMetric() {
-    return containsMetric(BenchmarkMetric.ComposableCodeSize);
+    return containsMetric(BenchmarkMetric.ComposableInstructionCodeSize);
   }
 
   public BenchmarkIdentifier getIdentifier() {
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
index 8fc8508..dc841ba 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.DexSegments.SegmentInfo;
 import com.android.tools.r8.errors.Unimplemented;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -23,24 +25,40 @@
 
   @Override
   public void addRuntimeResult(long result) {
-    throw new BenchmarkConfigError(
-        "Unexpected attempt to add a result to a the root of a benchmark with sub-benchmarks");
+    throw error();
   }
 
   @Override
   public void addCodeSizeResult(long result) {
-    throw new BenchmarkConfigError(
-        "Unexpected attempt to add a result to a the root of a benchmark with sub-benchmarks");
+    throw error();
   }
 
   @Override
-  public void addComposableCodeSizeResult(long result) {
-    throw new BenchmarkConfigError(
-        "Unexpected attempt to add a result to a the root of a benchmark with sub-benchmarks");
+  public void addInstructionCodeSizeResult(long result) {
+    throw error();
+  }
+
+  @Override
+  public void addComposableInstructionCodeSizeResult(long result) {
+    throw error();
+  }
+
+  @Override
+  public void addDexSegmentsSizeResult(Int2ReferenceMap<SegmentInfo> result) {
+    throw error();
+  }
+
+  @Override
+  public void addDex2OatSizeResult(long result) {
+    throw error();
   }
 
   @Override
   public void addResourceSizeResult(long result) {
+    throw error();
+  }
+
+  private BenchmarkConfigError error() {
     throw new BenchmarkConfigError(
         "Unexpected attempt to add a result to a the root of a benchmark with sub-benchmarks");
   }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
index ad6b1b3..7553ea8 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
@@ -3,12 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.DexSegments.SegmentInfo;
+import com.android.tools.r8.dex.DexSection;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
 import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.util.Set;
+import java.util.function.IntToLongFunction;
 import java.util.function.LongConsumer;
 
 public class BenchmarkResultsSingle implements BenchmarkResults {
@@ -16,8 +23,13 @@
   private final String name;
   private final Set<BenchmarkMetric> metrics;
   private final LongList runtimeResults = new LongArrayList();
+
+  // Consider using LongSet to eliminate duplicate results for size.
   private final LongList codeSizeResults = new LongArrayList();
-  private final LongList composableCodeSizeResults = new LongArrayList();
+  private final LongList instructionCodeSizeResults = new LongArrayList();
+  private final LongList composableInstructionCodeSizeResults = new LongArrayList();
+  private final LongList dex2OatSizeResult = new LongArrayList();
+  private final List<Int2ReferenceMap<SegmentInfo>> dexSegmentsSizeResults = new ArrayList<>();
 
   public BenchmarkResultsSingle(String name, Set<BenchmarkMetric> metrics) {
     this.name = name;
@@ -32,8 +44,20 @@
     return codeSizeResults;
   }
 
-  public LongList getComposableCodeSizeResults() {
-    return composableCodeSizeResults;
+  public LongList getInstructionCodeSizeResults() {
+    return instructionCodeSizeResults;
+  }
+
+  public LongList getComposableInstructionCodeSizeResults() {
+    return composableInstructionCodeSizeResults;
+  }
+
+  public List<Int2ReferenceMap<SegmentInfo>> getDexSegmentsSizeResults() {
+    return dexSegmentsSizeResults;
+  }
+
+  public LongList getDex2OatSizeResult() {
+    return dex2OatSizeResult;
   }
 
   public LongList getRuntimeResults() {
@@ -53,12 +77,37 @@
   }
 
   @Override
-  public void addComposableCodeSizeResult(long result) {
+  public void addInstructionCodeSizeResult(long result) {
     verifyMetric(
-        BenchmarkMetric.ComposableCodeSize,
-        metrics.contains(BenchmarkMetric.ComposableCodeSize),
+        BenchmarkMetric.InstructionCodeSize,
+        metrics.contains(BenchmarkMetric.InstructionCodeSize),
         true);
-    composableCodeSizeResults.add(result);
+    instructionCodeSizeResults.add(result);
+  }
+
+  @Override
+  public void addComposableInstructionCodeSizeResult(long result) {
+    verifyMetric(
+        BenchmarkMetric.ComposableInstructionCodeSize,
+        metrics.contains(BenchmarkMetric.ComposableInstructionCodeSize),
+        true);
+    composableInstructionCodeSizeResults.add(result);
+  }
+
+  @Override
+  public void addDexSegmentsSizeResult(Int2ReferenceMap<SegmentInfo> result) {
+    verifyMetric(
+        BenchmarkMetric.DexSegmentsCodeSize,
+        metrics.contains(BenchmarkMetric.DexSegmentsCodeSize),
+        true);
+    dexSegmentsSizeResults.add(result);
+  }
+
+  @Override
+  public void addDex2OatSizeResult(long result) {
+    verifyMetric(
+        BenchmarkMetric.Dex2OatCodeSize, metrics.contains(BenchmarkMetric.Dex2OatCodeSize), true);
+    dex2OatSizeResult.add(result);
   }
 
   @Override
@@ -91,12 +140,24 @@
         !runtimeResults.isEmpty());
     verifyMetric(
         BenchmarkMetric.CodeSize,
-        metrics.contains(BenchmarkMetric.CodeSize),
+        isBenchmarkingCodeSize() && metrics.contains(BenchmarkMetric.CodeSize),
         !codeSizeResults.isEmpty());
     verifyMetric(
-        BenchmarkMetric.ComposableCodeSize,
-        metrics.contains(BenchmarkMetric.ComposableCodeSize),
-        !composableCodeSizeResults.isEmpty());
+        BenchmarkMetric.InstructionCodeSize,
+        isBenchmarkingCodeSize() && metrics.contains(BenchmarkMetric.InstructionCodeSize),
+        !instructionCodeSizeResults.isEmpty());
+    verifyMetric(
+        BenchmarkMetric.ComposableInstructionCodeSize,
+        isBenchmarkingCodeSize() && metrics.contains(BenchmarkMetric.ComposableInstructionCodeSize),
+        !composableInstructionCodeSizeResults.isEmpty());
+    verifyMetric(
+        BenchmarkMetric.DexSegmentsCodeSize,
+        isBenchmarkingCodeSize() && metrics.contains(BenchmarkMetric.DexSegmentsCodeSize),
+        !dexSegmentsSizeResults.isEmpty());
+    verifyMetric(
+        BenchmarkMetric.Dex2OatCodeSize,
+        isBenchmarkingCodeSize() && metrics.contains(BenchmarkMetric.Dex2OatCodeSize),
+        !dex2OatSizeResult.isEmpty());
   }
 
   private void printRunTime(long duration) {
@@ -105,12 +166,29 @@
   }
 
   private void printCodeSize(long bytes) {
-    System.out.println(BenchmarkResults.prettyMetric(name, BenchmarkMetric.CodeSize, "" + bytes));
+    System.out.println(BenchmarkResults.prettyMetric(name, BenchmarkMetric.CodeSize, bytes));
   }
 
-  private void printComposableCodeSize(long bytes) {
+  private void printInstructionCodeSize(long bytes) {
     System.out.println(
-        BenchmarkResults.prettyMetric(name, BenchmarkMetric.ComposableCodeSize, "" + bytes));
+        BenchmarkResults.prettyMetric(name, BenchmarkMetric.InstructionCodeSize, bytes));
+  }
+
+  private void printComposableInstructionCodeSize(long bytes) {
+    System.out.println(
+        BenchmarkResults.prettyMetric(name, BenchmarkMetric.ComposableInstructionCodeSize, bytes));
+  }
+
+  private void printDexSegmentSize(int section, long bytes) {
+    System.out.println(
+        BenchmarkResults.prettyMetric(
+            name,
+            BenchmarkMetric.DexSegmentsCodeSize + ", " + DexSection.typeName(section),
+            bytes));
+  }
+
+  private void printDex2OatSize(long bytes) {
+    System.out.println(BenchmarkResults.prettyMetric(name, BenchmarkMetric.Dex2OatCodeSize, bytes));
   }
 
   @Override
@@ -123,23 +201,45 @@
     }
     printCodeSizeResults(codeSizeResults, failOnCodeSizeDifferences, this::printCodeSize);
     printCodeSizeResults(
-        composableCodeSizeResults, failOnCodeSizeDifferences, this::printComposableCodeSize);
+        instructionCodeSizeResults, failOnCodeSizeDifferences, this::printInstructionCodeSize);
+    printCodeSizeResults(
+        composableInstructionCodeSizeResults,
+        failOnCodeSizeDifferences,
+        this::printComposableInstructionCodeSize);
+    for (int section : DexSection.getConstants()) {
+      printCodeSizeResults(
+          dexSegmentsSizeResults,
+          i -> dexSegmentsSizeResults.get(i).get(section).getSegmentSize(),
+          failOnCodeSizeDifferences,
+          result -> printDexSegmentSize(section, result));
+    }
+    printCodeSizeResults(dex2OatSizeResult, failOnCodeSizeDifferences, this::printDex2OatSize);
   }
 
   private static void printCodeSizeResults(
       LongList codeSizeResults, boolean failOnCodeSizeDifferences, LongConsumer printer) {
-    if (!codeSizeResults.isEmpty()) {
-      long size = codeSizeResults.getLong(0);
-      if (failOnCodeSizeDifferences) {
-        for (int i = 1; i < codeSizeResults.size(); i++) {
-          if (size != codeSizeResults.getLong(i)) {
-            throw new RuntimeException(
-                "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i));
-          }
+    printCodeSizeResults(
+        codeSizeResults, codeSizeResults::getLong, failOnCodeSizeDifferences, printer);
+  }
+
+  private static void printCodeSizeResults(
+      Collection<?> codeSizeResults,
+      IntToLongFunction getter,
+      boolean failOnCodeSizeDifferences,
+      LongConsumer printer) {
+    if (codeSizeResults.isEmpty()) {
+      return;
+    }
+    long size = getter.applyAsLong(0);
+    if (failOnCodeSizeDifferences) {
+      for (int i = 1; i < codeSizeResults.size(); i++) {
+        if (size != getter.applyAsLong(i)) {
+          throw new RuntimeException(
+              "Unexpected code size difference: " + size + " and " + getter.applyAsLong(i));
         }
       }
-      printer.accept(size);
     }
+    printer.accept(size);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
index 68d6fb8..86cfe62 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
@@ -3,13 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
-import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.dex.DexSection;
+import com.google.common.base.CaseFormat;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonSerializationContext;
 import com.google.gson.JsonSerializer;
 import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.function.IntToLongFunction;
 
 public class BenchmarkResultsSingleAdapter implements JsonSerializer<BenchmarkResultsSingle> {
 
@@ -17,22 +20,74 @@
   public JsonElement serialize(
       BenchmarkResultsSingle result, Type type, JsonSerializationContext jsonSerializationContext) {
     JsonArray resultsArray = new JsonArray();
-    ListUtils.forEachWithIndex(
-        result.getCodeSizeResults(),
-        (codeSizeResult, iteration) -> {
-          JsonObject resultObject = new JsonObject();
-          resultObject.addProperty("code_size", codeSizeResult);
-          if (!result.getComposableCodeSizeResults().isEmpty()) {
-            resultObject.addProperty(
-                "composable_code_size", result.getComposableCodeSizeResults().getLong(iteration));
-          }
-          resultObject.addProperty("runtime", result.getRuntimeResults().getLong(iteration));
+    for (int iteration = 0; iteration < result.getCodeSizeResults().size(); iteration++) {
+      JsonObject resultObject = new JsonObject();
+      addPropertyIfValueDifferentFromRepresentative(
+          resultObject,
+          "code_size",
+          iteration,
+          result.getCodeSizeResults(),
+          i -> result.getCodeSizeResults().getLong(i));
+      addPropertyIfValueDifferentFromRepresentative(
+          resultObject,
+          "ins_code_size",
+          iteration,
+          result.getInstructionCodeSizeResults(),
+          i -> result.getInstructionCodeSizeResults().getLong(i));
+      addPropertyIfValueDifferentFromRepresentative(
+          resultObject,
+          "composable_code_size",
+          iteration,
+          result.getComposableInstructionCodeSizeResults(),
+          i -> result.getComposableInstructionCodeSizeResults().getLong(i));
+      for (int section : DexSection.getConstants()) {
+        String sectionName = DexSection.typeName(section);
+        String sectionNameUnderscore =
+            CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, sectionName);
+        addPropertyIfValueDifferentFromRepresentative(
+            resultObject,
+            "dex_" + sectionNameUnderscore + "_size",
+            iteration,
+            result.getDexSegmentsSizeResults(),
+            i -> result.getDexSegmentsSizeResults().get(i).get(section).getSegmentSize());
+      }
+      addPropertyIfValueDifferentFromRepresentative(
+          resultObject,
+          "oat_code_size",
+          iteration,
+          result.getDex2OatSizeResult(),
+          i -> result.getDex2OatSizeResult().getLong(i));
+      addPropertyIfValueDifferentFromRepresentative(
+          resultObject,
+          "runtime",
+          iteration,
+          result.getRuntimeResults(),
+          i -> result.getRuntimeResults().getLong(i));
           resultsArray.add(resultObject);
-        });
+    }
 
     JsonObject benchmarkObject = new JsonObject();
     benchmarkObject.addProperty("benchmark_name", result.getName());
     benchmarkObject.add("results", resultsArray);
     return benchmarkObject;
   }
+
+  private void addPropertyIfValueDifferentFromRepresentative(
+      JsonObject resultObject,
+      String propertyName,
+      int iteration,
+      Collection<?> results,
+      IntToLongFunction getter) {
+    if (results.isEmpty()) {
+      return;
+    }
+    long result = getter.applyAsLong(iteration);
+    if (iteration != 0) {
+      long representativeResult = getter.applyAsLong(0);
+      if (result == representativeResult) {
+        return;
+      }
+    }
+    resultObject.addProperty(propertyName, result);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
index 19c1de8..4f16cc4 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.DexSegments.SegmentInfo;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
 import java.io.PrintStream;
@@ -13,7 +16,6 @@
   private final String name;
   private final LongList runtimeResults = new LongArrayList();
   private long codeSizeResult = -1;
-  private long composableCodeSizeResult = -1;
   private long resourceSizeResult = -1;
 
   public BenchmarkResultsWarmup(String name) {
@@ -37,17 +39,27 @@
   }
 
   @Override
-  public void addComposableCodeSizeResult(long result) {
-    if (composableCodeSizeResult == -1) {
-      composableCodeSizeResult = result;
-    }
-    if (composableCodeSizeResult != result) {
-      throw new RuntimeException(
-          "Unexpected Composable code size difference: "
-              + result
-              + " and "
-              + composableCodeSizeResult);
-    }
+  public void addInstructionCodeSizeResult(long result) {
+    throw addSizeResultError();
+  }
+
+  @Override
+  public void addComposableInstructionCodeSizeResult(long result) {
+    throw addSizeResultError();
+  }
+
+  @Override
+  public void addDexSegmentsSizeResult(Int2ReferenceMap<SegmentInfo> result) {
+    throw addSizeResultError();
+  }
+
+  @Override
+  public void addDex2OatSizeResult(long result) {
+    throw addSizeResultError();
+  }
+
+  private Unreachable addSizeResultError() {
+    throw new Unreachable("Unexpected attempt to add size result for warmup run");
   }
 
   @Override
@@ -68,6 +80,11 @@
   }
 
   @Override
+  public boolean isBenchmarkingCodeSize() {
+    return false;
+  }
+
+  @Override
   public void printResults(ResultMode mode, boolean failOnCodeSizeDifferences) {
     if (runtimeResults.isEmpty()) {
       throw new BenchmarkConfigError("Expected runtime results for warmup run");
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index 09934bc..1ba1fd0 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -118,7 +118,10 @@
         .addDependency(dumpDependency)
         .measureRunTime()
         .measureCodeSize()
-        .measureComposableCodeSize()
+        .measureInstructionCodeSize()
+        .measureComposableInstructionCodeSize()
+        .measureDexSegmentsCodeSize()
+        .measureDex2OatCodeSize()
         .setTimeout(10, TimeUnit.MINUTES)
         .build();
   }
@@ -306,7 +309,9 @@
                           r ->
                               r.benchmarkCompile(results)
                                   .benchmarkCodeSize(results)
-                                  .benchmarkComposableCodeSize(results));
+                                  .benchmarkInstructionCodeSize(results)
+                                  .benchmarkDexSegmentsCodeSize(results)
+                                  .benchmarkDex2OatCodeSize(results));
                 });
   }
 
diff --git a/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
index 7c40462..b8f63c1 100644
--- a/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compose;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -40,7 +41,19 @@
   enum ComposableFunction {
     A,
     B,
-    C
+    C;
+
+    public int getExpectedNumberOfIfInstructions() {
+      switch (this) {
+        case A:
+        case B:
+          return 5;
+        case C:
+          return 3;
+        default:
+          throw new Unreachable();
+      }
+    }
   }
 
   static class CodeStats {
@@ -76,24 +89,15 @@
 
   @Test
   public void test() throws Exception {
-    EnumMap<ComposableFunction, CodeStats> defaultCodeStats = build(false);
-    EnumMap<ComposableFunction, CodeStats> optimizedCodeStats = build(true);
+    EnumMap<ComposableFunction, CodeStats> result = build();
     for (ComposableFunction composableFunction : ComposableFunction.values()) {
-      CodeStats defaultCodeStatsForFunction = defaultCodeStats.get(composableFunction);
-      CodeStats optimizedCodeStatsForFunction = optimizedCodeStats.get(composableFunction);
-      assertTrue(
-          composableFunction
-              + ": "
-              + defaultCodeStatsForFunction.numberOfIfInstructions
-              + " vs "
-              + optimizedCodeStatsForFunction.numberOfIfInstructions,
-          defaultCodeStatsForFunction.numberOfIfInstructions
-              > optimizedCodeStatsForFunction.numberOfIfInstructions);
+      CodeStats codeStats = result.get(composableFunction);
+      assertEquals(
+          composableFunction.getExpectedNumberOfIfInstructions(), codeStats.numberOfIfInstructions);
     }
   }
 
-  private EnumMap<ComposableFunction, CodeStats> build(boolean enableComposeOptimizations)
-      throws Exception {
+  private EnumMap<ComposableFunction, CodeStats> build() throws Exception {
     Box<ClassReference> mainActivityKtClassReference =
         new Box<>(Reference.classFromTypeName("com.example.MainActivityKt"));
     R8TestCompileResult compileResult =
@@ -114,12 +118,7 @@
                 updateMainActivityKt(
                     MinificationInspector::getTarget, mainActivityKtClassReference, true))
             .addOptionsModification(
-                options -> {
-                  options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces();
-                  options
-                      .getJetpackComposeOptions()
-                      .enableAllOptimizations(enableComposeOptimizations);
-                })
+                options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
             .setMinApi(AndroidApiLevel.N)
             .allowDiagnosticMessages()
             .allowUnnecessaryDontWarnWildcards()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeStandaloneDayTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeStandaloneDayTest.java
new file mode 100644
index 0000000..1cab68d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeStandaloneDayTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.time.DayOfWeek;
+import java.time.format.TextStyle;
+import java.util.List;
+import java.util.Locale;
+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 DateTimeStandaloneDayTest extends DesugaredLibraryTestBase {
+
+  // TODO(b/362277530): Replace expected output when desugared library is updated.
+  private static final String EXPECTED_OUTPUT_TO_FIX =
+      StringUtils.lines("1", "2", "3", "4", "5", "6", "7");
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday");
+
+  private final TestParameters parameters;
+  private final CompilationSpecification compilationSpecification;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public DateTimeStandaloneDayTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.compilationSpecification = compilationSpecification;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+  }
+
+  @Test
+  public void testStandaloneDay() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputIf(
+            parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O), EXPECTED_OUTPUT)
+        .assertSuccessWithOutputIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O), EXPECTED_OUTPUT_TO_FIX);
+  }
+
+  public static void main(String[] args) {
+    TestClass.main(args);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(DayOfWeek.MONDAY.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US));
+      System.out.println(DayOfWeek.TUESDAY.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US));
+      System.out.println(DayOfWeek.WEDNESDAY.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US));
+      System.out.println(DayOfWeek.THURSDAY.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US));
+      System.out.println(DayOfWeek.FRIDAY.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US));
+      System.out.println(DayOfWeek.SATURDAY.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US));
+      System.out.println(DayOfWeek.SUNDAY.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java
index d5b8f32..55f23f9 100644
--- a/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DexCodeDeduppingTest.java
@@ -239,7 +239,7 @@
   public SegmentInfo getCodeSegmentInfo(Path path)
       throws CompilationFailedException, ResourceException, IOException {
     Command.Builder builder = Command.builder().addProgramFiles(path);
-    Map<Integer, SegmentInfo> segmentInfoMap = DexSegments.run(builder.build());
+    Map<Integer, SegmentInfo> segmentInfoMap = DexSegments.runForTesting(builder.build());
     return segmentInfoMap.get(Constants.TYPE_CODE_ITEM);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java b/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java
index 8107aff..a174673 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.analysis.path.state.PathConstraintKind;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
 import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeUnopCompareNode;
@@ -56,7 +57,8 @@
     IRCode code =
         inspector.clazz(Main.class).uniqueMethodWithOriginalName("greet").buildIR(appView);
     PathConstraintAnalysis analysis =
-        new PathConstraintAnalysis(appView, code, new MethodParameterFactory());
+        new PathConstraintAnalysis(
+            appView, code, new FieldValueFactory(), new MethodParameterFactory());
     DataflowAnalysisResult result = analysis.run(code.entryBlock());
     assertTrue(result.isSuccessfulAnalysisResult());
     SuccessfulDataflowAnalysisResult<BasicBlock, PathConstraintAnalysisState> successfulResult =
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerR8Test.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerR8Test.java
new file mode 100644
index 0000000..37f133f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerR8Test.java
@@ -0,0 +1,201 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.annotations;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilation;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic;
+import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType.CovariantReturnTypes;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import java.util.Map;
+import org.hamcrest.Matcher;
+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 CovariantReturnTypeAnnotationTransformerR8Test extends TestBase {
+
+  private static final String covariantReturnTypeDescriptor =
+      "Ldalvik/annotation/codegen/CovariantReturnType;";
+  private static final String covariantReturnTypesDescriptor =
+      "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;";
+
+  private static final Map<String, String> descriptorTransformation =
+      ImmutableMap.of(
+          descriptor(com.android.tools.r8.ir.desugar.annotations.version2.B.class),
+          descriptor(B.class),
+          descriptor(com.android.tools.r8.ir.desugar.annotations.version2.C.class),
+          descriptor(C.class),
+          descriptor(com.android.tools.r8.ir.desugar.annotations.version2.E.class),
+          descriptor(E.class),
+          descriptor(com.android.tools.r8.ir.desugar.annotations.version2.F.class),
+          descriptor(F.class),
+          descriptor(CovariantReturnType.class),
+          covariantReturnTypeDescriptor,
+          descriptor(CovariantReturnTypes.class),
+          covariantReturnTypesDescriptor);
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withMaximumApiLevel().build();
+  }
+
+  @Test
+  public void testDontObfuscateDontOptimizeDontShrink() throws Exception {
+    R8TestCompileResult r8CompileResult =
+        compileWithR8(
+            testBuilder -> testBuilder.addDontObfuscate().addDontOptimize().addDontShrink());
+    testOnRuntime(r8CompileResult);
+  }
+
+  @Test
+  public void testUnconditionalKeepAllPublicMethods() throws Exception {
+    R8TestCompileResult r8CompileResult =
+        compileWithR8(
+            testBuilder -> testBuilder.addKeepRules("-keep public class * { public <methods>; }"));
+    testOnRuntime(r8CompileResult);
+  }
+
+  @Test
+  public void testUnconditionalKeepAllPublicMethodsAllowObfuscation() throws Exception {
+    assertFailsCompilation(
+        () ->
+            compileWithR8(
+                testBuilder ->
+                    testBuilder.addKeepRules(
+                        "-keep,allowobfuscation public class * { public <methods>; }"),
+                this::inspectDiagnostics));
+  }
+
+  @Test
+  public void testUnconditionalKeepAllPublicMethodsAllowOptimization() throws Exception {
+    assertFailsCompilation(
+        () ->
+            compileWithR8(
+                testBuilder ->
+                    testBuilder.addKeepRules(
+                        "-keep,allowoptimization public class * { public <methods>; }"),
+                this::inspectDiagnostics));
+  }
+
+  @Test
+  public void testConditionalKeepAllPublicMethods() throws Exception {
+    assertFailsCompilation(
+        () ->
+            compileWithR8(
+                testBuilder ->
+                    testBuilder.addKeepRules(
+                        "-if public class * -keep class <1> { public <methods>; }",
+                        "-keep public class *"),
+                this::inspectDiagnostics));
+  }
+
+  private R8TestCompileResult compileWithR8(
+      ThrowableConsumer<? super R8FullTestBuilder> configuration) throws Exception {
+    return compileWithR8(configuration, TestDiagnosticMessages::assertNoMessages);
+  }
+
+  private R8TestCompileResult compileWithR8(
+      ThrowableConsumer<? super R8FullTestBuilder> configuration,
+      DiagnosticsConsumer<?> diagnosticsConsumer)
+      throws Exception {
+    return testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, D.class)
+        .addProgramClassFileData(
+            transformer(com.android.tools.r8.ir.desugar.annotations.version2.B.class)
+                .replaceClassDescriptorInAnnotations(descriptorTransformation)
+                .replaceClassDescriptorInMethodInstructions(descriptorTransformation)
+                .setClassDescriptor(descriptor(B.class))
+                .transform(),
+            transformer(com.android.tools.r8.ir.desugar.annotations.version2.C.class)
+                .replaceClassDescriptorInAnnotations(descriptorTransformation)
+                .replaceClassDescriptorInMethodInstructions(descriptorTransformation)
+                .setClassDescriptor(descriptor(C.class))
+                .transform(),
+            transformer(com.android.tools.r8.ir.desugar.annotations.version2.E.class)
+                .replaceClassDescriptorInAnnotations(descriptorTransformation)
+                .replaceClassDescriptorInMethodInstructions(descriptorTransformation)
+                .setClassDescriptor(descriptor(E.class))
+                .transform(),
+            transformer(com.android.tools.r8.ir.desugar.annotations.version2.F.class)
+                .replaceClassDescriptorInAnnotations(descriptorTransformation)
+                .replaceClassDescriptorInMethodInstructions(descriptorTransformation)
+                .setClassDescriptor(descriptor(F.class))
+                .transform(),
+            transformer(CovariantReturnType.class)
+                .replaceClassDescriptorInAnnotations(descriptorTransformation)
+                .setClassDescriptor(covariantReturnTypeDescriptor)
+                .transform(),
+            transformer(CovariantReturnTypes.class)
+                .replaceClassDescriptorInAnnotations(descriptorTransformation)
+                .replaceClassDescriptorInMembers(
+                    descriptor(CovariantReturnType.class), covariantReturnTypeDescriptor)
+                .setClassDescriptor(covariantReturnTypesDescriptor)
+                .transform())
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+        .addOptionsModification(options -> options.processCovariantReturnTypeAnnotations = true)
+        .apply(configuration)
+        .setMinApi(parameters)
+        .compileWithExpectedDiagnostics(diagnosticsConsumer);
+  }
+
+  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) throws Exception {
+    List<MethodReference> methods =
+        ImmutableList.of(
+            Reference.methodFromMethod(B.class.getDeclaredMethod("method")),
+            Reference.methodFromMethod(C.class.getDeclaredMethod("method")),
+            Reference.methodFromMethod(F.class.getDeclaredMethod("method")));
+    List<String> messages =
+        ListUtils.map(
+            methods,
+            method ->
+                "Methods with @CovariantReturnType annotations should be kept, but was not: "
+                    + MethodReferenceUtils.toSourceString(method));
+    List<Matcher<Diagnostic>> matchers =
+        ListUtils.map(
+            messages,
+            message ->
+                allOf(
+                    diagnosticType(NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.class),
+                    diagnosticMessage(equalTo(message))));
+    diagnostics.assertErrorsMatch(matchers);
+  }
+
+  private void testOnRuntime(R8TestCompileResult r8CompileResult) throws Exception {
+    testForD8()
+        .addProgramClasses(Client.class)
+        .addClasspathClasses(A.class, B.class, C.class, D.class, E.class, F.class)
+        .setMinApi(parameters)
+        .compile()
+        .addRunClasspathFiles(r8CompileResult.writeToZip())
+        .run(parameters.getRuntime(), Client.class)
+        .assertSuccessWithOutputLines("a=A", "b=B", "c=C", "d=F", "e=F", "f=F");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
index 52fe17f..058e13e 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar.annotations.version2;
 
 import com.android.tools.r8.ir.desugar.annotations.A;
+import com.android.tools.r8.ir.desugar.annotations.B;
 import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
 
 public class C extends B {
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/F.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/F.java
index a133826..5423b42 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/F.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/F.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
 import com.android.tools.r8.ir.desugar.annotations.D;
+import com.android.tools.r8.ir.desugar.annotations.E;
 
 public class F extends E {
   @CovariantReturnType(returnType = E.class, presentAfter = 25)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
index 93c4be1..63f24f0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
@@ -41,8 +41,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> options.getTestingOptions().enableIfThenElseAbstractFunction = true)
         .enableInliningAnnotations()
         .setMinApi(parameters)
         .compile()
@@ -71,11 +69,10 @@
                       .streamInstructions()
                       .noneMatch(
                           instruction -> instruction.isConstString("DefaultValueNeverUsed")));
-              // TODO(b/302281503): This argument is never used and should be removed.
               assertTrue(
                   mainMethodSubject
                       .streamInstructions()
-                      .anyMatch(
+                      .noneMatch(
                           instruction ->
                               instruction.isConstString("Unused[DefaultValueAlwaysUsed]")));
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index e3944a9..4014e78 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_6_0;
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_9_21;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_0_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLIN_DEV;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -212,12 +214,26 @@
                                     kotlinParameters
                                         .getCompiler()
                                         .getCompilerVersion()
-                                        .isGreaterThanOrEqualTo(KOTLINC_1_9_21),
+                                        .isBetweenBothIncluded(KOTLINC_1_9_21, KOTLINC_2_0_20),
                                     i ->
                                         i.assertIsCompleteMergeGroup(
                                             SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 0),
                                             SyntheticItemsTestUtils.syntheticLambdaClass(
-                                                mainKt, 1)))
+                                                mainKt, 1)),
+                                    kotlinParameters
+                                        .getCompiler()
+                                        .getCompilerVersion()
+                                        .isEqualTo(KOTLIN_DEV),
+                                    i -> {
+                                      ClassReference sequencesKt =
+                                          Reference.classFromTypeName(
+                                              "kotlin.sequences.SequencesKt__SequencesKt");
+                                      i.assertIsCompleteMergeGroup(
+                                          SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 0),
+                                          SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 1),
+                                          SyntheticItemsTestUtils.syntheticLambdaClass(
+                                              sequencesKt, 0));
+                                    })
                                 .assertNoOtherClassesMerged();
                           } else {
                             assert kotlinParameters.getLambdaGeneration().isInvokeDynamic()
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
index 988651b..ce259f2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
@@ -4,12 +4,13 @@
 
 package com.android.tools.r8.kotlin.lambda;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_9_21;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_0_20;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -70,13 +71,13 @@
         KotlinLambdasInInput.create(getProgramFiles(), getTestName());
     assertEquals(
         kotlinParameters.getLambdaGeneration().isInvokeDynamic()
-                && kotlinParameters.getCompilerVersion() == KotlinCompilerVersion.KOTLIN_DEV
+                && kotlinParameters.getCompilerVersion().isLessThanOrEqualTo(KOTLINC_2_0_20)
             ? 8
             : 2,
         lambdasInInput.getNumberOfJStyleLambdas());
     assertEquals(
         kotlinParameters.getLambdaGeneration().isInvokeDynamic()
-            ? (kotlinParameters.getCompilerVersion() == KotlinCompilerVersion.KOTLIN_DEV ? 0 : 6)
+            ? (kotlinParameters.getCompilerVersion().isLessThanOrEqualTo(KOTLINC_2_0_20) ? 0 : 6)
             : 7,
         lambdasInInput.getNumberOfKStyleLambdas());
 
@@ -99,9 +100,7 @@
   private void inspect(
       HorizontallyMergedClassesInspector inspector, KotlinLambdasInInput lambdasInInput) {
     // All J-style Kotlin lambdas should be merged into one class.
-    if (kotlinParameters
-        .getCompilerVersion()
-        .isLessThanOrEqualTo(KotlinCompilerVersion.KOTLINC_1_9_21)) {
+    if (kotlinParameters.getCompilerVersion().isLessThanOrEqualTo(KOTLINC_1_9_21)) {
       inspector.assertIsCompleteMergeGroup(lambdasInInput.getJStyleLambdas());
     } else {
       assertEquals(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index e2339f6..055f31c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_0_20;
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertNotNull;
@@ -181,13 +182,16 @@
 
   protected String unresolvedReferenceMessage(KotlinTestParameters param, String ref) {
     if (param.isKotlinDev()) {
+      return "unresolved reference 'class " + ref + "'";
+    } else if (param.getCompilerVersion().isGreaterThanOrEqualTo(KOTLINC_2_0_20)) {
       return "unresolved reference '" + ref + "'";
+    } else {
+      return "unresolved reference: " + ref;
     }
-    return "unresolved reference: " + ref;
   }
 
   protected String cannotAccessMessage(KotlinTestParameters param, String ref) {
-    if (param.isKotlinDev()) {
+    if (param.isNewerThanOrEqualTo(KOTLINC_2_0_20)) {
       return "cannot access 'class " + ref + " : Any'";
     }
     return "cannot access '" + ref + "'";
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 24f257b..d6b8c34 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_0_20;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -178,8 +179,11 @@
   private String sealedClassErrorMessage() {
     if (kotlinParameters.isKotlinDev()) {
       return "a class can only extend a sealed class or interface declared in the same package";
+    } else if (kotlinParameters.is(KOTLINC_2_0_20)) {
+      return "extending sealed classes or interfaces from a different module is prohibited";
+    } else {
+      return "inheritance of sealed classes or interfaces from different module is prohibited";
     }
-    return "inheritance of sealed classes or interfaces from different module is prohibited";
   }
 
   private void inspectInvalid(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
index bcb01da..4824bf1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
@@ -55,6 +55,8 @@
     testForR8(parameters.getBackend())
         // Use android.jar with java.lang.ClassValue.
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.U))
+        // Add java.lang.invoke.LambdaMetafactory for class file generation.
+        .applyIf(parameters.isCfRuntime(), b -> b.addLibraryFiles(ToolHelper.getCoreLambdaStubs()))
         .addProgramFiles(
             kotlinJars.getForConfiguration(kotlinParameters), kotlinc.getKotlinAnnotationJar())
         .addKeepMainRule("enumswitch.EnumSwitchKt")
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 a5c6dd3..4d28c48 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
@@ -119,6 +119,8 @@
   @Test
   public void testR8KeepDataClass() throws Exception {
     configureR8(testForR8(parameters.getBackend()).addDontObfuscate())
+        // Add java.lang.invoke.LambdaMetafactory for class file generation.
+        .applyIf(parameters.isCfRuntime(), b -> b.addLibraryFiles(ToolHelper.getCoreLambdaStubs()))
         .compile()
         .assertNoErrorMessages()
         .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
@@ -130,6 +132,8 @@
   public void testR8KeepDataClassAndInlineClass() throws Exception {
     configureR8(testForR8(parameters.getBackend()))
         .addKeepRules("-keep class " + PKG + ".Value { *; }")
+        // Add java.lang.invoke.LambdaMetafactory for class file generation.
+        .applyIf(parameters.isCfRuntime(), b -> b.addLibraryFiles(ToolHelper.getCoreLambdaStubs()))
         .compile()
         .assertNoErrorMessages()
         .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/StringPoolSizeWithLazyDexStringsTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/StringPoolSizeWithLazyDexStringsTest.java
index 4598b8a..bfc8f65 100644
--- a/src/test/java/com/android/tools/r8/naming/sourcefile/StringPoolSizeWithLazyDexStringsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/StringPoolSizeWithLazyDexStringsTest.java
@@ -66,7 +66,7 @@
 
   private void checkStringSegmentSize(R8TestCompileResult result) throws Exception {
     Map<Integer, SegmentInfo> segments =
-        DexSegments.run(Command.builder().addProgramFiles(result.writeToZip()).build());
+        DexSegments.runForTesting(Command.builder().addProgramFiles(result.writeToZip()).build());
     SegmentInfo stringInfo = segments.get(Constants.TYPE_STRING_ID_ITEM);
     assertEquals(8, stringInfo.getItemCount());
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingTypeCollisionTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingTypeCollisionTest.java
index e1f0cb0..fff87b6 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageMissingTypeCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingTypeCollisionTest.java
@@ -43,7 +43,7 @@
             transformer(A.class).setClassDescriptor(newADescriptor).transform(),
             transformer(Anno.class)
                 .replaceClassDescriptorInMembers(descriptor(Missing.class), newMissingDescriptor)
-                .replaceClassDescriptorInAnnotationDefault(
+                .replaceClassDescriptorInAnnotations(
                     descriptor(Missing.class), newMissingDescriptor)
                 .transform(),
             transformer(Main.class)
@@ -96,7 +96,7 @@
             transformer(A.class).setClassDescriptor(newADescriptor).transform(),
             transformer(Anno.class)
                 .replaceClassDescriptorInMembers(descriptor(Missing.class), newMissingDescriptor)
-                .replaceClassDescriptorInAnnotationDefault(
+                .replaceClassDescriptorInAnnotations(
                     descriptor(Missing.class), newMissingDescriptor)
                 .transform(),
             transformer(Main.class)
diff --git a/src/test/testbase/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/testbase/java/com/android/tools/r8/Dex2OatTestRunResult.java
index 13ae71f..6b89f23 100644
--- a/src/test/testbase/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -8,14 +8,20 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import org.hamcrest.CoreMatchers;
 import org.hamcrest.Matcher;
 
 public class Dex2OatTestRunResult extends SingleTestRunResult<Dex2OatTestRunResult> {
 
+  private final Path oat;
+
   public Dex2OatTestRunResult(
-      AndroidApp app, TestRuntime runtime, ProcessResult result, TestState state) {
+      AndroidApp app, Path oat, TestRuntime runtime, ProcessResult result, TestState state) {
     super(app, runtime, result, state);
+    this.oat = oat;
   }
 
   @Override
@@ -44,13 +50,7 @@
     return self();
   }
 
-  public Dex2OatTestRunResult assertVerificationErrors() {
-    assertSuccess();
-    Matcher<? super String> matcher = CoreMatchers.containsString("Verification error");
-    assertThat(
-        errorMessage("Run dex2oat did not produce verification errors.", matcher.toString()),
-        getStdErr(),
-        matcher);
-    return self();
+  public long getOatSizeOrDefault(long defaultValue) throws IOException {
+    return Files.exists(oat) ? Files.size(oat) : defaultValue;
   }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java
index 8b7b4dd..eb7eb2d 100644
--- a/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -77,10 +77,11 @@
     KOTLINC_1_7_0("kotlin-compiler-1.7.0", KotlinLambdaGeneration.CLASS),
     KOTLINC_1_8_0("kotlin-compiler-1.8.0", KotlinLambdaGeneration.CLASS),
     KOTLINC_1_9_21("kotlin-compiler-1.9.21", KotlinLambdaGeneration.CLASS),
+    KOTLINC_2_0_20("kotlin-compiler-2.0.20", KotlinLambdaGeneration.INVOKE_DYNAMIC),
     KOTLIN_DEV("kotlin-compiler-dev", KotlinLambdaGeneration.INVOKE_DYNAMIC);
 
-    public static final KotlinCompilerVersion MIN_SUPPORTED_VERSION = KOTLINC_1_7_0;
-    public static final KotlinCompilerVersion MAX_SUPPORTED_VERSION = KOTLINC_1_9_21;
+    public static final KotlinCompilerVersion MIN_SUPPORTED_VERSION = KOTLINC_2_0_20;
+    public static final KotlinCompilerVersion MAX_SUPPORTED_VERSION = KOTLINC_2_0_20;
 
     private final String folder;
     private final KotlinLambdaGeneration defaultLambdaGeneration;
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
index 3ff7487..e9956ad 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
@@ -8,13 +8,18 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import com.android.tools.r8.DexSegments.SegmentInfo;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.androidresources.AndroidResourceTestingUtils;
 import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.ResourceTableInspector;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.benchmarks.InstructionCodeSizeResult;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.metadata.R8BuildMetadata;
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
 import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
@@ -30,6 +35,8 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.file.Files;
@@ -314,34 +321,71 @@
   @Override
   public R8TestCompileResult benchmarkCodeSize(BenchmarkResults results)
       throws IOException, ResourceException {
-    int applicationSizeWithFeatures =
-        AndroidApp.builder(app).addProgramFiles(features).build().applicationSize();
-    results.addCodeSizeResult(applicationSizeWithFeatures);
+    if (results.isBenchmarkingCodeSize()) {
+      int applicationSizeWithFeatures =
+          AndroidApp.builder(app).addProgramFiles(features).build().applicationSize();
+      results.addCodeSizeResult(applicationSizeWithFeatures);
+    }
     return self();
   }
 
   @Override
-  public R8TestCompileResult benchmarkComposableCodeSize(BenchmarkResults results)
+  public R8TestCompileResult benchmarkInstructionCodeSize(BenchmarkResults results)
       throws IOException {
-    int composableCodeSize = getComposableCodeSize(inspector());
-    for (Path feature : features) {
-      composableCodeSize += getComposableCodeSize(featureInspector(feature));
+    if (results.isBenchmarkingCodeSize()) {
+      InstructionCodeSizeResult result = getComposableCodeSize(inspector());
+      for (Path feature : features) {
+        result.add(getComposableCodeSize(featureInspector(feature)));
+      }
+      results.addInstructionCodeSizeResult(result.instructionCodeSize);
+      results.addComposableInstructionCodeSizeResult(result.composableInstructionCodeSize);
     }
-    results.addComposableCodeSizeResult(composableCodeSize);
     return self();
   }
 
-  private int getComposableCodeSize(CodeInspector inspector) {
+  private InstructionCodeSizeResult getComposableCodeSize(CodeInspector inspector) {
     DexType composableType =
         inspector.getFactory().createType("Landroidx/compose/runtime/Composable;");
-    int composableCodeSize = 0;
+    InstructionCodeSizeResult result = new InstructionCodeSizeResult();
     for (FoundClassSubject classSubject : inspector.allClasses()) {
-      for (ProgramMethod method : classSubject.getDexProgramClass().directProgramMethods()) {
-        if (method.getAnnotations().hasAnnotation(composableType)) {
-          composableCodeSize += method.getDefinition().getCode().asDexCode().codeSizeInBytes();
+      DexProgramClass clazz = classSubject.getDexProgramClass();
+      for (DexEncodedMethod method : clazz.methods(DexEncodedMethod::hasCode)) {
+        int instructionCodeSize = method.getCode().asDexCode().codeSizeInBytes();
+        result.instructionCodeSize += instructionCodeSize;
+        if (method.annotations().hasAnnotation(composableType)) {
+          result.composableInstructionCodeSize += instructionCodeSize;
         }
       }
     }
-    return composableCodeSize;
+    return result;
+  }
+
+  @Override
+  public R8TestCompileResult benchmarkDexSegmentsCodeSize(BenchmarkResults results)
+      throws IOException, ResourceException {
+    if (results.isBenchmarkingCodeSize()) {
+      AndroidApp appWithFeatures =
+          features.isEmpty() ? app : AndroidApp.builder(app).addProgramFiles(features).build();
+      results.addDexSegmentsSizeResult(runDexSegments(appWithFeatures));
+    }
+    return self();
+  }
+
+  private Int2ReferenceMap<SegmentInfo> runDexSegments(AndroidApp app)
+      throws IOException, ResourceException {
+    Map<Integer, SegmentInfo> result = DexSegments.runForTesting(app);
+    Int2ReferenceMap<SegmentInfo> rewrittenResult = new Int2ReferenceLinkedOpenHashMap<>();
+    rewrittenResult.putAll(result);
+    return rewrittenResult;
+  }
+
+  @Override
+  public R8TestCompileResult benchmarkDex2OatCodeSize(BenchmarkResults results) throws IOException {
+    if (results.isBenchmarkingCodeSize()) {
+      Dex2OatTestRunResult dex2OatTestRunResult =
+          runDex2Oat(new DexRuntime(DexVm.Version.LATEST_DEX2OAT));
+      results.addDex2OatSizeResult(dex2OatTestRunResult.getOatSizeOrDefault(0));
+    }
+    return self();
   }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
index 1f50960..80cf267 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompileResult.java
@@ -715,7 +715,7 @@
     Path oatFile = tmp.resolve("out.oat");
     app.writeToZipForTesting(jarFile, OutputMode.DexIndexed);
     return new Dex2OatTestRunResult(
-        app, runtime, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm), state);
+        app, oatFile, runtime, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm), state);
   }
 
   public CR benchmarkCodeSize(BenchmarkResults results) throws IOException, ResourceException {
@@ -723,7 +723,17 @@
     return self();
   }
 
-  public CR benchmarkComposableCodeSize(BenchmarkResults results)
+  public CR benchmarkInstructionCodeSize(BenchmarkResults results)
+      throws IOException, ResourceException {
+    throw new Unimplemented();
+  }
+
+  public CR benchmarkDexSegmentsCodeSize(BenchmarkResults results)
+      throws IOException, ResourceException {
+    throw new Unimplemented();
+  }
+
+  public CR benchmarkDex2OatCodeSize(BenchmarkResults results)
       throws IOException, ResourceException {
     throw new Unimplemented();
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index 2061f8f..d588f80 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -102,6 +102,13 @@
 public class ToolHelper {
 
   public static String getProjectRoot() {
+    String oracle = System.getProperty("REPO_ROOT");
+    if (oracle != null) {
+      if (!oracle.endsWith("/")) {
+        oracle = oracle + "/";
+      }
+      return oracle;
+    }
     String current = System.getProperty("user.dir");
     if (!current.contains("d8_r8")) {
       return "";
@@ -388,6 +395,8 @@
       // TODO(b/204855476): Rename to DEFAULT alias once the checked in VM is removed.
       public static final Version NEW_DEFAULT = DEFAULT;
 
+      public static final Version LATEST_DEX2OAT = V12_0_0;
+
       Version(String shortName) {
         this.shortName = shortName;
       }
diff --git a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkMetric.java b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkMetric.java
index abf93ce..11ffbcd 100644
--- a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkMetric.java
+++ b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkMetric.java
@@ -6,7 +6,10 @@
 public enum BenchmarkMetric {
   RunTimeRaw,
   CodeSize,
-  ComposableCodeSize,
+  InstructionCodeSize,
+  ComposableInstructionCodeSize,
+  DexSegmentsCodeSize,
+  Dex2OatCodeSize,
   StartupTime;
 
   public String getDartType() {
diff --git a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
index a925368..6f8d5b6 100644
--- a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
+++ b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
@@ -3,17 +3,26 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.DexSegments.SegmentInfo;
 import com.android.tools.r8.utils.StringUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.io.PrintStream;
 
 public interface BenchmarkResults {
+
   // Append a runtime result. This may be summed or averaged depending on the benchmark set up.
   void addRuntimeResult(long result);
 
   // Append a code size result. This is always assumed to be identical if called multiple times.
   void addCodeSizeResult(long result);
 
-  void addComposableCodeSizeResult(long result);
+  void addInstructionCodeSizeResult(long result);
+
+  void addComposableInstructionCodeSizeResult(long result);
+
+  void addDexSegmentsSizeResult(Int2ReferenceMap<SegmentInfo> result);
+
+  void addDex2OatSizeResult(long result);
 
   // Append a resource size result. This is always assumed to be identical if called multiple times.
   void addResourceSizeResult(long result);
@@ -22,6 +31,10 @@
   // This will throw if called on a benchmark without sub-benchmarks.
   BenchmarkResults getSubResults(String name);
 
+  default boolean isBenchmarkingCodeSize() {
+    return true;
+  }
+
   void printResults(ResultMode resultMode, boolean failOnCodeSizeDifferences);
 
   void writeResults(PrintStream out);
@@ -30,8 +43,20 @@
     return "" + (nanoTime / 1000000) + " ms";
   }
 
+  static String prettyMetric(String name, BenchmarkMetric metric, long value) {
+    return prettyMetric(name, metric.name(), Long.toString(value));
+  }
+
   static String prettyMetric(String name, BenchmarkMetric metric, String value) {
-    return name + "(" + metric.name() + "): " + value;
+    return prettyMetric(name, metric.name(), value);
+  }
+
+  static String prettyMetric(String name, String metricName, long value) {
+    return prettyMetric(name, metricName, Long.toString(value));
+  }
+
+  static String prettyMetric(String name, String metricName, String value) {
+    return name + "(" + metricName + "): " + value;
   }
 
   enum ResultMode {
diff --git a/src/test/testbase/java/com/android/tools/r8/benchmarks/InstructionCodeSizeResult.java b/src/test/testbase/java/com/android/tools/r8/benchmarks/InstructionCodeSizeResult.java
new file mode 100644
index 0000000..20af572
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/benchmarks/InstructionCodeSizeResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.benchmarks;
+
+public class InstructionCodeSizeResult {
+
+  public long instructionCodeSize;
+  public long composableInstructionCodeSize;
+
+  public void add(InstructionCodeSizeResult result) {
+    instructionCodeSize += result.instructionCodeSize;
+    composableInstructionCodeSize += result.composableInstructionCodeSize;
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 72bc22a..69bd9b2 100644
--- a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1089,27 +1089,67 @@
         });
   }
 
-  public ClassFileTransformer replaceClassDescriptorInAnnotationDefault(
+  public ClassFileTransformer replaceClassDescriptorInAnnotations(
       String oldDescriptor, String newDescriptor) {
-    return addMethodTransformer(
-        new MethodTransformer() {
+    return replaceClassDescriptorInAnnotations(ImmutableMap.of(oldDescriptor, newDescriptor));
+  }
 
-          @Override
-          public AnnotationVisitor visitAnnotationDefault() {
-            return new AnnotationVisitor(ASM_VERSION, super.visitAnnotationDefault()) {
-              @Override
-              public void visit(String name, Object value) {
-                super.visit(name, value);
-              }
+  public ClassFileTransformer replaceClassDescriptorInAnnotations(Map<String, String> map) {
+    class AnnotationTransformer extends AnnotationVisitor {
 
-              @Override
-              public void visitEnum(String name, String descriptor, String value) {
-                super.visitEnum(
-                    name, descriptor.equals(oldDescriptor) ? newDescriptor : descriptor, value);
-              }
-            };
+      protected AnnotationTransformer(AnnotationVisitor annotationVisitor) {
+        super(ASM_VERSION, annotationVisitor);
+      }
+
+      @Override
+      public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+        return new AnnotationTransformer(
+            super.visitAnnotation(name, map.getOrDefault(descriptor, descriptor)));
+      }
+
+      @Override
+      public AnnotationVisitor visitArray(String name) {
+        return new AnnotationTransformer(super.visitArray(name));
+      }
+
+      @Override
+      public void visitEnum(String name, String descriptor, String value) {
+        super.visitEnum(name, map.getOrDefault(descriptor, descriptor), value);
+      }
+
+      @Override
+      public void visit(String name, Object value) {
+        if (value instanceof Type) {
+          Type type = (Type) value;
+          if (map.containsKey(type.getDescriptor())) {
+            value = Type.getType(map.get(type.getDescriptor()));
           }
-        });
+        }
+        super.visit(name, value);
+      }
+    }
+    return addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                return new AnnotationTransformer(
+                    super.visitAnnotation(map.getOrDefault(descriptor, descriptor), visible));
+              }
+            })
+        .addMethodTransformer(
+            new MethodTransformer() {
+
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                return new AnnotationTransformer(
+                    super.visitAnnotation(map.getOrDefault(descriptor, descriptor), visible));
+              }
+
+              @Override
+              public AnnotationVisitor visitAnnotationDefault() {
+                return new AnnotationTransformer(super.visitAnnotationDefault());
+              }
+            });
   }
 
   public ClassFileTransformer replaceClassDescriptorInMethodInstructions(
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index b3ca5e2..04663af 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -136,6 +136,19 @@
     return this;
   }
 
+  public HorizontallyMergedClassesInspector applyIf(
+      boolean condition,
+      ThrowableConsumer<HorizontallyMergedClassesInspector> thenConsumer,
+      boolean elseIfCondition,
+      ThrowableConsumer<HorizontallyMergedClassesInspector> elseIfThenConsumer) {
+    if (condition) {
+      thenConsumer.acceptWithRuntimeException(this);
+    } else if (elseIfCondition) {
+      elseIfThenConsumer.acceptWithRuntimeException(this);
+    }
+    return this;
+  }
+
   public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) {
     return assertMergedInto(toDexType(from), toDexType(target));
   }
diff --git a/third_party/kotlin/kotlin-compiler-2.0.20.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-2.0.20.tar.gz.sha1
new file mode 100644
index 0000000..93c9bdc
--- /dev/null
+++ b/third_party/kotlin/kotlin-compiler-2.0.20.tar.gz.sha1
@@ -0,0 +1 @@
+4016121311b1e63f1fda5651fb92926028b63aaa
\ No newline at end of file
diff --git a/tools/perf/benchmark_data.json b/tools/perf/benchmark_data.json
index 9d18466..e54df9e 100644
--- a/tools/perf/benchmark_data.json
+++ b/tools/perf/benchmark_data.json
@@ -7,17 +7,48 @@
     "NowInAndroidApp": {
       "benchmark_name": "NowInAndroidApp",
       "results": [
-        { "code_size": 42, "runtime": 1 },
-        { "code_size": 42, "runtime": 2 },
-        { "code_size": 42, "runtime": 3 }
+        {
+          "code_size": 1,
+          "instruction_code_size": 2,
+          "composable_code_size": 3,
+          "oat_code_size": 4,
+          "runtime": 1
+        },
+        {
+          "code_size": 1,
+          "instruction_code_size": 2,
+          "composable_code_size": 3,
+          "oat_code_size": 4,
+          "runtime": 1
+        },
+        {
+          "code_size": 1,
+          "instruction_code_size": 2,
+          "composable_code_size": 3,
+          "oat_code_size": 4,
+          "runtime": 1
+        }
       ]
     },
     "TiviApp": {
       "benchmark_name": "TiviApp",
       "results": [
-        { "code_size": 84, "runtime": 4 },
-        { "code_size": 84, "runtime": 5 },
-        { "code_size": 84, "runtime": 6 }
+        {
+          "code_size": 1,
+          "instruction_code_size": 2,
+          "composable_code_size": 3,
+          "oat_code_size": 4,
+          "runtime": 1
+        },
+        {
+        },
+        {
+          "code_size": 1,
+          "instruction_code_size": 2,
+          "composable_code_size": 3,
+          "oat_code_size": 4,
+          "runtime": 1
+        }
       ]
     }
   }
diff --git a/tools/perf/index.html b/tools/perf/index.html
index 73c2931..8d4a6ef 100644
--- a/tools/perf/index.html
+++ b/tools/perf/index.html
@@ -121,6 +121,29 @@
       benchmarkSelectors.appendChild(label);
     }
 
+    function getSingleResult(benchmark, commit, resultName, resultIteration = 0) {
+      if (!(benchmark in commit.benchmarks)) {
+        return NaN;
+      }
+      const allResults = commit.benchmarks[benchmark].results;
+      const resultsForIteration = allResults[resultIteration];
+      // If a given iteration does not declare a result, then the result
+      // was the same as the first run.
+      if (resultIteration > 0 && !(resultName in resultsForIteration)) {
+        return allResults.first()[resultName];
+      }
+      return resultsForIteration[resultName];
+    }
+
+    function getAllResults(benchmark, commit, resultName) {
+      const result = [];
+      const allResults = commit.benchmarks[benchmark].results;
+      for (var iteration = 0; iteration < allResults.length; iteration++) {
+        result.push(getSingleResult(benchmark, commit, resultName, iteration));
+      }
+      return result;
+    }
+
     // Chart data provider.
     function getData() {
       const filteredCommits = commits.slice(zoom.left, zoom.right);
@@ -129,40 +152,28 @@
       for (const selectedBenchmark of selectedBenchmarks.values()) {
         const codeSizeData =
             filteredCommits.map(
-                (c, i) =>
-                    selectedBenchmark in filteredCommits[i].benchmarks
-                        ? filteredCommits[i]
-                            .benchmarks[selectedBenchmark]
-                            .results
-                            .first()
-                            .code_size
-                        : NaN);
-        const composableCodeSizeData =
+                (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "code_size"));
+        const instructionCodeSizeData =
             filteredCommits.map(
-                (c, i) =>
-                    selectedBenchmark in filteredCommits[i].benchmarks
-                        ? filteredCommits[i]
-                            .benchmarks[selectedBenchmark]
-                            .results
-                            .first()
-                            .composable_code_size
-                        : NaN);
+                (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "ins_code_size"));
+        const composableInstructionCodeSizeData =
+            filteredCommits.map(
+                (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "composable_code_size"));
+        const oatCodeSizeData =
+            filteredCommits.map(
+                (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "oat_code_size"));
         const codeSizeScatterData = [];
         for (const commit of filteredCommits.values()) {
           if (!(selectedBenchmark in commit.benchmarks)) {
             continue;
           }
-          const codeSizes =
-              commit.benchmarks[selectedBenchmark].results.map(result => result.code_size)
-          const expectedCodeSize = codeSizes.first();
-          if (codeSizes.any(codeSize => codeSize != expectedCodeSize)) {
-            const seen = new Set();
-            seen.add(expectedCodeSize);
-            for (const codeSize of codeSizes.values()) {
-              if (!seen.has(codeSize)) {
-                codeSizeScatterData.push({ x: commit.index, y: codeSize });
-                seen.add(codeSize);
-              }
+          const seen = new Set();
+          seen.add(getSingleResult(selectedBenchmark, commit, "code_size"));
+          const codeSizes = getAllResults(selectedBenchmark, commit, "code_size")
+          for (const codeSize of codeSizes.values()) {
+            if (!seen.has(codeSize)) {
+              codeSizeScatterData.push({ x: commit.index, y: codeSize });
+              seen.add(codeSize);
             }
           }
         }
@@ -170,10 +181,7 @@
             filteredCommits.map(
                 (c, i) =>
                     selectedBenchmark in filteredCommits[i].benchmarks
-                        ? filteredCommits[i]
-                            .benchmarks[selectedBenchmark]
-                            .results
-                            .map(result => result.runtime)
+                        ? getAllResults(selectedBenchmark, filteredCommits[i], "runtime")
                             .min()
                             .ns_to_s()
                         : NaN);
@@ -182,8 +190,7 @@
           if (!(selectedBenchmark in commit.benchmarks)) {
             continue;
           }
-          const runtimes =
-              commit.benchmarks[selectedBenchmark].results.map(result => result.runtime)
+          const runtimes = getAllResults(selectedBenchmark, commit, "runtime")
           for (const runtime of runtimes.values()) {
             runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
           }
@@ -194,7 +201,7 @@
           {
             benchmark: selectedBenchmark,
             type: 'line',
-            label: 'Code size',
+            label: 'Dex size',
             data: codeSizeData,
             datalabels: {
               align: 'end',
@@ -216,14 +223,58 @@
           {
             benchmark: selectedBenchmark,
             type: 'line',
+            label: 'Instruction size',
+            data: instructionCodeSizeData,
+            datalabels: {
+              align: 'end',
+              anchor: 'end'
+            },
+            tension: 0.1,
+            yAxisID: 'y_ins_code_size',
+            segment: {
+              borderColor: ctx =>
+                  skipped(
+                      ctx,
+                      myChart
+                          ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
+                          : undefined),
+              borderDash: ctx => skipped(ctx, [6, 6]),
+            },
+            spanGaps: true
+          },
+          {
+            benchmark: selectedBenchmark,
+            type: 'line',
             label: 'Composable size',
-            data: composableCodeSizeData,
+            data: composableInstructionCodeSizeData,
             datalabels: {
               align: 'start',
               anchor: 'start'
             },
             tension: 0.1,
-            yAxisID: 'y_composable_code_size',
+            yAxisID: 'y_ins_code_size',
+            segment: {
+              borderColor: ctx =>
+                  skipped(
+                      ctx,
+                      myChart
+                          ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
+                          : undefined),
+              borderDash: ctx => skipped(ctx, [6, 6]),
+            },
+            spanGaps: true
+          },
+          {
+            benchmark: selectedBenchmark,
+            type: 'line',
+            label: 'Oat size',
+            data: oatCodeSizeData,
+            datalabels: {
+              align: 'start',
+              anchor: 'start'
+            },
+            tension: 0.1,
+            yAxisID: 'y_oat_code_size',
             segment: {
               borderColor: ctx =>
                   skipped(
@@ -294,7 +345,7 @@
 
     // Legend tracking.
     const legends =
-        new Set(['Code size', 'Composable size', 'Nondeterminism', 'Runtime', 'Runtime variance']);
+        new Set(['Dex size', 'Instruction size', 'Composable size', 'Oat size', 'Nondeterminism', 'Runtime', 'Runtime variance']);
     const selectedLegends =
         new Set(
               unescape(window.location.hash.substring(1))
@@ -408,7 +459,7 @@
           position: 'left',
           title: {
             display: true,
-            text: 'Code size (bytes)'
+            text: 'Dex size (bytes)'
           }
         },
         y_runtime: {
@@ -418,13 +469,20 @@
             text: 'Runtime (seconds)'
           }
         },
-        y_composable_code_size: {
+        y_ins_code_size: {
           position: 'left',
           title: {
             display: true,
-            text: 'Composable code size (bytes)'
+            text: 'Instruction size (bytes)'
           }
         },
+        y_oat_code_size: {
+          position: 'left',
+          title: {
+            display: true,
+            text: 'Oat size (bytes)'
+          }
+        }
       }
     };
 
@@ -522,8 +580,10 @@
         }
 
         // Update scales.
-        options.scales.y.display = selectedLegends.has('Code size');
-        options.scales.y_composable_code_size.display = selectedLegends.has('Composable size');
+        options.scales.y.display = selectedLegends.has('Dex size');
+        options.scales.y_ins_code_size.display =
+            selectedLegends.has('Instruction size') || selectedLegends.has('Composable size');
+        options.scales.y_oat_code_size.display = selectedLegends.has('Oat size');
         options.scales.y_runtime.display =
             selectedLegends.has('Runtime') || selectedLegends.has('Runtime variance');
 
diff --git a/tools/r8_release.py b/tools/r8_release.py
index e524f5c..ad22cb0 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -17,7 +17,7 @@
 
 import utils
 
-R8_DEV_BRANCH = '8.7'
+R8_DEV_BRANCH = '8.8'
 R8_VERSION_FILE = os.path.join('src', 'main', 'java', 'com', 'android', 'tools',
                                'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
index f01535f..783e695 100755
--- a/tools/run_benchmark.py
+++ b/tools/run_benchmark.py
@@ -168,7 +168,9 @@
         jdk.GetJavaExecutable(jdkhome), '-Xms8g', '-Xmx8g',
         '-XX:+TieredCompilation', '-XX:TieredStopAtLevel=4',
         '-DBENCHMARK_IGNORE_CODE_SIZE_DIFFERENCES',
-        f'-DBUILD_PROP_KEEPANNO_RUNTIME_PATH={utils.REPO_ROOT}/d8_r8/keepanno/build/classes/java/main'
+        f'-DBUILD_PROP_KEEPANNO_RUNTIME_PATH={utils.REPO_ROOT}/d8_r8/keepanno/build/classes/java/main',
+        # Since we change the working directory to a temp folder.
+        f'-DREPO_ROOT={utils.REPO_ROOT}'
     ]
     if options.enable_assertions:
         cmd.append('-ea')
diff --git a/tools/test.py b/tools/test.py
index 973f8a4..aa63a0f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -319,9 +319,19 @@
     if not os.path.exists("tools/linux/art-7.0.0/lib/libncurses.so.5"):
         os.symlink("/usr/lib/i386-linux-gnu/libncurses.so.6",
                    art7 + "/lib/libncurses.so.5")
+    if not os.path.exists("tools/linux/art-7.0.0/lib/libtinfo.so.5"):
+        # We don't have libtinfo, but this is never used, so we just need
+        # a valid 32bit library file.
+        os.symlink("/usr/lib/i386-linux-gnu/libncurses.so.6",
+                   art7 + "/lib/libtinfo.so.5")
     if not os.path.exists("tools/linux/art-7.0.0/lib64/libncurses.so.5"):
         os.symlink("/usr/lib/x86_64-linux-gnu/libncurses.so.6",
                    art7 + "/lib64/libncurses.so.5")
+    if not os.path.exists("tools/linux/art-7.0.0/lib64/libtinfo.so.5"):
+        # We don't have libtinfo, but this is never used, so we just need
+        # a valid 64bit library file.
+        os.symlink("/usr/lib/x86_64-linux-gnu/libncurses.so.6",
+                   art7 + "/lib64/libtinfo.so.5")
 
 def Main():
     (options, args) = ParseOptions()