Merge commit '1dfab304817ad501af6ed49156e54379d3da7588' into dev-release

Change-Id: Ibae215a1d5266bc511fd7194eafcdbab2b0173c3
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index 9b9989a..4b2ab5a 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -36,7 +36,7 @@
 import com.android.tools.r8.graph.ThrowExceptionCode;
 import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordTagSynthesizer;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
 import com.android.tools.r8.jar.CfApplicationWriter;
@@ -182,7 +182,7 @@
 
     List<ProgramMethod> methodsToProcess = new ArrayList<>();
     // Add global synthetic class for records.
-    RecordDesugaring.ensureRecordClassHelper(
+    RecordTagSynthesizer.ensureRecordClassHelper(
         appView,
         synthesizingContext,
         recordTagClass -> recordTagClass.programMethods().forEach(methodsToProcess::add),
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 74f9c92..f84fd22 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -45,8 +45,8 @@
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.records.RecordFieldValuesRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -327,8 +327,8 @@
       if (options.enableEnumUnboxing) {
         EnumUnboxingCfMethods.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
-      if (options.shouldDesugarRecords()) {
-        RecordDesugaring.registerSynthesizedCodeReferences(appView.dexItemFactory());
+      if (options.desugarRecordState().isNotOff()) {
+        RecordInstructionDesugaring.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
       if (options.shouldDesugarVarHandle()) {
         VarHandleDesugaring.registerSynthesizedCodeReferences(appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index bf1f0c0..1adf080 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1324,6 +1324,7 @@
     internal.loadAllClassDefinitions = l8Shrinking;
     if (l8Shrinking) {
       internal.apiModelingOptions().disableStubbingOfClasses();
+      internal.ignoreUnusedProguardRules = true;
     }
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
diff --git a/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
index b5f3a31..63636d9 100644
--- a/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
+++ b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
@@ -16,7 +16,8 @@
 
   public static ApplicationReaderMap getInstance(InternalOptions options) {
     ApplicationReaderMap result = new EmptyMap();
-    if (options.shouldDesugarRecords() && !options.testing.disableRecordApplicationReaderMap) {
+    if (options.desugarRecordState().isNotOff()
+        && !options.testing.disableRecordApplicationReaderMap) {
       result = new RecordMap(options.dexItemFactory());
     }
     return result;
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index d6c5bdf..6c3be81 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordFullInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.keepanno.ast.KeepDeclaration;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -162,14 +162,15 @@
   }
 
   public void checkFieldForRecord(DexField dexField, ClassKind<?> classKind) {
-    if (options.shouldDesugarRecords() && RecordDesugaring.refersToRecord(dexField, getFactory())) {
+    if (options.desugarRecordState().isFull()
+        && RecordFullInstructionDesugaring.refersToRecord(dexField, getFactory())) {
       addRecordWitness(dexField.getHolderType(), classKind);
     }
   }
 
   public void checkMethodForRecord(DexMethod dexMethod, ClassKind<?> classKind) {
-    if (options.shouldDesugarRecords()
-        && RecordDesugaring.refersToRecord(dexMethod, getFactory())) {
+    if (options.desugarRecordState().isFull()
+        && RecordFullInstructionDesugaring.refersToRecord(dexMethod, getFactory())) {
       addRecordWitness(dexMethod.getHolderType(), classKind);
     }
   }
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 dd5b2c9..3d61f0e 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.MethodParameterFactory;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 /**
@@ -37,11 +38,15 @@
 public class PathConstraintAnalysis
     extends IntraproceduralDataflowAnalysis<PathConstraintAnalysisState> {
 
-  public PathConstraintAnalysis(AppView<AppInfoWithLiveness> appView, IRCode code) {
+  public PathConstraintAnalysis(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      MethodParameterFactory methodParameterFactory) {
     super(
         appView,
         PathConstraintAnalysisState.bottom(),
         code,
-        new PathConstraintAnalysisTransferFunction(appView.abstractValueFactory()));
+        new PathConstraintAnalysisTransferFunction(
+            appView.abstractValueFactory(), code.context(), 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 6b86310..794a9a6 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
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.analysis.path;
 
 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.PathConstraintAnalysisState;
@@ -11,6 +12,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
+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;
 
@@ -19,8 +21,12 @@
 
   private final ComputationTreeBuilder computationTreeBuilder;
 
-  PathConstraintAnalysisTransferFunction(AbstractValueFactory abstractValueFactory) {
-    computationTreeBuilder = new ComputationTreeBuilder(abstractValueFactory);
+  PathConstraintAnalysisTransferFunction(
+      AbstractValueFactory abstractValueFactory,
+      ProgramMethod method,
+      MethodParameterFactory methodParameterFactory) {
+    computationTreeBuilder =
+        new ComputationTreeBuilder(abstractValueFactory, method, methodParameterFactory);
   }
 
   @Override
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 b83c92e..6350b1e 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,6 +9,7 @@
 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.computation.ComputationTreeNode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.function.IntFunction;
@@ -29,6 +30,11 @@
     return this;
   }
 
+  @Override
+  public MethodParameter getSingleOpenVariable() {
+    return null;
+  }
+
   public abstract boolean isNonTrivial();
 
   public boolean isSingleBoolean() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
index f785b37..c6765f4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterL8Synthesizer;
 import com.android.tools.r8.ir.desugar.itf.ProgramEmulatedInterfaceSynthesizer;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordClassDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
@@ -35,7 +35,7 @@
       }
       synthesizers.add(new DesugaredLibraryWrapperSynthesizer(appView));
     }
-    RecordDesugaring recordRewriter = RecordDesugaring.create(appView);
+    RecordClassDesugaring recordRewriter = RecordClassDesugaring.create(appView);
     if (recordRewriter != null) {
       synthesizers.add(recordRewriter);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index a0db01b..e442766 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarerPostProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterPostProcessor;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordClassDesugaring;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -72,7 +72,7 @@
       if (apiCallbackSynthesizor != null) {
         desugarings.add(apiCallbackSynthesizor);
       }
-      RecordDesugaring recordRewriter = RecordDesugaring.create(appView);
+      RecordClassDesugaring recordRewriter = RecordClassDesugaring.create(appView);
       if (recordRewriter != null) {
         desugarings.add(recordRewriter);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index fc40466..5aa1c3f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -30,7 +30,7 @@
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.twr.TwrInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.typeswitch.TypeSwitchDesugaring;
@@ -59,7 +59,6 @@
   private final List<CfInstructionDesugaring> yieldingDesugarings = new ArrayList<>();
 
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
-  private final RecordDesugaring recordRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
@@ -81,7 +80,6 @@
     }
     if (appView.options().desugarState.isOff()) {
       this.nestBasedAccessDesugaring = null;
-      this.recordRewriter = null;
       this.desugaredLibraryRetargeter = null;
       this.interfaceMethodRewriter = null;
       this.desugaredLibraryAPIConverter = null;
@@ -111,7 +109,7 @@
     if (appView.options().enableTypeSwitchDesugaring) {
       desugarings.add(new TypeSwitchDesugaring(appView));
     }
-    recordRewriter = RecordDesugaring.create(appView);
+    RecordInstructionDesugaring recordRewriter = RecordInstructionDesugaring.create(appView);
     if (recordRewriter != null) {
       desugarings.add(recordRewriter);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordClassDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordClassDesugaring.java
new file mode 100644
index 0000000..74a616f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordClassDesugaring.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2021, 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.records;
+
+import static com.android.tools.r8.ir.desugar.records.RecordTagSynthesizer.ensureRecordClass;
+
+import com.android.tools.r8.contexts.CompilationContext.ClassSynthesisDesugaringContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplicationReadFlags;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+public class RecordClassDesugaring
+    implements CfClassSynthesizerDesugaring, CfPostProcessingDesugaring {
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+
+  public static RecordClassDesugaring create(AppView<?> appView) {
+    return appView.options().desugarRecordState().isFull()
+        ? new RecordClassDesugaring(appView)
+        : null;
+  }
+
+  private RecordClassDesugaring(AppView<?> appView) {
+    this.appView = appView;
+    factory = appView.dexItemFactory();
+  }
+
+  @Override
+  public String uniqueIdentifier() {
+    return "$record";
+  }
+
+  @Override
+  public void synthesizeClasses(
+      ClassSynthesisDesugaringContext processingContext,
+      CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
+    DexApplicationReadFlags flags = appView.appInfo().app().getFlags();
+    if (flags.hasReadRecordReferenceFromProgramClass()) {
+      List<DexProgramClass> classes = new ArrayList<>(flags.getRecordWitnesses().size());
+      for (DexType recordWitness : flags.getRecordWitnesses()) {
+        DexClass dexClass = appView.contextIndependentDefinitionFor(recordWitness);
+        assert dexClass != null;
+        assert dexClass.isProgramClass();
+        classes.add(dexClass.asProgramClass());
+      }
+      ensureRecordClass(eventConsumer, classes, appView);
+    }
+  }
+
+  @Override
+  @SuppressWarnings("ReferenceEquality")
+  public void postProcessingDesugaring(
+      Collection<DexProgramClass> programClasses,
+      CfPostProcessingDesugaringEventConsumer eventConsumer,
+      ExecutorService executorService) {
+    for (DexProgramClass clazz : programClasses) {
+      if (clazz.isRecord()) {
+        assert clazz.superType == factory.recordType;
+        clazz.accessFlags.unsetRecord();
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFullInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFullInstructionDesugaring.java
new file mode 100644
index 0000000..ee8f224
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFullInstructionDesugaring.java
@@ -0,0 +1,103 @@
+// 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.records;
+
+import static com.android.tools.r8.ir.desugar.records.RecordTagSynthesizer.ensureRecordClass;
+
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfTypeInstruction;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+
+public class RecordFullInstructionDesugaring extends RecordInstructionDesugaring {
+
+  RecordFullInstructionDesugaring(AppView<?> appView) {
+    super(appView);
+  }
+
+  @Override
+  public void scan(
+      ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+    CfCode cfCode = programMethod.getDefinition().getCode().asCfCode();
+    for (CfInstruction instruction : cfCode.getInstructions()) {
+      scanInstruction(instruction, eventConsumer, programMethod);
+    }
+  }
+
+  // The record rewriter scans the cf instructions to figure out if the record class needs to
+  // be added in the output. the analysis cannot be done in desugarInstruction because the analysis
+  // does not rewrite any instruction, and desugarInstruction is expected to rewrite at least one
+  // instruction for assertions to be valid.
+  private void scanInstruction(
+      CfInstruction instruction,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
+    assert !instruction.isInitClass();
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      if (refersToRecord(cfInvoke.getMethod(), factory)) {
+        ensureRecordClass(eventConsumer, context, appView);
+      }
+      return;
+    }
+    if (instruction.isFieldInstruction()) {
+      CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
+      if (refersToRecord(fieldInstruction.getField(), factory)) {
+        ensureRecordClass(eventConsumer, context, appView);
+      }
+      return;
+    }
+    if (instruction.isTypeInstruction()) {
+      CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
+      if (refersToRecord(typeInstruction.getType(), factory)) {
+        ensureRecordClass(eventConsumer, context, appView);
+      }
+      return;
+    }
+    // TODO(b/179146128): Analyse MethodHandle and MethodType.
+  }
+
+  public static boolean refersToRecord(DexField field, DexItemFactory factory) {
+    assert !refersToRecord(field.holder, factory) : "The java.lang.Record class has no fields.";
+    return refersToRecord(field.type, factory);
+  }
+
+  public static boolean refersToRecord(DexMethod method, DexItemFactory factory) {
+    if (refersToRecord(method.holder, factory)) {
+      return true;
+    }
+    return refersToRecord(method.proto, factory);
+  }
+
+  private static boolean refersToRecord(DexProto proto, DexItemFactory factory) {
+    if (refersToRecord(proto.returnType, factory)) {
+      return true;
+    }
+    return refersToRecord(proto.parameters.values, factory);
+  }
+
+  private static boolean refersToRecord(DexType[] types, DexItemFactory factory) {
+    for (DexType type : types) {
+      if (refersToRecord(type, factory)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  private static boolean refersToRecord(DexType type, DexItemFactory factory) {
+    return type == factory.recordType;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
similarity index 61%
rename from src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
rename to src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
index 71527c8..268c8a3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2021, 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.
 
@@ -12,22 +12,16 @@
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfTypeInstruction;
-import com.android.tools.r8.contexts.CompilationContext.ClassSynthesisDesugaringContext;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexApplicationReadFlags;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -36,55 +30,34 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
-import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
-import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
-import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 import org.objectweb.asm.Opcodes;
 
-public class RecordDesugaring
-    implements CfInstructionDesugaring, CfClassSynthesizerDesugaring, CfPostProcessingDesugaring {
+public class RecordInstructionDesugaring implements CfInstructionDesugaring {
 
-  private final AppView<?> appView;
-  private final DexItemFactory factory;
+  final AppView<?> appView;
+  final DexItemFactory factory;
   private final DexProto recordToStringHelperProto;
   private final DexProto recordHashCodeHelperProto;
 
   public static final String GET_FIELDS_AS_OBJECTS_METHOD_NAME = "$record$getFieldsAsObjects";
   public static final String EQUALS_RECORD_METHOD_NAME = "$record$equals";
 
-  public static RecordDesugaring create(AppView<?> appView) {
-    return appView.options().shouldDesugarRecords() ? new RecordDesugaring(appView) : null;
-  }
-
-  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
-    RecordCfMethods.registerSynthesizedCodeReferences(factory);
-    RecordGetFieldsAsObjectsCfCodeProvider.registerSynthesizedCodeReferences(factory);
-    RecordEqualsCfCodeProvider.registerSynthesizedCodeReferences(factory);
-  }
-
-  private RecordDesugaring(AppView<?> appView) {
+  RecordInstructionDesugaring(AppView<?> appView) {
     this.appView = appView;
     factory = appView.dexItemFactory();
     recordToStringHelperProto =
@@ -94,6 +67,24 @@
         factory.createProto(factory.intType, factory.classType, factory.objectArrayType);
   }
 
+  public static RecordInstructionDesugaring create(AppView<?> appView) {
+    switch (appView.options().desugarRecordState()) {
+      case OFF:
+        return null;
+      case PARTIAL:
+        return new RecordInstructionDesugaring(appView);
+      case FULL:
+        return new RecordFullInstructionDesugaring(appView);
+    }
+    throw new Unreachable();
+  }
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    RecordCfMethods.registerSynthesizedCodeReferences(factory);
+    RecordGetFieldsAsObjectsCfCodeProvider.registerSynthesizedCodeReferences(factory);
+    RecordEqualsCfCodeProvider.registerSynthesizedCodeReferences(factory);
+  }
+
   @Override
   public void prepare(
       ProgramMethod method,
@@ -128,47 +119,6 @@
     throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared.");
   }
 
-  @Override
-  public void scan(
-      ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
-    CfCode cfCode = programMethod.getDefinition().getCode().asCfCode();
-    for (CfInstruction instruction : cfCode.getInstructions()) {
-      scanInstruction(instruction, eventConsumer, programMethod);
-    }
-  }
-
-  // The record rewriter scans the cf instructions to figure out if the record class needs to
-  // be added in the output. the analysis cannot be done in desugarInstruction because the analysis
-  // does not rewrite any instruction, and desugarInstruction is expected to rewrite at least one
-  // instruction for assertions to be valid.
-  private void scanInstruction(
-      CfInstruction instruction,
-      CfInstructionDesugaringEventConsumer eventConsumer,
-      ProgramMethod context) {
-    assert !instruction.isInitClass();
-    if (instruction.isInvoke()) {
-      CfInvoke cfInvoke = instruction.asInvoke();
-      if (refersToRecord(cfInvoke.getMethod(), factory)) {
-        ensureRecordClass(eventConsumer, context);
-      }
-      return;
-    }
-    if (instruction.isFieldInstruction()) {
-      CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
-      if (refersToRecord(fieldInstruction.getField(), factory)) {
-        ensureRecordClass(eventConsumer, context);
-      }
-      return;
-    }
-    if (instruction.isTypeInstruction()) {
-      CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
-      if (refersToRecord(typeInstruction.getType(), factory)) {
-        ensureRecordClass(eventConsumer, context);
-      }
-      return;
-    }
-    // TODO(b/179146128): Analyse MethodHandle and MethodType.
-  }
 
   @Override
   @SuppressWarnings("ReferenceEquality")
@@ -182,9 +132,9 @@
     }
     if (instruction.isInvoke()) {
       CfInvoke cfInvoke = instruction.asInvoke();
-      if (needsDesugaring(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()))) {
-        DexMethod newMethod =
-            rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()));
+      boolean invokeSuper = cfInvoke.isInvokeSuper(context.getHolderType());
+      if (needsDesugaring(cfInvoke.getMethod(), invokeSuper)) {
+        DexMethod newMethod = rewriteMethod(cfInvoke.getMethod(), invokeSuper);
         assert newMethod != cfInvoke.getMethod();
         return desugarInvoke(cfInvoke, newMethod);
       } else {
@@ -294,7 +244,7 @@
     return result;
   }
 
-  private DexMethod ensureEqualsRecord(
+  private void ensureEqualsRecord(
       RecordInvokeDynamic recordInvokeDynamic,
       ProgramAdditions programAdditions,
       ProgramMethod context,
@@ -308,7 +258,6 @@
         programAdditions.ensureMethod(
             method, () -> synthesizeEqualsRecordMethod(clazz, getFieldsAsObjects, method));
     eventConsumer.acceptRecordEqualsHelperMethod(equalsHelperMethod, context);
-    return method;
   }
 
   private DexMethod ensureGetFieldsAsObjects(
@@ -397,8 +346,7 @@
       MethodProcessingContext methodProcessingContext) {
     localStackAllocator.allocateLocalStack(2);
     DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
-    assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects)
-        != null;
+    assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null;
     ArrayList<CfInstruction> instructions = new ArrayList<>();
     instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
     instructions.add(new CfConstClass(recordInvokeDynamic.getRecordType(), true));
@@ -420,122 +368,6 @@
     return instructions;
   }
 
-  private void ensureRecordClass(
-      RecordInstructionDesugaringEventConsumer eventConsumer, ProgramMethod context) {
-    internalEnsureRecordClass(eventConsumer, null, eventConsumer, ImmutableList.of(context));
-  }
-
-  private void ensureRecordClass(
-      RecordClassSynthesizerDesugaringEventConsumer eventConsumer,
-      Collection<DexProgramClass> recordClasses) {
-    internalEnsureRecordClass(eventConsumer, eventConsumer, null, recordClasses);
-  }
-
-  /**
-   * If java.lang.Record is referenced from a class' supertype or a program method/field signature,
-   * then the global synthetic is generated upfront of the compilation to avoid confusing D8/R8.
-   *
-   * <p>However, if java.lang.Record is referenced only from an instruction, for example, the code
-   * contains "x instance of java.lang.Record" but no record type is present, then the global
-   * synthetic is generated during instruction desugaring scanning.
-   */
-  private DexProgramClass internalEnsureRecordClass(
-      RecordDesugaringEventConsumer eventConsumer,
-      RecordClassSynthesizerDesugaringEventConsumer recordClassSynthesizerDesugaringEventConsumer,
-      RecordInstructionDesugaringEventConsumer recordInstructionDesugaringEventConsumer,
-      Collection<? extends ProgramDefinition> contexts) {
-    DexItemFactory factory = appView.dexItemFactory();
-    checkRecordTagNotPresent(factory);
-    return ensureRecordClassHelper(
-        appView,
-        contexts,
-        eventConsumer,
-        recordClassSynthesizerDesugaringEventConsumer,
-        recordInstructionDesugaringEventConsumer);
-  }
-
-  public static DexProgramClass ensureRecordClassHelper(
-      AppView<?> appView,
-      Collection<? extends ProgramDefinition> contexts,
-      RecordDesugaringEventConsumer eventConsumer,
-      RecordClassSynthesizerDesugaringEventConsumer recordClassSynthesizerDesugaringEventConsumer,
-      RecordInstructionDesugaringEventConsumer recordInstructionDesugaringEventConsumer) {
-    return appView
-        .getSyntheticItems()
-        .ensureGlobalClass(
-            () -> new MissingGlobalSyntheticsConsumerDiagnostic("Record desugaring"),
-            kinds -> kinds.RECORD_TAG,
-            appView.dexItemFactory().recordType,
-            contexts,
-            appView,
-            builder -> {
-              DexEncodedMethod init = synthesizeRecordInitMethod(appView);
-              builder.setAbstract().setDirectMethods(ImmutableList.of(init));
-            },
-            eventConsumer::acceptRecordClass,
-            clazz -> {
-              if (recordClassSynthesizerDesugaringEventConsumer != null) {
-                for (ProgramDefinition context : contexts) {
-                  recordClassSynthesizerDesugaringEventConsumer.acceptRecordClassContext(
-                      clazz, context.asClass());
-                }
-              }
-              if (recordInstructionDesugaringEventConsumer != null) {
-                for (ProgramDefinition context : contexts) {
-                  recordInstructionDesugaringEventConsumer.acceptRecordClassContext(
-                      clazz, context.asMethod());
-                }
-              }
-            });
-  }
-
-  private void checkRecordTagNotPresent(DexItemFactory factory) {
-    DexClass r8RecordClass =
-        appView.appInfo().definitionForWithoutExistenceAssert(factory.recordTagType);
-    if (r8RecordClass != null && r8RecordClass.isProgramClass()) {
-      appView
-          .options()
-          .reporter
-          .error(
-              "D8/R8 is compiling a mix of desugared and non desugared input using"
-                  + " java.lang.Record, but the application reader did not import correctly "
-                  + factory.recordTagType);
-    }
-  }
-
-  public static boolean refersToRecord(DexField field, DexItemFactory factory) {
-    assert !refersToRecord(field.holder, factory) : "The java.lang.Record class has no fields.";
-    return refersToRecord(field.type, factory);
-  }
-
-  public static boolean refersToRecord(DexMethod method, DexItemFactory factory) {
-    if (refersToRecord(method.holder, factory)) {
-      return true;
-    }
-    return refersToRecord(method.proto, factory);
-  }
-
-  private static boolean refersToRecord(DexProto proto, DexItemFactory factory) {
-    if (refersToRecord(proto.returnType, factory)) {
-      return true;
-    }
-    return refersToRecord(proto.parameters.values, factory);
-  }
-
-  private static boolean refersToRecord(DexType[] types, DexItemFactory factory) {
-    for (DexType type : types) {
-      if (refersToRecord(type, factory)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private static boolean refersToRecord(DexType type, DexItemFactory factory) {
-    return type == factory.recordType;
-  }
-
   @SuppressWarnings("ReferenceEquality")
   private boolean needsDesugaring(DexMethod method, boolean isSuper) {
     return rewriteMethod(method, isSuper) != method;
@@ -565,55 +397,4 @@
     assert method == factory.recordMembers.hashCode;
     return factory.objectMembers.hashCode;
   }
-
-  private static DexEncodedMethod synthesizeRecordInitMethod(AppView<?> appView) {
-    MethodAccessFlags methodAccessFlags =
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
-    return DexEncodedMethod.syntheticBuilder()
-        .setMethod(appView.dexItemFactory().recordMembers.constructor)
-        .setAccessFlags(methodAccessFlags)
-        .setCode(
-            new CallObjectInitCfCodeProvider(appView, appView.dexItemFactory().recordTagType)
-                .generateCfCode())
-        // Will be traced by the enqueuer.
-        .disableAndroidApiLevelCheck()
-        .build();
-  }
-
-  @Override
-  public String uniqueIdentifier() {
-    return "$record";
-  }
-
-  @Override
-  public void synthesizeClasses(
-      ClassSynthesisDesugaringContext processingContext,
-      CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
-    DexApplicationReadFlags flags = appView.appInfo().app().getFlags();
-    if (flags.hasReadRecordReferenceFromProgramClass()) {
-      List<DexProgramClass> classes = new ArrayList<>(flags.getRecordWitnesses().size());
-      for (DexType recordWitness : flags.getRecordWitnesses()) {
-        DexClass dexClass = appView.contextIndependentDefinitionFor(recordWitness);
-        assert dexClass != null;
-        assert dexClass.isProgramClass();
-        classes.add(dexClass.asProgramClass());
-      }
-      ensureRecordClass(eventConsumer, classes);
-    }
-  }
-
-  @Override
-  @SuppressWarnings("ReferenceEquality")
-  public void postProcessingDesugaring(
-      Collection<DexProgramClass> programClasses,
-      CfPostProcessingDesugaringEventConsumer eventConsumer,
-      ExecutorService executorService) {
-    for (DexProgramClass clazz : programClasses) {
-      if (clazz.isRecord()) {
-        assert clazz.superType == factory.recordType;
-        clazz.accessFlags.unsetRecord();
-      }
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordTagSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordTagSynthesizer.java
new file mode 100644
index 0000000..95b67e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordTagSynthesizer.java
@@ -0,0 +1,127 @@
+// 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.records;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordClassSynthesizerDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+
+public class RecordTagSynthesizer {
+
+  static void ensureRecordClass(
+      RecordInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      AppView<?> appView) {
+    internalEnsureRecordClass(
+        eventConsumer, null, eventConsumer, ImmutableList.of(context), appView);
+  }
+
+  static void ensureRecordClass(
+      RecordClassSynthesizerDesugaringEventConsumer eventConsumer,
+      Collection<DexProgramClass> recordClasses,
+      AppView<?> appView) {
+    internalEnsureRecordClass(eventConsumer, eventConsumer, null, recordClasses, appView);
+  }
+
+  /**
+   * If java.lang.Record is referenced from a class' supertype or a program method/field signature,
+   * then the global synthetic is generated upfront of the compilation to avoid confusing D8/R8.
+   *
+   * <p>However, if java.lang.Record is referenced only from an instruction, for example, the code
+   * contains "x instance of java.lang.Record" but no record type is present, then the global
+   * synthetic is generated during instruction desugaring scanning.
+   */
+  private static void internalEnsureRecordClass(
+      RecordDesugaringEventConsumer eventConsumer,
+      RecordClassSynthesizerDesugaringEventConsumer recordClassSynthesizerDesugaringEventConsumer,
+      RecordInstructionDesugaringEventConsumer recordInstructionDesugaringEventConsumer,
+      Collection<? extends ProgramDefinition> contexts,
+      AppView<?> appView) {
+    checkRecordTagNotPresent(appView);
+    ensureRecordClassHelper(
+        appView,
+        contexts,
+        eventConsumer,
+        recordClassSynthesizerDesugaringEventConsumer,
+        recordInstructionDesugaringEventConsumer);
+  }
+
+  public static void ensureRecordClassHelper(
+      AppView<?> appView,
+      Collection<? extends ProgramDefinition> contexts,
+      RecordDesugaringEventConsumer eventConsumer,
+      RecordClassSynthesizerDesugaringEventConsumer recordClassSynthesizerDesugaringEventConsumer,
+      RecordInstructionDesugaringEventConsumer recordInstructionDesugaringEventConsumer) {
+    appView
+        .getSyntheticItems()
+        .ensureGlobalClass(
+            () -> new MissingGlobalSyntheticsConsumerDiagnostic("Record desugaring"),
+            kinds -> kinds.RECORD_TAG,
+            appView.dexItemFactory().recordType,
+            contexts,
+            appView,
+            builder -> {
+              DexEncodedMethod init = synthesizeRecordInitMethod(appView);
+              builder.setAbstract().setDirectMethods(ImmutableList.of(init));
+            },
+            eventConsumer::acceptRecordClass,
+            clazz -> {
+              if (recordClassSynthesizerDesugaringEventConsumer != null) {
+                for (ProgramDefinition context : contexts) {
+                  recordClassSynthesizerDesugaringEventConsumer.acceptRecordClassContext(
+                      clazz, context.asClass());
+                }
+              }
+              if (recordInstructionDesugaringEventConsumer != null) {
+                for (ProgramDefinition context : contexts) {
+                  recordInstructionDesugaringEventConsumer.acceptRecordClassContext(
+                      clazz, context.asMethod());
+                }
+              }
+            });
+  }
+
+  private static void checkRecordTagNotPresent(AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexClass r8RecordClass =
+        appView.appInfo().definitionForWithoutExistenceAssert(factory.recordTagType);
+    if (r8RecordClass != null && r8RecordClass.isProgramClass()) {
+      appView
+          .options()
+          .reporter
+          .error(
+              "D8/R8 is compiling a mix of desugared and non desugared input using"
+                  + " java.lang.Record, but the application reader did not import correctly "
+                  + factory.recordTagType);
+    }
+  }
+
+  private static DexEncodedMethod synthesizeRecordInitMethod(AppView<?> appView) {
+    MethodAccessFlags methodAccessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
+    return DexEncodedMethod.syntheticBuilder()
+        .setMethod(appView.dexItemFactory().recordMembers.constructor)
+        .setAccessFlags(methodAccessFlags)
+        .setCode(
+            new CallObjectInitCfCodeProvider(appView, appView.dexItemFactory().recordTagType)
+                .generateCfCode())
+        // Will be traced by the enqueuer.
+        .disableAndroidApiLevelCheck()
+        .build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
index c6d68d3..d5a3582 100644
--- a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -21,7 +21,7 @@
   private final NamingLens namingLens;
 
   public static NamingLens createRecordRewritingNamingLens(AppView<?> appView) {
-    if (appView.options().shouldDesugarRecords()
+    if (appView.options().desugarRecordState().isFull()
         && appView
                 .appInfo()
                 .definitionForWithoutExistenceAssert(appView.dexItemFactory().recordType)
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 8b19ac3..7a8cbdb 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
@@ -724,7 +724,7 @@
         int argumentIndex,
         Value argument,
         ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
-      ValueState modeledState =
+      NonEmptyValueState modeledState =
           modeling.modelParameterStateForArgumentToFunction(
               invoke, singleTarget, argumentIndex, argument, context);
       if (modeledState != null) {
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
index b4f5f95..0a021d2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
@@ -7,7 +7,7 @@
 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.ValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
 import com.android.tools.r8.optimize.compose.ArgumentPropagatorComposeModeling;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -25,7 +25,7 @@
             : null;
   }
 
-  ValueState modelParameterStateForArgumentToFunction(
+  NonEmptyValueState modelParameterStateForArgumentToFunction(
       InvokeMethod invoke,
       ProgramMethod singleTarget,
       int argumentIndex,
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 852bd2b..ec8d43e 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
@@ -34,7 +34,7 @@
       ConcreteValueState inState);
 
   /** Returns true if the given {@param inFlow} is a declared input of this abstract function. */
-  boolean containsBaseInFlow(BaseInFlow inFlow);
+  boolean verifyContainsBaseInFlow(BaseInFlow inFlow);
 
   /**
    * Returns the program field or parameter graph nodes that this function depends on. Upon any
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java
index bcd03fc..609b769 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomValueState.java
@@ -26,4 +26,9 @@
   public final ValueState mutableCopy() {
     return this;
   }
+
+  @Override
+  public ValueState mutableCopyWithoutInFlow() {
+    return this;
+  }
 }
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 660c42a..a4eb77e 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
@@ -28,8 +28,9 @@
   }
 
   @Override
-  public boolean containsBaseInFlow(BaseInFlow inFlow) {
-    return inFlow.equals(this.inFlow);
+  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
+    assert inFlow.equals(this.inFlow);
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
index 053d921..26dc74a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.Supplier;
 
 public class ConcreteArrayTypeValueState extends ConcreteReferenceTypeValueState {
 
@@ -83,8 +84,8 @@
   }
 
   @Override
-  public boolean isEffectivelyBottom() {
-    return nullability.isBottom() && !hasInFlow();
+  public boolean isEffectivelyBottomIgnoringInFlow() {
+    return nullability.isBottom();
   }
 
   @Override
@@ -93,8 +94,8 @@
   }
 
   @Override
-  public ValueState mutableCopy() {
-    return new ConcreteArrayTypeValueState(nullability, copyInFlow());
+  public ConcreteArrayTypeValueState internalMutableCopy(Supplier<Set<InFlow>> inFlowSupplier) {
+    return new ConcreteArrayTypeValueState(nullability, inFlowSupplier.get());
   }
 
   public NonEmptyValueState mutableJoin(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
index 6378112..f6a608d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.Supplier;
 
 public class ConcreteClassTypeValueState extends ConcreteReferenceTypeValueState {
 
@@ -105,8 +106,8 @@
   }
 
   @Override
-  public boolean isEffectivelyBottom() {
-    return abstractValue.isBottom() && dynamicType.isBottom() && !hasInFlow();
+  public boolean isEffectivelyBottomIgnoringInFlow() {
+    return abstractValue.isBottom() && dynamicType.isBottom();
   }
 
   @Override
@@ -115,8 +116,8 @@
   }
 
   @Override
-  public ValueState mutableCopy() {
-    return new ConcreteClassTypeValueState(abstractValue, dynamicType, copyInFlow());
+  public ConcreteClassTypeValueState internalMutableCopy(Supplier<Set<InFlow>> inFlowSupplier) {
+    return new ConcreteClassTypeValueState(abstractValue, dynamicType, inFlowSupplier.get());
   }
 
   public NonEmptyValueState mutableJoin(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
index dedf78d..864c16e 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.Supplier;
 
 public class ConcretePrimitiveTypeValueState extends ConcreteValueState {
 
@@ -44,8 +45,8 @@
   }
 
   @Override
-  public ConcretePrimitiveTypeValueState mutableCopy() {
-    return new ConcretePrimitiveTypeValueState(abstractValue, copyInFlow());
+  public ConcretePrimitiveTypeValueState internalMutableCopy(Supplier<Set<InFlow>> inFlowSupplier) {
+    return new ConcretePrimitiveTypeValueState(abstractValue, inFlowSupplier.get());
   }
 
   public NonEmptyValueState mutableJoin(
@@ -108,8 +109,8 @@
   }
 
   @Override
-  public boolean isEffectivelyBottom() {
-    return abstractValue.isBottom() && !hasInFlow();
+  public boolean isEffectivelyBottomIgnoringInFlow() {
+    return abstractValue.isBottom();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
index abc4b6d..350d7f9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.utils.Action;
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.Supplier;
 
 public class ConcreteReceiverValueState extends ConcreteReferenceTypeValueState {
 
@@ -71,8 +72,8 @@
   }
 
   @Override
-  public boolean isEffectivelyBottom() {
-    return dynamicType.isBottom() && !hasInFlow();
+  public boolean isEffectivelyBottomIgnoringInFlow() {
+    return dynamicType.isBottom();
   }
 
   @Override
@@ -92,8 +93,8 @@
   }
 
   @Override
-  public ValueState mutableCopy() {
-    return new ConcreteReceiverValueState(dynamicType, copyInFlow());
+  public ConcreteReceiverValueState internalMutableCopy(Supplier<Set<InFlow>> inFlowSupplier) {
+    return new ConcreteReceiverValueState(dynamicType, inFlowSupplier.get());
   }
 
   @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 d6193f6..7261359 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
@@ -13,6 +13,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Supplier;
 
 public abstract class ConcreteValueState extends NonEmptyValueState {
 
@@ -87,7 +88,11 @@
 
   public abstract ConcreteParameterStateKind getKind();
 
-  public abstract boolean isEffectivelyBottom();
+  public final boolean isEffectivelyBottom() {
+    return isEffectivelyBottomIgnoringInFlow() && !hasInFlow();
+  }
+
+  public abstract boolean isEffectivelyBottomIgnoringInFlow();
 
   public abstract boolean isEffectivelyUnknown();
 
@@ -102,6 +107,23 @@
   }
 
   @Override
+  public final ConcreteValueState mutableCopy() {
+    return internalMutableCopy(this::copyInFlow);
+  }
+
+  protected abstract ConcreteValueState internalMutableCopy(Supplier<Set<InFlow>> inFlowSupplier);
+
+  @Override
+  public ValueState mutableCopyWithoutInFlow() {
+    if (isEffectivelyBottomIgnoringInFlow()) {
+      return getCorrespondingBottom();
+    }
+    ConcreteValueState result = internalMutableCopy(Collections::emptySet);
+    assert !result.isEffectivelyBottom();
+    return result;
+  }
+
+  @Override
   public final NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
       ValueState inState,
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 fce1a27..4b19a1d 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
@@ -42,13 +42,13 @@
 
       @Override
       public ValueState getState(DexField field) {
-        assert abstractFunction.containsBaseInFlow(new FieldValue(field));
+        assert abstractFunction.verifyContainsBaseInFlow(new FieldValue(field));
         return flowGraph.getState(field);
       }
 
       @Override
       public ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
-        assert abstractFunction.containsBaseInFlow(inFlow);
+        assert abstractFunction.verifyContainsBaseInFlow(inFlow);
         return flowGraph.getState(inFlow, 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 7b20e77..1b33495b 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
@@ -26,7 +26,7 @@
   }
 
   @Override
-  public boolean containsBaseInFlow(BaseInFlow inFlow) {
+  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
     throw new Unreachable();
   }
 
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 560f00d..893a043 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
@@ -47,8 +47,9 @@
   }
 
   @Override
-  public boolean containsBaseInFlow(BaseInFlow inFlow) {
-    return inFlow.equals(receiver) || inFlow.isFieldValue(field);
+  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
+    assert inFlow.equals(receiver) || inFlow.isFieldValue(field);
+    return true;
   }
 
   @Override
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 1c83bfa..138725e 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
@@ -7,10 +7,14 @@
 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.utils.BooleanUtils;
 import java.util.Objects;
+import java.util.function.IntFunction;
 
-public class MethodParameter implements BaseInFlow {
+public class MethodParameter implements BaseInFlow, ComputationTreeNode {
 
   private final DexMethod method;
   private final int index;
@@ -43,11 +47,22 @@
     return index;
   }
 
+  @Override
+  public MethodParameter getSingleOpenVariable() {
+    return this;
+  }
+
   public DexType getType() {
     return method.getArgumentType(index, isMethodStatic);
   }
 
   @Override
+  public AbstractValue evaluate(
+      IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
+    return argumentAssignment.apply(index);
+  }
+
+  @Override
   public int internalCompareToSameKind(InFlow other) {
     MethodParameter methodParameter = other.asMethodParameter();
     int result = method.compareTo(methodParameter.method);
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
index c5f9cc5..8e84a76 100644
--- 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
@@ -43,12 +43,14 @@
   }
 
   @Override
-  public boolean containsBaseInFlow(BaseInFlow otherInFlow) {
+  public boolean verifyContainsBaseInFlow(BaseInFlow otherInFlow) {
     if (inFlow.isAbstractFunction()) {
-      return inFlow.asAbstractFunction().containsBaseInFlow(otherInFlow);
+      assert inFlow.asAbstractFunction().verifyContainsBaseInFlow(otherInFlow);
+    } else {
+      assert inFlow.isBaseInFlow();
+      assert inFlow.equals(otherInFlow);
     }
-    assert inFlow.isBaseInFlow();
-    return inFlow.equals(otherInFlow);
+    return true;
   }
 
   @Override
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
index 4a4a665..dd06a31 100644
--- 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
@@ -26,7 +26,7 @@
   }
 
   @Override
-  public boolean containsBaseInFlow(BaseInFlow inFlow) {
+  public boolean verifyContainsBaseInFlow(BaseInFlow inFlow) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
index dbc2069..6fdedc4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
@@ -37,6 +37,11 @@
   }
 
   @Override
+  public ValueState mutableCopyWithoutInFlow() {
+    return this;
+  }
+
+  @Override
   public UnknownValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
       ValueState inState,
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 06cb1eb..b88d240 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
@@ -118,6 +118,8 @@
 
   public abstract ValueState mutableCopy();
 
+  public abstract ValueState mutableCopyWithoutInFlow();
+
   public final ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
       ValueState inState,
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeArgumentNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeArgumentNode.java
deleted file mode 100644
index 2ea279f..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeArgumentNode.java
+++ /dev/null
@@ -1,57 +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.computation;
-
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.utils.ArrayUtils;
-import java.util.Objects;
-import java.util.function.IntFunction;
-
-/** Represents the read of an argument. */
-public class ComputationTreeArgumentNode extends ComputationTreeBaseNode {
-
-  private static final int NUM_CANONICALIZED_INSTANCES = 32;
-  private static final ComputationTreeArgumentNode[] CANONICALIZED_INSTANCES =
-      ArrayUtils.initialize(
-          new ComputationTreeArgumentNode[NUM_CANONICALIZED_INSTANCES],
-          ComputationTreeArgumentNode::new);
-
-  private final int argumentIndex;
-
-  private ComputationTreeArgumentNode(int argumentIndex) {
-    this.argumentIndex = argumentIndex;
-  }
-
-  public static ComputationTreeArgumentNode create(int argumentIndex) {
-    return argumentIndex < NUM_CANONICALIZED_INSTANCES
-        ? CANONICALIZED_INSTANCES[argumentIndex]
-        : new ComputationTreeArgumentNode(argumentIndex);
-  }
-
-  @Override
-  public AbstractValue evaluate(
-      IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
-    return argumentAssignment.apply(argumentIndex);
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (!(obj instanceof ComputationTreeArgumentNode)) {
-      return false;
-    }
-    ComputationTreeArgumentNode node = (ComputationTreeArgumentNode) obj;
-    assert argumentIndex >= NUM_CANONICALIZED_INSTANCES
-        || node.argumentIndex >= NUM_CANONICALIZED_INSTANCES;
-    return argumentIndex == node.argumentIndex;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(getClass(), argumentIndex);
-  }
-}
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 aca8a03..7e7b541 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
@@ -8,6 +8,7 @@
 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.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;
@@ -17,13 +18,21 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
 
 public class ComputationTreeBuilder {
 
   private final AbstractValueFactory abstractValueFactory;
+  private final ProgramMethod method;
+  private final MethodParameterFactory methodParameterFactory;
 
-  public ComputationTreeBuilder(AbstractValueFactory abstractValueFactory) {
+  public ComputationTreeBuilder(
+      AbstractValueFactory abstractValueFactory,
+      ProgramMethod method,
+      MethodParameterFactory methodParameterFactory) {
     this.abstractValueFactory = abstractValueFactory;
+    this.method = method;
+    this.methodParameterFactory = methodParameterFactory;
   }
 
   // TODO(b/302281503): "Long lived" computation trees (i.e., the ones that survive past the IR
@@ -44,7 +53,7 @@
         {
           Argument argument = instruction.asArgument();
           if (argument.getOutType().isInt()) {
-            return ComputationTreeArgumentNode.create(argument.getIndex());
+            return methodParameterFactory.create(method, argument.getIndex());
           }
           break;
         }
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 8f0c95f..d7cf46b 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,6 +4,7 @@
 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;
 
 public abstract class ComputationTreeLogicalBinopNode extends ComputationTreeBaseNode {
 
@@ -20,6 +21,15 @@
     return NumericType.INT;
   }
 
+  @Override
+  public final MethodParameter getSingleOpenVariable() {
+    MethodParameter openVariable = left.getSingleOpenVariable();
+    if (openVariable != null) {
+      return right.getSingleOpenVariable() == null ? openVariable : null;
+    }
+    return right.getSingleOpenVariable();
+  }
+
   boolean internalIsEqualTo(ComputationTreeLogicalBinopNode node) {
     return left.equals(node.left) && right.equals(node.right);
   }
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 f055cff..1231d76 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
@@ -5,6 +5,7 @@
 
 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;
 
 /**
@@ -16,6 +17,8 @@
   AbstractValue evaluate(
       IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory);
 
+  MethodParameter getSingleOpenVariable();
+
   default boolean isUnknown() {
     return false;
   }
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 3c3fc1e..8ac1ca9 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,6 +3,8 @@
 // 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;
+
 public abstract class ComputationTreeUnopNode extends ComputationTreeBaseNode {
 
   final ComputationTreeNode operand;
@@ -12,6 +14,11 @@
     this.operand = operand;
   }
 
+  @Override
+  public MethodParameter getSingleOpenVariable() {
+    return operand.getSingleOpenVariable();
+  }
+
   boolean internalIsEqualTo(ComputationTreeUnopNode node) {
     return operand.equals(node.operand);
   }
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
index 6890b1c..69a0331 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
@@ -23,8 +23,8 @@
 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.optimize.argumentpropagation.codescanner.ValueState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.Iterables;
@@ -93,7 +93,7 @@
    *   }
    * </pre>
    */
-  public ValueState modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
+  public NonEmptyValueState modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
       InvokeMethod invoke,
       ProgramMethod singleTarget,
       int argumentIndex,
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
index 46c4d58..e57b4fb 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
@@ -106,12 +106,14 @@
   }
 
   @Override
-  public boolean containsBaseInFlow(BaseInFlow otherInFlow) {
+  public boolean verifyContainsBaseInFlow(BaseInFlow otherInFlow) {
     if (inFlow.isAbstractFunction()) {
-      return inFlow.asAbstractFunction().containsBaseInFlow(otherInFlow);
+      assert inFlow.asAbstractFunction().verifyContainsBaseInFlow(otherInFlow);
+    } else {
+      assert inFlow.isBaseInFlow();
+      assert inFlow.equals(otherInFlow);
     }
-    assert inFlow.isBaseInFlow();
-    return inFlow.equals(otherInFlow);
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 4836910..e821da8 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1931,6 +1931,9 @@
       if (!options.isShrinking()) {
         return;
       }
+      if (options.ignoreUnusedProguardRules) {
+        return;
+      }
       List<ProguardConfigurationRule> rules = options.getProguardConfiguration().getRules();
       if (rules == null) {
         return;
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 8ac9315..4e50c82 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -699,8 +699,31 @@
     return !canUseNestBasedAccess();
   }
 
-  public boolean shouldDesugarRecords() {
-    return desugarState.isOn() && !canUseRecords();
+  public enum DesugarRecordState {
+    OFF,
+    PARTIAL,
+    FULL;
+
+    public boolean isFull() {
+      return this == FULL;
+    }
+
+    public boolean isNotOff() {
+      return this != OFF;
+    }
+  }
+
+  public boolean recordPartialDesugaring =
+      System.getProperty("com.android.tools.r8.recordPartialDesugaring") != null;
+
+  public DesugarRecordState desugarRecordState() {
+    if (desugarState.isOff()) {
+      return DesugarRecordState.OFF;
+    }
+    if (!canUseRecords()) {
+      return DesugarRecordState.FULL;
+    }
+    return recordPartialDesugaring ? DesugarRecordState.PARTIAL : DesugarRecordState.OFF;
   }
 
   public boolean canUseDesugarBufferCovariantReturnType() {
@@ -930,6 +953,7 @@
     return testing.readInputStackMaps ? testing.readInputStackMaps : isGeneratingClassFiles();
   }
 
+  public boolean ignoreUnusedProguardRules = false;
   public boolean ignoreMissingClasses = false;
   public boolean reportMissingClassesInEnclosingMethodAttribute = false;
   public boolean reportMissingClassesInInnerClassAttributes = false;
@@ -1162,6 +1186,10 @@
     List<DexType> sortedKeys =
         ListUtils.sort(warningLibraryProgramDuplicates.keySet(), DexType::compareTo);
     for (DexType key : sortedKeys) {
+      // Allow for suppression of the duplicates with -dontwarn
+      if (appViewWithLiveness.getDontWarnConfiguration().matches(key)) {
+        continue;
+      }
       // If the type has been pruned from the program then don't issue a diagnostic.
       if (DexProgramClass.asProgramClassOrNull(
               appViewWithLiveness.appInfo().definitionForWithoutExistenceAssert(key))
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
index 7874b5c..7a82e3f 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -37,6 +37,8 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -48,8 +50,8 @@
 
 public class LegacyResourceShrinker {
   private final Map<String, byte[]> dexInputs;
-  private final List<PathAndBytes> resFolderInputs;
-  private final List<PathAndBytes> xmlInputs;
+  private final Collection<PathAndBytes> resFolderInputs;
+  private final Collection<PathAndBytes> xmlInputs;
   private List<String> proguardMapStrings;
   private final List<PathAndBytes> manifest;
   private final Map<PathAndBytes, FeatureSplit> resourceTables;
@@ -57,8 +59,8 @@
   public static class Builder {
 
     private final Map<String, byte[]> dexInputs = new HashMap<>();
-    private final List<PathAndBytes> resFolderInputs = new ArrayList<>();
-    private final List<PathAndBytes> xmlInputs = new ArrayList<>();
+    private final Map<Path, PathAndBytes> resFolderInputs = new HashMap<>();
+    private final Map<Path, PathAndBytes> xmlInputs = new HashMap<>();
 
     private final List<PathAndBytes> manifests = new ArrayList<>();
     private final Map<PathAndBytes, FeatureSplit> resourceTables = new HashMap<>();
@@ -88,19 +90,34 @@
     }
 
     public Builder addResFolderInput(Path path, byte[] bytes) {
-      resFolderInputs.add(new PathAndBytes(bytes, path));
+      PathAndBytes existing = resFolderInputs.get(path);
+      if (existing != null) {
+        assert Arrays.equals(existing.getBytes(), bytes);
+      } else {
+        resFolderInputs.put(path, new PathAndBytes(bytes, path));
+      }
       return this;
     }
 
     public Builder addXmlInput(Path path, byte[] bytes) {
-      xmlInputs.add(new PathAndBytes(bytes, path));
+      PathAndBytes existing = xmlInputs.get(path);
+      if (existing != null) {
+        assert Arrays.equals(existing.getBytes(), bytes);
+      } else {
+        xmlInputs.put(path, new PathAndBytes(bytes, path));
+      }
       return this;
     }
 
     public LegacyResourceShrinker build() {
       assert manifests != null && resourceTables != null;
       return new LegacyResourceShrinker(
-          dexInputs, resFolderInputs, manifests, resourceTables, xmlInputs, proguardMapStrings);
+          dexInputs,
+          resFolderInputs.values(),
+          manifests,
+          resourceTables,
+          xmlInputs.values(),
+          proguardMapStrings);
     }
 
     public void setProguardMapStrings(List<String> proguardMapStrings) {
@@ -110,10 +127,10 @@
 
   private LegacyResourceShrinker(
       Map<String, byte[]> dexInputs,
-      List<PathAndBytes> resFolderInputs,
+      Collection<PathAndBytes> resFolderInputs,
       List<PathAndBytes> manifests,
       Map<PathAndBytes, FeatureSplit> resourceTables,
-      List<PathAndBytes> xmlInputs,
+      Collection<PathAndBytes> xmlInputs,
       List<String> proguardMapStrings) {
     this.dexInputs = dexInputs;
     this.resFolderInputs = resFolderInputs;
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index f289319..49f3546 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -59,7 +59,7 @@
   private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>();
   private final Map<ResourceTable, FeatureSplit> resourceTables = new HashMap<>();
   private ClassReferenceCallback enqueuerCallback;
-  private Map<Integer, String> resourceIdToXmlFiles;
+  private Map<Integer, List<String>> resourceIdToXmlFiles;
   private Set<String> packageNames;
   private final Set<String> seenNoneClassValues = new HashSet<>();
   private final Set<Integer> seenResourceIds = new HashSet<>();
@@ -208,10 +208,12 @@
   }
 
   private void traceXmlForResourceId(int id) {
-    String xmlFile = getResourceIdToXmlFiles().get(id);
-    if (xmlFile != null) {
-      InputStream inputStream = xmlFileProviders.get(xmlFile).get();
-      traceXml(xmlFile, inputStream);
+    List<String> xmlFiles = getResourceIdToXmlFiles().get(id);
+    if (xmlFiles != null) {
+      for (String xmlFile : xmlFiles) {
+        InputStream inputStream = xmlFileProviders.get(xmlFile).get();
+        traceXml(xmlFile, inputStream);
+      }
     }
   }
 
@@ -255,7 +257,7 @@
     element.getChildList().forEach(e -> visitNode(e, xmlName));
   }
 
-  public Map<Integer, String> getResourceIdToXmlFiles() {
+  public Map<Integer, List<String>> getResourceIdToXmlFiles() {
     if (resourceIdToXmlFiles == null) {
       resourceIdToXmlFiles = new HashMap<>();
       for (ResourceTable resourceTable : resourceTables.keySet()) {
@@ -271,7 +273,9 @@
                       FileReference file = item.getFile();
                       if (file.getType() == FileReference.Type.PROTO_XML) {
                         int id = ResourceTableUtilKt.toIdentifier(packageEntry, type, entry);
-                        resourceIdToXmlFiles.put(id, file.getPath());
+                        resourceIdToXmlFiles
+                            .computeIfAbsent(id, unused -> new ArrayList<>())
+                            .add(file.getPath());
                       }
                     }
                   }
diff --git a/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java b/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java
new file mode 100644
index 0000000..c8ebee4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.androidresources;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MultiConfigXmlFilesTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+  }
+
+  public static String VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE =
+      "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" class=\"%s\"/>\n";
+
+  public AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addRClassInitializeWithDefaultValues(R.xml.class)
+        // We add two configurations of the xml file, one for default and one for -v24.
+        // They reference different classes, both of which we should keep.
+        .addXml(
+            "xml_with_class_reference.xml",
+            String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, Foo.class.getTypeName()))
+        .addApiSpecificXml(
+            "xml_with_class_reference.xml",
+            String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, Bar.class.getTypeName()))
+        .build(temp);
+  }
+
+  @Test
+  public void testClassReferences() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(TestClass.class, Foo.class, Bar.class)
+        .addAndroidResources(getTestResources(temp))
+        .addKeepMainRule(TestClass.class)
+        .enableOptimizedShrinking()
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName(
+                  "xml", "xml_with_class_reference");
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Foo.class), isPresent());
+              assertThat(codeInspector.clazz(Bar.class), isPresent());
+            })
+        .assertSuccess();
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {
+      System.out.println(R.xml.xml_with_class_reference);
+    }
+  }
+
+  // Only referenced from XML file
+  public static class Bar {}
+
+  // Only referenced from XML file
+  public static class Foo {}
+
+  public static class R {
+    public static class xml {
+      public static int xml_with_class_reference;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingMultiApkAsFeaturesplits.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingMultiApkAsFeaturesplits.java
new file mode 100644
index 0000000..97534c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingMultiApkAsFeaturesplits.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.androidresources;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ResourceShrinkingMultiApkAsFeaturesplits extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean optimized;
+
+  public static String VIEW =
+      "<view xmlns:android=\"http://schemas.android.com/apk/res/android\"/>\n";
+
+  @Parameters(name = "{0}, optimized: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+        BooleanUtils.values());
+  }
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp, boolean base, String xml)
+      throws Exception {
+    AndroidTestResourceBuilder androidTestResourceBuilder =
+        new AndroidTestResourceBuilder()
+            .withSimpleManifestAndAppNameString()
+            .addRClassInitializeWithDefaultValues(base, R.string.class, R.xml.class)
+            .addXml("both_used.xml", VIEW)
+            .addXml("both_unused.xml", VIEW);
+    if (base) {
+      androidTestResourceBuilder.addXml("only_in_base.xml", VIEW);
+    }
+    return androidTestResourceBuilder.build(temp);
+  }
+
+  @Test
+  public void test() throws Exception {
+    TemporaryFolder featureSplitTemp = ToolHelper.getTemporaryFolderForTest();
+    featureSplitTemp.create();
+    String featureSplitName = "featuresplit";
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(Base.class)
+        .addAndroidResources(getTestResources(temp, true, VIEW))
+        .addFeatureSplitAndroidResources(
+            // For the feature, we don't add the R class (we already have it in the base)
+            // and to test we add one less xml file.
+            getTestResources(featureSplitTemp, false, VIEW), featureSplitName)
+        .applyIf(optimized, R8FullTestBuilder::enableOptimizedShrinking)
+        .addKeepMainRule(Base.class)
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "used");
+              resourceTableInspector.assertDoesNotContainResourceWithName("string", "unused");
+              resourceTableInspector.assertContainsResourceWithName("xml", "both_used");
+              resourceTableInspector.assertDoesNotContainResourceWithName("xml", "both_unused");
+              resourceTableInspector.assertContainsResourceWithName("xml", "only_in_base");
+            })
+        .inspectShrunkenResourcesForFeature(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "used");
+              resourceTableInspector.assertDoesNotContainResourceWithName("string", "unused");
+              resourceTableInspector.assertContainsResourceWithName("xml", "both_used");
+              resourceTableInspector.assertDoesNotContainResourceWithName("xml", "both_unused");
+              resourceTableInspector.assertDoesNotContainResourceWithName("xml", "only_in_base");
+            },
+            featureSplitName)
+        .assertResourceFile("res/xml/both_used.xml", true)
+        .assertResourceFile("res/xml/only_in_base.xml", true)
+        .assertResourceFile("res/xml/both_unused.xml", false)
+        .assertFeatureResourceFile("res/xml/both_used.xml", true, featureSplitName)
+        .assertFeatureResourceFile("res/xml/both_unused.xml", false, featureSplitName)
+        .assertFeatureResourceFile("res/xml/only_in_base.xml", false, featureSplitName)
+        .run(parameters.getRuntime(), Base.class)
+        .assertSuccess();
+  }
+
+  public static class Base {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() == 0) {
+        System.out.println(R.string.used);
+        System.out.println(R.xml.both_used);
+        System.out.println(R.xml.only_in_base);
+      }
+    }
+  }
+
+  public static class R {
+
+    public static class string {
+      public static int used;
+      public static int unused;
+    }
+
+    public static class xml {
+      public static int both_used;
+      public static int both_unused;
+      public static int only_in_base;
+    }
+  }
+}
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 56573a5..597cca6 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
+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;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -50,7 +51,8 @@
     CodeInspector inspector = new CodeInspector(app);
     IRCode code =
         inspector.clazz(Main.class).uniqueMethodWithOriginalName("greet").buildIR(appView);
-    PathConstraintAnalysis analysis = new PathConstraintAnalysis(appView, code);
+    PathConstraintAnalysis analysis =
+        new PathConstraintAnalysis(appView, code, 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/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
index f0cd1ab..448acc4 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.profile.art.completeness;
 
-import static com.android.tools.r8.ir.desugar.records.RecordDesugaring.EQUALS_RECORD_METHOD_NAME;
-import static com.android.tools.r8.ir.desugar.records.RecordDesugaring.GET_FIELDS_AS_OBJECTS_METHOD_NAME;
+import static com.android.tools.r8.ir.desugar.records.RecordFullInstructionDesugaring.EQUALS_RECORD_METHOD_NAME;
+import static com.android.tools.r8.ir.desugar.records.RecordFullInstructionDesugaring.GET_FIELDS_AS_OBJECTS_METHOD_NAME;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.ifThen;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
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 942cc92..172fb2b 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
@@ -195,6 +195,20 @@
     return self();
   }
 
+  public <E extends Throwable> R8TestCompileResult assertResourceFile(String name, boolean present)
+      throws IOException {
+    assertNotNull(resourceShrinkerOutput);
+    assertEquals(ZipUtils.containsEntry(resourceShrinkerOutput, name), present);
+    return self();
+  }
+
+  public <E extends Throwable> R8TestCompileResult assertFeatureResourceFile(
+      String name, boolean present, String featureName) throws IOException {
+    Path path = resourceShrinkerOutputForFeatures.get(featureName);
+    assertEquals(ZipUtils.containsEntry(path, name), present);
+    return self();
+  }
+
   public String dumpResources() throws IOException {
     ProcessResult processResult = AndroidResourceTestingUtils.dumpWithAapt2(resourceShrinkerOutput);
     assert processResult.exitCode == 0;
diff --git a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index eb5b8e2..80333c3 100644
--- a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -293,6 +293,8 @@
     private final Map<String, Integer> styleables = new TreeMap<>();
     private final Map<String, byte[]> drawables = new TreeMap<>();
     private final Map<String, String> xmlFiles = new TreeMap<>();
+    // Used for xml files that we hard code to -v24
+    private final Map<String, String> apiSpecificXmlFiles = new TreeMap<>();
     private final Map<String, String> rawXmlFiles = new TreeMap<>();
     private final List<Class<?>> classesToRemap = new ArrayList<>();
     private int packageId = 0x7f;
@@ -304,8 +306,15 @@
     // These R classes will be used to rewrite the namespace and class names on the aapt2
     // generated names.
     public AndroidTestResourceBuilder addRClassInitializeWithDefaultValues(Class<?>... rClasses) {
+      return addRClassInitializeWithDefaultValues(true, rClasses);
+    }
+
+    public AndroidTestResourceBuilder addRClassInitializeWithDefaultValues(
+        boolean addRClass, Class<?>... rClasses) {
       for (Class<?> rClass : rClasses) {
-        classesToRemap.add(rClass);
+        if (addRClass) {
+          classesToRemap.add(rClass);
+        }
         RClassType rClassType = RClassType.fromClass(rClass);
         for (Field declaredField : rClass.getDeclaredFields()) {
           String name = declaredField.getName();
@@ -364,6 +373,11 @@
       return this;
     }
 
+    public AndroidTestResourceBuilder addApiSpecificXml(String name, String content) {
+      apiSpecificXmlFiles.put(name, content);
+      return this;
+    }
+
     public AndroidTestResourceBuilder addKeepXmlFor(String... resourceReferences) {
       addRawXml("keep.xml", String.format(KEEP_XML, String.join(",", resourceReferences)));
       return this;
@@ -435,6 +449,13 @@
         }
       }
 
+      if (apiSpecificXmlFiles.size() > 0) {
+        File xmlFolder = temp.newFolder("res", "xml-v24");
+        for (Entry<String, String> entry : apiSpecificXmlFiles.entrySet()) {
+          FileUtils.writeTextFile(xmlFolder.toPath().resolve(entry.getKey()), entry.getValue());
+        }
+      }
+
       if (rawXmlFiles.size() > 0) {
         File rawXmlFolder = temp.newFolder("res", "raw");
         for (Entry<String, String> entry : rawXmlFiles.entrySet()) {