Merge commit '7d174afaaaf084fe85772b1e60db1a945947f83e' into dev-release

Change-Id: I01f170b8c438c1b5d032f4eb0981a981b34b807b
diff --git a/.gitignore b/.gitignore
index 67d9a55..27d89f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -219,6 +219,8 @@
 third_party/opensource-apps/applymapping.tar.gz
 third_party/opensource-apps/chanu
 third_party/opensource-apps/chanu.tar.gz
+third_party/opensource-apps/chrome
+third_party/opensource-apps/chrome.tar.gz
 third_party/opensource-apps/compose-examples/changed-bitwise-value-propagation
 third_party/opensource-apps/compose-examples/changed-bitwise-value-propagation.tar.gz
 third_party/opensource-apps/empty-activity
@@ -283,6 +285,8 @@
 third_party/proto/test/proto2.tar.gz
 third_party/proto/test/proto3
 third_party/proto/test/proto3.tar.gz
+third_party/protoc
+third_party/protoc.tar.gz
 third_party/r8
 third_party/r8.tar.gz
 third_party/r8-releases/2.0.74
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index 25d24e38..1f748ae 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -400,7 +400,7 @@
   const val javassist = "3.29.2-GA"
   const val junitVersion = "4.13-beta-2"
   const val kotlinVersion = "1.9.20"
-  const val kotlinMetadataVersion = "2.0.0-Beta5"
+  const val kotlinMetadataVersion = "2.0.0"
   const val mockito = "2.10.0"
   const val smaliVersion = "3.0.3"
 }
@@ -453,6 +453,10 @@
     Paths.get("third_party", "chrome", "chrome_200430").toFile(),
     Paths.get("third_party", "chrome", "chrome_200430.tar.gz.sha1").toFile(),
     DependencyType.X20)
+  val chromeBenchmark = ThirdPartyDependency(
+    "chrome-benchmark",
+    Paths.get("third_party", "opensource-apps", "chrome").toFile(),
+    Paths.get("third_party", "opensource-apps", "chrome.tar.gz.sha1").toFile())
   val compilerApi = ThirdPartyDependency(
     "compiler-api",
     Paths.get(
@@ -469,6 +473,70 @@
     Paths.get(
       "third_party", "opensource-apps", "compose-examples",
       "changed-bitwise-value-propagation.tar.gz.sha1").toFile())
+  val composeSamplesCrane = ThirdPartyDependency(
+    "compose-samples-crane",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "crane").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "crane.tar.gz.sha1").toFile())
+  val composeSamplesJetCaster = ThirdPartyDependency(
+    "compose-samples-jetcaster",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetcaster").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetcaster.tar.gz.sha1").toFile())
+  val composeSamplesJetChat = ThirdPartyDependency(
+    "compose-samples-jetchat",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetchat").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetchat.tar.gz.sha1").toFile())
+  val composeSamplesJetLagged = ThirdPartyDependency(
+    "compose-samples-jetlagged",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetlagged").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetlagged.tar.gz.sha1").toFile())
+  val composeSamplesJetNews = ThirdPartyDependency(
+    "compose-samples-jetnews",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetnews").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetnews.tar.gz.sha1").toFile())
+  val composeSamplesJetSnack = ThirdPartyDependency(
+    "compose-samples-jetsnack",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetsnack").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "jetsnack.tar.gz.sha1").toFile())
+  val composeSamplesOwl = ThirdPartyDependency(
+    "compose-samples-owl",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "owl").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "owl.tar.gz.sha1").toFile())
+  val composeSamplesReply = ThirdPartyDependency(
+    "compose-samples-reply",
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "reply").toFile(),
+    Paths.get(
+      "third_party", "opensource-apps", "android", "compose-samples",
+      "reply.tar.gz.sha1").toFile())
   val coreLambdaStubs = ThirdPartyDependency(
     "coreLambdaStubs",
     Paths.get("third_party", "core-lambda-stubs").toFile(),
@@ -615,6 +683,10 @@
     Paths.get("third_party", "proguardsettings").toFile(),
     Paths.get("third_party", "proguardsettings.tar.gz.sha1").toFile(),
     DependencyType.X20)
+  val protoc = ThirdPartyDependency(
+    "protoc",
+    Paths.get("third_party", "protoc").toFile(),
+    Paths.get("third_party", "protoc.tar.gz.sha1").toFile())
   val protoRuntimeEdition2023 = ThirdPartyDependency(
     "protoRuntimeEdition2023",
     Paths.get("third_party", "proto", "runtime", "edition2023").toFile(),
diff --git a/d8_r8/keepanno/build.gradle.kts b/d8_r8/keepanno/build.gradle.kts
index e58f672..a747444 100644
--- a/d8_r8/keepanno/build.gradle.kts
+++ b/d8_r8/keepanno/build.gradle.kts
@@ -2,14 +2,43 @@
 // 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.
 
+import com.google.protobuf.gradle.proto
+import com.google.protobuf.gradle.ProtobufExtension
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+
 plugins {
   `kotlin-dsl`
   id("dependencies-plugin")
 }
 
+// It seems like the use of a local maven repo does not allow adding the plugin with the id+version
+// syntax. Also, for some reason the 'protobuf' extension object cannot be directly referenced.
+// This configures the plugin "old style" and pulls out the extension object manually.
+buildscript {
+  dependencies {
+    classpath("com.google.protobuf:protobuf-gradle-plugin:0.9.4")
+  }
+}
+apply(plugin = "com.google.protobuf")
+var os = DefaultNativePlatform.getCurrentOperatingSystem()
+var protobuf = project.extensions.getByName("protobuf") as ProtobufExtension
+protobuf.protoc {
+  if (os.isLinux) {
+    path = getRoot().resolveAll("third_party", "protoc", "linux-x86_64", "bin", "protoc").path
+  } else if (os.isMacOsX) {
+    path = getRoot().resolveAll("third_party", "protoc", "osx-x86_64", "bin", "protoc").path
+  } else {
+    assert(os.isWindows);
+    path = getRoot().resolveAll("third_party", "protoc", "win64", "bin", "protoc.exe").path
+  }
+}
+
 java {
   sourceSets.main.configure {
     java.srcDir(getRoot().resolveAll("src", "keepanno", "java"))
+    proto {
+      srcDir(getRoot().resolveAll("src", "keepanno", "proto"))
+    }
   }
   sourceCompatibility = JvmCompatibility.sourceCompatibility
   targetCompatibility = JvmCompatibility.targetCompatibility
@@ -22,6 +51,7 @@
 dependencies {
   compileOnly(Deps.asm)
   compileOnly(Deps.guava)
+  implementation("com.google.protobuf:protobuf-java:3.19.3")
 }
 
 tasks {
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts
index 9eaf601..fd3282d 100644
--- a/d8_r8/main/build.gradle.kts
+++ b/d8_r8/main/build.gradle.kts
@@ -223,6 +223,7 @@
     }
     exclude("META-INF/*.kotlin_module")
     exclude("**/*.kotlin_metadata")
+    exclude("keepanno.proto")
     destinationDirectory.set(getRoot().resolveAll("build", "libs"))
     archiveFileName.set("r8-full-exclude-deps.jar")
   }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index e2ce313..607d981 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+import com.android.tools.r8.keepanno.proto.KeepAnnoProtos;
+
 /**
  * An edge in the keep graph.
  *
@@ -144,6 +146,11 @@
 
     private Builder() {}
 
+    public Builder applyProto(KeepAnnoProtos.Edge edge) {
+      // TODO(b/343389186): implement this.
+      return this;
+    }
+
     public Builder setMetaInfo(KeepEdgeMetaInfo metaInfo) {
       this.metaInfo = metaInfo;
       return this;
@@ -227,4 +234,8 @@
         + consequences
         + '}';
   }
+
+  public void buildProto(KeepAnnoProtos.Edge.Builder builder) {
+    // TODO(b/343389186): implement this.
+  }
 }
diff --git a/src/keepanno/proto/keepanno.proto b/src/keepanno/proto/keepanno.proto
new file mode 100644
index 0000000..a4f96d7
--- /dev/null
+++ b/src/keepanno/proto/keepanno.proto
@@ -0,0 +1,56 @@
+// 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.
+syntax = "proto3";
+
+package com.android.tools.r8.keepanno.proto;
+
+// All messages are placed under the outer class. This makes it a bit nicer to
+// implement the AST <-> Proto conversions without type conflicts.
+option java_multiple_files = false;
+
+// Descriptive name of the outer class (default would have been `Keepanno`).
+option java_outer_classname = "KeepAnnoProtos";
+
+// Java package consistent with R8 convention.
+option java_package = "com.android.tools.r8.keepanno.proto";
+
+message Version {
+  uint32 major = 1;
+  uint32 minor = 2;
+  uint32 patch = 3;
+}
+
+message Context {
+  oneof context_oneof {
+    string class_descriptor = 1;
+    string method_descriptor = 2;
+    string field_descriptor = 3;
+  }
+}
+
+message MetaInfo {
+  Version version = 1;
+  Context context = 2;
+}
+
+message Declaration {
+  MetaInfo meta_info = 1;
+  oneof decl_oneof {
+    Edge edge = 2;
+    CheckRemoved check_removed = 3;
+    CheckDiscarded check_discarded = 4;
+  }
+}
+
+message CheckRemoved {
+  // TODO(b/343389186): Add content.
+}
+
+message CheckDiscarded {
+  // TODO(b/343389186): Add content.
+}
+
+message Edge {
+  // TODO(b/343389186): Add content.
+}
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index c52f655..3e87ab7 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -273,6 +273,10 @@
     throw new RuntimeException("Unknown key value pair: " + key + " = " + value);
   }
 
+  public String getAndroidApiExtensionPackages() {
+    return systemProperties.get("com.android.tools.r8.androidApiExtensionPackages");
+  }
+
   public Tool getTool() {
     return tool;
   }
@@ -281,10 +285,18 @@
     return compilationMode;
   }
 
+  public boolean getIsolatedSplits() {
+    return isolatedSplits.orElse(false);
+  }
+
   public int getMinApi() {
     return minApi;
   }
 
+  public boolean getEnableSameFilePolicy() {
+    return systemProperties.containsKey("com.android.tools.r8.enableSameFilePolicy");
+  }
+
   private void addOptionalDumpEntry(
       Map<String, String> buildProperties, String key, Optional<?> optionalValue) {
     optionalValue.ifPresent(bool -> addDumpEntry(buildProperties, key, bool));
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index bd27dfc..d6216c3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -35,7 +35,7 @@
 import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
@@ -1579,13 +1579,16 @@
     }
 
     public Builder rewriteParameterAnnotations(
-        DexEncodedMethod method, ArgumentInfoCollection argumentInfoCollection) {
+        DexEncodedMethod method, RewrittenPrototypeDescription rewrittenPrototypeDescription) {
       if (parameterAnnotations.isEmpty()) {
         // Nothing to do.
         return this;
       }
+
+      var argumentInfoCollection = rewrittenPrototypeDescription.getArgumentInfoCollection();
       if (!argumentInfoCollection.hasArgumentPermutation()
-          && !argumentInfoCollection.hasRemovedArguments()) {
+          && !argumentInfoCollection.hasRemovedArguments()
+          && !rewrittenPrototypeDescription.hasExtraParameters()) {
         // Nothing to do.
         return this;
       }
@@ -1638,6 +1641,14 @@
         newNumberOfMissingParameterAnnotations = 0;
       }
 
+      if (rewrittenPrototypeDescription.hasExtraParameters()) {
+        for (int extraParameter = 0;
+            extraParameter < rewrittenPrototypeDescription.getExtraParameters().size();
+            extraParameter++) {
+          newParameterAnnotations.add(DexAnnotationSet.empty());
+        }
+      }
+
       return setParameterAnnotations(
           ParameterAnnotationsList.create(
               newParameterAnnotations.toArray(DexAnnotationSet.EMPTY_ARRAY),
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 34c1b21..60bcbdc 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -68,12 +68,9 @@
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.position.TextRange;
-import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -155,12 +152,7 @@
     JarApplicationReader application = this.application;
     assert context != null;
     assert application != null;
-    DexProgramClass programOwner = context.owner.asProgramClass();
-    ReachabilitySensitiveValue reachabilitySensitive =
-        programOwner != null
-            ? programOwner.getReachabilitySensitiveValue()
-            : ReachabilitySensitiveValue.DISABLED;
-    DebugParsingOptions parsingOptions = getParsingOptions(application, reachabilitySensitive);
+    DebugParsingOptions parsingOptions = getParsingOptions(application);
     // The ClassCodeVisitor is in charge of setting this.context to null.
     try {
       parseCode(context, false, parsingOptions);
@@ -1177,34 +1169,13 @@
     }
   }
 
-  private static DebugParsingOptions getParsingOptions(
-      JarApplicationReader application, ReachabilitySensitiveValue reachabilitySensitive) {
+  private static DebugParsingOptions getParsingOptions(JarApplicationReader application) {
     // TODO(b/166841731): We should compute our own from the compressed format.
     int parsingOptions =
         application.options.canUseInputStackMaps()
             ? ClassReader.EXPAND_FRAMES
             : ClassReader.SKIP_FRAMES;
-    ProguardConfiguration configuration = application.options.getProguardConfiguration();
-    if (configuration == null) {
-      return new DebugParsingOptions(true, true, parsingOptions);
-    }
-    ProguardKeepAttributes keep =
-        application.options.getProguardConfiguration().getKeepAttributes();
-
-    boolean localsInfo =
-        configuration.isKeepParameterNames()
-            || keep.localVariableTable
-            || keep.localVariableTypeTable
-            || reachabilitySensitive.isEnabled();
-    boolean lineInfo =
-        (keep.lineNumberTable || application.options.canUseNativeDexPcInsteadOfDebugInfo());
-    boolean methodParaeters = keep.methodParameters;
-
-    if (!localsInfo && !lineInfo && !methodParaeters) {
-      parsingOptions |= ClassReader.SKIP_DEBUG;
-    }
-
-    return new DebugParsingOptions(lineInfo, localsInfo, parsingOptions);
+    return new DebugParsingOptions(true, true, parsingOptions);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java b/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java
index 85ee2db..396f92d 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/ArgumentInfoCollection.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.graph.proto;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
@@ -24,7 +23,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Objects;
-import java.util.function.Consumer;
 
 public class ArgumentInfoCollection {
 
@@ -370,13 +368,4 @@
         RewrittenPrototypeDescription.create(Collections.emptyList(), null, this);
     return prototypeChanges.createMethodOptimizationInfoFixer();
   }
-
-  /**
-   * Returns a function for rewriting the parameter annotations on a method info after prototype
-   * changes were made.
-   */
-  public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover(
-      DexEncodedMethod method) {
-    return builder -> builder.rewriteParameterAnnotations(method, this);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
index 67659b6..46d40d5 100644
--- a/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/proto/RewrittenPrototypeDescription.java
@@ -84,7 +84,7 @@
 
   public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover(
       DexEncodedMethod method) {
-    return getArgumentInfoCollection().createParameterAnnotationsRemover(method);
+    return builder -> builder.rewriteParameterAnnotations(method, this);
   }
 
   public MethodOptimizationInfoFixer createMethodOptimizationInfoFixer() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index a6f39aa..7bafe1b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -200,6 +200,15 @@
     return operands;
   }
 
+  public boolean hasOperandThatMatches(Predicate<Value> predicate) {
+    for (Value operand : operands) {
+      if (predicate.test(operand)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public void removeOperand(int index) {
     removeOperand(index, null, alwaysFalse());
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java
index 526068d..aa46ee2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteIfNotNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
@@ -19,22 +18,14 @@
 import java.util.List;
 import java.util.function.Consumer;
 import kotlin.metadata.KmProperty;
+import kotlin.metadata.KmPropertyAccessorAttributes;
 import kotlin.metadata.jvm.JvmExtensionsKt;
 
 // Holds information about KmProperty
 public class ConcreteKotlinPropertyInfo implements KotlinPropertyInfo {
 
-  // Original flags.
-  private final int flags;
-
-  // Original getter flags. E.g., for property getter.
-  private final int getterFlags;
-
-  // Original setter flags. E.g., for property setter.
-  private final int setterFlags;
-
-  // Original property name for (extension) property. Otherwise, null.
-  private final String name;
+  // Original property.
+  private final KmProperty kmProperty;
 
   // Original return type information. This should never be NULL (even for setters without field).
   private final KotlinTypeInfo returnType;
@@ -45,10 +36,6 @@
 
   private final List<KotlinTypeParameterInfo> typeParameters;
 
-  private final KotlinVersionRequirementInfo versionRequirements;
-
-  private final int jvmFlags;
-
   private final KotlinJvmFieldSignatureInfo fieldSignature;
 
   private final KotlinJvmMethodSignatureInfo getterSignature;
@@ -62,33 +49,23 @@
   private final List<KotlinTypeInfo> contextReceiverTypes;
 
   private ConcreteKotlinPropertyInfo(
-      int flags,
-      int getterFlags,
-      int setterFlags,
-      String name,
+      KmProperty kmProperty,
       KotlinTypeInfo returnType,
       KotlinTypeInfo receiverParameterType,
       KotlinValueParameterInfo setterParameter,
       List<KotlinTypeParameterInfo> typeParameters,
-      KotlinVersionRequirementInfo versionRequirements,
-      int jvmFlags,
       KotlinJvmFieldSignatureInfo fieldSignature,
       KotlinJvmMethodSignatureInfo getterSignature,
       KotlinJvmMethodSignatureInfo setterSignature,
       KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations,
       KotlinJvmMethodSignatureInfo syntheticMethodForDelegate,
       List<KotlinTypeInfo> contextReceiverTypes) {
-    this.flags = flags;
-    this.getterFlags = getterFlags;
-    this.setterFlags = setterFlags;
-    this.name = name;
     assert returnType != null;
+    this.kmProperty = kmProperty;
     this.returnType = returnType;
     this.receiverParameterType = receiverParameterType;
     this.setterParameter = setterParameter;
     this.typeParameters = typeParameters;
-    this.versionRequirements = versionRequirements;
-    this.jvmFlags = jvmFlags;
     this.fieldSignature = fieldSignature;
     this.getterSignature = getterSignature;
     this.setterSignature = setterSignature;
@@ -100,16 +77,11 @@
   public static ConcreteKotlinPropertyInfo create(
       KmProperty kmProperty, DexItemFactory factory, Reporter reporter) {
     return new ConcreteKotlinPropertyInfo(
-        kmProperty.getFlags(),
-        kmProperty.getGetterFlags(),
-        kmProperty.getSetterFlags(),
-        kmProperty.getName(),
+        kmProperty,
         KotlinTypeInfo.create(kmProperty.getReturnType(), factory, reporter),
         KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), factory, reporter),
         KotlinValueParameterInfo.create(kmProperty.getSetterParameter(), factory, reporter),
         KotlinTypeParameterInfo.create(kmProperty.getTypeParameters(), factory, reporter),
-        KotlinVersionRequirementInfo.create(kmProperty.getVersionRequirements()),
-        JvmExtensionsKt.getJvmFlags(kmProperty),
         KotlinJvmFieldSignatureInfo.create(JvmExtensionsKt.getFieldSignature(kmProperty), factory),
         KotlinJvmMethodSignatureInfo.create(
             JvmExtensionsKt.getGetterSignature(kmProperty), factory),
@@ -152,54 +124,60 @@
       DexEncodedMethod setter,
       DexEncodedMethod syntheticMethodForAnnotationsMethod,
       AppView<?> appView) {
-    KmProperty kmProperty =
-        consume(new KmProperty(flags, name, getterFlags, setterFlags), consumer);
+    KmProperty rewrittenKmProperty = new KmProperty(kmProperty.getName());
+    consumer.accept(rewrittenKmProperty);
+    KotlinFlagUtils.copyAllFlags(kmProperty, rewrittenKmProperty);
+    KotlinFlagUtils.copyAllFlags(kmProperty.getGetter(), rewrittenKmProperty.getGetter());
+    if (kmProperty.getSetter() != null) {
+      rewrittenKmProperty.setSetter(new KmPropertyAccessorAttributes());
+      KotlinFlagUtils.copyAllFlags(kmProperty.getSetter(), rewrittenKmProperty.getSetter());
+    }
     boolean rewritten =
-        rewriteIfNotNull(appView, returnType, kmProperty::setReturnType, KotlinTypeInfo::rewrite);
-    assert returnType != null;
+        rewriteIfNotNull(
+            appView, returnType, rewrittenKmProperty::setReturnType, KotlinTypeInfo::rewrite);
     rewritten |=
         rewriteIfNotNull(
             appView,
             receiverParameterType,
-            kmProperty::setReceiverParameterType,
+            rewrittenKmProperty::setReceiverParameterType,
             KotlinTypeInfo::rewrite);
     rewritten |=
         rewriteIfNotNull(
             appView,
             setterParameter,
-            kmProperty::setSetterParameter,
+            rewrittenKmProperty::setSetterParameter,
             KotlinValueParameterInfo::rewrite);
     rewritten |=
         rewriteList(
             appView,
             typeParameters,
-            kmProperty.getTypeParameters(),
+            rewrittenKmProperty.getTypeParameters(),
             KotlinTypeParameterInfo::rewrite);
     rewritten |=
         rewriteList(
             appView,
             contextReceiverTypes,
-            kmProperty.getContextReceiverTypes(),
+            rewrittenKmProperty.getContextReceiverTypes(),
             KotlinTypeInfo::rewrite);
-    rewritten |= versionRequirements.rewrite(kmProperty.getVersionRequirements()::addAll);
+    rewrittenKmProperty.getVersionRequirements().addAll(kmProperty.getVersionRequirements());
     if (fieldSignature != null) {
       rewritten |=
           fieldSignature.rewrite(
-              newSignature -> JvmExtensionsKt.setFieldSignature(kmProperty, newSignature),
+              newSignature -> JvmExtensionsKt.setFieldSignature(rewrittenKmProperty, newSignature),
               field,
               appView);
     }
     if (getterSignature != null) {
       rewritten |=
           getterSignature.rewrite(
-              newSignature -> JvmExtensionsKt.setGetterSignature(kmProperty, newSignature),
+              newSignature -> JvmExtensionsKt.setGetterSignature(rewrittenKmProperty, newSignature),
               getter,
               appView);
     }
     if (setterSignature != null) {
       rewritten |=
           setterSignature.rewrite(
-              newSignature -> JvmExtensionsKt.setSetterSignature(kmProperty, newSignature),
+              newSignature -> JvmExtensionsKt.setSetterSignature(rewrittenKmProperty, newSignature),
               setter,
               appView);
     }
@@ -207,23 +185,23 @@
       rewritten |=
           syntheticMethodForAnnotations.rewrite(
               newSignature ->
-                  JvmExtensionsKt.setSyntheticMethodForAnnotations(kmProperty, newSignature),
+                  JvmExtensionsKt.setSyntheticMethodForAnnotations(
+                      rewrittenKmProperty, newSignature),
               syntheticMethodForAnnotationsMethod,
               appView);
     }
-    JvmExtensionsKt.setJvmFlags(kmProperty, jvmFlags);
     rewritten |=
         rewriteIfNotNull(
             appView,
             syntheticMethodForDelegate,
-            newMethod -> JvmExtensionsKt.setSyntheticMethodForDelegate(kmProperty, newMethod),
+            newMethod ->
+                JvmExtensionsKt.setSyntheticMethodForDelegate(rewrittenKmProperty, newMethod),
             KotlinJvmMethodSignatureInfo::rewriteNoBacking);
     return rewritten;
   }
 
   @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
-    assert returnType != null;
     returnType.trace(definitionSupplier);
     if (receiverParameterType != null) {
       receiverParameterType.trace(definitionSupplier);
@@ -252,6 +230,6 @@
 
   @Override
   public String toString() {
-    return "KotlinPropertyInfo(" + name + ")";
+    return "KotlinPropertyInfo(" + kmProperty.getName() + ")";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
index 329dbbe..ffbbe57 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
@@ -59,18 +59,15 @@
   private static class KotlinAnnotationClassValueInfo extends KotlinAnnotationArgumentInfo {
 
     private final KotlinTypeReference value;
-    private final int arrayDimensionCount;
 
-    private KotlinAnnotationClassValueInfo(KotlinTypeReference value, int arrayDimensionCount) {
+    private KotlinAnnotationClassValueInfo(KotlinTypeReference value) {
       this.value = value;
-      this.arrayDimensionCount = arrayDimensionCount;
     }
 
     private static KotlinAnnotationClassValueInfo create(KClassValue arg, DexItemFactory factory) {
       return new KotlinAnnotationClassValueInfo(
           KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
-              arg.getClassName(), factory, arg.getClassName()),
-          arg.getArrayDimensionCount());
+              arg.getClassName(), factory, arg.getClassName()));
     }
 
     @Override
@@ -81,7 +78,7 @@
     @Override
     boolean rewrite(Consumer<KmAnnotationArgument> consumer, AppView<?> appView) {
       return value.toRenamedBinaryNameOrDefault(
-          rewrittenValue -> consumer.accept(new KClassValue(rewrittenValue, arrayDimensionCount)),
+          rewrittenValue -> consumer.accept(new KClassValue(rewrittenValue)),
           appView,
           ClassClassifiers.anyName);
     }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 0314b43..1b00d0c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -4,12 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.updateJvmMetadataVersionIfRequired;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
-import static kotlin.metadata.jvm.KotlinClassMetadata.Companion;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -41,8 +40,7 @@
 
 public class KotlinClassInfo implements KotlinClassLevelInfo {
 
-  private final int flags;
-  private final String name;
+  private final KotlinClassMetadata.Class classMetadata;
   private final boolean nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin;
   private final String moduleName;
   private final List<KotlinConstructorInfo> constructorsWithNoBacking;
@@ -54,14 +52,11 @@
 
   private final List<KotlinTypeReference> nestedClasses;
   private final List<String> enumEntries;
-  private final KotlinVersionRequirementInfo versionRequirements;
   private final KotlinTypeReference anonymousObjectOrigin;
   private final String packageName;
   private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
-  private final int[] metadataVersion;
   private final String inlineClassUnderlyingPropertyName;
   private final KotlinTypeInfo inlineClassUnderlyingType;
-  private final int jvmFlags;
   private final String companionObjectName;
   // Collection of context receiver types
   private final List<KotlinTypeInfo> contextReceiverTypes;
@@ -70,8 +65,7 @@
   private final KotlinMetadataMembersTracker originalMembersWithKotlinInfo;
 
   private KotlinClassInfo(
-      int flags,
-      String name,
+      KotlinClassMetadata.Class classMetadata,
       boolean nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin,
       String moduleName,
       KotlinDeclarationContainerInfo declarationContainerInfo,
@@ -81,19 +75,15 @@
       List<KotlinTypeReference> sealedSubClasses,
       List<KotlinTypeReference> nestedClasses,
       List<String> enumEntries,
-      KotlinVersionRequirementInfo versionRequirements,
       KotlinTypeReference anonymousObjectOrigin,
       String packageName,
       KotlinLocalDelegatedPropertyInfo localDelegatedProperties,
-      int[] metadataVersion,
       String inlineClassUnderlyingPropertyName,
       KotlinTypeInfo inlineClassUnderlyingType,
       KotlinMetadataMembersTracker originalMembersWithKotlinInfo,
-      int jvmFlags,
       String companionObjectName,
       List<KotlinTypeInfo> contextReceiverTypes) {
-    this.flags = flags;
-    this.name = name;
+    this.classMetadata = classMetadata;
     this.nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin =
         nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin;
     this.moduleName = moduleName;
@@ -104,15 +94,12 @@
     this.sealedSubClasses = sealedSubClasses;
     this.nestedClasses = nestedClasses;
     this.enumEntries = enumEntries;
-    this.versionRequirements = versionRequirements;
     this.anonymousObjectOrigin = anonymousObjectOrigin;
     this.packageName = packageName;
     this.localDelegatedProperties = localDelegatedProperties;
-    this.metadataVersion = metadataVersion;
     this.inlineClassUnderlyingPropertyName = inlineClassUnderlyingPropertyName;
     this.inlineClassUnderlyingType = inlineClassUnderlyingType;
     this.originalMembersWithKotlinInfo = originalMembersWithKotlinInfo;
-    this.jvmFlags = jvmFlags;
     this.companionObjectName = companionObjectName;
     this.contextReceiverTypes = contextReceiverTypes;
   }
@@ -120,7 +107,6 @@
   public static KotlinClassInfo create(
       KotlinClassMetadata.Class metadata,
       String packageName,
-      int[] metadataVersion,
       DexClass hostClass,
       AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
@@ -170,8 +156,7 @@
             || (anonymousObjectOrigin != null
                 && kmClass.name.equals(anonymousObjectOrigin.toKotlinClassifier(true)));
     return new KotlinClassInfo(
-        kmClass.getFlags(),
-        kmClass.name,
+        metadata,
         nameCanBeDeducedFromClassOrOrigin,
         JvmExtensionsKt.getModuleName(kmClass),
         container,
@@ -181,16 +166,13 @@
         getSealedSubClasses(kmClass.getSealedSubclasses(), factory),
         getNestedClasses(hostClass, kmClass.getNestedClasses(), factory),
         setEnumEntries(kmClass, hostClass),
-        KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
         anonymousObjectOrigin,
         packageName,
         KotlinLocalDelegatedPropertyInfo.create(
             JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter),
-        metadataVersion,
         kmClass.getInlineClassUnderlyingPropertyName(),
         KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter),
         originalMembersWithKotlinInfo,
-        JvmExtensionsKt.getJvmFlags(kmClass),
         setCompanionObject(kmClass, hostClass, reporter),
         ListUtils.map(
             kmClass.getContextReceiverTypes(),
@@ -291,15 +273,16 @@
   @Override
   @SuppressWarnings("ReferenceEquality")
   public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
-    KmClass kmClass = new KmClass();
-    // TODO(b/154348683): Set flags.
-    kmClass.setFlags(flags);
+    KmClass kmClass = classMetadata.getKmClass();
+    KmClass rewrittenKmClass = new KmClass();
+    KotlinFlagUtils.copyAllFlags(kmClass, rewrittenKmClass);
     // Set potentially renamed class name.
     DexString originalDescriptor = clazz.type.descriptor;
     DexString rewrittenDescriptor = appView.getNamingLens().lookupDescriptor(clazz.type);
     boolean rewritten = !originalDescriptor.equals(rewrittenDescriptor);
+    String name = kmClass.getName();
     if (!nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin) {
-      kmClass.setName(this.name);
+      rewrittenKmClass.setName(name);
     } else {
       String rewrittenName = null;
       // When the class has an anonymousObjectOrigin and the name equals the identifier there, we
@@ -316,7 +299,7 @@
         rewrittenName =
             KotlinMetadataUtils.getKotlinClassName(clazz, rewrittenDescriptor.toString());
       }
-      kmClass.setName(rewrittenName);
+      rewrittenKmClass.setName(rewrittenName);
       rewritten |= !name.equals(rewrittenName);
     }
     // Find a companion object.
@@ -328,12 +311,13 @@
         rewritten |=
             kotlinInfo
                 .asCompanion()
-                .rewrite(kmClass, field.getReference(), appView.getNamingLens());
+                .rewrite(rewrittenKmClass, field.getReference(), appView.getNamingLens());
         foundCompanion = true;
       } else if (kotlinInfo.isEnumEntry()) {
         KotlinEnumEntryInfo kotlinEnumEntryInfo = kotlinInfo.asEnumEntry();
         rewritten |=
-            kotlinEnumEntryInfo.rewrite(kmClass, field.getReference(), appView.getNamingLens());
+            kotlinEnumEntryInfo.rewrite(
+                rewrittenKmClass, field.getReference(), appView.getNamingLens());
         if (numberOfEnumEntries >= enumEntries.size()
             || !enumEntries.get(numberOfEnumEntries).equals(kotlinEnumEntryInfo.getEnumEntry())) {
           rewritten = true;
@@ -353,32 +337,35 @@
     }
     // Take all not backed constructors because we will never find them in definitions.
     for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
-      rewritten |= constructorInfo.rewrite(kmClass, null, appView);
+      rewritten |= constructorInfo.rewrite(rewrittenKmClass, null, appView);
     }
     // Find all constructors.
     KotlinMetadataMembersTracker rewrittenReferences = new KotlinMetadataMembersTracker(appView);
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.getKotlinInfo().isConstructor()) {
         KotlinConstructorInfo constructorInfo = method.getKotlinInfo().asConstructor();
-        rewritten |= constructorInfo.rewrite(kmClass, method, appView);
+        rewritten |= constructorInfo.rewrite(rewrittenKmClass, method, appView);
         rewrittenReferences.add(method.getReference());
       }
     }
     // Rewrite functions, type-aliases and type-parameters.
     rewritten |=
         declarationContainerInfo.rewrite(
-            kmClass.getFunctions()::add,
-            kmClass.getProperties()::add,
-            kmClass.getTypeAliases()::add,
+            rewrittenKmClass.getFunctions()::add,
+            rewrittenKmClass.getProperties()::add,
+            rewrittenKmClass.getTypeAliases()::add,
             clazz,
             appView,
             rewrittenReferences);
     // Rewrite type parameters.
     rewritten |=
         rewriteList(
-            appView, typeParameters, kmClass.getTypeParameters(), KotlinTypeParameterInfo::rewrite);
+            appView,
+            typeParameters,
+            rewrittenKmClass.getTypeParameters(),
+            KotlinTypeParameterInfo::rewrite);
     // Rewrite super types.
-    List<KmType> rewrittenSuperTypes = kmClass.getSupertypes();
+    List<KmType> rewrittenSuperTypes = rewrittenKmClass.getSupertypes();
     for (KotlinTypeInfo superType : superTypes) {
       // Ensure the rewritten super type is not this type.
       DexType rewrittenSuperType =
@@ -390,7 +377,7 @@
       }
     }
     // Rewrite nested classes.
-    List<String> rewrittenNestedClasses = kmClass.getNestedClasses();
+    List<String> rewrittenNestedClasses = rewrittenKmClass.getNestedClasses();
     for (KotlinTypeReference nestedClass : this.nestedClasses) {
       Box<String> nestedDescriptorBox = new Box<>();
       boolean nestedClassRewritten =
@@ -409,7 +396,7 @@
       rewritten |= nestedClassRewritten;
     }
     // Rewrite sealed sub-classes.
-    List<String> rewrittenSealedClasses = kmClass.getSealedSubclasses();
+    List<String> rewrittenSealedClasses = rewrittenKmClass.getSealedSubclasses();
     for (KotlinTypeReference sealedSubClass : sealedSubClasses) {
       rewritten |=
           sealedSubClass.toRenamedBinaryNameOrDefault(
@@ -424,26 +411,26 @@
               appView,
               null);
     }
-    rewritten |= versionRequirements.rewrite(kmClass.getVersionRequirements()::addAll);
+    rewrittenKmClass.getVersionRequirements().addAll(kmClass.getVersionRequirements());
     if (inlineClassUnderlyingPropertyName != null && inlineClassUnderlyingType != null) {
-      kmClass.setInlineClassUnderlyingPropertyName(inlineClassUnderlyingPropertyName);
+      rewrittenKmClass.setInlineClassUnderlyingPropertyName(inlineClassUnderlyingPropertyName);
       rewritten |=
-          inlineClassUnderlyingType.rewrite(kmClass::setInlineClassUnderlyingType, appView);
+          inlineClassUnderlyingType.rewrite(
+              rewrittenKmClass::setInlineClassUnderlyingType, appView);
     }
     rewritten |=
         rewriteList(
             appView,
             contextReceiverTypes,
-            kmClass.getContextReceiverTypes(),
+            rewrittenKmClass.getContextReceiverTypes(),
             KotlinTypeInfo::rewrite);
-    JvmExtensionsKt.setJvmFlags(kmClass, jvmFlags);
-    JvmExtensionsKt.setModuleName(kmClass, moduleName);
+    JvmExtensionsKt.setModuleName(rewrittenKmClass, moduleName);
     if (anonymousObjectOrigin != null) {
       rewritten |=
           anonymousObjectOrigin.toRenamedBinaryNameOrDefault(
               renamedAnon -> {
                 if (renamedAnon != null) {
-                  JvmExtensionsKt.setAnonymousObjectOriginName(kmClass, renamedAnon);
+                  JvmExtensionsKt.setAnonymousObjectOriginName(rewrittenKmClass, renamedAnon);
                 }
               },
               appView,
@@ -451,9 +438,11 @@
     }
     rewritten |=
         localDelegatedProperties.rewrite(
-            JvmExtensionsKt.getLocalDelegatedProperties(kmClass)::add, appView);
+            JvmExtensionsKt.getLocalDelegatedProperties(rewrittenKmClass)::add, appView);
+    classMetadata.setKmClass(rewrittenKmClass);
+    updateJvmMetadataVersionIfRequired(classMetadata);
     return Pair.create(
-        Companion.writeClass(kmClass, getCompatibleKotlinInfo(), 0),
+        classMetadata.write(),
         rewritten || !originalMembersWithKotlinInfo.isEqual(rewrittenReferences, appView));
   }
 
@@ -464,7 +453,7 @@
 
   @Override
   public int[] getMetadataVersion() {
-    return metadataVersion;
+    return KotlinJvmMetadataVersionUtils.toIntArray(classMetadata.getVersion());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 4a66565..715d376 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -218,29 +218,26 @@
       Consumer<DexEncodedMethod> keepByteCode) {
     Metadata metadata = extractMetadataWithPossiblyUnsupportedMetadataVersion(kMetadata);
     String packageName = metadata.pn();
-    int[] metadataVersion = KotlinJvmMetadataVersionUtils.toIntArray(kMetadata.getVersion());
     if (kMetadata instanceof KotlinClassMetadata.Class) {
       return KotlinClassInfo.create(
           (KotlinClassMetadata.Class) kMetadata,
           packageName,
-          metadataVersion,
           clazz,
           appView,
           keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
       // e.g., B.kt becomes class `BKt`
       return KotlinFileFacadeInfo.create(
-          (FileFacade) kMetadata, packageName, metadataVersion, clazz, appView, keepByteCode);
+          (FileFacade) kMetadata, packageName, clazz, appView, keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
       // multi-file class with the same @JvmName.
       return KotlinMultiFileClassFacadeInfo.create(
-          (MultiFileClassFacade) kMetadata, packageName, metadataVersion, appView.dexItemFactory());
+          (MultiFileClassFacade) kMetadata, packageName, appView.dexItemFactory());
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
       // A single file, which is part of multi-file class.
       return KotlinMultiFileClassPartInfo.create(
           (MultiFileClassPart) kMetadata,
           packageName,
-          metadataVersion,
           clazz,
           appView,
           keepByteCode);
@@ -248,7 +245,6 @@
       return KotlinSyntheticClassInfo.create(
           (KotlinClassMetadata.SyntheticClass) kMetadata,
           packageName,
-          metadataVersion,
           clazz,
           kotlin,
           appView);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
index ef21b62..83f3612 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -18,7 +18,7 @@
 import kotlin.metadata.KmClassifier;
 import kotlin.metadata.KmClassifier.TypeAlias;
 import kotlin.metadata.KmClassifier.TypeParameter;
-import kotlin.metadata.KmTypeVisitor;
+import kotlin.metadata.KmType;
 
 public abstract class KotlinClassifierInfo implements EnqueuerMetadataTraceable {
 
@@ -49,7 +49,7 @@
     }
   }
 
-  abstract boolean rewrite(KmTypeVisitor visitor, AppView<?> appView);
+  abstract boolean rewrite(KmType kmType, AppView<?> appView);
 
   public DexType rewriteType(GraphLens graphLens, GraphLens codeLens) {
     return null;
@@ -66,11 +66,12 @@
     }
 
     @Override
-    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView) {
+    boolean rewrite(KmType kmType, AppView<?> appView) {
       return type.toRenamedDescriptorOrDefault(
           descriptor ->
-              visitor.visitClass(
-                  getKotlinLocalOrAnonymousNameFromDescriptor(descriptor, isLocalOrAnonymous)),
+              kmType.setClassifier(
+                  new KmClassifier.Class(
+                      getKotlinLocalOrAnonymousNameFromDescriptor(descriptor, isLocalOrAnonymous))),
           appView,
           ClassClassifiers.anyDescriptor);
     }
@@ -95,8 +96,8 @@
     }
 
     @Override
-    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView) {
-      visitor.visitTypeParameter(typeId);
+    boolean rewrite(KmType kmType, AppView<?> appView) {
+      kmType.setClassifier(new KmClassifier.TypeParameter(typeId));
       return false;
     }
 
@@ -115,8 +116,8 @@
     }
 
     @Override
-    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView) {
-      visitor.visitTypeAlias(typeAlias);
+    boolean rewrite(KmType kmType, AppView<?> appView) {
+      kmType.setClassifier(new KmClassifier.TypeAlias(typeAlias));
       return false;
     }
 
@@ -134,8 +135,8 @@
     }
 
     @Override
-    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView) {
-      visitor.visitClass(classifier);
+    boolean rewrite(KmType kmType, AppView<?> appView) {
+      kmType.setClassifier(new KmClassifier.Class(classifier));
       return false;
     }
 
@@ -153,8 +154,8 @@
     }
 
     @Override
-    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView) {
-      visitor.visitTypeAlias(classifier);
+    boolean rewrite(KmType kmType, AppView<?> appView) {
+      kmType.setClassifier(new KmClassifier.TypeAlias(classifier));
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
index c6981c9..7cbe845 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.NamingLens;
-import kotlin.metadata.KmClassVisitor;
+import kotlin.metadata.KmClass;
 
 // Structure around a kotlin companion object that can be assigned to a field.
 public class KotlinCompanionInfo implements KotlinFieldLevelInfo {
@@ -29,10 +29,10 @@
     return this;
   }
 
-  boolean rewrite(KmClassVisitor visitor, DexField field, NamingLens lens) {
+  boolean rewrite(KmClass clazz, DexField field, NamingLens lens) {
     DexString dexString = lens.lookupName(field);
     String finalName = dexString.toString();
-    visitor.visitCompanionObject(finalName);
+    clazz.setCompanionObject(finalName);
     return !finalName.equals(companionObjectFieldName);
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
index 28de1a1..44fb122 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
@@ -20,45 +20,41 @@
 // Holds information about a KmConstructor object.
 public class KotlinConstructorInfo implements KotlinMethodLevelInfo {
 
-  // Information from original KmValueParameter(s) if available.
-  private final int flags;
+  // Information from original constructor.
+  private final KmConstructor kmConstructor;
   // Information about the value parameters.
   private final List<KotlinValueParameterInfo> valueParameters;
   // Information about version requirements.
-  private final KotlinVersionRequirementInfo versionRequirements;
-  // Information about the signature.
   private final KotlinJvmMethodSignatureInfo signature;
 
   private KotlinConstructorInfo(
-      int flags,
+      KmConstructor kmConstructor,
       List<KotlinValueParameterInfo> valueParameters,
-      KotlinVersionRequirementInfo versionRequirements,
       KotlinJvmMethodSignatureInfo signature) {
-    this.flags = flags;
+    this.kmConstructor = kmConstructor;
     this.valueParameters = valueParameters;
-    this.versionRequirements = versionRequirements;
     this.signature = signature;
   }
 
   public static KotlinConstructorInfo create(
       KmConstructor kmConstructor, DexItemFactory factory, Reporter reporter) {
     return new KotlinConstructorInfo(
-        kmConstructor.getFlags(),
+        kmConstructor,
         KotlinValueParameterInfo.create(kmConstructor.getValueParameters(), factory, reporter),
-        KotlinVersionRequirementInfo.create(kmConstructor.getVersionRequirements()),
         KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmConstructor), factory));
   }
 
   boolean rewrite(KmClass kmClass, DexEncodedMethod method, AppView<?> appView) {
     // Note that JvmExtensionsKt.setSignature does not have an overload for KmConstructorVisitor,
     // thus we rely on creating the KmConstructor manually.
-    // TODO(b/154348683): Check for special flags to pass in.
-    KmConstructor kmConstructor = new KmConstructor(flags);
+    KmConstructor rewrittenKmConstructor = new KmConstructor();
+    KotlinFlagUtils.copyAllFlags(kmConstructor, rewrittenKmConstructor);
     boolean rewritten = false;
     if (signature != null) {
       rewritten =
           signature.rewrite(
-              rewrittenSignature -> JvmExtensionsKt.setSignature(kmConstructor, rewrittenSignature),
+              rewrittenSignature ->
+                  JvmExtensionsKt.setSignature(rewrittenKmConstructor, rewrittenSignature),
               method,
               appView);
     }
@@ -66,10 +62,10 @@
         rewriteList(
             appView,
             valueParameters,
-            kmConstructor.getValueParameters(),
+            rewrittenKmConstructor.getValueParameters(),
             KotlinValueParameterInfo::rewrite);
-    rewritten |= versionRequirements.rewrite(kmConstructor.getVersionRequirements()::addAll);
-    kmClass.getConstructors().add(kmConstructor);
+    rewrittenKmConstructor.getVersionRequirements().addAll(kmConstructor.getVersionRequirements());
+    kmClass.getConstructors().add(rewrittenKmConstructor);
     return rewritten;
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
index 1306045..4fb6774 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
@@ -54,7 +53,8 @@
     if (this == NO_EFFECT) {
       return false;
     }
-    KmContract kmContract = consume(new KmContract(), consumer);
+    KmContract kmContract = new KmContract();
+    consumer.accept(kmContract);
     return rewriteList(appView, effects, kmContract.getEffects(), KotlinEffectInfo::rewrite);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index 21f5469..d1d9441 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -26,11 +26,12 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
+import kotlin.metadata.Attributes;
 import kotlin.metadata.KmDeclarationContainer;
 import kotlin.metadata.KmFunction;
 import kotlin.metadata.KmProperty;
+import kotlin.metadata.KmPropertyAccessorAttributes;
 import kotlin.metadata.KmTypeAlias;
-import kotlin.metadata.internal.metadata.deserialization.Flags;
 import kotlin.metadata.jvm.JvmExtensionsKt;
 import kotlin.metadata.jvm.JvmMethodSignature;
 
@@ -85,7 +86,7 @@
         }
         continue;
       }
-      keepIfInline(kmFunction.getFlags(), method, signature, methodSignatureMap, keepByteCode);
+      keepIfInline(kmFunction, method, signature, methodSignatureMap, keepByteCode);
       method.setKotlinMemberInfo(kotlinFunctionInfo);
       originalAssignmentTracker.add(method.getReference());
     }
@@ -110,7 +111,7 @@
             methodSignatureMap.get(propertyProcessor.getterSignature().toString());
         if (method != null) {
           hasBacking = true;
-          keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
+          keepIfAccessorInline(kmProperty.getGetter(), method, keepByteCode);
           method.setKotlinMemberInfo(
               new KotlinPropertyInfoDelegate(kotlinPropertyInfo, PropertyType.GETTER));
           originalAssignmentTracker.add(method.getReference());
@@ -121,7 +122,7 @@
             methodSignatureMap.get(propertyProcessor.setterSignature().toString());
         if (method != null) {
           hasBacking = true;
-          keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
+          keepIfAccessorInline(kmProperty.getGetter(), method, keepByteCode);
           method.setKotlinMemberInfo(
               new KotlinPropertyInfoDelegate(kotlinPropertyInfo, PropertyType.SETTER));
           originalAssignmentTracker.add(method.getReference());
@@ -150,12 +151,12 @@
   }
 
   private static void keepIfInline(
-      int flags,
+      KmFunction kmFunction,
       DexEncodedMethod method,
       JvmMethodSignature signature,
       Map<String, DexEncodedMethod> methodSignatureMap,
       Consumer<DexEncodedMethod> keepByteCode) {
-    if (Flags.IS_INLINE.get(flags)) {
+    if (Attributes.isInline(kmFunction)) {
       // Check if we can find a default method. If there are more than 32 arguments another int
       // index will be added to the default method.
       for (int i = 1;
@@ -173,8 +174,10 @@
   }
 
   private static void keepIfAccessorInline(
-      int flags, DexEncodedMethod method, Consumer<DexEncodedMethod> keepByteCode) {
-    if (Flags.IS_INLINE_ACCESSOR.get(flags)) {
+      KmPropertyAccessorAttributes kmPropertyAccessorAttributes,
+      DexEncodedMethod method,
+      Consumer<DexEncodedMethod> keepByteCode) {
+    if (Attributes.isInline(kmPropertyAccessorAttributes)) {
       keepByteCode.accept(method);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
index ae218d2..f87cd78 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteIfNotNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
@@ -24,42 +23,35 @@
 
   private static final List<KotlinEffectExpressionInfo> NO_EXPRESSIONS = ImmutableList.of();
   private static final KotlinEffectExpressionInfo NO_EXPRESSION =
-      new KotlinEffectExpressionInfo(0, 0, null, null, NO_EXPRESSIONS, NO_EXPRESSIONS);
+      new KotlinEffectExpressionInfo(
+          new KmEffectExpression(), null, NO_EXPRESSIONS, NO_EXPRESSIONS);
 
-  private final int flags;
-  private final Integer parameterIndex;
-  private final KmConstantValue constantValue;
+  private final KmEffectExpression kmEffectExpression;
   private final KotlinTypeInfo isInstanceType;
   private final List<KotlinEffectExpressionInfo> andArguments;
   private final List<KotlinEffectExpressionInfo> orArguments;
 
   private KotlinEffectExpressionInfo(
-      int flags,
-      Integer parameterIndex,
-      KmConstantValue constantValue,
+      KmEffectExpression kmEffectExpression,
       KotlinTypeInfo isInstanceType,
       List<KotlinEffectExpressionInfo> andArguments,
       List<KotlinEffectExpressionInfo> orArguments) {
-    this.flags = flags;
-    this.parameterIndex = parameterIndex;
-    this.constantValue = constantValue;
+    this.kmEffectExpression = kmEffectExpression;
     this.isInstanceType = isInstanceType;
     this.andArguments = andArguments;
     this.orArguments = orArguments;
   }
 
   static KotlinEffectExpressionInfo create(
-      KmEffectExpression effectExpression, DexItemFactory factory, Reporter reporter) {
-    if (effectExpression == null) {
+      KmEffectExpression kmEffectExpression, DexItemFactory factory, Reporter reporter) {
+    if (kmEffectExpression == null) {
       return NO_EXPRESSION;
     }
     return new KotlinEffectExpressionInfo(
-        effectExpression.getFlags(),
-        effectExpression.getParameterIndex(),
-        effectExpression.getConstantValue(),
-        KotlinTypeInfo.create(effectExpression.isInstanceType(), factory, reporter),
-        create(effectExpression.getAndArguments(), factory, reporter),
-        create(effectExpression.getOrArguments(), factory, reporter));
+        kmEffectExpression,
+        KotlinTypeInfo.create(kmEffectExpression.isInstanceType(), factory, reporter),
+        create(kmEffectExpression.getAndArguments(), factory, reporter),
+        create(kmEffectExpression.getOrArguments(), factory, reporter));
   }
 
   static List<KotlinEffectExpressionInfo> create(
@@ -90,26 +82,31 @@
     if (this == NO_EXPRESSION) {
       return false;
     }
-    KmEffectExpression effectExpression = consume(new KmEffectExpression(), consumer);
-    effectExpression.setFlags(flags);
-    effectExpression.setParameterIndex(parameterIndex);
+    KmEffectExpression rewrittenKmEffectExpression = new KmEffectExpression();
+    consumer.accept(rewrittenKmEffectExpression);
+    KotlinFlagUtils.copyAllFlags(kmEffectExpression, rewrittenKmEffectExpression);
+    rewrittenKmEffectExpression.setParameterIndex(kmEffectExpression.getParameterIndex());
+    KmConstantValue constantValue = kmEffectExpression.getConstantValue();
     if (constantValue != null) {
-      effectExpression.setConstantValue(constantValue);
+      rewrittenKmEffectExpression.setConstantValue(constantValue);
     }
     boolean rewritten =
         rewriteIfNotNull(
-            appView, isInstanceType, effectExpression::setInstanceType, KotlinTypeInfo::rewrite);
+            appView,
+            isInstanceType,
+            rewrittenKmEffectExpression::setInstanceType,
+            KotlinTypeInfo::rewrite);
     rewritten |=
         rewriteList(
             appView,
             andArguments,
-            effectExpression.getAndArguments(),
+            rewrittenKmEffectExpression.getAndArguments(),
             KotlinEffectExpressionInfo::rewrite);
     rewritten |=
         rewriteList(
             appView,
             orArguments,
-            effectExpression.getOrArguments(),
+            rewrittenKmEffectExpression.getOrArguments(),
             KotlinEffectExpressionInfo::rewrite);
     return rewritten;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
index 0a973a1..6fc3950 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
@@ -52,7 +51,8 @@
   }
 
   boolean rewrite(Consumer<KmEffect> consumer, AppView<?> appView) {
-    KmEffect kmEffect = consume(new KmEffect(type, invocationKind), consumer);
+    KmEffect kmEffect = new KmEffect(type, invocationKind);
+    consumer.accept(kmEffect);
     boolean rewritten = conclusion.rewrite(kmEffect::setConclusion, appView);
     rewritten |=
         rewriteList(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java
index ff1336d..4c3197d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.NamingLens;
-import kotlin.metadata.KmClassVisitor;
+import kotlin.metadata.KmClass;
 
 // Structure around a kotlin enum value that can be assigned to a field.
 public class KotlinEnumEntryInfo implements KotlinFieldLevelInfo {
@@ -29,10 +29,10 @@
     return this;
   }
 
-  boolean rewrite(KmClassVisitor visitor, DexField field, NamingLens lens) {
+  boolean rewrite(KmClass clazz, DexField field, NamingLens lens) {
     DexString dexString = lens.lookupName(field);
     String finalName = dexString.toString();
-    visitor.visitEnumEntry(finalName);
+    clazz.getEnumEntries().add(finalName);
     return !finalName.equals(enumEntry);
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index 2c46b82..07d4faa 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -4,8 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
-import static kotlin.metadata.jvm.KotlinClassMetadata.Companion;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.updateJvmMetadataVersionIfRequired;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -20,29 +19,28 @@
 // Holds information about Metadata.FileFacade
 public class KotlinFileFacadeInfo implements KotlinClassLevelInfo {
 
+  private final FileFacade kmFileFacade;
   private final KotlinPackageInfo packageInfo;
   private final String packageName;
-  private final int[] metadataVersion;
 
   private KotlinFileFacadeInfo(
-      KotlinPackageInfo packageInfo, String packageName, int[] metadataVersion) {
+      FileFacade kmFileFacade, KotlinPackageInfo packageInfo, String packageName) {
+    this.kmFileFacade = kmFileFacade;
     this.packageInfo = packageInfo;
     this.packageName = packageName;
-    this.metadataVersion = metadataVersion;
   }
 
   public static KotlinFileFacadeInfo create(
       FileFacade kmFileFacade,
       String packageName,
-      int[] metadataVersion,
       DexClass clazz,
       AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
     KmPackage kmPackage = kmFileFacade.getKmPackage();
     return new KotlinFileFacadeInfo(
+        kmFileFacade,
         KotlinPackageInfo.create(kmPackage, clazz, appView, keepByteCode),
-        packageName,
-        metadataVersion);
+        packageName);
   }
 
   @Override
@@ -59,8 +57,9 @@
   public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
     KmPackage kmPackage = new KmPackage();
     boolean rewritten = packageInfo.rewrite(kmPackage, clazz, appView);
-    return Pair.create(
-        Companion.writeFileFacade(kmPackage, getCompatibleKotlinInfo(), 0), rewritten);
+    updateJvmMetadataVersionIfRequired(kmFileFacade);
+    kmFileFacade.setKmPackage(kmPackage);
+    return Pair.create(kmFileFacade.write(), rewritten);
   }
 
   @Override
@@ -74,7 +73,7 @@
 
   @Override
   public int[] getMetadataVersion() {
-    return metadataVersion;
+    return KotlinJvmMetadataVersionUtils.toIntArray(kmFileFacade.getVersion());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlagUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlagUtils.java
new file mode 100644
index 0000000..3bb712e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlagUtils.java
@@ -0,0 +1,261 @@
+// 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.kotlin;
+
+import java.util.HashMap;
+import java.util.Map;
+import kotlin.metadata.Attributes;
+import kotlin.metadata.KmClass;
+import kotlin.metadata.KmConstructor;
+import kotlin.metadata.KmEffectExpression;
+import kotlin.metadata.KmFunction;
+import kotlin.metadata.KmProperty;
+import kotlin.metadata.KmPropertyAccessorAttributes;
+import kotlin.metadata.KmType;
+import kotlin.metadata.KmTypeAlias;
+import kotlin.metadata.KmTypeParameter;
+import kotlin.metadata.KmValueParameter;
+import kotlin.metadata.jvm.JvmAttributes;
+
+public class KotlinFlagUtils {
+
+  private static final String ANNOTATIONS_KEY = "hasAnnotations";
+  private static final String VISIBILITY_KEY = "visibility";
+  private static final String MODALITY_KEY = "modality";
+  private static final String KIND_KEY = "kind";
+  private static final String INNER_KEY = "inner";
+  private static final String DATA_KEY = "data";
+  private static final String VALUE_KEY = "value";
+  private static final String FUN_INTERFACE_KEY = "funInterface";
+  private static final String ENUM_ENTRIES_KEY = "enumEntries";
+  private static final String VAR_KEY = "var";
+  private static final String CONST_KEY = "const";
+  private static final String LATE_INIT_KEY = "lateInit";
+  private static final String CONSTANT_KEY = "hasConstant";
+  private static final String EXTERNAL_KEY = "external";
+  private static final String DELEGATED_KEY = "delegated";
+  private static final String EXPECT_KEY = "expect";
+  private static final String NOT_DEFAULT_KEY = "notDefault";
+  private static final String INLINE_KEY = "inline";
+  private static final String SECONDARY_KEY = "secondary";
+  private static final String NON_STABLE_PARAMETER_NAMES_KEY = "nonStableParameterNames";
+  private static final String NEGATED_KEY = "negated";
+  private static final String NULL_CHECK_PREDICATE_KEY = "nullCheckPredicate";
+  private static final String OPERATOR_KEY = "operator";
+  private static final String INFIX_KEY = "infix";
+  private static final String TAIL_REC_KEY = "tailRec";
+  private static final String SUSPEND_KEY = "suspend";
+  private static final String NULLABLE_KEY = "nullable";
+  private static final String DEFINITELY_NON_NULL_KEY = "definitelyNonNull";
+  private static final String DECLARES_DEFAULT_VALUE_KEY = "declaresDefaultValue";
+  private static final String CROSS_INLINE_KEY = "crossInline";
+  private static final String NO_INLINE_KEY = "noInline";
+  public static final String REIFIED_KEY = "reified";
+
+  public static Map<String, Object> extractFlags(KmProperty src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(ANNOTATIONS_KEY, Attributes.getHasAnnotations(src));
+    map.put(VISIBILITY_KEY, Attributes.getVisibility(src));
+    map.put(MODALITY_KEY, Attributes.getModality(src));
+    map.put(KIND_KEY, Attributes.getKind(src));
+    map.put(VAR_KEY, Attributes.isVar(src));
+    map.put(CONST_KEY, Attributes.isConst(src));
+    map.put(LATE_INIT_KEY, Attributes.isLateinit(src));
+    map.put(CONSTANT_KEY, Attributes.getHasConstant(src));
+    map.put(EXTERNAL_KEY, Attributes.isExternal(src));
+    map.put(DELEGATED_KEY, Attributes.isDelegated(src));
+    map.put(EXPECT_KEY, Attributes.isExpect(src));
+
+    map.put("movedFromInterfaceCompanion", JvmAttributes.isMovedFromInterfaceCompanion(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmProperty src, KmProperty dest) {
+    Attributes.setHasAnnotations(dest, Attributes.getHasAnnotations(src));
+    Attributes.setVisibility(dest, Attributes.getVisibility(src));
+    Attributes.setModality(dest, Attributes.getModality(src));
+    Attributes.setKind(dest, Attributes.getKind(src));
+    Attributes.setVar(dest, Attributes.isVar(src));
+    Attributes.setConst(dest, Attributes.isConst(src));
+    Attributes.setLateinit(dest, Attributes.isLateinit(src));
+    Attributes.setHasConstant(dest, Attributes.getHasConstant(src));
+    Attributes.setExternal(dest, Attributes.isExternal(src));
+    Attributes.setDelegated(dest, Attributes.isDelegated(src));
+    Attributes.setExpect(dest, Attributes.isExpect(src));
+
+    JvmAttributes.setMovedFromInterfaceCompanion(
+        dest, JvmAttributes.isMovedFromInterfaceCompanion(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmType src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(NULLABLE_KEY, Attributes.isNullable(src));
+    map.put(SUSPEND_KEY, Attributes.isSuspend(src));
+    map.put(DEFINITELY_NON_NULL_KEY, Attributes.isDefinitelyNonNull(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmType src, KmType dest) {
+    Attributes.setNullable(dest, Attributes.isNullable(src));
+    Attributes.setSuspend(dest, Attributes.isSuspend(src));
+    Attributes.setDefinitelyNonNull(dest, Attributes.isDefinitelyNonNull(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmPropertyAccessorAttributes src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(ANNOTATIONS_KEY, Attributes.getHasAnnotations(src));
+    map.put(VISIBILITY_KEY, Attributes.getVisibility(src));
+    map.put(MODALITY_KEY, Attributes.getModality(src));
+    map.put(NOT_DEFAULT_KEY, Attributes.isNotDefault(src));
+    map.put(EXTERNAL_KEY, Attributes.isExternal(src));
+    map.put(INLINE_KEY, Attributes.isInline(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmPropertyAccessorAttributes src, KmPropertyAccessorAttributes dest) {
+    Attributes.setHasAnnotations(dest, Attributes.getHasAnnotations(src));
+    Attributes.setVisibility(dest, Attributes.getVisibility(src));
+    Attributes.setModality(dest, Attributes.getModality(src));
+    Attributes.setNotDefault(dest, Attributes.isNotDefault(src));
+    Attributes.setExternal(dest, Attributes.isExternal(src));
+    Attributes.setInline(dest, Attributes.isInline(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmClass src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(ANNOTATIONS_KEY, Attributes.getHasAnnotations(src));
+    map.put(VISIBILITY_KEY, Attributes.getVisibility(src));
+    map.put(MODALITY_KEY, Attributes.getModality(src));
+    map.put(KIND_KEY, Attributes.getKind(src));
+    map.put(INNER_KEY, Attributes.isInner(src));
+    map.put(DATA_KEY, Attributes.isData(src));
+    map.put(EXTERNAL_KEY, Attributes.isExternal(src));
+    map.put(EXPECT_KEY, Attributes.isExpect(src));
+    map.put(VALUE_KEY, Attributes.isValue(src));
+    map.put(FUN_INTERFACE_KEY, Attributes.isFunInterface(src));
+    map.put(ENUM_ENTRIES_KEY, Attributes.getHasEnumEntries(src));
+
+    map.put("compiledInCompatibilityMode", JvmAttributes.isCompiledInCompatibilityMode(src));
+    map.put("hasMethodBodiesInInterface", JvmAttributes.getHasMethodBodiesInInterface(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmClass src, KmClass dest) {
+    Attributes.setHasAnnotations(dest, Attributes.getHasAnnotations(src));
+    Attributes.setVisibility(dest, Attributes.getVisibility(src));
+    Attributes.setModality(dest, Attributes.getModality(src));
+    Attributes.setKind(dest, Attributes.getKind(src));
+    Attributes.setInner(dest, Attributes.isInner(src));
+    Attributes.setData(dest, Attributes.isData(src));
+    Attributes.setExternal(dest, Attributes.isExternal(src));
+    Attributes.setExpect(dest, Attributes.isExpect(src));
+    Attributes.setValue(dest, Attributes.isValue(src));
+    Attributes.setFunInterface(dest, Attributes.isFunInterface(src));
+    Attributes.setHasEnumEntries(dest, Attributes.getHasEnumEntries(src));
+
+    JvmAttributes.setCompiledInCompatibilityMode(
+        dest, JvmAttributes.isCompiledInCompatibilityMode(src));
+    JvmAttributes.setHasMethodBodiesInInterface(
+        dest, JvmAttributes.getHasMethodBodiesInInterface(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmConstructor src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(ANNOTATIONS_KEY, Attributes.getHasAnnotations(src));
+    map.put(VISIBILITY_KEY, Attributes.getVisibility(src));
+    map.put(SECONDARY_KEY, Attributes.isSecondary(src));
+    map.put(NON_STABLE_PARAMETER_NAMES_KEY, Attributes.getHasNonStableParameterNames(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmConstructor src, KmConstructor dest) {
+    Attributes.setHasAnnotations(dest, Attributes.getHasAnnotations(src));
+    Attributes.setVisibility(dest, Attributes.getVisibility(src));
+    Attributes.setSecondary(dest, Attributes.isSecondary(src));
+    Attributes.setHasNonStableParameterNames(dest, Attributes.getHasNonStableParameterNames(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmFunction src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(ANNOTATIONS_KEY, Attributes.getHasAnnotations(src));
+    map.put(KIND_KEY, Attributes.getKind(src));
+    map.put(MODALITY_KEY, Attributes.getModality(src));
+    map.put(OPERATOR_KEY, Attributes.isOperator(src));
+    map.put(INFIX_KEY, Attributes.isInfix(src));
+    map.put(INLINE_KEY, Attributes.isInline(src));
+    map.put(TAIL_REC_KEY, Attributes.isTailrec(src));
+    map.put(EXTERNAL_KEY, Attributes.isExternal(src));
+    map.put(SUSPEND_KEY, Attributes.isSuspend(src));
+    map.put(EXPECT_KEY, Attributes.isExpect(src));
+    map.put(VISIBILITY_KEY, Attributes.getVisibility(src));
+    map.put(NON_STABLE_PARAMETER_NAMES_KEY, Attributes.getHasNonStableParameterNames(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmFunction src, KmFunction dest) {
+    Attributes.setHasAnnotations(dest, Attributes.getHasAnnotations(src));
+    Attributes.setKind(dest, Attributes.getKind(src));
+    Attributes.setModality(dest, Attributes.getModality(src));
+    Attributes.setOperator(dest, Attributes.isOperator(src));
+    Attributes.setInfix(dest, Attributes.isInfix(src));
+    Attributes.setInline(dest, Attributes.isInline(src));
+    Attributes.setTailrec(dest, Attributes.isTailrec(src));
+    Attributes.setExternal(dest, Attributes.isExternal(src));
+    Attributes.setSuspend(dest, Attributes.isSuspend(src));
+    Attributes.setExpect(dest, Attributes.isExpect(src));
+    Attributes.setVisibility(dest, Attributes.getVisibility(src));
+    Attributes.setHasNonStableParameterNames(dest, Attributes.getHasNonStableParameterNames(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmEffectExpression src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(NEGATED_KEY, Attributes.isNegated(src));
+    map.put(NULL_CHECK_PREDICATE_KEY, Attributes.isNullCheckPredicate(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmEffectExpression src, KmEffectExpression dest) {
+    Attributes.setNegated(dest, Attributes.isNegated(src));
+    Attributes.setNullCheckPredicate(dest, Attributes.isNullCheckPredicate(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmTypeAlias src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(ANNOTATIONS_KEY, Attributes.getHasAnnotations(src));
+    map.put(VISIBILITY_KEY, Attributes.getVisibility(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmTypeAlias src, KmTypeAlias dest) {
+    Attributes.setHasAnnotations(dest, Attributes.getHasAnnotations(src));
+    Attributes.setVisibility(dest, Attributes.getVisibility(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmValueParameter src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(ANNOTATIONS_KEY, Attributes.getHasAnnotations(src));
+    map.put(DECLARES_DEFAULT_VALUE_KEY, Attributes.getDeclaresDefaultValue(src));
+    map.put(CROSS_INLINE_KEY, Attributes.isCrossinline(src));
+    map.put(NO_INLINE_KEY, Attributes.isNoinline(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmValueParameter src, KmValueParameter dest) {
+    Attributes.setHasAnnotations(dest, Attributes.getHasAnnotations(src));
+    Attributes.setDeclaresDefaultValue(dest, Attributes.getDeclaresDefaultValue(src));
+    Attributes.setCrossinline(dest, Attributes.isCrossinline(src));
+    Attributes.setNoinline(dest, Attributes.isNoinline(src));
+  }
+
+  public static Map<String, Object> extractFlags(KmTypeParameter src) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(REIFIED_KEY, Attributes.isReified(src));
+    return map;
+  }
+
+  static void copyAllFlags(KmTypeParameter src, KmTypeParameter dest) {
+    Attributes.setReified(dest, Attributes.isReified(src));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index aa453f6..c880a0e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteIfNotNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
@@ -22,10 +21,8 @@
 
 // Holds information about KmFunction
 public final class KotlinFunctionInfo implements KotlinMethodLevelInfo {
-  // Original flags
-  private final int flags;
-  // Original name;
-  private final String name;
+  // Original function.
+  private final KmFunction kmFunction;
   // Information from original KmValueParameter(s) if available.
   private final List<KotlinValueParameterInfo> valueParameters;
   // Information from original KmFunction.returnType. Null if this is from a KmConstructor.
@@ -38,8 +35,6 @@
   private final KotlinJvmMethodSignatureInfo signature;
   // Information about the lambdaClassOrigin.
   private final KotlinTypeReference lambdaClassOrigin;
-  // Information about version requirements.
-  private final KotlinVersionRequirementInfo versionRequirements;
   // Kotlin contract information.
   private final KotlinContractInfo contract;
   // A value describing if any of the parameters are crossinline.
@@ -48,27 +43,23 @@
   private final List<KotlinTypeInfo> contextReceiverTypes;
 
   private KotlinFunctionInfo(
-      int flags,
-      String name,
+      KmFunction kmFunction,
       KotlinTypeInfo returnType,
       KotlinTypeInfo receiverParameterType,
       List<KotlinValueParameterInfo> valueParameters,
       List<KotlinTypeParameterInfo> typeParameters,
       KotlinJvmMethodSignatureInfo signature,
       KotlinTypeReference lambdaClassOrigin,
-      KotlinVersionRequirementInfo versionRequirements,
       KotlinContractInfo contract,
       boolean crossInlineParameter,
       List<KotlinTypeInfo> contextReceiverTypes) {
-    this.flags = flags;
-    this.name = name;
+    this.kmFunction = kmFunction;
     this.returnType = returnType;
     this.receiverParameterType = receiverParameterType;
     this.valueParameters = valueParameters;
     this.typeParameters = typeParameters;
     this.signature = signature;
     this.lambdaClassOrigin = lambdaClassOrigin;
-    this.versionRequirements = versionRequirements;
     this.contract = contract;
     this.crossInlineParameter = crossInlineParameter;
     this.contextReceiverTypes = contextReceiverTypes;
@@ -90,15 +81,13 @@
       }
     }
     return new KotlinFunctionInfo(
-        kmFunction.getFlags(),
-        kmFunction.getName(),
+        kmFunction,
         KotlinTypeInfo.create(kmFunction.getReturnType(), factory, reporter),
         KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), factory, reporter),
         valueParameters,
         KotlinTypeParameterInfo.create(kmFunction.getTypeParameters(), factory, reporter),
         KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory),
         getlambdaClassOrigin(kmFunction, factory),
-        KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()),
         KotlinContractInfo.create(kmFunction.getContract(), factory, reporter),
         isCrossInline,
         ListUtils.map(
@@ -117,7 +106,7 @@
   }
 
   public String getName() {
-    return name;
+    return kmFunction.getName();
   }
 
   boolean rewriteNoBacking(Consumer<KmFunction> consumer, AppView<?> appView) {
@@ -127,7 +116,7 @@
   boolean rewrite(Consumer<KmFunction> consumer, DexEncodedMethod method, AppView<?> appView) {
     // TODO(b/154348683): Check method for flags to pass in.
     boolean rewritten = false;
-    String finalName = name;
+    String finalName = getName();
     // Only rewrite the kotlin method name if it was equal to the method name when reading the
     // metadata.
     if (method != null) {
@@ -138,51 +127,56 @@
         finalName = rewrittenName;
       }
     }
-    KmFunction kmFunction = consume(new KmFunction(flags, finalName), consumer);
+    KmFunction rewrittenKmFunction = new KmFunction(finalName);
+    consumer.accept(rewrittenKmFunction);
+    KotlinFlagUtils.copyAllFlags(kmFunction, rewrittenKmFunction);
     // TODO(b/154348149): ReturnType could have been merged to a subtype.
-    rewritten |= returnType.rewrite(kmFunction::setReturnType, appView);
+    rewritten |= returnType.rewrite(rewrittenKmFunction::setReturnType, appView);
     rewritten |=
         rewriteList(
             appView,
             valueParameters,
-            kmFunction.getValueParameters(),
+            rewrittenKmFunction.getValueParameters(),
             KotlinValueParameterInfo::rewrite);
     rewritten |=
         rewriteList(
             appView,
             typeParameters,
-            kmFunction.getTypeParameters(),
+            rewrittenKmFunction.getTypeParameters(),
             KotlinTypeParameterInfo::rewrite);
     rewritten |=
         rewriteList(
             appView,
             contextReceiverTypes,
-            kmFunction.getContextReceiverTypes(),
+            rewrittenKmFunction.getContextReceiverTypes(),
             KotlinTypeInfo::rewrite);
     rewritten |=
         rewriteIfNotNull(
             appView,
             receiverParameterType,
-            kmFunction::setReceiverParameterType,
+            rewrittenKmFunction::setReceiverParameterType,
             KotlinTypeInfo::rewrite);
-    rewritten |= versionRequirements.rewrite(kmFunction.getVersionRequirements()::addAll);
+    rewrittenKmFunction.getVersionRequirements().addAll(kmFunction.getVersionRequirements());
     if (signature != null) {
       rewritten |=
           signature.rewrite(
-              signature -> JvmExtensionsKt.setSignature(kmFunction, signature), method, appView);
+              signature -> JvmExtensionsKt.setSignature(rewrittenKmFunction, signature),
+              method,
+              appView);
     }
     if (lambdaClassOrigin != null) {
       rewritten |=
           lambdaClassOrigin.toRenamedBinaryNameOrDefault(
               lambdaClassOriginName -> {
                 if (lambdaClassOriginName != null) {
-                  JvmExtensionsKt.setLambdaClassOriginName(kmFunction, lambdaClassOriginName);
+                  JvmExtensionsKt.setLambdaClassOriginName(
+                      rewrittenKmFunction, lambdaClassOriginName);
                 }
               },
               appView,
               null);
     }
-    rewritten |= contract.rewrite(kmFunction::setContract, appView);
+    rewritten |= contract.rewrite(rewrittenKmFunction::setContract, appView);
     return rewritten;
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
index 77673c0..d9ecef0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
 
 import com.android.tools.r8.graph.AppView;
@@ -51,7 +50,8 @@
   }
 
   boolean rewrite(Consumer<KmLambda> consumer, DexClass clazz, AppView<?> appView) {
-    KmLambda kmLambda = consume(new KmLambda(), consumer);
+    KmLambda kmLambda = new KmLambda();
+    consumer.accept(kmLambda);
     if (!hasBacking) {
       return function.rewrite(kmLambda::setFunction, null, appView);
     }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index c2f31ef..9516e45 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -29,9 +29,11 @@
 import kotlin.metadata.jvm.JvmFieldSignature;
 import kotlin.metadata.jvm.JvmMetadataVersion;
 import kotlin.metadata.jvm.JvmMethodSignature;
+import kotlin.metadata.jvm.KotlinClassMetadata;
 
 public class KotlinMetadataUtils {
 
+  private static final JvmMetadataVersion VERSION_1_4_0 = new JvmMetadataVersion(1, 4, 0);
   private static final NoKotlinInfo NO_KOTLIN_INFO = new NoKotlinInfo("NO_KOTLIN_INFO");
   private static final NoKotlinInfo INVALID_KOTLIN_INFO = new NoKotlinInfo("INVALID_KOTLIN_INFO");
 
@@ -220,18 +222,10 @@
     return DescriptorUtils.descriptorToKotlinClassifier(descriptor);
   }
 
-  static int[] getCompatibleKotlinInfo() {
-    // The kotlin metadata changelog recommends:
-    // "Main migration path here is to replace KotlinClassMetadata.COMPATIBLE_METADATA_VERSION
-    // with new value with the same meaning: JvmMetadataVersion.LATEST_STABLE_SUPPORTED."
-    // The inspection error "Usage of Kotlin internal declaration from different module" does not
-    // prevent the code to work correctly.
-    return JvmMetadataVersion.LATEST_STABLE_SUPPORTED.toIntArray();
-  }
-
-  static <TKm> TKm consume(TKm tKm, Consumer<TKm> consumer) {
-    consumer.accept(tKm);
-    return tKm;
+  public static void updateJvmMetadataVersionIfRequired(KotlinClassMetadata metadata) {
+    if (metadata.getVersion().compareTo(VERSION_1_4_0) < 0) {
+      metadata.setVersion(VERSION_1_4_0);
+    }
   }
 
   static <TInfo, TKm> boolean rewriteIfNotNull(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index 24c5af9..9509c98 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -286,9 +286,23 @@
                 (nextNextIndent, kmProperty) -> appendKmProperty(nextNextIndent, sb, kmProperty)));
   }
 
+  public static void appendFlags(
+      String indent, String keyword, StringBuilder sb, Map<String, Object> flags) {
+    sb.append(indent).append(keyword).append(": [").append(LINE_SEPARATOR);
+    flags.forEach(
+        (name, value) ->
+            sb.append(indent)
+                .append(INDENT)
+                .append(name)
+                .append(": ")
+                .append(value)
+                .append(",")
+                .append(LINE_SEPARATOR));
+    sb.append(indent).append("]");
+  }
+
   public static void appendKmClass(String indent, StringBuilder sb, KmClass kmClass) {
-    appendKeyValue(indent, "flags", sb, kmClass.getFlags() + "");
-    appendKeyValue(indent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmClass) + "");
+    appendFlags(indent, "flags", sb, KotlinFlagUtils.extractFlags(kmClass));
     appendKeyValue(indent, "name", sb, kmClass.getName());
     appendKeyValue(
         indent,
@@ -387,7 +401,7 @@
         "KmConstructor",
         sb,
         newIndent -> {
-          appendKeyValue(newIndent, "flags", sb, constructor.getFlags() + "");
+          appendFlags(indent, "flags", sb, KotlinFlagUtils.extractFlags(constructor));
           appendKeyValue(
               newIndent,
               "valueParameters",
@@ -406,7 +420,7 @@
         "KmFunction",
         sb,
         newIndent -> {
-          appendKeyValue(newIndent, "flags", sb, function.getFlags() + "");
+          appendFlags(indent, "flags", sb, KotlinFlagUtils.extractFlags(function));
           appendKeyValue(newIndent, "name", sb, function.getName());
           appendKeyValue(
               newIndent,
@@ -466,7 +480,7 @@
         "KmProperty",
         sb,
         newIndent -> {
-          appendKeyValue(newIndent, "flags", sb, kmProperty.getFlags() + "");
+          appendFlags(indent, "flags", sb, KotlinFlagUtils.extractFlags(kmProperty));
           appendKeyValue(newIndent, "name", sb, kmProperty.getName());
           appendKeyValue(
               newIndent,
@@ -483,8 +497,12 @@
               "typeParameters",
               sb,
               nextIndent -> appendTypeParameters(nextIndent, sb, kmProperty.getTypeParameters()));
-          appendKeyValue(newIndent, "getterFlags", sb, kmProperty.getGetterFlags() + "");
-          appendKeyValue(newIndent, "setterFlags", sb, kmProperty.getSetterFlags() + "");
+          appendFlags(
+              indent, "getterFlags", sb, KotlinFlagUtils.extractFlags(kmProperty.getGetter()));
+          if (kmProperty.getSetter() != null) {
+            appendFlags(
+                indent, "setterFlags", sb, KotlinFlagUtils.extractFlags(kmProperty.getSetter()));
+          }
           appendKeyValue(
               newIndent,
               "setterParameter",
@@ -502,7 +520,6 @@
                       sb,
                       kmProperty.getContextReceiverTypes(),
                       (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType)));
-          appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + "");
           JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
           appendKeyValue(newIndent, "fieldSignature", sb, Objects.toString(fieldSignature));
           JvmMethodSignature getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty);
@@ -536,7 +553,7 @@
         "KmType",
         sb,
         newIndent -> {
-          appendKeyValue(newIndent, "flags", sb, kmType.getFlags() + "");
+          appendFlags(newIndent, "flags", sb, KotlinFlagUtils.extractFlags(kmType));
           appendKeyValue(newIndent, "classifier", sb, kmType.classifier.toString());
           appendKeyValue(
               newIndent,
@@ -641,7 +658,7 @@
         "KmValueParameter",
         sb,
         newIndent -> {
-          appendKeyValue(newIndent, "flags", sb, valueParameter.getFlags() + "");
+          appendFlags(newIndent, "flags", sb, KotlinFlagUtils.extractFlags(valueParameter));
           appendKeyValue(newIndent, "name", sb, valueParameter.getName());
           appendKeyValue(
               newIndent,
@@ -674,7 +691,7 @@
         sb,
         newIndent -> {
           appendKeyValue(newIndent, "id", sb, typeParameter.getId() + "");
-          appendKeyValue(newIndent, "flags", sb, typeParameter.getFlags() + "");
+          appendFlags(newIndent, "flags", sb, KotlinFlagUtils.extractFlags(typeParameter));
           appendKeyValue(newIndent, "name", sb, typeParameter.getName());
           appendKeyValue(newIndent, "variance", sb, typeParameter.getVariance().name());
           appendKeyValue(
@@ -695,7 +712,7 @@
               nextIndent ->
                   appendKmList(
                       nextIndent,
-                      "KmAnnotion",
+                      "KmAnnotation",
                       sb,
                       JvmExtensionsKt.getAnnotations(typeParameter),
                       (nextNextIndent, kmAnnotation) ->
@@ -726,7 +743,7 @@
               "expandedType",
               sb,
               nextIndent -> appendKmType(nextIndent, sb, kmTypeAlias.expandedType));
-          appendKeyValue(newIndent, "flags", sb, kmTypeAlias.getFlags() + "");
+          appendFlags(newIndent, "flags", sb, KotlinFlagUtils.extractFlags(kmTypeAlias));
           appendKeyValue(newIndent, "name", sb, kmTypeAlias.getName());
           appendKeyValue(
               newIndent,
@@ -895,7 +912,7 @@
         "KmEffectExpression",
         sb,
         newIndent -> {
-          appendKeyValue(newIndent, "flags", sb, expression.getFlags() + "");
+          appendFlags(indent, "flags", sb, KotlinFlagUtils.extractFlags(expression));
           appendKeyValue(
               newIndent,
               "foo",
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index db4ab66..b5a7771 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -4,9 +4,8 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.updateJvmMetadataVersionIfRequired;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
-import static kotlin.metadata.jvm.KotlinClassMetadata.Companion;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -22,21 +21,22 @@
 // Holds information about Metadata.MultiFileClassFace
 public class KotlinMultiFileClassFacadeInfo implements KotlinClassLevelInfo {
 
+  private final MultiFileClassFacade kmMultiFileClassFacade;
   private final List<KotlinTypeReference> partClassNames;
   private final String packageName;
-  private final int[] metadataVersion;
 
   private KotlinMultiFileClassFacadeInfo(
-      List<KotlinTypeReference> partClassNames, String packageName, int[] metadataVersion) {
+      MultiFileClassFacade kmMultiFileClassFacade,
+      List<KotlinTypeReference> partClassNames,
+      String packageName) {
+    this.kmMultiFileClassFacade = kmMultiFileClassFacade;
     this.partClassNames = partClassNames;
     this.packageName = packageName;
-    this.metadataVersion = metadataVersion;
   }
 
   static KotlinMultiFileClassFacadeInfo create(
       MultiFileClassFacade kmMultiFileClassFacade,
       String packageName,
-      int[] metadataVersion,
       DexItemFactory factory) {
     ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder();
     for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
@@ -44,7 +44,7 @@
           KotlinTypeReference.fromBinaryNameOrKotlinClassifier(
               partClassName, factory, partClassName));
     }
-    return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName, metadataVersion);
+    return new KotlinMultiFileClassFacadeInfo(kmMultiFileClassFacade, builder.build(), packageName);
   }
 
   @Override
@@ -72,9 +72,9 @@
               appView,
               null);
     }
-    return Pair.create(
-        Companion.writeMultiFileClassFacade(partClassNameStrings, getCompatibleKotlinInfo(), 0),
-        rewritten);
+    kmMultiFileClassFacade.setPartClassNames(partClassNameStrings);
+    updateJvmMetadataVersionIfRequired(kmMultiFileClassFacade);
+    return Pair.create(kmMultiFileClassFacade.write(), rewritten);
   }
 
   @Override
@@ -84,7 +84,7 @@
 
   @Override
   public int[] getMetadataVersion() {
-    return metadataVersion;
+    return KotlinJvmMetadataVersionUtils.toIntArray(kmMultiFileClassFacade.getVersion());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index 6c00f88..e18afb0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -4,8 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
-import static kotlin.metadata.jvm.KotlinClassMetadata.Companion;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.updateJvmMetadataVersionIfRequired;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -20,35 +19,26 @@
 // Holds information about Metadata.MultiFileClassPartInfo
 public class KotlinMultiFileClassPartInfo implements KotlinClassLevelInfo {
 
-  private final String facadeClassName;
+  private final MultiFileClassPart classPart;
   private final KotlinPackageInfo packageInfo;
   private final String packageName;
-  private final int[] metadataVersion;
 
   private KotlinMultiFileClassPartInfo(
-      String facadeClassName,
-      KotlinPackageInfo packageInfo,
-      String packageName,
-      int[] metadataVersion) {
-    this.facadeClassName = facadeClassName;
+      MultiFileClassPart classPart, KotlinPackageInfo packageInfo, String packageName) {
+    this.classPart = classPart;
     this.packageInfo = packageInfo;
     this.packageName = packageName;
-    this.metadataVersion = metadataVersion;
   }
 
   static KotlinMultiFileClassPartInfo create(
       MultiFileClassPart classPart,
       String packageName,
-      int[] metadataVersion,
       DexClass clazz,
       AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
     KmPackage kmPackage = classPart.getKmPackage();
     return new KotlinMultiFileClassPartInfo(
-        classPart.getFacadeClassName(),
-        KotlinPackageInfo.create(kmPackage, clazz, appView, keepByteCode),
-        packageName,
-        metadataVersion);
+        classPart, KotlinPackageInfo.create(kmPackage, clazz, appView, keepByteCode), packageName);
   }
 
   @Override
@@ -65,9 +55,9 @@
   public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
     KmPackage kmPackage = new KmPackage();
     boolean rewritten = packageInfo.rewrite(kmPackage, clazz, appView);
-    return Pair.create(
-        Companion.writeMultiFileClassPart(kmPackage, facadeClassName, getCompatibleKotlinInfo(), 0),
-        rewritten);
+    updateJvmMetadataVersionIfRequired(classPart);
+    classPart.setKmPackage(kmPackage);
+    return Pair.create(classPart.write(), rewritten);
   }
 
   @Override
@@ -81,7 +71,7 @@
 
   @Override
   public int[] getMetadataVersion() {
-    return metadataVersion;
+    return KotlinJvmMetadataVersionUtils.toIntArray(classPart.getVersion());
   }
 
   @Override
@@ -90,6 +80,6 @@
   }
 
   public String getFacadeClassName() {
-    return facadeClassName;
+    return classPart.getFacadeClassName();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index 5e31efb..6b3c485 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -4,8 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
-import static kotlin.metadata.jvm.KotlinClassMetadata.Companion;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.updateJvmMetadataVersionIfRequired;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -19,9 +18,9 @@
 // Holds information about a Metadata.SyntheticClass object.
 public class KotlinSyntheticClassInfo implements KotlinClassLevelInfo {
 
+  private final SyntheticClass syntheticClass;
   private final KotlinLambdaInfo lambda;
   private final String packageName;
-  private final int[] metadataVersion;
 
   public enum Flavour {
     KotlinStyleLambda,
@@ -32,29 +31,28 @@
   private final Flavour flavour;
 
   private KotlinSyntheticClassInfo(
-      KotlinLambdaInfo lambda, Flavour flavour, String packageName, int[] metadataVersion) {
+      SyntheticClass syntheticClass, KotlinLambdaInfo lambda, Flavour flavour, String packageName) {
+    this.syntheticClass = syntheticClass;
     this.lambda = lambda;
     this.flavour = flavour;
     this.packageName = packageName;
-    this.metadataVersion = metadataVersion;
   }
 
   static KotlinSyntheticClassInfo create(
       SyntheticClass syntheticClass,
       String packageName,
-      int[] metadataVersion,
       DexClass clazz,
       Kotlin kotlin,
       AppView<?> appView) {
     KmLambda lambda = syntheticClass.getKmLambda();
     assert lambda == null || syntheticClass.isLambda();
     return new KotlinSyntheticClassInfo(
+        syntheticClass,
         lambda != null
             ? KotlinLambdaInfo.create(clazz, lambda, appView.dexItemFactory(), appView.reporter())
             : null,
         getFlavour(clazz, kotlin),
-        packageName,
-        metadataVersion);
+        packageName);
   }
 
   public boolean isLambda() {
@@ -73,14 +71,16 @@
 
   @Override
   public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
+    updateJvmMetadataVersionIfRequired(syntheticClass);
     if (lambda == null) {
-      return Pair.create(Companion.writeSyntheticClass(getCompatibleKotlinInfo(), 0), false);
+      return Pair.create(syntheticClass.write(), false);
     }
     Box<KmLambda> newLambda = new Box<>();
     boolean rewritten = lambda.rewrite(newLambda::set, clazz, appView);
     assert newLambda.isSet();
-    return Pair.create(
-        Companion.writeLambda(newLambda.get(), getCompatibleKotlinInfo(), 0), rewritten);
+    syntheticClass.setKmLambda(newLambda.get());
+    syntheticClass.setFlags(0);
+    return Pair.create(syntheticClass.write(), rewritten);
   }
 
   @Override
@@ -97,7 +97,7 @@
 
   @Override
   public int[] getMetadataVersion() {
-    return metadataVersion;
+    return KotlinJvmMetadataVersionUtils.toIntArray(syntheticClass.getVersion());
   }
 
   @SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
index 038f838..9371fa1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
@@ -20,59 +19,56 @@
 // Holds information about KmTypeAlias
 public class KotlinTypeAliasInfo implements EnqueuerMetadataTraceable {
 
-  private final int flags;
-  private final String name;
+  private final KmTypeAlias kmTypeAlias;
   private final KotlinTypeInfo underlyingType;
   private final KotlinTypeInfo expandedType;
   private final List<KotlinTypeParameterInfo> typeParameters;
   private final List<KotlinAnnotationInfo> annotations;
-  private final KotlinVersionRequirementInfo versionRequirements;
 
   private KotlinTypeAliasInfo(
-      int flags,
-      String name,
+      KmTypeAlias kmTypeAlias,
       KotlinTypeInfo underlyingType,
       KotlinTypeInfo expandedType,
       List<KotlinTypeParameterInfo> typeParameters,
-      List<KotlinAnnotationInfo> annotations,
-      KotlinVersionRequirementInfo versionRequirements) {
-    this.flags = flags;
-    this.name = name;
+      List<KotlinAnnotationInfo> annotations) {
+    this.kmTypeAlias = kmTypeAlias;
     assert underlyingType != null;
     assert expandedType != null;
     this.underlyingType = underlyingType;
     this.expandedType = expandedType;
     this.typeParameters = typeParameters;
     this.annotations = annotations;
-    this.versionRequirements = versionRequirements;
   }
 
   public static KotlinTypeAliasInfo create(
       KmTypeAlias alias, DexItemFactory factory, Reporter reporter) {
     return new KotlinTypeAliasInfo(
-        alias.getFlags(),
-        alias.getName(),
+        alias,
         KotlinTypeInfo.create(alias.underlyingType, factory, reporter),
         KotlinTypeInfo.create(alias.expandedType, factory, reporter),
         KotlinTypeParameterInfo.create(alias.getTypeParameters(), factory, reporter),
-        KotlinAnnotationInfo.create(alias.getAnnotations(), factory),
-        KotlinVersionRequirementInfo.create(alias.getVersionRequirements()));
+        KotlinAnnotationInfo.create(alias.getAnnotations(), factory));
   }
 
   boolean rewrite(Consumer<KmTypeAlias> consumer, AppView<?> appView) {
-    KmTypeAlias kmTypeAlias = consume(new KmTypeAlias(flags, name), consumer);
-    boolean rewritten = underlyingType.rewrite(kmTypeAlias::setUnderlyingType, appView);
-    rewritten |= expandedType.rewrite(kmTypeAlias::setExpandedType, appView);
+    KmTypeAlias rewrittenKmTypeAlias = new KmTypeAlias(kmTypeAlias.getName());
+    consumer.accept(rewrittenKmTypeAlias);
+    KotlinFlagUtils.copyAllFlags(kmTypeAlias, rewrittenKmTypeAlias);
+    boolean rewritten = underlyingType.rewrite(rewrittenKmTypeAlias::setUnderlyingType, appView);
+    rewritten |= expandedType.rewrite(rewrittenKmTypeAlias::setExpandedType, appView);
     rewritten |=
         rewriteList(
             appView,
             typeParameters,
-            kmTypeAlias.getTypeParameters(),
+            rewrittenKmTypeAlias.getTypeParameters(),
             KotlinTypeParameterInfo::rewrite);
     rewritten |=
         rewriteList(
-            appView, annotations, kmTypeAlias.getAnnotations(), KotlinAnnotationInfo::rewrite);
-    rewritten |= versionRequirements.rewrite(kmTypeAlias.getVersionRequirements()::addAll);
+            appView,
+            annotations,
+            rewrittenKmTypeAlias.getAnnotations(),
+            KotlinAnnotationInfo::rewrite);
+    rewrittenKmTypeAlias.getVersionRequirements().addAll(kmTypeAlias.getVersionRequirements());
     return rewritten;
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index a0090b0..25dc018 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteIfNotNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
@@ -28,7 +27,7 @@
 
   private static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of();
 
-  private final int flags;
+  private final KmType kmType;
   private final KotlinClassifierInfo classifier;
   private final KotlinTypeInfo abbreviatedType;
   private final KotlinTypeInfo outerType;
@@ -38,7 +37,7 @@
   private final boolean isRaw;
 
   KotlinTypeInfo(
-      int flags,
+      KmType kmType,
       KotlinClassifierInfo classifier,
       KotlinTypeInfo abbreviatedType,
       KotlinTypeInfo outerType,
@@ -46,7 +45,7 @@
       List<KotlinAnnotationInfo> annotations,
       KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBound,
       boolean isRaw) {
-    this.flags = flags;
+    this.kmType = kmType;
     this.classifier = classifier;
     this.abbreviatedType = abbreviatedType;
     this.outerType = outerType;
@@ -61,7 +60,7 @@
       return null;
     }
     return new KotlinTypeInfo(
-        kmType.getFlags(),
+        kmType,
         KotlinClassifierInfo.create(kmType.classifier, factory, reporter),
         KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter),
         KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter),
@@ -85,17 +84,21 @@
   }
 
   boolean rewrite(Consumer<KmType> consumer, AppView<?> appView) {
-    // TODO(b/154348683): Check for correct flags
-    KmType kmType = consume(new KmType(flags), consumer);
-    boolean rewritten = classifier.rewrite(kmType, appView);
+    KmType rewrittenKmType = new KmType();
+    consumer.accept(rewrittenKmType);
+    KotlinFlagUtils.copyAllFlags(kmType, rewrittenKmType);
+    boolean rewritten = classifier.rewrite(rewrittenKmType, appView);
     rewritten |=
         rewriteIfNotNull(
-            appView, abbreviatedType, kmType::setAbbreviatedType, KotlinTypeInfo::rewrite);
+            appView, abbreviatedType, rewrittenKmType::setAbbreviatedType, KotlinTypeInfo::rewrite);
     rewritten |=
-        rewriteIfNotNull(appView, outerType, kmType::setOuterType, KotlinTypeInfo::rewrite);
+        rewriteIfNotNull(
+            appView, outerType, rewrittenKmType::setOuterType, KotlinTypeInfo::rewrite);
     rewritten |=
-        rewriteList(appView, arguments, kmType.getArguments(), KotlinTypeProjectionInfo::rewrite);
-    rewritten |= flexibleTypeUpperBound.rewrite(kmType::setFlexibleTypeUpperBound, appView);
+        rewriteList(
+            appView, arguments, rewrittenKmType.getArguments(), KotlinTypeProjectionInfo::rewrite);
+    rewritten |=
+        flexibleTypeUpperBound.rewrite(rewrittenKmType::setFlexibleTypeUpperBound, appView);
     if (annotations.isEmpty() && !isRaw) {
       return rewritten;
     }
@@ -103,9 +106,9 @@
         rewriteList(
             appView,
             annotations,
-            JvmExtensionsKt.getAnnotations(kmType),
+            JvmExtensionsKt.getAnnotations(rewrittenKmType),
             KotlinAnnotationInfo::rewrite);
-    JvmExtensionsKt.setRaw(kmType, isRaw);
+    JvmExtensionsKt.setRaw(rewrittenKmType, isRaw);
     return rewritten;
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
index ba2aaa9..4ba90b6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteList;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
@@ -18,7 +17,6 @@
 import java.util.function.Consumer;
 import kotlin.metadata.KmType;
 import kotlin.metadata.KmTypeParameter;
-import kotlin.metadata.KmVariance;
 import kotlin.metadata.jvm.JvmExtensionsKt;
 
 // Provides access to Kotlin information about a type-parameter.
@@ -27,24 +25,15 @@
   private static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAMETERS = ImmutableList.of();
   private static final List<KotlinTypeInfo> EMPTY_UPPER_BOUNDS = ImmutableList.of();
 
-  private final int flags;
-  private final int id;
-  private final String name;
-  private final KmVariance variance;
+  private final KmTypeParameter kmTypeParameter;
   private final List<KotlinTypeInfo> originalUpperBounds;
   private final List<KotlinAnnotationInfo> annotations;
 
   private KotlinTypeParameterInfo(
-      int flags,
-      int id,
-      String name,
-      KmVariance variance,
+      KmTypeParameter kmTypeParameter,
       List<KotlinTypeInfo> originalUpperBounds,
       List<KotlinAnnotationInfo> annotations) {
-    this.flags = flags;
-    this.id = id;
-    this.name = name;
-    this.variance = variance;
+    this.kmTypeParameter = kmTypeParameter;
     this.originalUpperBounds = originalUpperBounds;
     this.annotations = annotations;
   }
@@ -52,10 +41,7 @@
   private static KotlinTypeParameterInfo create(
       KmTypeParameter kmTypeParameter, DexItemFactory factory, Reporter reporter) {
     return new KotlinTypeParameterInfo(
-        kmTypeParameter.getFlags(),
-        kmTypeParameter.getId(),
-        kmTypeParameter.getName(),
-        kmTypeParameter.getVariance(),
+        kmTypeParameter,
         getUpperBounds(kmTypeParameter.getUpperBounds(), factory, reporter),
         KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmTypeParameter), factory));
   }
@@ -85,19 +71,22 @@
   }
 
   boolean rewrite(Consumer<KmTypeParameter> consumer, AppView<?> appView) {
-    KmTypeParameter kmTypeParameter =
-        consume(new KmTypeParameter(flags, name, id, variance), consumer);
+    KmTypeParameter rewrittenTypeParameter =
+        new KmTypeParameter(
+            kmTypeParameter.getName(), kmTypeParameter.getId(), kmTypeParameter.getVariance());
+    consumer.accept(rewrittenTypeParameter);
+    KotlinFlagUtils.copyAllFlags(kmTypeParameter, rewrittenTypeParameter);
     boolean rewritten =
         rewriteList(
             appView,
             originalUpperBounds,
-            kmTypeParameter.getUpperBounds(),
+            rewrittenTypeParameter.getUpperBounds(),
             KotlinTypeInfo::rewrite);
     rewritten |=
         rewriteList(
             appView,
             annotations,
-            JvmExtensionsKt.getAnnotations(kmTypeParameter),
+            JvmExtensionsKt.getAnnotations(rewrittenTypeParameter),
             KotlinAnnotationInfo::rewrite);
     return rewritten;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index dca2632..5f54ce1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.consume;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.rewriteIfNotNull;
 
 import com.android.tools.r8.graph.AppView;
@@ -15,32 +14,29 @@
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
+import kotlin.metadata.Attributes;
 import kotlin.metadata.KmType;
 import kotlin.metadata.KmValueParameter;
-import kotlin.metadata.internal.metadata.deserialization.Flags;
 
 // Provides access to Kotlin information about value parameter.
 class KotlinValueParameterInfo implements EnqueuerMetadataTraceable {
   private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAMETERS = ImmutableList.of();
-  // Original parameter name.
-  final String name;
-  // Original parameter flags, e.g., has default value.
-  final int flags;
+  // Original parameter.
+  final KmValueParameter kmValueParameter;
   // Original information about the type.
   final KotlinTypeInfo type;
   // Indicates whether the formal parameter is originally `vararg`.
   final KotlinTypeInfo varargElementType;
 
   private KotlinValueParameterInfo(
-      int flags, String name, KotlinTypeInfo type, KotlinTypeInfo varargElementType) {
-    this.name = name;
-    this.flags = flags;
+      KmValueParameter kmValueParameter, KotlinTypeInfo type, KotlinTypeInfo varargElementType) {
+    this.kmValueParameter = kmValueParameter;
     this.type = type;
     this.varargElementType = varargElementType;
   }
 
   boolean isCrossInline() {
-    return Flags.IS_CROSSINLINE.get(flags);
+    return Attributes.isCrossinline(kmValueParameter);
   }
 
   static KotlinValueParameterInfo create(
@@ -50,8 +46,7 @@
     }
     KmType kmType = kmValueParameter.getType();
     return new KotlinValueParameterInfo(
-        kmValueParameter.getFlags(),
-        kmValueParameter.getName(),
+        kmValueParameter,
         KotlinTypeInfo.create(kmType, factory, reporter),
         KotlinTypeInfo.create(kmValueParameter.getVarargElementType(), factory, reporter));
   }
@@ -69,13 +64,15 @@
   }
 
   boolean rewrite(Consumer<KmValueParameter> consumer, AppView<?> appView) {
-    KmValueParameter kmValueParameter = consume(new KmValueParameter(flags, name), consumer);
-    boolean rewritten = type.rewrite(kmValueParameter::setType, appView);
+    KmValueParameter rewrittenKmValueParameter = new KmValueParameter(kmValueParameter.getName());
+    consumer.accept(rewrittenKmValueParameter);
+    KotlinFlagUtils.copyAllFlags(kmValueParameter, rewrittenKmValueParameter);
+    boolean rewritten = type.rewrite(rewrittenKmValueParameter::setType, appView);
     rewritten |=
         rewriteIfNotNull(
             appView,
             varargElementType,
-            kmValueParameter::setVarargElementType,
+            rewrittenKmValueParameter::setVarargElementType,
             KotlinTypeInfo::rewrite);
     return rewritten;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
deleted file mode 100644
index 7242cf5..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.function.Consumer;
-import kotlin.metadata.KmVersionRequirement;
-
-class KotlinVersionRequirementInfo {
-
-  private static final KotlinVersionRequirementInfo NO_VERSION_REQUIREMENTS =
-      new KotlinVersionRequirementInfo(ImmutableList.of());
-
-  private final List<KmVersionRequirement> versionRequirements;
-
-  private KotlinVersionRequirementInfo(List<KmVersionRequirement> versionRequirements) {
-    this.versionRequirements = versionRequirements;
-  }
-
-  static KotlinVersionRequirementInfo create(List<KmVersionRequirement> kmVersionRequirements) {
-    if (kmVersionRequirements.isEmpty()) {
-      return NO_VERSION_REQUIREMENTS;
-    }
-    return new KotlinVersionRequirementInfo(ImmutableList.copyOf(kmVersionRequirements));
-  }
-
-  boolean rewrite(Consumer<List<KmVersionRequirement>> consumer) {
-    if (this == NO_VERSION_REQUIREMENTS) {
-      return false;
-    }
-    consumer.accept(versionRequirements);
-    return false;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 80cf637..45848ad 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -7,6 +7,8 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
@@ -17,6 +19,8 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
@@ -26,7 +30,6 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -35,6 +38,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -44,6 +48,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class DefaultFieldValueJoiner {
 
@@ -69,6 +74,9 @@
     // Find all the fields where we need to determine if each field read is guaranteed to be
     // dominated by a write.
     Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = getFieldsOfInterest();
+    if (fieldsOfInterest.isEmpty()) {
+      return Collections.emptyMap();
+    }
 
     // If constructor inlining is disabled, then we focus on whether each instance initializer
     // definitely assigns the given field before it is read. We do the same for final and static
@@ -94,12 +102,12 @@
     // constructor inlining, we find all new-instance instructions (including subtype allocations)
     // and check if the field is written on each allocation before it is possibly read.
     analyzeNewInstanceInstructions(
-        fieldsNotSubjectToInitializerAnalysis, fieldsWithLiveDefaultValue::add);
+        fieldsNotSubjectToInitializerAnalysis, fieldsWithLiveDefaultValue::add, executorService);
 
     return updateFlowGraphs(fieldsWithLiveDefaultValue, executorService);
   }
 
-  private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
+  protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
     Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       clazz.forEachProgramField(
@@ -240,13 +248,101 @@
 
   private void analyzeNewInstanceInstructions(
       Map<DexType, ProgramFieldSet> nonFinalInstanceFields,
-      Consumer<ProgramField> liveDefaultValueConsumer) {
-    // Conservatively treat all fields as maybe read before written.
-    // TODO(b/296030319): Implement analysis by building IR for all methods that instantiate the
-    //  relevant classes and analyzing the puts to the newly created instances.
-    for (ProgramField field : IterableUtils.flatten(nonFinalInstanceFields.values())) {
-      liveDefaultValueConsumer.accept(field);
+      Consumer<ProgramField> liveDefaultValueConsumer,
+      ExecutorService executorService)
+      throws ExecutionException {
+    // To simplify the analysis, we currently bail out for non-final classes.
+    // TODO(b/296030319): Handle non-final classes.
+    MapUtils.removeIf(
+        nonFinalInstanceFields,
+        (holderType, fields) -> {
+          assert !fields.isEmpty();
+          DexProgramClass holder = fields.iterator().next().getHolder();
+          // If the class is kept it could be instantiated directly, in which case all default field
+          // values could be live.
+          if (appView.getKeepInfo(holder).isPinned(appView.options())) {
+            fields.forEach(liveDefaultValueConsumer);
+            return true;
+          }
+          if (holder.isFinal() || !appView.appInfo().isInstantiatedIndirectly(holder)) {
+            // When the class is not explicitly marked final, the class could in principle have
+            // injected subclasses if it is pinned. However, none of the fields are pinned, so we
+            // should be allowed to reason about the field assignments in the program.
+            assert fields.stream()
+                .allMatch(
+                    field -> appView.getKeepInfo(field).isValuePropagationAllowed(appView, field));
+            return false;
+          }
+          fields.forEach(liveDefaultValueConsumer);
+          return true;
+        });
+
+    // We analyze all allocations of the classes that declare one of the given fields.
+    ThreadUtils.processMethods(
+        appView,
+        method ->
+            analyzeNewInstanceInstructionsInMethod(
+                nonFinalInstanceFields, liveDefaultValueConsumer, method),
+        appView.options().getThreadingModule(),
+        executorService);
+  }
+
+  private void analyzeNewInstanceInstructionsInMethod(
+      Map<DexType, ProgramFieldSet> nonFinalInstanceFields,
+      Consumer<ProgramField> liveDefaultValueConsumer,
+      ProgramMethod method) {
+    if (!maybeHasNewInstanceThatMatches(method, nonFinalInstanceFields::containsKey)) {
+      return;
     }
+    IRCode code = method.buildIR(appView, MethodConversionOptions.nonConverting());
+    for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
+      ProgramFieldSet fieldsOfInterest = nonFinalInstanceFields.get(newInstance.getType());
+      if (fieldsOfInterest == null) {
+        continue;
+      }
+      FieldReadBeforeWriteDfsAnalysis analysis =
+          new FieldReadBeforeWriteDfsAnalysis(appView, code, fieldsOfInterest, newInstance) {
+
+            @Override
+            public AnalysisContinuation acceptFieldMaybeReadBeforeWrite(ProgramField field) {
+              // Remove this field from the `fieldsOfInterest`, so that we do not spend more time
+              // analyzing it.
+              if (fieldsOfInterest.remove(field)) {
+                liveDefaultValueConsumer.accept(field);
+              }
+              return AnalysisContinuation.abortIf(fieldsOfInterest.isEmpty());
+            }
+          };
+      analysis.run();
+      if (fieldsOfInterest.isEmpty()) {
+        nonFinalInstanceFields.remove(newInstance.getType());
+      }
+    }
+  }
+
+  private boolean maybeHasNewInstanceThatMatches(
+      ProgramMethod method, Predicate<DexType> predicate) {
+    Code code = method.getDefinition().getCode();
+    if (code == null || code.isSharedCodeObject()) {
+      return false;
+    }
+    if (code.isLirCode()) {
+      return code.asLirCode()
+          .hasConstantItemThatMatches(
+              constant -> constant instanceof DexType && predicate.test((DexType) constant));
+    }
+    assert appView.isCfByteCodePassThrough(method);
+    assert code.isCfCode();
+    return method.registerCodeReferencesWithResult(
+        new DefaultUseRegistryWithResult<>(appView, method, false) {
+
+          @Override
+          public void registerNewInstance(DexType type) {
+            if (predicate.test(type)) {
+              setResult(true);
+            }
+          }
+        });
   }
 
   private Map<FlowGraph, Deque<FlowGraphNode>> updateFlowGraphs(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java
new file mode 100644
index 0000000..1fdfb27
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java
@@ -0,0 +1,387 @@
+// 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.propagation;
+
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.GOTO;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.THROW;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockInstructionIterator;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.Goto;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Throw;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.ProgramFieldSet;
+
+/**
+ * Analysis that is given an allocation site (a {@link NewInstance} instruction) and a set of fields
+ * that belong to that newly allocated instance.
+ *
+ * <p>The analysis computes the subset of the given fields which are maybe read before they are
+ * written. The default value of these fields is potentially read, whereas the default value of the
+ * complement field set are guaranteed to never be read.
+ *
+ * <p>The analysis works by exploring all possible paths starting from the given allocation site to
+ * the normal and the exceptional exits of the method, keeping track of which fields are definitely
+ * written before they are read and which fields have maybe been read.
+ */
+public abstract class FieldReadBeforeWriteDfsAnalysis extends FieldReadBeforeWriteDfsAnalysisState {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final IRCode code;
+  private final DexItemFactory dexItemFactory;
+  // The set of fields to consider. Note that this is a concurrent set and that the caller may
+  // concurrently remove fields from the set. This may happen if we concurrently find a
+  // read-before-write of one of the fields.
+  private final ProgramFieldSet fields;
+  private final WorkList<WorkItem> worklist = WorkList.newIdentityWorkList();
+
+  private final FieldReadBeforeWriteDfsAnalysis self = this;
+
+  public FieldReadBeforeWriteDfsAnalysis(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      ProgramFieldSet fields,
+      NewInstance newInstance) {
+    super(newInstance);
+    this.appView = appView;
+    this.code = code;
+    this.dexItemFactory = appView.dexItemFactory();
+    this.fields = fields;
+  }
+
+  // Returns ABORT if all fields of interest are now maybe-read-before-written.
+  // Otherwise returns CONTINUE.
+  public abstract AnalysisContinuation acceptFieldMaybeReadBeforeWrite(ProgramField field);
+
+  public void run() {
+    worklist.addIfNotSeen(new InitialWorkItem());
+    worklist.run(WorkItem::process);
+  }
+
+  public enum AnalysisContinuation {
+    // Signals to abort the analysis completely (i.e., to break out of the DFS). This is used when
+    // we've reported all fields as being maybe read before written.
+    ABORT,
+    // Signals to continue the current DFS.
+    CONTINUE,
+    // Signals that all fields have been written before they are read on the current program path,
+    // meaning that the algorithm does not need to explore any further. The algorithm should instead
+    // backtrack and explore any other program paths.
+    BREAK;
+
+    static AnalysisContinuation abortIf(boolean condition) {
+      if (condition) {
+        return ABORT;
+      }
+      return CONTINUE;
+    }
+
+    boolean isAbort() {
+      return this == ABORT;
+    }
+
+    boolean isAbortOrContinue() {
+      return isAbort() || isContinue();
+    }
+
+    boolean isBreak() {
+      return this == BREAK;
+    }
+
+    boolean isContinue() {
+      return this == CONTINUE;
+    }
+
+    TraversalContinuation<?, ?> toTraversalContinuation() {
+      assert isAbortOrContinue();
+      return TraversalContinuation.breakIf(isAbort());
+    }
+  }
+
+  abstract class WorkItem {
+
+    abstract TraversalContinuation<?, ?> process();
+
+    void applyPhis(BasicBlock block) {
+      // TODO(b/339210038): When adding support for non-linear control flow, we need to implement
+      //  backtracking of this (i.e., we should remove the out value from the object aliases again).
+      for (Phi phi : block.getPhis()) {
+        if (phi.hasOperandThatMatches(self::isMaybeInstance)) {
+          addInstanceAlias(phi);
+        }
+      }
+    }
+
+    AnalysisContinuation applyInstructions(BasicBlockInstructionIterator instructionIterator) {
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        assert !instruction.hasOutValue() || !isMaybeInstance(instruction.outValue());
+        AnalysisContinuation continuation;
+        // TODO(b/339210038): Extend this to many other instructions, such as ConstClass,
+        //  InstanceOf, *Binop, etc.
+        switch (instruction.opcode()) {
+          case ASSUME:
+            continuation = applyAssumeInstruction(instruction.asAssume());
+            break;
+          case CHECK_CAST:
+            continuation = applyCheckCastInstruction(instruction.asCheckCast());
+            break;
+          case CONST_NUMBER:
+            continuation = applyConstNumber(instruction.asConstNumber());
+            break;
+          case CONST_STRING:
+            continuation = applyConstString(instruction.asConstString());
+            break;
+          case GOTO:
+            continuation = applyGotoInstruction(instruction.asGoto());
+            break;
+          case INSTANCE_PUT:
+            continuation = applyInstancePut(instruction.asInstancePut());
+            break;
+          case INVOKE_DIRECT:
+          case INVOKE_INTERFACE:
+          case INVOKE_STATIC:
+          case INVOKE_SUPER:
+          case INVOKE_VIRTUAL:
+            continuation = applyInvokeMethodInstruction(instruction.asInvokeMethod());
+            break;
+          case RETURN:
+            continuation = applyReturnInstruction(instruction.asReturn());
+            break;
+          case THROW:
+            continuation = applyThrowInstruction(instruction.asThrow());
+            break;
+          default:
+            continuation = applyUnhandledInstruction();
+            break;
+        }
+        if (continuation.isAbort()) {
+          return continuation;
+        }
+        if (continuation.isBreak()) {
+          break;
+        }
+      }
+      return AnalysisContinuation.CONTINUE;
+    }
+
+    // TODO(b/339210038): When adding support for non-linear control flow, we need to implement
+    //  backtracking of this (i.e., we should remove the out value from the object aliases again).
+    private AnalysisContinuation applyAssumeInstruction(Assume assume) {
+      if (isMaybeInstance(assume.src())) {
+        addInstanceAlias(assume.outValue());
+      }
+      return AnalysisContinuation.CONTINUE;
+    }
+
+    // TODO(b/339210038): When adding support for non-linear control flow, we need to implement
+    //  backtracking of this (i.e., we should remove the out value from the object aliases again).
+    private AnalysisContinuation applyCheckCastInstruction(CheckCast checkCast) {
+      if (isMaybeInstance(checkCast.object())) {
+        addInstanceAlias(checkCast.outValue());
+      }
+      // If the instance has escaped to the heap and this check-cast instruction throws, then it is
+      // possible that the instance is retrieved from the heap and all fields are read.
+      return markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped();
+    }
+
+    private AnalysisContinuation applyConstNumber(ConstNumber unusedConstNumber) {
+      return AnalysisContinuation.CONTINUE;
+    }
+
+    private AnalysisContinuation applyConstString(ConstString unusedConstString) {
+      return AnalysisContinuation.CONTINUE;
+    }
+
+    private AnalysisContinuation applyGotoInstruction(Goto gotoInstruction) {
+      BasicBlock targetBlock = gotoInstruction.getTarget();
+      if (isBlockOnStack(targetBlock)) {
+        // Bail out in case of cycles.
+        return markRemainingFieldsAsMaybeReadBeforeWritten();
+      } else {
+        // Continue exploration into the successor block.
+        worklist.addIgnoringSeenSet(new ProcessBlockWorkItem(targetBlock));
+        return AnalysisContinuation.CONTINUE;
+      }
+    }
+
+    private AnalysisContinuation applyInstancePut(InstancePut instancePut) {
+      // If the instance has escaped and this instance-put instruction can throw, then the program
+      // can get the instance from the heap and read any field. Give up in this case.
+      if (isEscaped() && instancePut.instructionInstanceCanThrow(appView, code.context())) {
+        return markRemainingFieldsAsMaybeReadBeforeWritten();
+      }
+
+      // Record if this is a definite write to one of the fields of interest.
+      if (isDefinitelyInstance(instancePut.object())) {
+        ProgramField resolvedField =
+            instancePut.resolveField(appView, code.context()).getProgramField();
+        if (resolvedField != null && fields.contains(resolvedField)) {
+          addWrittenBeforeRead(resolvedField);
+        }
+
+        // If all fields of interest are written before read, then stop the exploration of the
+        // current program path (but continue to explore any program paths from previous unexplored
+        // branches).
+        if (fields.allMatch(self::isWrittenBeforeRead)) {
+          return AnalysisContinuation.BREAK;
+        }
+      }
+
+      // Record if the instance has escaped as a result of this instance-put.
+      if (!isEscaped() && isMaybeInstance(instancePut.value())) {
+        setEscaped(instancePut);
+      }
+      return AnalysisContinuation.CONTINUE;
+    }
+
+    private AnalysisContinuation applyInvokeMethodInstruction(InvokeMethod invoke) {
+      // Allow calls to java.lang.Object.<init>().
+      // TODO(b/339210038): Generalize this to other constructors.
+      if (invoke.isInvokeConstructor(dexItemFactory)
+          && isDefinitelyInstance(invoke.getFirstArgument())) {
+        DexClassAndMethod resolvedMethod =
+            invoke.resolveMethod(appView, code.context()).getResolutionPair();
+        if (resolvedMethod != null
+            && resolvedMethod
+                .getReference()
+                .isIdenticalTo(dexItemFactory.objectMembers.constructor)) {
+          return AnalysisContinuation.CONTINUE;
+        }
+      }
+
+      // Conservatively treat calls as reading any field if the receiver has escaped or is escaping.
+      if (!isEscaped()
+          && invoke.hasInValueThatMatches(self::isMaybeInstance)
+          && invoke.instructionMayHaveSideEffects(appView, code.context())) {
+        setEscaped(invoke);
+      }
+
+      if (isEscaped()) {
+        return markRemainingFieldsAsMaybeReadBeforeWritten();
+      }
+
+      // Otherwise, this is a call to a method where none of the arguments is an alias of the
+      // instance, and the instance has not escaped. Therefore, this call cannot read any of fields
+      // from the instance.
+      return AnalysisContinuation.CONTINUE;
+    }
+
+    private AnalysisContinuation applyReturnInstruction(Return unusedReturnInstruction) {
+      return markRemainingFieldsAsMaybeReadBeforeWritten();
+    }
+
+    private AnalysisContinuation applyThrowInstruction(Throw unusedThrowInstruction) {
+      return markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped();
+    }
+
+    private AnalysisContinuation applyUnhandledInstruction() {
+      return markRemainingFieldsAsMaybeReadBeforeWritten();
+    }
+
+    AnalysisContinuation markRemainingFieldsAsMaybeReadBeforeWritten() {
+      for (ProgramField field : fields) {
+        if (!isWrittenBeforeRead(field)) {
+          AnalysisContinuation continuation = acceptFieldMaybeReadBeforeWrite(field);
+          assert continuation.isAbortOrContinue();
+          if (continuation.isAbort()) {
+            return continuation;
+          }
+        }
+      }
+      // At this point we could also CONTINUE, but we check if the fields of interest have become
+      // empty as a result of concurrent modification.
+      return AnalysisContinuation.abortIf(fields.isEmpty());
+    }
+
+    AnalysisContinuation markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped() {
+      if (isEscaped()) {
+        return markRemainingFieldsAsMaybeReadBeforeWritten();
+      }
+      return AnalysisContinuation.CONTINUE;
+    }
+  }
+
+  class InitialWorkItem extends WorkItem {
+
+    @Override
+    TraversalContinuation<?, ?> process() {
+      // We start the analysis from the unique constructor invoke instead of from the NewInstance
+      // instruction, since no instructions before the constructor call can read any fields from the
+      // uninitialized this.
+      // TODO(b/339210038): In principle it may be possible for the NewInstance value to flow into a
+      //  phi before the unique constructor invoke. If this happens we would not record the phi as
+      //  an alias when starting the analysis from the invoke-direct.
+      InvokeDirect uniqueConstructorInvoke =
+          getNewInstance().getUniqueConstructorInvoke(dexItemFactory);
+      if (uniqueConstructorInvoke == null) {
+        return markRemainingFieldsAsMaybeReadBeforeWritten().toTraversalContinuation();
+      }
+      BasicBlock block = uniqueConstructorInvoke.getBlock();
+      // TODO(b/339210038): Maybe allow exceptional control flow.
+      if (block.hasCatchHandlers()) {
+        return markRemainingFieldsAsMaybeReadBeforeWritten().toTraversalContinuation();
+      }
+      addBlockToStack(block);
+      addInstanceAlias(getNewInstance().outValue());
+      BasicBlockInstructionIterator instructionIterator = block.iterator(uniqueConstructorInvoke);
+      // Start the analysis from the invoke-direct instruction. This is important if we can tell
+      // that the constructor definitely writes some fields.
+      instructionIterator.previous();
+      return applyInstructions(instructionIterator).toTraversalContinuation();
+    }
+  }
+
+  class ProcessBlockWorkItem extends WorkItem {
+
+    private final BasicBlock block;
+
+    ProcessBlockWorkItem(BasicBlock block) {
+      this.block = block;
+    }
+
+    @Override
+    TraversalContinuation<?, ?> process() {
+      // TODO(b/339210038): Maybe allow exceptional control flow.
+      if (block.hasCatchHandlers()) {
+        return TraversalContinuation.breakIf(
+            markRemainingFieldsAsMaybeReadBeforeWritten().isAbort());
+      }
+      addBlockToStack(block);
+      applyPhis(block);
+      AnalysisContinuation continuation = applyInstructions(block.iterator());
+      assert continuation.isAbortOrContinue();
+      return TraversalContinuation.breakIf(continuation.isAbort());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java
new file mode 100644
index 0000000..7e24c03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java
@@ -0,0 +1,84 @@
+// 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.propagation;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * The state we track during the field-maybe-read-before-write/field-never-read-before-written
+ * analysis.
+ */
+public class FieldReadBeforeWriteDfsAnalysisState {
+
+  // The current allocation we are analyzing.
+  private final NewInstance newInstance;
+
+  // The first instruction on the current program path starting from the `newInstance` instruction
+  // from which the `newInstance` value escapes.
+  private Instruction escape = null;
+
+  // The set of values that *may* be aliases of the `newInstance` value.
+  private final Set<Value> instanceAliases = Sets.newIdentityHashSet();
+
+  // The set of blocks on the current program path.
+  private final Set<BasicBlock> stack = Sets.newIdentityHashSet();
+
+  // The set of fields that are guaranteed to be written before they are read on the current program
+  // path.
+  private final ProgramFieldSet writtenBeforeRead = ProgramFieldSet.create();
+
+  FieldReadBeforeWriteDfsAnalysisState(NewInstance newInstance) {
+    this.newInstance = newInstance;
+  }
+
+  void addInstanceAlias(Value instanceAlias) {
+    boolean changed = instanceAliases.add(instanceAlias);
+    assert changed;
+  }
+
+  void addBlockToStack(BasicBlock block) {
+    boolean changed = stack.add(block);
+    assert changed;
+  }
+
+  void addWrittenBeforeRead(ProgramField field) {
+    writtenBeforeRead.add(field);
+  }
+
+  NewInstance getNewInstance() {
+    return newInstance;
+  }
+
+  boolean isBlockOnStack(BasicBlock block) {
+    return stack.contains(block);
+  }
+
+  boolean isEscaped() {
+    return escape != null;
+  }
+
+  boolean isDefinitelyInstance(Value value) {
+    return value.getAliasedValue() == newInstance.outValue();
+  }
+
+  boolean isMaybeInstance(Value value) {
+    return instanceAliases.contains(value);
+  }
+
+  boolean isWrittenBeforeRead(ProgramField field) {
+    return writtenBeforeRead.contains(field);
+  }
+
+  void setEscaped(Instruction escape) {
+    assert !isEscaped();
+    this.escape = escape;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index d62328d..81e3424 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -46,7 +46,7 @@
   final AppView<AppInfoWithLiveness> appView;
   final Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers;
   final IRConverter converter;
-  final FieldStateCollection fieldStates;
+  protected final FieldStateCollection fieldStates;
   final MethodStateCollectionByReference methodStates;
 
   public InFlowPropagator(
@@ -113,12 +113,15 @@
 
   private Map<FlowGraph, Deque<FlowGraphNode>> includeDefaultValuesInFieldStates(
       List<FlowGraph> flowGraphs, ExecutorService executorService) throws ExecutionException {
-    DefaultFieldValueJoiner joiner =
-        new DefaultFieldValueJoiner(
-            appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs);
+    DefaultFieldValueJoiner joiner = createDefaultFieldValueJoiner(flowGraphs);
     return joiner.joinDefaultFieldValuesForFieldsWithReadBeforeWrite(executorService);
   }
 
+  protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(List<FlowGraph> flowGraphs) {
+    return new DefaultFieldValueJoiner(
+        appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs);
+  }
+
   private void processFlowGraphs(List<FlowGraph> flowGraphs, ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
index 8d7498c..22ed377 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
@@ -33,6 +34,8 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.DefaultFieldValueJoiner;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.FlowGraph;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InFlowPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
@@ -42,6 +45,8 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -92,7 +97,22 @@
 
     InFlowPropagator inFlowPropagator =
         new InFlowPropagator(
-            appView, null, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates());
+            appView, null, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates()) {
+
+          @Override
+          protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(
+              List<FlowGraph> flowGraphs) {
+            return new DefaultFieldValueJoiner(appView, null, fieldStates, flowGraphs) {
+
+              @Override
+              protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
+                // We do not rely on the optimization of any fields in the Composable optimization
+                // pass.
+                return Collections.emptyMap();
+              }
+            };
+          }
+        };
     inFlowPropagator.run(executorService);
 
     ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index f58aab9..bcfa888 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -114,10 +114,9 @@
           // TODO(b/343909250): Is this supposed to be kept on all live items?
           return config.sourceDebugExtension;
         }
-        if (config.methodParameters
-            && DexAnnotation.isParameterNameAnnotation(annotation, dexItemFactory)) {
-          // TODO(b/343907109): This should be conditional on its own keep info bit.
-          return true;
+        if (DexAnnotation.isParameterNameAnnotation(annotation, dexItemFactory)) {
+          KeepMethodInfo methodInfo = keepInfo.asMethodInfo();
+          return methodInfo != null && !methodInfo.isParameterNamesRemovalAllowed(options);
         }
         if (isAnnotationOnAnnotationClass
             && DexAnnotation.isAnnotationDefaultAnnotation(annotation, dexItemFactory)
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index c924664..7dfbfeb 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -24,6 +24,8 @@
 
   boolean isForceKeepExceptionsAttributeEnabled();
 
+  boolean isForceKeepMethodParametersAttributeEnabled();
+
   boolean isKeepEnclosingMethodAttributeEnabled();
 
   boolean isKeepInnerClassesAttributeEnabled();
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index 663d045..2483029 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -90,13 +90,15 @@
     @Override
     public Builder makeTop() {
       return super.makeTop()
-          .disallowFieldTypeStrengthening()
-          .disallowRedundantFieldLoadElimination();
+          .setAllowFieldTypeStrengthening(false)
+          .setAllowRedundantFieldLoadElimination(false);
     }
 
     @Override
     public Builder makeBottom() {
-      return super.makeBottom().allowFieldTypeStrengthening().allowRedundantFieldLoadElimination();
+      return super.makeBottom()
+          .setAllowFieldTypeStrengthening(true)
+          .setAllowRedundantFieldLoadElimination(true);
     }
 
     public boolean isFieldTypeStrengtheningAllowed() {
@@ -108,14 +110,6 @@
       return self();
     }
 
-    public Builder allowFieldTypeStrengthening() {
-      return setAllowFieldTypeStrengthening(true);
-    }
-
-    public Builder disallowFieldTypeStrengthening() {
-      return setAllowFieldTypeStrengthening(false);
-    }
-
     public boolean isRedundantFieldLoadEliminationAllowed() {
       return allowRedundantFieldLoadElimination;
     }
@@ -126,14 +120,6 @@
       return self();
     }
 
-    public Builder allowRedundantFieldLoadElimination() {
-      return setAllowRedundantFieldLoadElimination(true);
-    }
-
-    public Builder disallowRedundantFieldLoadElimination() {
-      return setAllowRedundantFieldLoadElimination(false);
-    }
-
     @Override
     public KeepFieldInfo getTopInfo() {
       return TOP;
@@ -175,12 +161,12 @@
     }
 
     public Joiner disallowFieldTypeStrengthening() {
-      builder.disallowFieldTypeStrengthening();
+      builder.setAllowFieldTypeStrengthening(false);
       return self();
     }
 
     public Joiner disallowRedundantFieldLoadElimination() {
-      builder.disallowRedundantFieldLoadElimination();
+      builder.setAllowRedundantFieldLoadElimination(false);
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 4610887..23be490 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -334,28 +334,28 @@
     }
 
     B makeTop() {
-      disallowAccessModification();
-      disallowAccessModificationForTesting();
-      disallowAnnotationRemoval();
-      disallowTypeAnnotationRemoval();
-      disallowMinification();
-      disallowOptimization();
-      disallowShrinking();
-      disallowSignatureRemoval();
-      unsetCheckDiscarded();
+      setAllowAccessModification(false);
+      setAllowAccessModificationForTesting(false);
+      setAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createTop());
+      setTypeAnnotationsInfo(KeepAnnotationCollectionInfo.Builder.createTop());
+      setAllowMinification(false);
+      setAllowOptimization(false);
+      setAllowShrinking(false);
+      setAllowSignatureRemoval(false);
+      setCheckDiscarded(false);
       return self();
     }
 
     B makeBottom() {
-      allowAccessModification();
-      allowAccessModificationForTesting();
-      allowAnnotationRemoval();
-      allowTypeAnnotationRemoval();
-      allowMinification();
-      allowOptimization();
-      allowShrinking();
-      allowSignatureRemoval();
-      unsetCheckDiscarded();
+      setAllowAccessModification(true);
+      setAllowAccessModificationForTesting(true);
+      setAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createBottom());
+      setTypeAnnotationsInfo(KeepAnnotationCollectionInfo.Builder.createBottom());
+      setAllowMinification(true);
+      setAllowOptimization(true);
+      setAllowShrinking(true);
+      setAllowSignatureRemoval(true);
+      setCheckDiscarded(false);
       return self();
     }
 
@@ -387,84 +387,44 @@
           && typeAnnotationsInfo.isEqualTo(other.internalTypeAnnotationsInfo());
     }
 
-    public boolean isAccessModificationAllowed() {
-      return allowAccessModification;
-    }
-
-    public boolean isAccessModificationAllowedForTesting() {
-      return allowAccessModificationForTesting;
-    }
-
     public boolean isCheckDiscardedEnabled() {
       return checkDiscarded;
     }
 
-    public boolean isMinificationAllowed() {
-      return allowMinification;
-    }
-
-    public boolean isOptimizationAllowed() {
-      return allowOptimization;
-    }
-
-    public boolean isShrinkingAllowed() {
-      return allowShrinking;
-    }
-
-    public boolean isSignatureRemovalAllowed() {
-      return allowSignatureRemoval;
-    }
-
-    public B setAllowMinification(boolean allowMinification) {
-      this.allowMinification = allowMinification;
-      return self();
-    }
-
-    public B allowMinification() {
-      return setAllowMinification(true);
-    }
-
-    public B disallowMinification() {
-      return setAllowMinification(false);
-    }
-
-    public B setAllowOptimization(boolean allowOptimization) {
-      this.allowOptimization = allowOptimization;
-      return self();
-    }
-
-    public B allowOptimization() {
-      return setAllowOptimization(true);
-    }
-
-    public B disallowOptimization() {
-      return setAllowOptimization(false);
-    }
-
-    public B setAllowShrinking(boolean allowShrinking) {
-      this.allowShrinking = allowShrinking;
-      return self();
-    }
-
-    public B allowShrinking() {
-      return setAllowShrinking(true);
-    }
-
-    public B disallowShrinking() {
-      return setAllowShrinking(false);
-    }
-
     public B setCheckDiscarded(boolean checkDiscarded) {
       this.checkDiscarded = checkDiscarded;
       return self();
     }
 
-    public B setCheckDiscarded() {
-      return setCheckDiscarded(true);
+    public boolean isMinificationAllowed() {
+      return allowMinification;
     }
 
-    public B unsetCheckDiscarded() {
-      return setCheckDiscarded(false);
+    public B setAllowMinification(boolean allowMinification) {
+      this.allowMinification = allowMinification;
+      return self();
+    }
+
+    public boolean isOptimizationAllowed() {
+      return allowOptimization;
+    }
+
+    public B setAllowOptimization(boolean allowOptimization) {
+      this.allowOptimization = allowOptimization;
+      return self();
+    }
+
+    public boolean isShrinkingAllowed() {
+      return allowShrinking;
+    }
+
+    public B setAllowShrinking(boolean allowShrinking) {
+      this.allowShrinking = allowShrinking;
+      return self();
+    }
+
+    public boolean isAccessModificationAllowed() {
+      return allowAccessModification;
     }
 
     public B setAllowAccessModification(boolean allowAccessModification) {
@@ -472,12 +432,8 @@
       return self();
     }
 
-    public B allowAccessModification() {
-      return setAllowAccessModification(true);
-    }
-
-    public B disallowAccessModification() {
-      return setAllowAccessModification(false);
+    public boolean isAccessModificationAllowedForTesting() {
+      return allowAccessModificationForTesting;
     }
 
     public B setAllowAccessModificationForTesting(boolean allowAccessModificationForTesting) {
@@ -485,69 +441,32 @@
       return self();
     }
 
-    public B allowAccessModificationForTesting() {
-      return setAllowAccessModificationForTesting(true);
-    }
-
-    public B disallowAccessModificationForTesting() {
-      return setAllowAccessModificationForTesting(false);
-    }
-
-    KeepAnnotationCollectionInfo.Builder getAnnotationsInfo() {
+    public KeepAnnotationCollectionInfo.Builder getAnnotationsInfo() {
       return annotationsInfo;
     }
 
-    public B allowAnnotationRemoval() {
-      annotationsInfo = KeepAnnotationCollectionInfo.Builder.createBottom();
+    public B setAnnotationInfo(KeepAnnotationCollectionInfo.Builder infoBuilder) {
+      annotationsInfo = infoBuilder;
       return self();
     }
 
-    public B disallowAnnotationRemoval() {
-      annotationsInfo = KeepAnnotationCollectionInfo.Builder.createTop();
-      return self();
-    }
-
-    public B disallowAnnotationRemoval(RetentionInfo retention) {
-      annotationsInfo.destructiveJoinAnyTypeInfo(retention);
-      return self();
-    }
-
-    public B disallowAnnotationRemoval(RetentionInfo retention, DexType type) {
-      annotationsInfo.destructiveJoinTypeInfo(type, retention);
-      return self();
-    }
-
-    KeepAnnotationCollectionInfo.Builder getTypeAnnotationsInfo() {
+    public KeepAnnotationCollectionInfo.Builder getTypeAnnotationsInfo() {
       return typeAnnotationsInfo;
     }
 
-    public B allowTypeAnnotationRemoval() {
-      typeAnnotationsInfo = KeepAnnotationCollectionInfo.Builder.createBottom();
+    public B setTypeAnnotationsInfo(KeepAnnotationCollectionInfo.Builder infoBuilder) {
+      typeAnnotationsInfo = infoBuilder;
       return self();
     }
 
-    public B disallowTypeAnnotationRemoval() {
-      typeAnnotationsInfo = KeepAnnotationCollectionInfo.Builder.createTop();
-      return self();
+    public boolean isSignatureRemovalAllowed() {
+      return allowSignatureRemoval;
     }
 
-    public B disallowTypeAnnotationRemoval(RetentionInfo retention) {
-      typeAnnotationsInfo.destructiveJoinAnyTypeInfo(retention);
-      return self();
-    }
-
-    private B setAllowSignatureRemoval(boolean allowSignatureRemoval) {
+    public B setAllowSignatureRemoval(boolean allowSignatureRemoval) {
       this.allowSignatureRemoval = allowSignatureRemoval;
       return self();
     }
-
-    public B allowSignatureRemoval() {
-      return setAllowSignatureRemoval(true);
-    }
-
-    public B disallowSignatureRemoval() {
-      return setAllowSignatureRemoval(false);
-    }
   }
 
   /** Joiner to construct monotonically increasing keep info object. */
@@ -653,52 +572,52 @@
     }
 
     public J disallowAccessModification() {
-      builder.disallowAccessModification();
+      builder.setAllowAccessModification(false);
       return self();
     }
 
     public J disallowAccessModificationForTesting() {
-      builder.disallowAccessModificationForTesting();
+      builder.setAllowAccessModificationForTesting(false);
       return self();
     }
 
     public J disallowAnnotationRemoval(RetentionInfo retention) {
-      builder.disallowAnnotationRemoval(retention);
+      builder.getAnnotationsInfo().destructiveJoinAnyTypeInfo(retention);
       return self();
     }
 
     public J disallowAnnotationRemoval(RetentionInfo retention, DexType type) {
-      builder.disallowAnnotationRemoval(retention, type);
+      builder.getAnnotationsInfo().destructiveJoinTypeInfo(type, retention);
       return self();
     }
 
     public J disallowTypeAnnotationRemoval(RetentionInfo retention) {
-      builder.disallowTypeAnnotationRemoval(retention);
+      builder.getTypeAnnotationsInfo().destructiveJoinAnyTypeInfo(retention);
       return self();
     }
 
     public J disallowMinification() {
-      builder.disallowMinification();
+      builder.setAllowMinification(false);
       return self();
     }
 
     public J disallowOptimization() {
-      builder.disallowOptimization();
+      builder.setAllowOptimization(false);
       return self();
     }
 
     public J disallowShrinking() {
-      builder.disallowShrinking();
+      builder.setAllowShrinking(false);
       return self();
     }
 
     public J disallowSignatureRemoval() {
-      builder.disallowSignatureRemoval();
+      builder.setAllowSignatureRemoval(false);
       return self();
     }
 
     public J setCheckDiscarded() {
-      builder.setCheckDiscarded();
+      builder.setCheckDiscarded(true);
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
index 201c2f9..7394058 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
@@ -74,14 +74,6 @@
       return self();
     }
 
-    public B allowValuePropagation() {
-      return setAllowValuePropagation(true);
-    }
-
-    public B disallowValuePropagation() {
-      return setAllowValuePropagation(false);
-    }
-
     @Override
     boolean internalIsEqualTo(K other) {
       return super.internalIsEqualTo(other)
@@ -90,12 +82,12 @@
 
     @Override
     public B makeTop() {
-      return super.makeTop().disallowValuePropagation();
+      return super.makeTop().setAllowValuePropagation(false);
     }
 
     @Override
     public B makeBottom() {
-      return super.makeBottom().allowValuePropagation();
+      return super.makeBottom().setAllowValuePropagation(true);
     }
   }
 
@@ -108,7 +100,7 @@
     }
 
     public J disallowValuePropagation() {
-      builder.disallowValuePropagation();
+      builder.setAllowValuePropagation(false);
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index caaba95..571d3e8 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -42,6 +42,7 @@
   private final boolean allowSingleCallerInlining;
   private final boolean allowUnusedArgumentOptimization;
   private final boolean allowUnusedReturnValueOptimization;
+  private final boolean allowParameterNamesRemoval;
   private final KeepAnnotationCollectionInfo parameterAnnotationsInfo;
 
   protected KeepMethodInfo(Builder builder) {
@@ -60,6 +61,7 @@
     this.allowSingleCallerInlining = builder.isSingleCallerInliningAllowed();
     this.allowUnusedArgumentOptimization = builder.isUnusedArgumentOptimizationAllowed();
     this.allowUnusedReturnValueOptimization = builder.isUnusedReturnValueOptimizationAllowed();
+    this.allowParameterNamesRemoval = builder.isParameterNamesRemovalAllowed();
     this.parameterAnnotationsInfo = builder.getParameterAnnotationsInfo().build();
   }
 
@@ -242,6 +244,15 @@
     return allowUnusedReturnValueOptimization;
   }
 
+  public boolean isParameterNamesRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
+    return !configuration.isForceKeepMethodParametersAttributeEnabled()
+        && internalIsParameterNamesRemovalAllowed();
+  }
+
+  boolean internalIsParameterNamesRemovalAllowed() {
+    return allowParameterNamesRemoval;
+  }
+
   public Joiner joiner() {
     assert !isTop();
     return new Joiner(this);
@@ -273,6 +284,7 @@
     private boolean allowSingleCallerInlining;
     private boolean allowUnusedArgumentOptimization;
     private boolean allowUnusedReturnValueOptimization;
+    private boolean allowParameterNamesRemoval;
     private KeepAnnotationCollectionInfo.Builder parameterAnnotationsInfo;
 
     public Builder() {
@@ -296,6 +308,7 @@
       allowUnusedArgumentOptimization = original.internalIsUnusedArgumentOptimizationAllowed();
       allowUnusedReturnValueOptimization =
           original.internalIsUnusedReturnValueOptimizationAllowed();
+      allowParameterNamesRemoval = original.internalIsParameterNamesRemovalAllowed();
       parameterAnnotationsInfo = original.internalParameterAnnotationsInfo().toBuilder();
     }
 
@@ -303,21 +316,11 @@
       return allowThrowsRemoval;
     }
 
-    private Builder setAllowThrowsRemoval(boolean allowThrowsRemoval) {
+    public Builder setAllowThrowsRemoval(boolean allowThrowsRemoval) {
       this.allowThrowsRemoval = allowThrowsRemoval;
       return self();
     }
 
-    public Builder allowThrowsRemoval() {
-      return setAllowThrowsRemoval(true);
-    }
-
-    public Builder disallowThrowsRemoval() {
-      return setAllowThrowsRemoval(false);
-    }
-
-    // Class inlining.
-
     public boolean isClassInliningAllowed() {
       return allowClassInlining;
     }
@@ -327,16 +330,6 @@
       return self();
     }
 
-    public Builder allowClassInlining() {
-      return setAllowClassInlining(true);
-    }
-
-    public Builder disallowClassInlining() {
-      return setAllowClassInlining(false);
-    }
-
-    // Closed world reasoning.
-
     public boolean isClosedWorldReasoningAllowed() {
       return allowClosedWorldReasoning;
     }
@@ -346,16 +339,6 @@
       return self();
     }
 
-    public Builder allowClosedWorldReasoning() {
-      return setAllowClosedWorldReasoning(true);
-    }
-
-    public Builder disallowClosedWorldReasoning() {
-      return setAllowClosedWorldReasoning(false);
-    }
-
-    // Constant argument optimization.
-
     public boolean isConstantArgumentOptimizationAllowed() {
       return allowConstantArgumentOptimization;
     }
@@ -365,16 +348,6 @@
       return self();
     }
 
-    public Builder allowConstantArgumentOptimization() {
-      return setAllowConstantArgumentOptimization(true);
-    }
-
-    public Builder disallowConstantArgumentOptimization() {
-      return setAllowConstantArgumentOptimization(false);
-    }
-
-    // Inlining.
-
     public boolean isInliningAllowed() {
       return allowInlining;
     }
@@ -384,16 +357,6 @@
       return self();
     }
 
-    public Builder allowInlining() {
-      return setAllowInlining(true);
-    }
-
-    public Builder disallowInlining() {
-      return setAllowInlining(false);
-    }
-
-    // Method staticizing.
-
     public boolean isMethodStaticizingAllowed() {
       return allowMethodStaticizing;
     }
@@ -403,16 +366,6 @@
       return self();
     }
 
-    public Builder allowMethodStaticizing() {
-      return setAllowMethodStaticizing(true);
-    }
-
-    public Builder disallowMethodStaticizing() {
-      return setAllowMethodStaticizing(false);
-    }
-
-    // Parameter removal.
-
     public boolean isParameterRemovalAllowed() {
       return allowParameterRemoval;
     }
@@ -422,16 +375,6 @@
       return self();
     }
 
-    public Builder allowParameterRemoval() {
-      return setAllowParameterRemoval(true);
-    }
-
-    public Builder disallowParameterRemoval() {
-      return setAllowParameterRemoval(false);
-    }
-
-    // Parameter reordering.
-
     public boolean isParameterReorderingAllowed() {
       return allowParameterReordering;
     }
@@ -441,16 +384,6 @@
       return self();
     }
 
-    public Builder allowParameterReordering() {
-      return setAllowParameterReordering(true);
-    }
-
-    public Builder disallowParameterReordering() {
-      return setAllowParameterReordering(false);
-    }
-
-    // Parameter type strengthening.
-
     public boolean isParameterTypeStrengtheningAllowed() {
       return allowParameterTypeStrengthening;
     }
@@ -460,16 +393,6 @@
       return self();
     }
 
-    public Builder allowParameterTypeStrengthening() {
-      return setAllowParameterTypeStrengthening(true);
-    }
-
-    public Builder disallowParameterTypeStrengthening() {
-      return setAllowParameterTypeStrengthening(false);
-    }
-
-    // Reprocessing.
-
     public boolean isReprocessingAllowed() {
       return allowReprocessing;
     }
@@ -479,16 +402,6 @@
       return self();
     }
 
-    public Builder allowReprocessing() {
-      return setAllowReprocessing(true);
-    }
-
-    public Builder disallowReprocessing() {
-      return setAllowReprocessing(false);
-    }
-
-    // Return type strengthening.
-
     public boolean isReturnTypeStrengtheningAllowed() {
       return allowReturnTypeStrengthening;
     }
@@ -498,16 +411,6 @@
       return self();
     }
 
-    public Builder allowReturnTypeStrengthening() {
-      return setAllowReturnTypeStrengthening(true);
-    }
-
-    public Builder disallowReturnTypeStrengthening() {
-      return setAllowReturnTypeStrengthening(false);
-    }
-
-    // Single caller inlining.
-
     public boolean isSingleCallerInliningAllowed() {
       return allowSingleCallerInlining;
     }
@@ -517,16 +420,6 @@
       return self();
     }
 
-    public Builder allowSingleCallerInlining() {
-      return setAllowSingleCallerInlining(true);
-    }
-
-    public Builder disallowSingleCallerInlining() {
-      return setAllowSingleCallerInlining(false);
-    }
-
-    // Unused argument optimization.
-
     public boolean isUnusedArgumentOptimizationAllowed() {
       return allowUnusedArgumentOptimization;
     }
@@ -536,16 +429,6 @@
       return self();
     }
 
-    public Builder allowUnusedArgumentOptimization() {
-      return setAllowUnusedArgumentOptimization(true);
-    }
-
-    public Builder disallowUnusedArgumentOptimization() {
-      return setAllowUnusedArgumentOptimization(false);
-    }
-
-    // Unused return value optimization.
-
     public boolean isUnusedReturnValueOptimizationAllowed() {
       return allowUnusedReturnValueOptimization;
     }
@@ -556,32 +439,21 @@
       return self();
     }
 
-    public Builder allowUnusedReturnValueOptimization() {
-      return setAllowUnusedReturnValueOptimization(true);
+    public boolean isParameterNamesRemovalAllowed() {
+      return allowParameterNamesRemoval;
     }
 
-    public Builder disallowUnusedReturnValueOptimization() {
-      return setAllowUnusedReturnValueOptimization(false);
+    public Builder setAllowParameterNamesRemoval(boolean allowParameterNamesRemoval) {
+      this.allowParameterNamesRemoval = allowParameterNamesRemoval;
+      return self();
     }
 
-    // Parameter annotations
-
-    KeepAnnotationCollectionInfo.Builder getParameterAnnotationsInfo() {
+    public KeepAnnotationCollectionInfo.Builder getParameterAnnotationsInfo() {
       return parameterAnnotationsInfo;
     }
 
-    public Builder allowParameterAnnotationsRemoval() {
-      parameterAnnotationsInfo = KeepAnnotationCollectionInfo.Builder.createBottom();
-      return self();
-    }
-
-    public Builder disallowParameterAnnotationsRemoval() {
-      parameterAnnotationsInfo = KeepAnnotationCollectionInfo.Builder.createTop();
-      return self();
-    }
-
-    public Builder disallowParameterAnnotationsRemoval(RetentionInfo retention) {
-      parameterAnnotationsInfo.destructiveJoinAnyTypeInfo(retention);
+    public Builder setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder infoBuilder) {
+      parameterAnnotationsInfo = infoBuilder;
       return self();
     }
 
@@ -626,6 +498,7 @@
               == other.internalIsUnusedArgumentOptimizationAllowed()
           && isUnusedReturnValueOptimizationAllowed()
               == other.internalIsUnusedReturnValueOptimizationAllowed()
+          && isParameterNamesRemovalAllowed() == other.internalIsParameterNamesRemovalAllowed()
           && parameterAnnotationsInfo.isEqualTo(other.parameterAnnotationsInfo);
     }
 
@@ -637,41 +510,43 @@
     @Override
     public Builder makeTop() {
       return super.makeTop()
-          .disallowThrowsRemoval()
-          .disallowClassInlining()
-          .disallowClosedWorldReasoning()
-          .disallowConstantArgumentOptimization()
-          .disallowInlining()
-          .disallowMethodStaticizing()
-          .disallowParameterRemoval()
-          .disallowParameterReordering()
-          .disallowParameterTypeStrengthening()
-          .disallowReprocessing()
-          .disallowReturnTypeStrengthening()
-          .disallowSingleCallerInlining()
-          .disallowUnusedArgumentOptimization()
-          .disallowUnusedReturnValueOptimization()
-          .disallowParameterAnnotationsRemoval();
+          .setAllowThrowsRemoval(false)
+          .setAllowClassInlining(false)
+          .setAllowClosedWorldReasoning(false)
+          .setAllowConstantArgumentOptimization(false)
+          .setAllowInlining(false)
+          .setAllowMethodStaticizing(false)
+          .setAllowParameterRemoval(false)
+          .setAllowParameterReordering(false)
+          .setAllowParameterTypeStrengthening(false)
+          .setAllowReprocessing(false)
+          .setAllowReturnTypeStrengthening(false)
+          .setAllowSingleCallerInlining(false)
+          .setAllowUnusedArgumentOptimization(false)
+          .setAllowUnusedReturnValueOptimization(false)
+          .setAllowParameterNamesRemoval(false)
+          .setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createTop());
     }
 
     @Override
     public Builder makeBottom() {
       return super.makeBottom()
-          .allowThrowsRemoval()
-          .allowClassInlining()
-          .allowClosedWorldReasoning()
-          .allowConstantArgumentOptimization()
-          .allowInlining()
-          .allowMethodStaticizing()
-          .allowParameterRemoval()
-          .allowParameterReordering()
-          .allowParameterTypeStrengthening()
-          .allowReprocessing()
-          .allowReturnTypeStrengthening()
-          .allowSingleCallerInlining()
-          .allowUnusedArgumentOptimization()
-          .allowUnusedReturnValueOptimization()
-          .allowParameterAnnotationsRemoval();
+          .setAllowThrowsRemoval(true)
+          .setAllowClassInlining(true)
+          .setAllowClosedWorldReasoning(true)
+          .setAllowConstantArgumentOptimization(true)
+          .setAllowInlining(true)
+          .setAllowMethodStaticizing(true)
+          .setAllowParameterRemoval(true)
+          .setAllowParameterReordering(true)
+          .setAllowParameterTypeStrengthening(true)
+          .setAllowReprocessing(true)
+          .setAllowReturnTypeStrengthening(true)
+          .setAllowSingleCallerInlining(true)
+          .setAllowUnusedArgumentOptimization(true)
+          .setAllowUnusedReturnValueOptimization(true)
+          .setAllowParameterNamesRemoval(true)
+          .setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createBottom());
     }
   }
 
@@ -686,82 +561,88 @@
     }
 
     public Joiner disallowThrowsRemoval() {
-      builder.disallowThrowsRemoval();
+      builder.setAllowThrowsRemoval(false);
       return self();
     }
 
     public Joiner disallowClassInlining() {
-      builder.disallowClassInlining();
+      builder.setAllowClassInlining(false);
       return self();
     }
 
     public Joiner disallowClosedWorldReasoning() {
-      builder.disallowClosedWorldReasoning();
+      builder.setAllowClosedWorldReasoning(false);
       return self();
     }
 
     public Joiner disallowConstantArgumentOptimization() {
-      builder.disallowConstantArgumentOptimization();
+      builder.setAllowConstantArgumentOptimization(false);
       return self();
     }
 
     public Joiner disallowInlining() {
-      builder.disallowInlining();
+      builder.setAllowInlining(false);
       return self();
     }
 
     public Joiner disallowMethodStaticizing() {
-      builder.disallowMethodStaticizing();
+      builder.setAllowMethodStaticizing(false);
       return self();
     }
 
     public Joiner disallowParameterRemoval() {
-      builder.disallowParameterRemoval();
+      builder.setAllowParameterRemoval(false);
       return self();
     }
 
     public Joiner disallowParameterReordering() {
-      builder.disallowParameterReordering();
+      builder.setAllowParameterReordering(false);
       return self();
     }
 
     public Joiner disallowParameterTypeStrengthening() {
-      builder.disallowParameterTypeStrengthening();
+      builder.setAllowParameterTypeStrengthening(false);
       return self();
     }
 
     public Joiner disallowReprocessing() {
-      builder.disallowReprocessing();
+      builder.setAllowReprocessing(false);
       return self();
     }
 
     public Joiner disallowReturnTypeStrengthening() {
-      builder.disallowReturnTypeStrengthening();
+      builder.setAllowReturnTypeStrengthening(false);
       return self();
     }
 
     public Joiner disallowSingleCallerInlining() {
-      builder.disallowSingleCallerInlining();
+      builder.setAllowSingleCallerInlining(false);
       return self();
     }
 
     public Joiner disallowUnusedArgumentOptimization() {
-      builder.disallowUnusedArgumentOptimization();
+      builder.setAllowUnusedArgumentOptimization(false);
       return self();
     }
 
     public Joiner disallowUnusedReturnValueOptimization() {
-      builder.disallowUnusedReturnValueOptimization();
+      builder.setAllowUnusedReturnValueOptimization(false);
+      return self();
+    }
+
+    public Joiner disallowParameterNamesRemoval() {
+      builder.setAllowParameterNamesRemoval(false);
       return self();
     }
 
     public Joiner disallowParameterAnnotationsRemoval() {
-      builder.disallowParameterAnnotationsRemoval();
+      builder.setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createTop());
       return self();
     }
 
     public Joiner disallowParameterAnnotationsRemoval(RetentionInfo retention) {
-      builder.disallowParameterAnnotationsRemoval(retention);
+      builder.getParameterAnnotationsInfo().destructiveJoinAnyTypeInfo(retention);
+      builder.self();
       return self();
     }
 
@@ -803,7 +684,10 @@
               Joiner::disallowUnusedArgumentOptimization)
           .applyIf(
               !joiner.builder.isUnusedReturnValueOptimizationAllowed(),
-              Joiner::disallowUnusedReturnValueOptimization);
+              Joiner::disallowUnusedReturnValueOptimization)
+          .applyIf(
+              !joiner.builder.isParameterNamesRemovalAllowed(),
+              Joiner::disallowParameterNamesRemoval);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index 19a6946..445e0b0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -42,7 +42,6 @@
   public boolean enclosingMethod = false;
   public boolean signature = false;
   public boolean exceptions = false;
-  public boolean lineNumberTable = false;
   public boolean localVariableTable = false;
   public boolean localVariableTypeTable = false;
   public boolean methodParameters = false;
@@ -119,8 +118,6 @@
     sourceDir = update(sourceDir, SOURCE_DIR, patterns);
     innerClasses = update(innerClasses, INNER_CLASSES, patterns);
     enclosingMethod = update(enclosingMethod, ENCLOSING_METHOD, patterns);
-    lineNumberTable = update(lineNumberTable, LINE_NUMBER_TABLE, patterns);
-    localVariableTable = update(localVariableTable, LOCAL_VARIABLE_TABLE, patterns);
     localVariableTypeTable = update(localVariableTypeTable, LOCAL_VARIABLE_TYPE_TABLE, patterns);
     exceptions = update(exceptions, EXCEPTIONS, patterns);
     methodParameters = update(methodParameters, METHOD_PARAMETERS, patterns);
@@ -156,16 +153,6 @@
       throw new CompilationError("Attribute EnclosingMethod requires InnerClasses attribute. "
           + "Check -keepattributes directive.");
     }
-    if (forceProguardCompatibility && localVariableTable && !lineNumberTable) {
-      // If locals are kept, assume line numbers should be kept too.
-      lineNumberTable = true;
-    }
-    if (localVariableTable && !lineNumberTable) {
-      throw new CompilationError(
-          "Attribute " + LOCAL_VARIABLE_TABLE
-              + " requires " + LINE_NUMBER_TABLE
-              + ". Check -keepattributes directive.");
-    }
   }
 
   @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 f6ff126..3d5324b 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1678,6 +1678,11 @@
         context.markAsUsed();
       }
 
+      if (attributesConfig.methodParameters && item.isMethod()) {
+        itemJoiner.computeIfAbsent().asMethodJoiner().disallowParameterNamesRemoval();
+        context.markAsUsed();
+      }
+
       if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
         itemJoiner.computeIfAbsent().disallowMinification();
         context.markAsUsed();
diff --git a/src/main/java/com/android/tools/r8/shaking/SyntheticKeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/SyntheticKeepMethodInfo.java
index 1b2c6a5..4686173 100644
--- a/src/main/java/com/android/tools/r8/shaking/SyntheticKeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/SyntheticKeepMethodInfo.java
@@ -32,21 +32,21 @@
     }
 
     @Override
-    public Builder disallowMinification() {
-      // Ignore as synthetic items can always be minified.
-      return self();
+    public boolean isMinificationAllowed() {
+      // Synthetic items can always be minified.
+      return true;
     }
 
     @Override
-    public Builder disallowOptimization() {
-      // Ignore as synthetic items can always be optimized.
-      return self();
+    public boolean isOptimizationAllowed() {
+      // Synthetic items can always be optimized.
+      return true;
     }
 
     @Override
-    public Builder disallowShrinking() {
-      // Ignore as synthetic items can always be removed.
-      return self();
+    public boolean isShrinkingAllowed() {
+      // Synthetic items can always be removed.
+      return true;
     }
 
     @Override
@@ -77,7 +77,7 @@
     }
 
     @Override
-    public KeepMethodInfo.Joiner disallowMinification() {
+    public Joiner disallowMinification() {
       // Ignore as synthetic items can always be minified.
       return self();
     }
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 d68f123..91fbada 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -317,7 +317,6 @@
     assert !proguardConfiguration.isObfuscating();
     getProguardConfiguration().getKeepAttributes().sourceFile = true;
     getProguardConfiguration().getKeepAttributes().sourceDebugExtension = true;
-    getProguardConfiguration().getKeepAttributes().lineNumberTable = true;
     getProguardConfiguration().getKeepAttributes().localVariableTable = true;
     getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
   }
@@ -829,6 +828,13 @@
   }
 
   @Override
+  public boolean isForceKeepMethodParametersAttributeEnabled() {
+    return proguardConfiguration == null
+        || (isForceProguardCompatibilityEnabled()
+            && proguardConfiguration.getKeepAttributes().methodParameters);
+  }
+
+  @Override
   public boolean isKeepEnclosingMethodAttributeEnabled() {
     return proguardConfiguration.getKeepAttributes().enclosingMethod;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
index c778df3..891593a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
@@ -8,14 +8,15 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramField;
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 public class ProgramFieldSet implements Iterable<ProgramField> {
@@ -54,6 +55,10 @@
     backing.putAll(fields.backing);
   }
 
+  public boolean allMatch(Predicate<? super ProgramField> predicate) {
+    return Iterables.all(this, predicate);
+  }
+
   public boolean createAndAdd(DexProgramClass clazz, DexEncodedField definition) {
     return add(new ProgramField(clazz, definition));
   }
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 c3e2ad4..f289319 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -219,6 +219,11 @@
     try {
       XmlNode xmlNode = XmlNode.parseFrom(inputStream);
       visitNode(xmlNode, xmlFile);
+      // Ensure that we trace the transitive reachable ids, without us having to iterate all
+      // resources for the reachable marker.
+      ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(xmlNode, r8ResourceShrinkerModel)
+          .iterator()
+          .forEachRemaining(resource -> trace(resource.value));
     } catch (IOException e) {
       errorHandler.apply(e);
     }
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt b/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt
index 77bb8f6..c0f683e 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/usages/ProtoAndroidManifestUsageRecorder.kt
@@ -19,6 +19,7 @@
 import com.android.aapt.Resources.XmlNode
 import com.android.build.shrinker.ResourceShrinkerModel
 import com.android.ide.common.resources.usage.ResourceUsageModel
+import com.android.ide.common.resources.usage.ResourceUsageModel.Resource
 import java.nio.file.Files
 import java.nio.file.Path
 
@@ -35,13 +36,13 @@
     }
 
 }
-fun recordUsagesFromNode(node: XmlNode, model: ResourceShrinkerModel) {
+fun recordUsagesFromNode(node: XmlNode, model: ResourceShrinkerModel) : Sequence<Resource>{
     // Records only resources from element attributes that have reference items with resolved
     // ids or names.
     if (!node.hasElement()) {
-        return
+        return emptySequence()
     }
-    node.element.attributeList.asSequence()
+    val reachableResources = node.element.attributeList.asSequence()
         .filter { it.hasCompiledItem() }
         .map { it.compiledItem }
         .filter { it.hasRef() }
@@ -53,6 +54,6 @@
                 else -> model.resourceStore.getResourcesFromUrl("@${it.name}")
             }.asSequence()
         }
-        .forEach { ResourceUsageModel.markReachable(it) }
-    node.element.childList.forEach { recordUsagesFromNode(it, model) }
+    reachableResources.forEach {ResourceUsageModel.markReachable(it)}
+    return reachableResources + node.element.childList.flatMap{ recordUsagesFromNode(it, model) }
 }
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithTransitiveXmlReferenceTest.java b/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithTransitiveXmlReferenceTest.java
new file mode 100644
index 0000000..117e0b2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithTransitiveXmlReferenceTest.java
@@ -0,0 +1,74 @@
+// 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.androidresources.AndroidResourceTestingUtils.TINY_PNG;
+
+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 AndroidManifestWithTransitiveXmlReferenceTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+  }
+
+  public static String MANIFEST_WITH_XML_REFERENCE =
+      "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+          + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+          + "    xmlns:tools=\"http://schemas.android.com/tools\""
+          + "    package=\"com.android.tools.r8\""
+          + ">\n"
+          + "    <application\n"
+          + "        android:label=\"@string/app_name\">\n"
+          + "             <meta-data\n"
+          + "                android:name=\"android.service.dream\"\n"
+          + "                android:resource=\"@xml/xml_with_reference\" />\n"
+          + "    </application>\n"
+          + "</manifest>";
+
+  public static String XML_WITH_XML_REFERENCE =
+      "<dream xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+          + "       android:previewImage=\"@drawable/image_with_ref_from_xml\"\n"
+          + "    />\n";
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withManifest(MANIFEST_WITH_XML_REFERENCE)
+        .addXml("xml_with_reference.xml", XML_WITH_XML_REFERENCE)
+        .addDrawable("image_with_ref_from_xml.png", TINY_PNG)
+        .addStringValue("app_name", "The one and only.")
+        .build(temp);
+  }
+
+  @Test
+  public void testManifestReferences() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addAndroidResources(getTestResources(temp))
+        .enableOptimizedShrinking()
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "app_name");
+              resourceTableInspector.assertContainsResourceWithName("xml", "xml_with_reference");
+              resourceTableInspector.assertContainsResourceWithName(
+                  "drawable", "image_with_ref_from_xml");
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/XmlSelfReferenceTest.java b/src/test/java/com/android/tools/r8/androidresources/XmlSelfReferenceTest.java
new file mode 100644
index 0000000..f14c778
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/XmlSelfReferenceTest.java
@@ -0,0 +1,70 @@
+// 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.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 XmlSelfReferenceTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+  }
+
+  public static String MANIFEST_WITH_XML_REFERENCE =
+      "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+          + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+          + "    xmlns:tools=\"http://schemas.android.com/tools\""
+          + "    package=\"com.android.tools.r8\""
+          + ">\n"
+          + "    <application\n"
+          + "        android:label=\"@string/app_name\">\n"
+          + "             <meta-data\n"
+          + "                android:name=\"android.service.dream\"\n"
+          + "                android:resource=\"@xml/xml_with_self_reference\" />\n"
+          + "    </application>\n"
+          + "</manifest>";
+
+  public static String XML_WITH_SELF_REFERENCE =
+      "<dream xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+          + "       android:previewImage=\"@xml/xml_with_self_reference\"\n"
+          + "    />\n";
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withManifest(MANIFEST_WITH_XML_REFERENCE)
+        .addXml("xml_with_self_reference.xml", XML_WITH_SELF_REFERENCE)
+        .addStringValue("app_name", "The one and only.")
+        .build(temp);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addAndroidResources(getTestResources(temp))
+        .enableOptimizedShrinking()
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "app_name");
+              resourceTableInspector.assertContainsResourceWithName(
+                  "xml", "xml_with_self_reference");
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index f7c60a4..4e2a4c2 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -5,6 +5,8 @@
 
 import static java.util.Collections.emptyList;
 
+import com.android.tools.r8.benchmarks.appdumps.ChromeBenchmarks;
+import com.android.tools.r8.benchmarks.appdumps.ComposeSamplesBenchmarks;
 import com.android.tools.r8.benchmarks.appdumps.NowInAndroidBenchmarks;
 import com.android.tools.r8.benchmarks.appdumps.TiviBenchmarks;
 import com.android.tools.r8.benchmarks.desugaredlib.L8Benchmark;
@@ -24,6 +26,13 @@
   // Actual list of all configured benchmarks.
   private final Map<String, List<BenchmarkConfig>> benchmarks = new HashMap<>();
 
+  @SafeVarargs
+  public BenchmarkCollection(List<BenchmarkConfig>... benchmarksCollection) {
+    for (List<BenchmarkConfig> benchmarks : benchmarksCollection) {
+      benchmarks.forEach(this::addBenchmark);
+    }
+  }
+
   private void addBenchmark(BenchmarkConfig benchmark) {
     List<BenchmarkConfig> variants =
         benchmarks.computeIfAbsent(benchmark.getName(), k -> new ArrayList<>());
@@ -52,15 +61,16 @@
   }
 
   public static BenchmarkCollection computeCollection() {
-    BenchmarkCollection collection = new BenchmarkCollection();
     // Every benchmark that should be active on golem must be setup in this method.
-    HelloWorldBenchmark.configs().forEach(collection::addBenchmark);
-    LegacyDesugaredLibraryBenchmark.configs().forEach(collection::addBenchmark);
-    L8Benchmark.configs().forEach(collection::addBenchmark);
-    NowInAndroidBenchmarks.configs().forEach(collection::addBenchmark);
-    TiviBenchmarks.configs().forEach(collection::addBenchmark);
-    RetraceStackTraceBenchmark.configs().forEach(collection::addBenchmark);
-    return collection;
+    return new BenchmarkCollection(
+        HelloWorldBenchmark.configs(),
+        LegacyDesugaredLibraryBenchmark.configs(),
+        L8Benchmark.configs(),
+        NowInAndroidBenchmarks.configs(),
+        TiviBenchmarks.configs(),
+        RetraceStackTraceBenchmark.configs(),
+        ComposeSamplesBenchmarks.configs(),
+        ChromeBenchmarks.configs());
   }
 
   /** Compute and print the golem configuration. */
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index ac4f229..9454d6e 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -4,9 +4,11 @@
 package com.android.tools.r8.benchmarks.appdumps;
 
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.benchmarks.BenchmarkBase;
 import com.android.tools.r8.benchmarks.BenchmarkConfig;
 import com.android.tools.r8.benchmarks.BenchmarkConfigError;
@@ -83,12 +85,16 @@
   }
 
   public BenchmarkConfig buildR8() {
+    return buildR8(getDefaultR8Configuration());
+  }
+
+  public BenchmarkConfig buildR8(ThrowableConsumer<? super R8FullTestBuilder> configuration) {
     verify();
     return BenchmarkConfig.builder()
         .setName(name)
         .setTarget(BenchmarkTarget.R8_NON_COMPAT)
         .setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
-        .setMethod(runR8(this))
+        .setMethod(runR8(this, configuration))
         .setFromRevision(fromRevision)
         .addDependency(dumpDependency)
         .measureRunTime()
@@ -103,7 +109,7 @@
         .setName(name)
         .setTarget(BenchmarkTarget.R8_NON_COMPAT)
         .setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
-        .setMethod(runR8WithResourceShrinking(this))
+        .setMethod(runR8WithResourceShrinking(this, getDefaultR8Configuration()))
         .setFromRevision(fromRevision)
         .addDependency(dumpDependency)
         .addSubBenchmark(nameForCodePart(), BenchmarkMetric.CodeSize)
@@ -192,16 +198,32 @@
     }
   }
 
-  private static BenchmarkMethod runR8(AppDumpBenchmarkBuilder builder) {
-    return internalRunR8(builder, false);
+  private static ThrowableConsumer<? super R8FullTestBuilder> getDefaultR8Configuration() {
+    return testBuilder ->
+        testBuilder
+            .allowUnnecessaryDontWarnWildcards()
+            .allowUnusedDontWarnPatterns()
+            .allowUnusedProguardConfigurationRules()
+            // TODO(b/222228826): Disallow unrecognized diagnostics and open interfaces.
+            .allowDiagnosticMessages()
+            .addOptionsModification(
+                options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces());
   }
 
-  private static BenchmarkMethod runR8WithResourceShrinking(AppDumpBenchmarkBuilder builder) {
-    return internalRunR8(builder, true);
+  private static BenchmarkMethod runR8(
+      AppDumpBenchmarkBuilder builder, ThrowableConsumer<? super R8FullTestBuilder> configuration) {
+    return internalRunR8(builder, false, configuration);
+  }
+
+  private static BenchmarkMethod runR8WithResourceShrinking(
+      AppDumpBenchmarkBuilder builder, ThrowableConsumer<? super R8FullTestBuilder> configuration) {
+    return internalRunR8(builder, true, configuration);
   }
 
   private static BenchmarkMethod internalRunR8(
-      AppDumpBenchmarkBuilder builder, boolean enableResourceShrinking) {
+      AppDumpBenchmarkBuilder builder,
+      boolean enableResourceShrinking,
+      ThrowableConsumer<? super R8FullTestBuilder> configuration) {
     return environment ->
         BenchmarkBase.runner(environment)
             .setWarmupIterations(1)
@@ -214,16 +236,22 @@
                       .addProgramFiles(dump.getProgramArchive())
                       .addLibraryFiles(dump.getLibraryArchive())
                       .addKeepRuleFiles(dump.getProguardConfigFile())
-                      .setMinApi(dumpProperties.getMinApi())
-                      .apply(b -> addDesugaredLibrary(b, dump))
-                      .allowUnnecessaryDontWarnWildcards()
-                      .allowUnusedDontWarnPatterns()
-                      .allowUnusedProguardConfigurationRules()
-                      // TODO(b/222228826): Disallow unrecognized diagnostics and open interfaces.
-                      .allowDiagnosticMessages()
                       .addOptionsModification(
-                          options ->
-                              options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+                          options -> {
+                            options.apiModelingOptions().androidApiExtensionPackages =
+                                dumpProperties.getAndroidApiExtensionPackages();
+                            options
+                                .horizontalClassMergerOptions()
+                                .setEnableSameFilePolicy(dumpProperties.getEnableSameFilePolicy());
+                          })
+                      .enableIsolatedSplits(dumpProperties.getIsolatedSplits())
+                      .setMinApi(dumpProperties.getMinApi())
+                      .apply(
+                          testBuilder -> {
+                            dump.forEachFeatureArchive(testBuilder::addFeatureSplit);
+                            addDesugaredLibrary(testBuilder, dump);
+                          })
+                      .apply(configuration)
                       .applyIf(
                           enableResourceShrinking,
                           b ->
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
new file mode 100644
index 0000000..9bcfcb7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.benchmarks.appdumps;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ChromeBenchmarks extends BenchmarkBase {
+
+  private static final Path dir = Paths.get(ToolHelper.THIRD_PARTY_DIR, "opensource-apps/chrome");
+
+  public ChromeBenchmarks(BenchmarkConfig config, TestParameters parameters) {
+    super(config, parameters);
+  }
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return parametersFromConfigs(configs());
+  }
+
+  public static List<BenchmarkConfig> configs() {
+    return ImmutableList.of(
+        AppDumpBenchmarkBuilder.builder()
+            .setName("ChromeApp")
+            .setDumpDependencyPath(dir)
+            .setFromRevision(16457)
+            .buildR8(ChromeBenchmarks::configure));
+  }
+
+  private static void configure(R8FullTestBuilder testBuilder) {
+    testBuilder
+        .addDontWarn("android.adservices.common.AdServicesOutcomeReceiver")
+        .addOptionsModification(
+            options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+        .allowDiagnosticMessages()
+        .allowUnusedDontWarnPatterns()
+        .allowUnusedProguardConfigurationRules()
+        .allowUnnecessaryDontWarnWildcards();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
new file mode 100644
index 0000000..b74a7df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.benchmarks.appdumps;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ComposeSamplesBenchmarks extends BenchmarkBase {
+
+  private static final Path dir =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "opensource-apps/android/compose-samples");
+
+  public ComposeSamplesBenchmarks(BenchmarkConfig config, TestParameters parameters) {
+    super(config, parameters);
+  }
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return parametersFromConfigs(configs());
+  }
+
+  public static List<BenchmarkConfig> configs() {
+    return ImmutableList.of(
+        AppDumpBenchmarkBuilder.builder()
+            .setName("CraneApp")
+            .setDumpDependencyPath(dir.resolve("crane"))
+            .setFromRevision(16457)
+            .buildR8(),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("JetLaggedApp")
+            .setDumpDependencyPath(dir.resolve("jetlagged"))
+            .setFromRevision(16457)
+            .buildR8(),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("JetNewsApp")
+            .setDumpDependencyPath(dir.resolve("jetnews"))
+            .setFromRevision(16457)
+            .buildR8(),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("JetCasterApp")
+            .setDumpDependencyPath(dir.resolve("jetcaster"))
+            .setFromRevision(16457)
+            .buildR8(ComposeSamplesBenchmarks::configureJetCasterApp),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("JetChatApp")
+            .setDumpDependencyPath(dir.resolve("jetchat"))
+            .setFromRevision(16457)
+            .buildR8(ComposeSamplesBenchmarks::configureJetChatApp),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("JetSnackApp")
+            .setDumpDependencyPath(dir.resolve("jetsnack"))
+            .setFromRevision(16457)
+            .buildR8(),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("OwlApp")
+            .setDumpDependencyPath(dir.resolve("owl"))
+            .setFromRevision(16457)
+            .buildR8(),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("ReplyApp")
+            .setDumpDependencyPath(dir.resolve("reply"))
+            .setFromRevision(16457)
+            .buildR8());
+  }
+
+  private static void configureJetCasterApp(R8FullTestBuilder testBuilder) {
+    testBuilder
+        .addDontWarn(
+            "org.bouncycastle.jsse.BCSSLParameters",
+            "org.bouncycastle.jsse.BCSSLSocket",
+            "org.bouncycastle.jsse.provider.BouncyCastleJsseProvider",
+            "org.conscrypt.Conscrypt",
+            "org.conscrypt.Conscrypt$Version",
+            "org.openjsse.javax.net.ssl.SSLParameters",
+            "org.openjsse.javax.net.ssl.SSLSocket",
+            "org.openjsse.net.ssl.OpenJSSE",
+            "org.slf4j.impl.StaticLoggerBinder")
+        .allowDiagnosticInfoMessages()
+        .allowUnnecessaryDontWarnWildcards()
+        .allowUnusedDontWarnPatterns()
+        .allowUnusedProguardConfigurationRules()
+        .addOptionsModification(
+            options -> {
+              options.getCfCodeAnalysisOptions().setAllowUnreachableCfBlocks(true);
+              options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces();
+            });
+  }
+
+  private static void configureJetChatApp(R8FullTestBuilder testBuilder) {
+    testBuilder
+        .allowDiagnosticInfoMessages()
+        .allowUnnecessaryDontWarnWildcards()
+        .allowUnusedDontWarnPatterns()
+        .allowUnusedProguardConfigurationRules();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index 5927841..80a7beb 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -8,10 +8,13 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,6 +27,10 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 /**
  * This tests that we produce valid code when having normal-flow with exceptional edges in blocks.
@@ -31,11 +38,19 @@
  * instructions that lie on the boundary of the exception table that is generated for a basic block.
  * If live-ranges are minimized this could produce VerifyErrors.
  */
+@RunWith(Parameterized.class)
 public class TryRangeTestRunner extends TestBase {
 
+  @Parameters
+  public static TestParametersCollection data() {
+    return TestParameters.builder().withDefaultCfRuntime().build();
+  }
+
+  @Parameter public TestParameters parameters;
+
   @Test
   public void testRegisterAllocationLimitTrailingRange() throws Exception {
-    testForR8(Backend.CF)
+    testForR8(parameters.getBackend())
         .addProgramClasses(TryRangeTest.class)
         .addKeepMainRule(TryRangeTest.class)
         .setMode(CompilationMode.RELEASE)
@@ -43,14 +58,14 @@
         .noTreeShaking()
         .enableInliningAnnotations()
         .addOptionsModification(o -> o.enableLoadStoreOptimization = false)
-        .run(TryRangeTest.class)
+        .run(parameters.getRuntime(), TryRangeTest.class)
         .assertSuccessWithOutput(StringUtils.lines("10", "7.0"));
   }
 
   @Test
   public void testRegisterAllocationLimitLeadingRange() throws Exception {
     CodeInspector inspector =
-        testForR8(Backend.CF)
+        testForR8(parameters.getBackend())
             .addProgramClasses(TryRangeTestLimitRange.class)
             .addKeepMainRule(TryRangeTestLimitRange.class)
             .setMode(CompilationMode.RELEASE)
@@ -62,7 +77,7 @@
                   o.enableLoadStoreOptimization = false;
                   o.testing.irModifier = this::processIR;
                 })
-            .run(TryRangeTestLimitRange.class)
+            .run(parameters.getRuntime(), TryRangeTestLimitRange.class)
             .assertSuccessWithOutput("")
             .inspector();
     // Assert that we do not have any register-modifying instructions in the throwing block:
@@ -80,9 +95,11 @@
       index++;
     }
     assert instructions.get(index + 1) instanceof CfLoad;
-    assert instructions.get(index + 2) instanceof CfInvoke;
-    assert instructions.get(index + 3) == cfCode.getTryCatchRanges().get(0).end;
-    assert instructions.get(index + 4) instanceof CfStackInstruction;
+    assert instructions.get(index + 2) instanceof CfLabel;
+    assert instructions.get(index + 3) instanceof CfPosition;
+    assert instructions.get(index + 4) instanceof CfInvoke;
+    assert instructions.get(index + 5) == cfCode.getTryCatchRanges().get(0).end;
+    assert instructions.get(index + 6) instanceof CfStackInstruction;
   }
 
   private void processIR(IRCode code, AppView<?> appView) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
index 0d912b3..fa46f4d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -35,7 +36,9 @@
         .assertSuccessWithOutputLines("foo", "hello", "5", "foobar")
         .inspect(
             codeInspector -> {
-              assertThat(codeInspector.clazz(C.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(C.class),
+                  isAbsentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
               assertThat(codeInspector.clazz(D.class), isPresent());
               assertThat(codeInspector.clazz(E.class), isPresent());
             });
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoKeepLineAttributeTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoKeepLineAttributeTest.java
index 35ae932..002d369 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoKeepLineAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoKeepLineAttributeTest.java
@@ -47,18 +47,11 @@
               List<StackTraceLine> stackTraceLines = stacktrace.getStackTraceLines();
               assertEquals(1, stackTraceLines.size());
               StackTraceLine stackTraceLine = stackTraceLines.get(0);
-              // The frame will always have a line as the VM is reporting the PC.
+              // The frame will always have a line, either real or a PC.
               assertTrue(stackTraceLine.hasLineNumber());
-              if (parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) {
-                // If the compile-time API is before native support then no line info is present.
-                // The "line" will be the PC and thus small.
-                assertTrue(stackTraceLine.lineNumber < 10);
-              } else {
-                // If the compile-time API is after native support then the compiler will retain and
-                // emit the mapping from PC to original line. Here line 50 is to ensure it is not a
-                // low PC value.
-                assertTrue(stackTraceLine.lineNumber > 50);
-              }
+              // The line should always map back to the original line.
+              // Here line 50 is to ensure it is not a low PC value.
+              assertTrue(stackTraceLine.lineNumber > 50);
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoKeepSourceFileAttributeTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoKeepSourceFileAttributeTest.java
index 9a68a09..dcf1db9 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoKeepSourceFileAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoKeepSourceFileAttributeTest.java
@@ -29,14 +29,6 @@
     this.parameters = parameters;
   }
 
-  public boolean isRuntimeWithPcAsLineNumberSupport() {
-    return parameters.isDexRuntime()
-        && parameters
-            .getRuntime()
-            .maxSupportedApiLevel()
-            .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
-  }
-
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -50,11 +42,9 @@
               List<StackTraceLine> stackTraceLines = stacktrace.getStackTraceLines();
               assertEquals(1, stackTraceLines.size());
               StackTraceLine stackTraceLine = stackTraceLines.get(0);
-              if (!isRuntimeWithPcAsLineNumberSupport()) {
+              if (parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) {
                 assertEquals("SourceFile", stackTraceLine.fileName);
               } else {
-                // VMs with native PC support and no debug info print "Unknown Source".
-                // TODO(b/146565491): This will need a check for new VMs once fixed.
                 assertEquals("Unknown Source", stackTraceLine.fileName);
               }
             })
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
index 29d8d11..7c2fab0 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
@@ -44,7 +44,6 @@
           "main(" + fileName + ")");
 
   private final TestParameters parameters;
-  private final boolean isAndroidOOrLater;
   private final boolean isDalvik;
 
   @Parameterized.Parameters(name = "{0}")
@@ -55,9 +54,6 @@
   public LambdaInStacktraceTest(TestParameters parameters) {
     this.parameters = parameters;
     isDalvik = parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik();
-    isAndroidOOrLater =
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0);
   }
 
   @Test
@@ -102,11 +98,6 @@
                       .getApiLevel()
                       .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport())) {
                     return s.contains("(NULL)");
-                  } else if (isAndroidOOrLater) {
-                    // On VMs with native support, no line info results in no source file printing.
-                    // TODO(b/260384637): Create debug info for such methods to avoid this.
-                    return s.equals("main(NULL)")
-                        || (!s.startsWith("main") && s.contains("(SourceFile)"));
                   } else {
                     return s.contains("(SourceFile)");
                   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java
index f952fa9..142d565 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
@@ -42,17 +42,11 @@
         .inspect(
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
-              // TODO(b/339210038): Should always be absent.
-              assertThat(
-                  aClassSubject,
-                  isPresentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
+              assertThat(aClassSubject, isAbsent());
 
               MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
               assertThat(mainMethodSubject, isPresent());
-              // TODO(b/339210038): Should always contain 42.
-              assertEquals(
-                  parameters.canInitNewInstanceUsingSuperclassConstructor(),
-                  mainMethodSubject.streamInstructions().noneMatch(i -> i.isConstNumber(42)));
+              assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstNumber(42)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("42");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LiveDefaultFieldValueOfReflectivelyInstantiatedClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LiveDefaultFieldValueOfReflectivelyInstantiatedClassTest.java
new file mode 100644
index 0000000..358227d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LiveDefaultFieldValueOfReflectivelyInstantiatedClassTest.java
@@ -0,0 +1,60 @@
+// 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.optimize.membervaluepropagation;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LiveDefaultFieldValueOfReflectivelyInstantiatedClassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndDefaultConstructor(BooleanBox.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("false");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) throws Exception {
+      Class<?> clazz = System.currentTimeMillis() > 0 ? BooleanBox.class : Object.class;
+      BooleanBox box = (BooleanBox) clazz.getDeclaredConstructor().newInstance();
+      System.out.println(box.value);
+      box.set();
+    }
+  }
+
+  static class BooleanBox {
+
+    boolean value;
+
+    BooleanBox() {}
+
+    @NeverInline
+    void set() {
+      value = true;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
index fca4473..862fbc9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -18,21 +18,20 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class FieldInitializedByConstantArgumentMultipleConstructorsTest extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public FieldInitializedByConstantArgumentMultipleConstructorsTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -51,10 +50,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithOriginalName("live"), isPresent());
-    // TODO(b/280275115): Constructor inlining regresses instance field value analysis.
-    assertThat(
-        testClassSubject.uniqueMethodWithOriginalName("dead"),
-        isPresentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
+    assertThat(testClassSubject.uniqueMethodWithOriginalName("dead"), isAbsent());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
index a78dbbf..04b2b4a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderTests.java
@@ -222,12 +222,8 @@
               FoundMethodSubject foundMethodSubject = method.asFoundMethodSubject();
               assertEquals(
                   stringBuilderTest.stringBuilders, countStringBuilderInits(foundMethodSubject));
-              if ((parameters.isCfRuntime()
-                      || parameters
-                          .getApiLevel()
-                          .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport()))
-                  && (stringBuilderTest.getMethodName().equals("diamondWithUseTest")
-                      || stringBuilderTest.getMethodName().equals("intoPhiTest"))) {
+              if (stringBuilderTest.getMethodName().equals("diamondWithUseTest")
+                  || stringBuilderTest.getMethodName().equals("intoPhiTest")) {
                 // We are not doing block suffix optimization in CF and line/pc info prohibits
                 // sharing.
                 assertEquals(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithUnusedArgumentAndParameterAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithUnusedArgumentAndParameterAnnotationsTest.java
new file mode 100644
index 0000000..17b34b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithUnusedArgumentAndParameterAnnotationsTest.java
@@ -0,0 +1,97 @@
+// 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.optimize.unusedarguments;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CollisionWithUnusedArgumentAndParameterAnnotationsTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("A.<init>(B))", "a B");
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRuntimeVisibleParameterAnnotations()
+        .addKeepClassAndMembersRules(ParameterAnnotation.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8Compat() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRuntimeVisibleParameterAnnotations()
+        .addKeepClassAndMembersRules(ParameterAnnotation.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.PARAMETER)
+  public @interface ParameterAnnotation {}
+
+  static class B {
+    public String toString() {
+      return "a B";
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+    B b;
+
+    // Unused argument is removed, but then an extra argument is added to avoid the signature
+    // collision.
+    public A(@ParameterAnnotation B b, int unused) {
+      this.b = b;
+      System.out.println("A.<init>(B, int)");
+    }
+
+    public A(@ParameterAnnotation B b) {
+      this.b = b;
+      System.out.println("A.<init>(B))");
+    }
+
+    public String toString() {
+      return b.toString();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(args.length == 0 ? new A(new B()) : new A(new B(), 0));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepConjunctiveBindingsWithInlineMembersTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepConjunctiveBindingsWithInlineMembersTest.java
new file mode 100644
index 0000000..1163008
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepConjunctiveBindingsWithInlineMembersTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.keepanno.annotations.KeepBinding;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class KeepConjunctiveBindingsWithInlineMembersTest extends KeepAnnoTestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  @Parameter public KeepAnnoParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    SingleTestRunResult<?> result =
+        testForKeepAnno(parameters)
+            .addProgramClasses(getInputClasses())
+            .addKeepMainRule(TestClass.class)
+            .setExcludedOuterClass(getClass())
+            .allowUnusedProguardConfigurationRules()
+            .run(TestClass.class);
+    if (parameters.isReference()) {
+      result.assertSuccessWithOutput(EXPECTED);
+    } else if (parameters.isPG()) {
+      // PG will make the field private and result in access error.
+      result.assertFailureWithErrorThatThrows(IllegalAccessException.class);
+    } else {
+      // R8 will remove the field.
+      result.assertSuccessWithOutput("").inspect(this::checkOutput);
+    }
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class);
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldA"), isAbsent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldB"), isAbsent());
+  }
+
+  static class A {
+
+    public String fieldA = "Hello, world";
+    public Integer fieldB = 42;
+
+    @KeepEdge(
+        bindings = @KeepBinding(bindingName = "A", classConstant = A.class),
+        consequences = {
+          // The binding on A defines the required structure of A on input, thus the binding will
+          // fail to find the required match when used. (This mirrors -keepclasseswithmembers).
+          // Contrast this with the test in KeepDisjunctiveConsequencesTest.
+          // This test should have equivalent behavior as KeepConjunctiveBindings and shows that
+          // the binding on A is sufficient to impose the two member structure on A.
+          @KeepTarget(classFromBinding = "A"),
+          @KeepTarget(classFromBinding = "A", fieldTypeConstant = String.class),
+          @KeepTarget(classFromBinding = "A", fieldType = "some.NonExistingClass")
+        })
+    public void foo() throws Exception {
+      for (Field field : getClass().getDeclaredFields()) {
+        if (field.getType().equals(String.class)) {
+          System.out.println(field.get(this));
+        }
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepConjunctiveBindingsWithoutHolderTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepConjunctiveBindingsWithoutHolderTest.java
new file mode 100644
index 0000000..cfd59fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepConjunctiveBindingsWithoutHolderTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.keepanno.annotations.KeepBinding;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class KeepConjunctiveBindingsWithoutHolderTest extends KeepAnnoTestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  @Parameter public KeepAnnoParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    SingleTestRunResult<?> result =
+        testForKeepAnno(parameters)
+            .addProgramClasses(getInputClasses())
+            .addKeepMainRule(TestClass.class)
+            .setExcludedOuterClass(getClass())
+            .run(TestClass.class);
+    if (parameters.isNativeR8()) {
+      // R8 native will remove the field.
+      result.assertSuccessWithOutput("").inspect(this::checkOutput);
+    } else {
+      result.assertSuccessWithOutput(EXPECTED);
+    }
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class);
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldA"), isAbsent());
+    assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldB"), isAbsent());
+  }
+
+  static class A {
+
+    public String fieldA = "Hello, world";
+    public Integer fieldB = 42;
+
+    @KeepEdge(
+        bindings = {
+          @KeepBinding(bindingName = "A", classConstant = A.class),
+          @KeepBinding(
+              bindingName = "StringFields",
+              classFromBinding = "A",
+              fieldTypeConstant = String.class),
+          @KeepBinding(
+              bindingName = "NonMatchingFields",
+              classFromBinding = "A",
+              fieldType = "some.NonExistingClass"),
+        },
+        consequences = {
+          // The bindings on A defines the required structure of A on input, thus the binding will
+          // fail to find the required match when used. Since the consequences do not explicitly
+          // include the class, the extraction cannot use `keepclasseswithmembers` and will
+          // conservatively extract this to a disjunction.
+          @KeepTarget(memberFromBinding = "StringFields"),
+          @KeepTarget(memberFromBinding = "NonMatchingFields")
+        })
+    public void foo() throws Exception {
+      for (Field field : getClass().getDeclaredFields()) {
+        if (field.getType().equals(String.class)) {
+          System.out.println(field.get(this));
+        }
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index c6fa158..3df45e3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -203,7 +203,7 @@
     KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName(foo.getFinalName());
     assertThat(kmFunction, isPresent());
     assertThat(kmFunction, not(isExtensionFunction()));
-    assertEquals(foo.getJvmMethodSignatureAsString(), kmFunction.signature().asString());
+    assertEquals(foo.getJvmMethodSignatureAsString(), kmFunction.signature().toString());
 
     ClassSubject impl = inspector.clazz(bClassName);
     assertThat(impl, isPresentAndNotRenamed());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index aa96b5b..70f3091 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -132,17 +132,17 @@
     // Property name is not renamed, due to the kept getter.
     assertEquals("name", name.name());
     assertNotNull(name.fieldSignature());
-    assertEquals(backingField.getJvmFieldSignatureAsString(), name.fieldSignature().asString());
+    assertEquals(backingField.getJvmFieldSignatureAsString(), name.fieldSignature().toString());
     assertNotNull(name.getterSignature());
-    assertEquals(getterForName.getJvmMethodSignatureAsString(), name.getterSignature().asString());
-    assertEquals(name.setterSignature().asString(), "setName(Ljava/lang/String;)V");
+    assertEquals(getterForName.getJvmMethodSignatureAsString(), name.getterSignature().toString());
+    assertEquals(name.setterSignature().toString(), "setName(Ljava/lang/String;)V");
 
     KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
     assertThat(familyName, isPresent());
     assertThat(familyName, not(isExtensionProperty()));
     // No backing field for property `familyName`
     assertNull(familyName.fieldSignature());
-    assertEquals(familyName.getterSignature().asString(), "getFamilyName()Ljava/lang/String;");
+    assertEquals(familyName.getterSignature().toString(), "getFamilyName()Ljava/lang/String;");
     assertNull(familyName.setterSignature());
 
     KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
@@ -150,9 +150,9 @@
     assertThat(age, not(isExtensionProperty()));
     assertEquals(
         kotlinParameters.isNewerThanOrEqualTo(KOTLINC_1_5_0) ? "b:I" : "a:I",
-        age.fieldSignature().asString());
-    assertEquals("getAge()I", age.getterSignature().asString());
-    assertEquals("setAge(I)V", age.setterSignature().asString());
+        age.fieldSignature().toString());
+    assertEquals("getAge()I", age.getterSignature().toString());
+    assertEquals("setAge(I)V", age.setterSignature().toString());
   }
 
   @Test
@@ -244,9 +244,9 @@
     // #        fieldSignature: name:Ljava/lang/String;,
     // #        getterSignature: getName()Ljava/lang/String;,
     // #        setterSignature: setName(Ljava/lang/String;)V,
-    assertEquals("name:Ljava/lang/String;", name.fieldSignature().asString());
-    assertEquals("getName()Ljava/lang/String;", name.getterSignature().asString());
-    assertEquals("setName(Ljava/lang/String;)V", name.setterSignature().asString());
+    assertEquals("name:Ljava/lang/String;", name.fieldSignature().toString());
+    assertEquals("getName()Ljava/lang/String;", name.getterSignature().toString());
+    assertEquals("setName(Ljava/lang/String;)V", name.setterSignature().toString());
 
     KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
     assertThat(familyName, not(isPresent()));
@@ -257,8 +257,8 @@
     KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
     assertThat(age, isPresent());
     assertThat(age, not(isExtensionProperty()));
-    assertEquals("age:I", age.fieldSignature().asString());
-    assertEquals("getAge()I", age.getterSignature().asString());
-    assertEquals("setAge(I)V", age.setterSignature().asString());
+    assertEquals("age:I", age.fieldSignature().toString());
+    assertEquals("getAge()I", age.getterSignature().toString());
+    assertEquals("setAge(I)V", age.setterSignature().toString());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index 12fe3cb..117ae24 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -15,6 +15,7 @@
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.kotlin.KotlinFlagUtils;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -28,9 +29,11 @@
 import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
 import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
 import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
+import com.google.common.collect.ImmutableMap;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import kotlin.metadata.KmClassifier.TypeParameter;
 import kotlin.metadata.KmVariance;
 import org.junit.Test;
@@ -42,8 +45,10 @@
 
   private static final String LIB_PKG = PKG + ".typeargument_lib.";
 
-  private static final int FLAG_NONE = 0;
-  private static final int FLAG_REIFIED = 1;
+  private static final Map<String, Object> FLAG_NONE =
+      ImmutableMap.of(KotlinFlagUtils.REIFIED_KEY, false);
+  private static final Map<String, Object> FLAG_REIFIED =
+      ImmutableMap.of(KotlinFlagUtils.REIFIED_KEY, true);
 
   private static final String EXPECTED =
       StringUtils.lines(
@@ -258,7 +263,11 @@
   }
 
   private void inspectTypeParameter(
-      KmTypeParameterSubjectMixin subject, String name, int id, int flags, KmVariance variance) {
+      KmTypeParameterSubjectMixin subject,
+      String name,
+      int id,
+      Map<String, Object> flags,
+      KmVariance variance) {
     KmTypeParameterSubject typeParameter = subject.kmTypeParameterWithUniqueName(name);
     assertThat(typeParameter, isPresent());
     assertEquals(id, typeParameter.getId());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
index b36ef5e..c7fef81 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
@@ -138,7 +138,7 @@
     assertThat(kmClass, isPresent());
     KmFunctionSubject staticFun = kmClass.kmFunctionWithUniqueName("staticFun");
     assertThat(staticFun, isPresent());
-    assertEquals("staticFun(Lkotlin/jvm/functions/Function0;)V", staticFun.signature().asString());
+    assertEquals("staticFun(Lkotlin/jvm/functions/Function0;)V", staticFun.signature().toString());
     KmPropertySubject staticProp = kmClass.kmPropertyWithUniqueName("staticProp");
     assertThat(staticProp, isPresent());
   }
@@ -154,6 +154,6 @@
     KmClassSubject kmClass = itfCompanion.getKmClass();
     KmFunctionSubject greetKm = kmClass.kmFunctionWithUniqueName("greet");
     assertThat(greetKm, isPresent());
-    assertEquals("greet(Ljava/lang/String;)V", greetKm.signature().asString());
+    assertEquals("greet(Ljava/lang/String;)V", greetKm.signature().toString());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java b/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
index 6bffb78..b9a007e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.FileUtils;
@@ -52,6 +53,7 @@
     assertThat(mapping, containsString("# pg_map_id: "));
     assertThat(mapping, containsString("# pg_map_hash: SHA-256 "));
     // Check the mapping is the identity, e.g., only comments and identity entries are defined.
+    int numberOfIdentityMappings = 0;
     for (String line : StringUtils.splitLines(mapping)) {
       if (line.startsWith("#")) {
         continue;
@@ -61,18 +63,21 @@
         String left = parts[0];
         String right = parts[1];
         if (left.equals(right.substring(0, right.length() - 1))) {
+          numberOfIdentityMappings++;
           continue;
         }
       }
       fail("Expected comment or identity, got: " + line);
     }
+    // It may be ok to not actually include any identity mapping, but currently we do.
+    assertTrue(numberOfIdentityMappings > 0);
   }
 
   @Test
   public void testTheTestBuilder() throws Exception {
     String mapping =
         testForR8(Backend.DEX)
-            .addProgramClasses(Main.class)
+            .addProgramClassFileData(getMainWithoutLineTable())
             .setMinApi(AndroidApiLevel.B)
             .addKeepMainRule(Main.class)
             .compile()
@@ -85,7 +90,7 @@
     Path mappingPath = temp.newFolder().toPath().resolve("mapping.map");
     R8.run(
         R8Command.builder()
-            .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+            .addClassProgramData(getMainWithoutLineTable(), Origin.unknown())
             .addProguardConfiguration(
                 ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
@@ -102,7 +107,7 @@
     StringBuilder mappingContent = new StringBuilder();
     R8.run(
         R8Command.builder()
-            .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+            .addClassProgramData(getMainWithoutLineTable(), Origin.unknown())
             .addProguardConfiguration(
                 ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
@@ -124,6 +129,10 @@
     checkIdentityMappingContent(mappingContent.toString());
   }
 
+  private byte[] getMainWithoutLineTable() throws Exception {
+    return transformer(Main.class).removeLineNumberTable(MethodPredicate.all()).transform();
+  }
+
   // Compiling this program with a keep main will result in an identity mapping for the residual
   // program. The (identity) mapping should still be created and emitted to the client.
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index 04efdac..e45c4ab 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -138,6 +138,7 @@
     // as this is a release build.
     assertTrue(
         (code.getDebugInfo() == null)
+            || code.getDebugInfo().isPcBasedInfo()
             || Arrays.stream(code.getDebugInfo().asEventBasedInfo().events)
                 .allMatch(event -> !(event instanceof StartLocal)));
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
index b0fdc84..940e463 100644
--- a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableSet;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
@@ -35,7 +36,9 @@
   @Test
   public void testNoLinesForNonInline() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(DebuginfoForInlineFrameRegressionTest.class)
+        .addProgramClassFileData(
+            transformer(InlineInto.class).removeLineNumberTable(MethodPredicate.all()).transform(),
+            transformer(InlineFrom.class).removeLineNumberTable(MethodPredicate.all()).transform())
         .addKeepMainRule(InlineInto.class)
         .addKeepRules("-keepparameternames")
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleMaximallySpecificTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleMaximallySpecificTest.java
new file mode 100644
index 0000000..082d51b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleMaximallySpecificTest.java
@@ -0,0 +1,210 @@
+// 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.resolution.interfacetargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Regression for b/249859332
+@RunWith(Parameterized.class)
+public class MultipleMaximallySpecificTest extends TestBase {
+
+  private static final String EXPECTED_UPTO_JDK9 = StringUtils.lines("A.foo", "Got ICCE");
+  private static final String EXPECTED_AFTER_JDK9 = StringUtils.lines("A.foo", "Got AME");
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MultipleMaximallySpecificTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.isOrSimulateNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        TestAppViewBuilder.builder()
+            .addProgramClasses(getInputClasses())
+            .addProgramClassFileData(getTransformedClasses())
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addTestingAnnotations()
+            .addKeepMainRule(Main.class)
+            .setMinApi(AndroidApiLevel.LATEST)
+            .buildWithLiveness();
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexMethod fooI = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooJ = buildNullaryVoidMethod(J.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooK = buildNullaryVoidMethod(K.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    DexString fooName = fooA.getName();
+    DexProto fooProto = fooA.getProto();
+
+    // Resolving on A succeeds and is the override placed in A.
+    MethodResolutionResult resolveFooA =
+        appInfo.resolveMethodOnClass(fooA.holder, fooProto, fooName);
+    assertTrue(resolveFooA.isSingleResolution());
+
+    MethodResolutionResult resolveFooB =
+        appInfo.resolveMethodOnClass(fooB.holder, fooProto, fooName);
+    MethodResolutionResult resolveFooK = appInfo.resolveMethodOnInterface(fooK.holder, fooK);
+
+    // TODO(b/249859332): These should not be failed resolution results, but rather multi results.
+    //  This is the likely cause of the issue as it may lead to replacing the call-site by ICCE.
+    for (MethodResolutionResult resolution : ImmutableList.of(resolveFooB, resolveFooK)) {
+      assertTrue(resolution.isFailedResolution());
+      Set<DexType> typesCausingFailures = new HashSet<>();
+      Set<DexMethod> methodCausingFailures = new HashSet<>();
+      resolution
+          .asFailedResolution()
+          .forEachFailureDependency(
+              typesCausingFailures::add, m -> methodCausingFailures.add(m.getReference()));
+      assertEquals(ImmutableSet.of(fooI, fooJ), methodCausingFailures);
+      assertEquals(
+          ImmutableSet.of(fooI.getHolderType(), fooJ.getHolderType()), typesCausingFailures);
+
+      // TODO(b/249859332): Why is it possible to do 'lookup' on a failed result, and since
+      // possible,
+      //  why are the cause-of-failure methods not present in the lookup result?
+      DexProgramClass context =
+          appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
+      LookupResult lookupResult = resolution.lookupVirtualDispatchTargets(context, appView);
+      assertTrue(lookupResult.isLookupResultSuccess());
+      assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets());
+      Set<MethodReference> targets = new HashSet<>();
+      lookupResult
+          .asLookupResultSuccess()
+          .forEach(
+              target -> targets.add(target.getReference().asMethodReference()), lambda -> fail());
+      assertEquals(ImmutableSet.of(), targets);
+    }
+  }
+
+  private boolean isDesugaring() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
+  private boolean isNewCfRuntime() {
+    return parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThan(CfVm.JDK9);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(isNewCfRuntime() ? EXPECTED_AFTER_JDK9 : EXPECTED_UPTO_JDK9);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getInputClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/249859332): This should be same as for reference.
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+  }
+
+  private Collection<Class<?>> getInputClasses() {
+    return ImmutableList.of(I.class, J.class, A.class, B.class, Main.class);
+  }
+
+  private Collection<byte[]> getTransformedClasses() throws Exception {
+    return ImmutableList.of(transformer(K.class).setImplements(I.class, J.class).transform());
+  }
+
+  @NoVerticalClassMerging
+  public interface I {
+
+    default void foo() {
+      System.out.println("I.foo");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public interface J {
+
+    default void foo() {
+      System.out.println("J.foo");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public interface K extends I /* and J via transformer */ {}
+
+  @NeverClassInline
+  public static class A implements K {
+
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B implements K {}
+
+  public static class Main {
+
+    public static K getK(int x) {
+      return x == 0 ? new A() : new B();
+    }
+
+    public static void main(String[] args) {
+      getK(args.length).foo();
+      try {
+        getK(args.length + 1).foo();
+      } catch (AbstractMethodError e) {
+        System.out.println("Got AME");
+      } catch (IncompatibleClassChangeError e) {
+        System.out.println("Got ICCE");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTestRunner.java b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTestRunner.java
index 8258c8b..778c78a 100644
--- a/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTestRunner.java
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTestRunner.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import org.junit.Test;
@@ -61,16 +60,7 @@
               assertEquals(
                   "SourceFile",
                   inspector.clazz(getTestClass()).getDexProgramClass().sourceFile.toString());
-              // TODO(b/202919530): The stack-trace should have line positions since they are
-              //  essentially free when compiling for dex.
-              if (parameters.isDexRuntime()
-                  && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0)) {
-                assertThat(
-                    stacktrace, StackTrace.isSameExceptForLineNumbers(getExpectedStackTrace(true)));
-              } else {
-                // Having stripped the line-number table, the raw stack will not have line info.
-                assertThat(stacktrace, StackTrace.isSame(getExpectedStackTrace(false)));
-              }
+              assertThat(stacktrace, StackTrace.isSame(getExpectedStackTrace(true)));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
index f37d00a..0b278cf 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
@@ -76,7 +76,6 @@
     assertTrue(
         clazz.getDexProgramClass().sourceFile == null
             || clazz.getDexProgramClass().sourceFile.toString().equals("SourceFile"));
-    assertNull(main.getLineNumberTable());
     assertTrue(main.getLocalVariableTable().isEmpty());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
index 5314db5..32c535c 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
@@ -6,7 +6,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
@@ -59,7 +58,8 @@
         "-keep class ** { *; }"
     );
     MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
-    assertFalse(mainMethod.hasLineNumberTable());
+    // R8 now defaults to retain the line number table regardless of -keepattributes.
+    assertEquals(doesNotHavePcSupport(), mainMethod.hasLineNumberTable());
     assertFalse(mainMethod.hasLocalVariableTable());
   }
 
@@ -95,19 +95,12 @@
   }
 
   @Test
-  public void keepLocalVariableTable() throws IOException, ExecutionException {
+  public void keepLocalVariableTable() throws Exception {
     List<String> keepRules = ImmutableList.of(
         "-keepattributes " + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
     );
-    // Compiling with a keep rule for locals but no line results in an error in R8.
-    try {
-      compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
-    } catch (CompilationFailedException e) {
-      assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LOCAL_VARIABLE_TABLE));
-      assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LINE_NUMBER_TABLE));
-      return;
-    }
-    fail("Expected error");
+    // Compiling with a keep rule for locals but no line table no longer results in an error in R8.
+    compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
   }
 
   private MethodSubject compileRunAndGetMain(List<String> keepRules, CompilationMode mode)
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
index 5f68743..9d35920 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
@@ -36,6 +36,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MapIdTemplateProvider;
@@ -859,6 +860,18 @@
     return self();
   }
 
+  public T addFeatureSplit(Path featureJar) {
+    Path featureOutJar = getState().getNewTempFileUnchecked("feature.zip");
+    builder.addFeatureSplit(
+        builder ->
+            builder
+                .addProgramResourceProvider(ArchiveResourceProvider.fromArchive(featureJar, true))
+                .setProgramConsumer(new ArchiveConsumer(featureOutJar, true))
+                .build());
+    features.add(featureOutJar);
+    return self();
+  }
+
   public T addFeatureSplitWithResources(
       Collection<Pair<String, String>> nonJavaFiles, Class<?>... classes) throws IOException {
     Path path = getState().getNewTempFolder().resolve("feature.zip");
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 9b51098..4edcc33 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -239,14 +238,17 @@
       throws IOException {
     assert getBackend() == runtime.getBackend();
     ClassSubject mainClassSubject = inspector().clazz(SplitRunner.class);
-    assertThat("Did you forget a keep rule for the main method?", mainClassSubject, Matchers.isPresent());
+    assertThat(
+        "Did you forget a keep rule for the main method?", mainClassSubject, Matchers.isPresent());
     assertThat(
         "Did you forget a keep rule for the main method?",
         mainClassSubject.mainMethod(),
         Matchers.isPresent());
     ClassSubject mainFeatureClassSubject = featureInspector(feature).clazz(mainFeatureClass);
     assertThat(
-        "Did you forget a keep rule for the run method?", mainFeatureClassSubject, Matchers.isPresent());
+        "Did you forget a keep rule for the run method?",
+        mainFeatureClassSubject,
+        Matchers.isPresent());
     assertThat(
         "Did you forget a keep rule for the run method?",
         mainFeatureClassSubject.uniqueMethodWithOriginalName("run"),
@@ -280,4 +282,13 @@
     FileUtils.writeTextFile(path, getProguardMap());
     return self();
   }
+
+  @Override
+  public R8TestCompileResult benchmarkCodeSize(BenchmarkResults results)
+      throws IOException, ResourceException {
+    int applicationSizeWithFeatures =
+        AndroidApp.builder(app).addProgramFiles(features).build().applicationSize();
+    results.addCodeSizeResult(applicationSizeWithFeatures);
+    return self();
+  }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestState.java b/src/test/testbase/java/com/android/tools/r8/TestState.java
index 6b01a75..77d4a0b 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestState.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestState.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.nio.file.Path;
 import java.util.Set;
 import java.util.function.BiFunction;
@@ -40,6 +41,14 @@
     return getNewTempFolder().resolve(name);
   }
 
+  public Path getNewTempFileUnchecked(String name) {
+    try {
+      return getNewTempFile(name);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
   public DiagnosticsHandler getDiagnosticsHandler() {
     return messages;
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 8ce018e..9904b9b 100644
--- a/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -3,10 +3,8 @@
 import static junit.framework.TestCase.fail;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
@@ -92,17 +90,7 @@
 
     builder
         .addProgramResourceProvider(ArchiveResourceProvider.fromArchive(featureJar, true))
-        .setProgramConsumer(
-            new ArchiveConsumer(outputPath, true) {
-              @Override
-              public void accept(
-                  int fileIndex,
-                  ByteDataView data,
-                  Set<String> descriptors,
-                  DiagnosticsHandler handler) {
-                super.accept(fileIndex, data, descriptors, handler);
-              }
-            });
+        .setProgramConsumer(new ArchiveConsumer(outputPath, true));
   }
 
   public static FeatureSplit splitWithNonJavaFile(
diff --git a/src/test/testbase/java/com/android/tools/r8/dump/CompilerDump.java b/src/test/testbase/java/com/android/tools/r8/dump/CompilerDump.java
index c95193c..f572d3d 100644
--- a/src/test/testbase/java/com/android/tools/r8/dump/CompilerDump.java
+++ b/src/test/testbase/java/com/android/tools/r8/dump/CompilerDump.java
@@ -10,6 +10,7 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.function.Consumer;
 
 public class CompilerDump {
 
@@ -29,6 +30,18 @@
     this.directory = directory;
   }
 
+  public void forEachFeatureArchive(Consumer<? super Path> consumer) {
+    int i = 1;
+    while (true) {
+      Path featureJar = directory.resolve("feature-" + i + ".jar");
+      if (!Files.exists(featureJar)) {
+        break;
+      }
+      consumer.accept(featureJar);
+      i++;
+    }
+  }
+
   public Path getProgramArchive() {
     return directory.resolve("program.jar");
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java
index 87e4427..1023d84 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.google.common.collect.ImmutableMap;
 import java.util.List;
+import java.util.Map;
 import kotlin.metadata.KmVariance;
 
 public class AbsentKmTypeParameterSubject extends KmTypeParameterSubject {
@@ -31,8 +33,8 @@
   }
 
   @Override
-  public int getFlags() {
-    return 0;
+  public Map<String, Object> getFlags() {
+    return ImmutableMap.of();
   }
 
   @Override
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 7a6ebc9..21329bf 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -10,19 +10,13 @@
 import java.util.Objects;
 import java.util.stream.Collectors;
 import kotlin.metadata.KmDeclarationContainer;
-import kotlin.metadata.KmExtensionType;
 import kotlin.metadata.KmFunction;
-import kotlin.metadata.KmFunctionExtensionVisitor;
-import kotlin.metadata.KmFunctionVisitor;
 import kotlin.metadata.KmProperty;
-import kotlin.metadata.KmPropertyExtensionVisitor;
-import kotlin.metadata.KmPropertyVisitor;
 import kotlin.metadata.KmType;
 import kotlin.metadata.KmTypeAlias;
+import kotlin.metadata.jvm.JvmExtensionsKt;
 import kotlin.metadata.jvm.JvmFieldSignature;
-import kotlin.metadata.jvm.JvmFunctionExtensionVisitor;
 import kotlin.metadata.jvm.JvmMethodSignature;
-import kotlin.metadata.jvm.JvmPropertyExtensionVisitor;
 
 public interface FoundKmDeclarationContainerSubject extends KmDeclarationContainerSubject {
 
@@ -61,21 +55,7 @@
     JvmMethodSignature signature = null;
 
     KmFunctionProcessor(KmFunction kmFunction) {
-      kmFunction.accept(new KmFunctionVisitor() {
-        @Override
-        public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
-          if (type != JvmFunctionExtensionVisitor.TYPE) {
-            return null;
-          }
-          return new JvmFunctionExtensionVisitor() {
-            @Override
-            public void visit(JvmMethodSignature desc) {
-              assert signature == null : signature.asString();
-              signature = desc;
-            }
-          };
-        }
-      });
+      signature = JvmExtensionsKt.getSignature(kmFunction);
       // We don't check Kotlin types in tests, but be aware of the relocation issue.
       // See b/70169921#comment57 for more details.
     }
@@ -161,29 +141,9 @@
     JvmMethodSignature setterSignature = null;
 
     KmPropertyProcessor(KmProperty kmProperty) {
-      kmProperty.accept(new KmPropertyVisitor() {
-        @Override
-        public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
-          if (type != JvmPropertyExtensionVisitor.TYPE) {
-            return null;
-          }
-          return new JvmPropertyExtensionVisitor() {
-            @Override
-            public void visit(
-                int flags,
-                JvmFieldSignature fieldDesc,
-                JvmMethodSignature getterDesc,
-                JvmMethodSignature setterDesc) {
-              assert fieldSignature == null : fieldSignature.asString();
-              fieldSignature = fieldDesc;
-              assert getterSignature == null : getterSignature.asString();
-              getterSignature = getterDesc;
-              assert setterSignature == null : setterSignature.asString();
-              setterSignature = setterDesc;
-            }
-          };
-        }
-      });
+      fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
+      getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty);
+      setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty);
       // We don't check Kotlin types in tests, but be aware of the relocation issue.
       // See b/70169921#comment57 for more details.
     }
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
index 7640064..c8ce440 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
@@ -1,6 +1,8 @@
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.kotlin.KotlinFlagUtils;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 import kotlin.metadata.KmTypeParameter;
 import kotlin.metadata.KmVariance;
@@ -36,8 +38,8 @@
   }
 
   @Override
-  public int getFlags() {
-    return kmTypeParameter.getFlags();
+  public Map<String, Object> getFlags() {
+    return KotlinFlagUtils.extractFlags(kmTypeParameter);
   }
 
   @Override
@@ -60,7 +62,8 @@
     KmTypeParameter other = ((FoundKmTypeParameterSubject) obj).kmTypeParameter;
     if (!kmTypeParameter.getName().equals(other.getName())
         || kmTypeParameter.getId() != other.getId()
-        || kmTypeParameter.getFlags() != other.getFlags()
+        || !(KotlinFlagUtils.extractFlags(kmTypeParameter)
+            .equals(KotlinFlagUtils.extractFlags(other)))
         || kmTypeParameter.getVariance() != other.getVariance()) {
       return false;
     }
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java
index bc70b90..d484199 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java
@@ -5,13 +5,14 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import java.util.List;
+import java.util.Map;
 import kotlin.metadata.KmVariance;
 
 public abstract class KmTypeParameterSubject extends Subject {
 
   public abstract int getId();
 
-  public abstract int getFlags();
+  public abstract Map<String, Object> getFlags();
 
   public abstract KmVariance getVariance();
 
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index 0bdeb7e..81828a7 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -6,15 +6,16 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.kotlin.KotlinFlagUtils;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 import kotlin.metadata.KmAnnotation;
+import kotlin.metadata.KmClassifier;
+import kotlin.metadata.KmClassifier.TypeAlias;
 import kotlin.metadata.KmFlexibleTypeUpperBound;
 import kotlin.metadata.KmType;
-import kotlin.metadata.KmTypeVisitor;
 import kotlin.metadata.jvm.JvmExtensionsKt;
 
 public class KmTypeSubject extends Subject {
@@ -32,23 +33,17 @@
     if (kmType == null) {
       return null;
     }
-    Box<String> descriptor = new Box<>(null);
-    kmType.accept(new KmTypeVisitor() {
-      @Override
-      public void visitClass(String name) {
-        // We don't check Kotlin types in tests, but be aware of the relocation issue.
-        // See b/70169921#comment25 for more details.
-        assert descriptor.get() == null;
-        descriptor.set(getDescriptorFromKotlinClassifier(name));
-      }
-
-      @Override
-      public void visitTypeAlias(String name) {
-        assert descriptor.get() == null;
-        descriptor.set(getDescriptorFromKotlinClassifier(name));
-      }
-    });
-    return descriptor.get();
+    KmClassifier classifier = kmType.getClassifier();
+    if (classifier instanceof KmClassifier.Class) {
+      KmClassifier.Class classClassifier = (KmClassifier.Class) classifier;
+      return getDescriptorFromKotlinClassifier(classClassifier.getName());
+    }
+    if (classifier instanceof KmClassifier.TypeAlias) {
+      TypeAlias typeAliasClassifier = (TypeAlias) classifier;
+      return getDescriptorFromKotlinClassifier(typeAliasClassifier.getName());
+    }
+    // The case KmClassifier.TypeParameter is not implemented (?).
+    return null;
   }
 
   public String descriptor() {
@@ -107,7 +102,7 @@
     if (one == null || other == null) {
       return false;
     }
-    if (one.getFlags() != other.getFlags()) {
+    if (!KotlinFlagUtils.extractFlags(one).equals(KotlinFlagUtils.extractFlags(other))) {
       return false;
     }
     if (!one.classifier.toString().equals(other.classifier.toString())) {
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java
index 2586325..45ffeda 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmValueParameterSubject.java
@@ -3,9 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
-import static kotlin.metadata.Flag.ValueParameter.DECLARES_DEFAULT_VALUE;
-
 import com.android.tools.r8.errors.Unreachable;
+import kotlin.metadata.Attributes;
 import kotlin.metadata.KmValueParameter;
 
 public class KmValueParameterSubject extends Subject {
@@ -33,7 +32,7 @@
   }
 
   public boolean declaresDefaultValue() {
-    return DECLARES_DEFAULT_VALUE.invoke(kmValueParameter.getFlags());
+    return Attributes.getDeclaresDefaultValue(kmValueParameter);
   }
 
   @Override
diff --git a/third_party/dependencies.tar.gz.sha1 b/third_party/dependencies.tar.gz.sha1
index 362cc92..eacd0fe 100644
--- a/third_party/dependencies.tar.gz.sha1
+++ b/third_party/dependencies.tar.gz.sha1
@@ -1 +1 @@
-fd84e5adc03d7770a211a2fcb7978f0339c6c02a
\ No newline at end of file
+69699e487d7f4e51da88351227dd09a2eb3147f1
\ No newline at end of file
diff --git a/third_party/dependencies_plugin.tar.gz.sha1 b/third_party/dependencies_plugin.tar.gz.sha1
index d6ec64f..7a7c496 100644
--- a/third_party/dependencies_plugin.tar.gz.sha1
+++ b/third_party/dependencies_plugin.tar.gz.sha1
@@ -1 +1 @@
-ce80383f6ea9554ad1bc99e9be4b5c040a7f7e09
\ No newline at end of file
+a98c3dc84926e632f3c7bd2599bb1c692c26be45
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/crane.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/crane.tar.gz.sha1
index 43b6ec0..8151eb0 100644
--- a/third_party/opensource-apps/android/compose-samples/crane.tar.gz.sha1
+++ b/third_party/opensource-apps/android/compose-samples/crane.tar.gz.sha1
@@ -1 +1 @@
-54e1cfb2bd83e005ccd07179958261d5ed2c7102
\ No newline at end of file
+d8cd1490244b1dc97d889b9fabec76d08cef7c0e
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetcaster.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetcaster.tar.gz.sha1
index e4fdd09..a850e28 100644
--- a/third_party/opensource-apps/android/compose-samples/jetcaster.tar.gz.sha1
+++ b/third_party/opensource-apps/android/compose-samples/jetcaster.tar.gz.sha1
@@ -1 +1 @@
-158d1e78d2055793960120b1c58654f83cd6d4d3
\ No newline at end of file
+0604c1cbdefc095b510e057720432024ea359dbc
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetchat.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetchat.tar.gz.sha1
index 5e613de..94883e7 100644
--- a/third_party/opensource-apps/android/compose-samples/jetchat.tar.gz.sha1
+++ b/third_party/opensource-apps/android/compose-samples/jetchat.tar.gz.sha1
@@ -1 +1 @@
-0a6e35687efada2890624783e9936047ed10b434
\ No newline at end of file
+49259c45a5fedfd6eaf89c776a84a5398cf27384
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetlagged.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetlagged.tar.gz.sha1
new file mode 100644
index 0000000..1bf9ac9
--- /dev/null
+++ b/third_party/opensource-apps/android/compose-samples/jetlagged.tar.gz.sha1
@@ -0,0 +1 @@
+c649ac9267753c0e1451b3954d210b8657516660
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetnews.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetnews.tar.gz.sha1
index 3fb24df..6574b20 100644
--- a/third_party/opensource-apps/android/compose-samples/jetnews.tar.gz.sha1
+++ b/third_party/opensource-apps/android/compose-samples/jetnews.tar.gz.sha1
@@ -1 +1 @@
-d1c89d1a22c716d3c9e2c8b7b725bc4716d12ea6
\ No newline at end of file
+0c8d2ca9a1cf572072830a8de6b5226b43ce74e7
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetsnack.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetsnack.tar.gz.sha1
index 9562d67..c82ebde 100644
--- a/third_party/opensource-apps/android/compose-samples/jetsnack.tar.gz.sha1
+++ b/third_party/opensource-apps/android/compose-samples/jetsnack.tar.gz.sha1
@@ -1 +1 @@
-2e7d404796f7c4b20f47957fef00755665623526
\ No newline at end of file
+11905a3eb956d99d89259ca84b0e0c47f651dd39
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/jetsurvey.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/jetsurvey.tar.gz.sha1
deleted file mode 100644
index 736edd4..0000000
--- a/third_party/opensource-apps/android/compose-samples/jetsurvey.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-0dc41fbe14dbfb3bfc70ed64ff129b311bfcbf94
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/owl.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/owl.tar.gz.sha1
index d6c218c..86fdccf 100644
--- a/third_party/opensource-apps/android/compose-samples/owl.tar.gz.sha1
+++ b/third_party/opensource-apps/android/compose-samples/owl.tar.gz.sha1
@@ -1 +1 @@
-9bb9c1cc3fea6d4ceb33df8e99057caa1bbe94f6
\ No newline at end of file
+be9dbb7f37c872d619ad3a89cbfa6fb4a93cc9fb
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/rally.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/rally.tar.gz.sha1
deleted file mode 100644
index 3947935..0000000
--- a/third_party/opensource-apps/android/compose-samples/rally.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-86f3577a407a3cc3883f2d016d23879f3e8bd64e
\ No newline at end of file
diff --git a/third_party/opensource-apps/android/compose-samples/reply.tar.gz.sha1 b/third_party/opensource-apps/android/compose-samples/reply.tar.gz.sha1
new file mode 100644
index 0000000..120bf4c
--- /dev/null
+++ b/third_party/opensource-apps/android/compose-samples/reply.tar.gz.sha1
@@ -0,0 +1 @@
+343b9b35f15e8d5952514f91b9934f9e925aa880
\ No newline at end of file
diff --git a/third_party/opensource-apps/chrome.tar.gz.sha1 b/third_party/opensource-apps/chrome.tar.gz.sha1
new file mode 100644
index 0000000..b74ce8a
--- /dev/null
+++ b/third_party/opensource-apps/chrome.tar.gz.sha1
@@ -0,0 +1 @@
+2011d6b4b3394b50c514a9c281845b62b42db078
\ No newline at end of file
diff --git a/third_party/protoc.tar.gz.sha1 b/third_party/protoc.tar.gz.sha1
new file mode 100644
index 0000000..dbc9c9e
--- /dev/null
+++ b/third_party/protoc.tar.gz.sha1
@@ -0,0 +1 @@
+aa0e87a89da6cbbe443db5452725dcb643baa5db
\ No newline at end of file
diff --git a/tools/create_local_maven_with_dependencies.py b/tools/create_local_maven_with_dependencies.py
index c3f4819..0df88f7 100755
--- a/tools/create_local_maven_with_dependencies.py
+++ b/tools/create_local_maven_with_dependencies.py
@@ -24,7 +24,7 @@
 ASM_VERSION = '9.7' # When updating update tools/asmifier.py and Toolhelper as well.
 ESPRESSO_VERSION = '3.0.0'
 FASTUTIL_VERSION = '7.2.1'
-KOTLIN_METADATA_VERSION = '2.0.0-Beta5'
+KOTLIN_METADATA_VERSION = '2.0.0'
 KOTLIN_VERSION = '1.9.20'
 GUAVA_VERSION = '32.1.2-jre'
 GSON_VERSION = '2.10.1'
@@ -35,9 +35,11 @@
 ERROR_PRONE_VERSION = '2.18.0'
 TESTNG_VERSION = '6.10'
 
+# keepanno & resource shrinker dependencies
+PROTOBUF_VERSION = '3.19.3'
+
 # Resource shrinker dependency versions
 AAPT2_PROTO_VERSION = '8.2.0-alpha10-10154469'
-PROTOBUF_VERSION = '3.19.3'
 STUDIO_SDK_VERSION = '31.5.0-alpha04'
 
 BUILD_DEPENDENCIES = [
@@ -97,6 +99,7 @@
 ]
 
 PLUGIN_DEPENDENCIES = [
+  'com.google.protobuf:protobuf-gradle-plugin:0.9.4',
   'org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:pom:4.2.1',
   'org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.9.10',
   'net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:pom:3.0.1',
diff --git a/tools/gradle.py b/tools/gradle.py
index ef77a5c..688b01f 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -18,6 +18,9 @@
 GRADLE8_SHA1 = os.path.join(utils.THIRD_PARTY, 'gradle.tar.gz.sha1')
 GRADLE8_TGZ = os.path.join(utils.THIRD_PARTY, 'gradle.tar.gz')
 
+PROTOC_ROOT = os.path.join(utils.THIRD_PARTY, 'protoc')
+PROTOC_SHA1 = os.path.join(utils.THIRD_PARTY, 'protoc.tar.gz.sha1')
+PROTOC_TGZ = os.path.join(utils.THIRD_PARTY, 'protoc.tar.gz')
 
 def get_gradle():
     gradle_dir = os.path.join(utils.THIRD_PARTY, 'gradle')
@@ -80,10 +83,18 @@
         jdkSha1 = jdkTgz + '.sha1'
         utils.EnsureDepFromGoogleCloudStorage(root, jdkTgz, jdkSha1, root)
 
+def EnsureProtoc():
+    utils.EnsureDepFromGoogleCloudStorage(
+        PROTOC_ROOT,
+        PROTOC_TGZ,
+        PROTOC_SHA1,
+        'Proto Compiler')
+
 
 def EnsureDeps():
     EnsureGradle()
     EnsureJdk()
+    EnsureProtoc()
 
 
 def RunGradleIn(gradleCmd, args, cwd, throw_on_failure=True, env=None):
diff --git a/tools/perf.py b/tools/perf.py
index 0186435..5d3cb2b 100755
--- a/tools/perf.py
+++ b/tools/perf.py
@@ -11,9 +11,15 @@
 import subprocess
 import sys
 
-import upload_benchmark_data_to_google_storage
 import utils
+if utils.is_bot():
+    import upload_benchmark_data_to_google_storage
 
+APPS = [
+    'ChromeApp', 'CraneApp', 'JetLaggedApp', 'JetNewsApp', 'JetCasterApp',
+    'JetChatApp', 'JetSnackApp', 'NowInAndroidApp', 'OwlApp', 'ReplyApp',
+    'TiviApp'
+]
 BUCKET = "r8-perf-results"
 SAMPLE_BENCHMARK_RESULT_JSON = {
     'benchmark_name': '<benchmark_name>',
@@ -23,13 +29,12 @@
     }]
 }
 
+
 # Result structure on cloud storage
-# gs://bucket/benchmark_results/APP/TARGET/GIT_HASH/results
+# gs://bucket/benchmark_results/APP/TARGET/GIT_HASH/result.json
 #                                                   meta
 # where results simply contains the result lines and
 # meta contains information about the execution (machine)
-
-
 def ParseOptions():
     result = argparse.ArgumentParser()
     result.add_argument('--app',
@@ -62,12 +67,30 @@
                         help='Use R8 hash for the run (default local build)',
                         default=None)
     options, args = result.parse_known_args()
-    options.apps = options.app or ['NowInAndroidApp', 'TiviApp']
+    options.apps = options.app or APPS
     options.quiet = not options.verbose
     del options.app
     return options, args
 
 
+def Build(options):
+    utils.Print('Building', quiet=options.quiet)
+    build_cmd = GetRunCmd('N/A', options, ['--iterations', '0'])
+    subprocess.check_call(build_cmd)
+
+
+def GetRunCmd(app, options, args):
+    base_cmd = [
+        'tools/run_benchmark.py', '--benchmark', app, '--target', options.target
+    ]
+    if options.verbose:
+        base_cmd.append('--verbose')
+    if options.version:
+        base_cmd.extend(
+            ['--version', options.version, '--version-jar', r8jar, '--nolib'])
+    return base_cmd + args
+
+
 def MergeBenchmarkResultJsonFiles(benchmark_result_json_files):
     merged_benchmark_result_json = None
     for benchmark_result_json_file in benchmark_result_json_files:
@@ -124,13 +147,14 @@
 #     --bottom 7486f01e0622cb5935b77a92b59ddf1ca8dbd2e2
 def main():
     options, args = ParseOptions()
+    Build(options)
+    any_failed = False
     with utils.TempDir() as temp:
         if options.version:
             # Download r8.jar once instead of once per run_benchmark.py invocation.
             download_options = argparse.Namespace(no_build=True, nolib=True)
             r8jar = compiledump.download_distribution(options.version,
                                                       download_options, temp)
-
         for app in options.apps:
             if options.skip_if_output_exists:
                 if options.outdir:
@@ -142,36 +166,28 @@
                     print(f'Skipping run, {output} already exists.')
                     continue
 
-            base_cmd = [
-                'tools/run_benchmark.py', '--benchmark', app, '--target',
-                options.target
-            ]
-            if options.verbose:
-                base_cmd.append('--verbose')
-            if options.version:
-                base_cmd.extend([
-                    '--version', options.version, '--version-jar', r8jar,
-                    '--nolib'
-                ])
-
-            # Build
-            utils.Print(f'Preparing {app}', quiet=options.quiet)
-            build_cmd = base_cmd + ['--iterations', '0']
-            subprocess.check_output(build_cmd)
-
             # Run benchmark.
             benchmark_result_json_files = []
+            failed = False
             for i in range(options.iterations):
                 utils.Print(f'Benchmarking {app} ({i+1}/{options.iterations})',
                             quiet=options.quiet)
                 benchhmark_result_file = os.path.join(temp, f'result_file_{i}')
-                iteration_cmd = base_cmd + [
+                iteration_cmd = GetRunCmd(app, options, [
                     '--iterations',
                     str(options.iterations_inner), '--output',
                     benchhmark_result_file, '--no-build'
-                ]
-                subprocess.check_output(iteration_cmd)
-                benchmark_result_json_files.append(benchhmark_result_file)
+                ])
+                try:
+                    subprocess.check_call(iteration_cmd)
+                    benchmark_result_json_files.append(benchhmark_result_file)
+                except subprocess.CalledProcessError as e:
+                    failed = True
+                    any_failed = True
+                    break
+
+            if failed:
+                continue
 
             # Merge results and write output.
             result_file = os.path.join(temp, 'result_file')
@@ -198,6 +214,9 @@
     if utils.is_bot():
         upload_benchmark_data_to_google_storage.run()
 
+    if any_failed:
+        return 1
+
 
 if __name__ == '__main__':
     sys.exit(main())
diff --git a/tools/perf/index.html b/tools/perf/index.html
index 8d3089c..ec09f14 100644
--- a/tools/perf/index.html
+++ b/tools/perf/index.html
@@ -5,7 +5,7 @@
   <title>R8 perf</title>
 </head>
 <body>
-  <select id="benchmark-selector"></select>
+  <div id="benchmark-selectors"></div>
   <div>
       <canvas id="myChart"></canvas>
   </div>
@@ -54,7 +54,7 @@
 
     // Import and reverse commits so that newest are last.
     import commits from "./benchmark_data.json" with { type: "json" };
-    commits.reverseInPlace()
+    commits.reverseInPlace();
 
     // Amend the commits with their unique index.
     for (var i = 0; i < commits.length; i++) {
@@ -62,125 +62,148 @@
     }
 
     // DOM references.
-    const benchmarkSelector = document.getElementById('benchmark-selector')
+    const benchmarkSelectors = document.getElementById('benchmark-selectors');
     const canvas = document.getElementById('myChart');
     const showMoreLeft = document.getElementById('show-more-left');
     const showLessLeft = document.getElementById('show-less-left');
     const showLessRight = document.getElementById('show-less-right');
     const showMoreRight = document.getElementById('show-more-right');
 
-    // Initialize benchmark selector.
+    // Initialize benchmark selectors.
     const benchmarks = new Set();
     for (const commit of commits.values()) {
       for (const benchmark in commit.benchmarks) {
-          benchmarks.add(benchmark)
+          benchmarks.add(benchmark);
       }
     }
-    var selectedBenchmark = window.location.hash.substring(1)
-    if (!benchmarks.has(selectedBenchmark)) {
-      selectedBenchmark = benchmarks.values().next().value;
-    }
+    const selectedBenchmarks = new Set();
+    const urlOptions = unescape(window.location.hash.substring(1)).split(',');
     for (const benchmark of benchmarks.values()) {
-      const opt = document.createElement('option');
-      opt.value = benchmark;
-      opt.innerHTML = benchmark;
-      benchmarkSelector.appendChild(opt);
-      if (benchmark == selectedBenchmark) {
-        benchmarkSelector.selectedIndex = benchmarkSelector.options.length - 1
-      }
-    }
-    benchmarkSelector.onchange = function(event) {
-      selectedBenchmark =
-          benchmarkSelector.options[benchmarkSelector.selectedIndex].value;
-      updateChart();
-      window.location.hash = selectedBenchmark;
-    };
-
-    // Chart data provider.
-    function getData(start = 0, end = commits.length) {
-      const filteredCommits =
-          commits
-              .slice(start, end);
-              //.filter(
-              //    commit =>
-              //        selectedBenchmark in commit.benchmarks
-              //            && commit.benchmarks[selectedBenchmark].results.length > 0);
-      const labels = filteredCommits.map((c, i) => i);
-      const codeSizeData =
-          filteredCommits.map(
-              (c, i) =>
-                  selectedBenchmark in filteredCommits[i].benchmarks
-                      ? filteredCommits[i]
-                          .benchmarks[selectedBenchmark]
-                          .results
-                          .first()
-                          .code_size
-                      : NaN);
-      const codeSizeScatterData = [];
-      for (const commit of filteredCommits.values()) {
-        if (!(selectedBenchmark in commit.benchmarks)) {
-          continue;
-        }
-        const codeSizes =
-            commit.benchmarks[selectedBenchmark].results.map(result => result.code_size)
-        const expectedCodeSize = codeSizes.first();
-        if (codeSizes.any(codeSize => codeSize != expectedCodeSize)) {
-          const seen = new Set();
-          seen.add(expectedCodeSize);
-          for (const codeSize of codeSizes.values()) {
-            if (!seen.has(codeSize)) {
-              codeSizeScatterData.push({ x: commit.index, y: codeSize });
-              seen.add(codeSize);
-            }
+      for (const filter of urlOptions.values()) {
+        if (filter) {
+          const match = benchmark.match(new RegExp(filter.replace("*", ".*")));
+          if (match) {
+            selectedBenchmarks.add(benchmark);
+            break;
           }
         }
       }
-      const runtimeData =
-          filteredCommits.map(
-              (c, i) =>
-                  selectedBenchmark in filteredCommits[i].benchmarks
-                      ? filteredCommits[i]
-                          .benchmarks[selectedBenchmark]
-                          .results
-                          .map(result => result.runtime)
-                          .min()
-                          .ns_to_s()
-                      : NaN);
-      const runtimeScatterData = [];
-      for (const commit of filteredCommits.values()) {
-        if (!(selectedBenchmark in commit.benchmarks)) {
-          continue;
+    }
+    if (selectedBenchmarks.size == 0) {
+      selectedBenchmarks.add(benchmarks.values().next().value);
+    }
+    for (const benchmark of benchmarks.values()) {
+      const input = document.createElement('input');
+      input.type = 'checkbox';
+      input.name = 'benchmark';
+      input.id = benchmark;
+      input.value = benchmark;
+      input.checked = selectedBenchmarks.has(benchmark);
+      input.onchange = function (e) {
+        if (e.target.checked) {
+          selectedBenchmarks.add(e.target.value);
+        } else {
+          selectedBenchmarks.delete(e.target.value);
         }
-        const runtimes =
-            commit.benchmarks[selectedBenchmark].results.map(result => result.runtime)
-        for (const runtime of runtimes.values()) {
-          runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
-        }
-      }
+        updateChart(true, false);
+      };
 
-      const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;
-      return {
-        labels: labels,
-        datasets: [
+      const label = document.createElement('label');
+      label.id = benchmark + 'Label';
+      label.htmlFor = benchmark;
+      label.innerHTML = benchmark;
+
+      benchmarkSelectors.appendChild(input);
+      benchmarkSelectors.appendChild(label);
+    }
+
+    // Chart data provider.
+    function getData() {
+      const filteredCommits = commits.slice(zoom.left, zoom.right);
+      const labels = filteredCommits.map((c, i) => c.index);
+      const datasets = [];
+      for (const selectedBenchmark of selectedBenchmarks.values()) {
+        const codeSizeData =
+            filteredCommits.map(
+                (c, i) =>
+                    selectedBenchmark in filteredCommits[i].benchmarks
+                        ? filteredCommits[i]
+                            .benchmarks[selectedBenchmark]
+                            .results
+                            .first()
+                            .code_size
+                        : NaN);
+        const codeSizeScatterData = [];
+        for (const commit of filteredCommits.values()) {
+          if (!(selectedBenchmark in commit.benchmarks)) {
+            continue;
+          }
+          const codeSizes =
+              commit.benchmarks[selectedBenchmark].results.map(result => result.code_size)
+          const expectedCodeSize = codeSizes.first();
+          if (codeSizes.any(codeSize => codeSize != expectedCodeSize)) {
+            const seen = new Set();
+            seen.add(expectedCodeSize);
+            for (const codeSize of codeSizes.values()) {
+              if (!seen.has(codeSize)) {
+                codeSizeScatterData.push({ x: commit.index, y: codeSize });
+                seen.add(codeSize);
+              }
+            }
+          }
+        }
+        const runtimeData =
+            filteredCommits.map(
+                (c, i) =>
+                    selectedBenchmark in filteredCommits[i].benchmarks
+                        ? filteredCommits[i]
+                            .benchmarks[selectedBenchmark]
+                            .results
+                            .map(result => result.runtime)
+                            .min()
+                            .ns_to_s()
+                        : NaN);
+        const runtimeScatterData = [];
+        for (const commit of filteredCommits.values()) {
+          if (!(selectedBenchmark in commit.benchmarks)) {
+            continue;
+          }
+          const runtimes =
+              commit.benchmarks[selectedBenchmark].results.map(result => result.runtime)
+          for (const runtime of runtimes.values()) {
+            runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
+          }
+        }
+
+        const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;
+        datasets.push(...[
           {
+            benchmark: selectedBenchmark,
             type: 'line',
             label: 'Code size',
             data: codeSizeData,
             tension: 0.1,
             segment: {
               borderColor: ctx =>
-                  skipped(ctx, 'rgba(54, 162, 235, 0.5)'),
+                  skipped(
+                      ctx,
+                      myChart
+                          ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
+                          : undefined),
               borderDash: ctx => skipped(ctx, [6, 6]),
             },
             spanGaps: true
           },
           {
+            benchmark: selectedBenchmark,
             type: 'scatter',
             label: 'Nondeterminism',
             data: codeSizeScatterData,
+            radius: 6,
             pointBackgroundColor: 'red'
           },
           {
+            benchmark: selectedBenchmark,
             type: 'line',
             label: 'Runtime',
             data: runtimeData,
@@ -188,34 +211,77 @@
             yAxisID: 'y2',
             segment: {
               borderColor: ctx =>
-                  skipped(ctx, 'rgba(255, 160, 64, 0.5)'),
+                  skipped(
+                      ctx,
+                      myChart
+                          ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
+                          : undefined),
               borderDash: ctx => skipped(ctx, [6, 6]),
             },
             spanGaps: true
           },
           {
+            benchmark: selectedBenchmark,
             type: 'scatter',
             label: 'Runtime variance',
             data: runtimeScatterData,
             yAxisID: 'y2'
           }
-        ],
+        ]);
+      }
+
+      return {
+        labels: labels,
+        datasets: datasets,
       };
     }
 
+    // Legend tracking.
+    const legends =
+        new Set(['Code size', 'Nondeterminism', 'Runtime', 'Runtime variance']);
+    const selectedLegends =
+        new Set(
+              unescape(window.location.hash.substring(1))
+                  .split(',')
+                  .filter(l => legends.has(l)));
+    if (selectedLegends.size == 0) {
+      legends.forEach(l => selectedLegends.add(l));
+    }
+
     // Chart options.
     const options = {
       onHover: (event, chartElement) =>
           event.native.target.style.cursor =
               chartElement[0] ? 'pointer' : 'default',
       plugins: {
+        legend: {
+          labels: {
+            filter: function(legendItem, data) {
+              // Only retain the legends for the first selected benchmark. If
+              // multiple benchmarks are selected, then use the legends of the
+              // first selected benchmark to control all selected benchmarks.
+              const numUniqueLegends =
+                  data.datasets.length / selectedBenchmarks.size;
+              return legendItem.datasetIndex < numUniqueLegends;
+            },
+          },
+          onClick: function legendClickHandler(e, legendItem, legend) {
+            const clickedLegend = legendItem.text;
+            if (selectedLegends.has(clickedLegend)) {
+              selectedLegends.delete(clickedLegend);
+            } else {
+              selectedLegends.add(clickedLegend);
+            }
+            updateChart(false, true);
+          },
+        },
         tooltip: {
           callbacks: {
             title: (context) => {
               const elementInfo = context[0];
               var commit;
               if (elementInfo.dataset.type == 'line') {
-                commit = commits[elementInfo.dataIndex];
+                commit = commits[zoom.left + elementInfo.dataIndex];
               } else {
                 console.assert(elementInfo.dataset.type == 'scatter');
                 commit = commits[elementInfo.raw.x];
@@ -226,14 +292,17 @@
               const elementInfo = context[0];
               var commit;
               if (elementInfo.dataset.type == 'line') {
-                commit = commits[elementInfo.dataIndex];
+                commit = commits[zoom.left + elementInfo.dataIndex];
               } else {
                 console.assert(elementInfo.dataset.type == 'scatter');
                 commit = commits[elementInfo.raw.x];
               }
-              return `Author: ${commit.author}\n`
+              const dataset = myChart.data.datasets[elementInfo.datasetIndex];
+              return `App: ${dataset.benchmark}\n`
+                  + `Author: ${commit.author}\n`
                   + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n`
-                  + `Hash: ${commit.hash}`;
+                  + `Hash: ${commit.hash}\n`
+                  + `Index: ${commit.index}`;
             }
           }
         }
@@ -258,12 +327,6 @@
       }
     };
 
-    // Create chart.
-    const myChart = new Chart(canvas, {
-      data: getData(),
-      options: options
-    });
-
     // Setup click handler.
     canvas.onclick = function (event) {
       const points =
@@ -277,60 +340,140 @@
     };
 
     // Setup chart navigation.
-    var left = 0;
-    var right = commits.length;
+    var zoom = { left: 0, right: commits.length };
+    for (const urlOption of urlOptions.values()) {
+      if (urlOption.startsWith('L')) {
+        var left = parseInt(urlOption.substring(1));
+        if (isNaN(left)) {
+          continue;
+        }
+        left = left >= 0 ? left : commits.length + left;
+        if (left < 0) {
+          zoom.left = 0;
+        } else if (left >= commits.length) {
+          zoom.left = commits.length - 1;
+        } else {
+          zoom.left = left;
+        }
+      }
+    }
 
     showMoreLeft.onclick = function (event) {
-      if (left == 0) {
+      if (zoom.left == 0) {
         return;
       }
-      const currentSize = right - left;
-      left = left - currentSize;
-      if (left < 0) {
-        left = 0;
+      const currentSize = zoom.right - zoom.left;
+      zoom.left = zoom.left - currentSize;
+      if (zoom.left < 0) {
+        zoom.left = 0;
       }
-      updateChart();
+      updateChart(true, false);
     };
 
     showLessLeft.onclick = function (event) {
-      const currentSize = right - left;
-      left = left + Math.floor(currentSize / 2);
-      if (left >= right) {
-        left = right - 1;
+      const currentSize = zoom.right - zoom.left;
+      zoom.left = zoom.left + Math.floor(currentSize / 2);
+      if (zoom.left >= zoom.right) {
+        zoom.left = zoom.right - 1;
       }
-      updateChart();
+      updateChart(true, false);
     };
 
     showLessRight.onclick = function (event) {
-      if (right == 0) {
+      if (zoom.right == 0) {
         return;
       }
-      const currentSize = right - left;
-      right = right - Math.floor(currentSize / 2);
-      if (right < left) {
-        right = left;
+      const currentSize = zoom.right - zoom.left;
+      zoom.right = zoom.right - Math.floor(currentSize / 2);
+      if (zoom.right < zoom.left) {
+        zoom.right = zoom.left;
       }
-      updateChart();
+      updateChart(true, false);
     };
 
     showMoreRight.onclick = function (event) {
-      const currentSize = right - left;
-      right = right + currentSize;
-      if (right > commits.length) {
-        right = commits.length;
+      const currentSize = zoom.right - zoom.left;
+      zoom.right = zoom.right + currentSize;
+      if (zoom.right > commits.length) {
+        zoom.right = commits.length;
       }
-      updateChart();
+      updateChart(true, false);
     };
 
-    function updateChart() {
-      console.assert(left <= right);
-      const newData = getData(left, right);
-      Object.assign(myChart.data, newData);
-      myChart.update();
-      showMoreLeft.disabled = left == 0;
-      showLessLeft.disabled = left == right - 1;
-      showLessRight.disabled = left == right - 1;
-      showMoreRight.disabled = right == commits.length;
+    function updateChart(dataChanged, legendsChanged) {
+      console.assert(zoom.left <= zoom.right);
+
+      // Update datasets.
+      if (dataChanged) {
+        const newData = getData();
+        Object.assign(myChart.data, newData);
+        // Update chart.
+        myChart.update();
+      }
+
+      // Update legends.
+      if (legendsChanged || (dataChanged && selectedLegends.size < legends.size)) {
+        for (var datasetIndex = 0;
+            datasetIndex < myChart.data.datasets.length;
+            datasetIndex++) {
+          const datasetMeta = myChart.getDatasetMeta(datasetIndex);
+          datasetMeta.hidden = !selectedLegends.has(datasetMeta.label);
+        }
+        // Update chart.
+        myChart.update();
+      }
+
+
+      // Update checkbox colors.
+      const benchmarkColors = {};
+      for (var datasetIndex = 0;
+          datasetIndex < myChart.data.datasets.length;
+          datasetIndex++) {
+        if (myChart.getDatasetMeta(datasetIndex).hidden) {
+          continue;
+        }
+        const dataset = myChart.data.datasets[datasetIndex];
+        const benchmark = dataset.benchmark;
+        const benchmarkColor = dataset.borderColor;
+        if (!(benchmark in benchmarkColors)) {
+          benchmarkColors[benchmark] = benchmarkColor;
+        }
+      }
+      for (const benchmark of benchmarks.values()) {
+        const benchmarkLabel = document.getElementById(benchmark + 'Label');
+        const benchmarkColor = benchmarkColors[benchmark] || '#000000';
+        const benchmarkFontWeight = benchmark in benchmarkColors ? 'bold' : 'normal';
+        benchmarkLabel.style.color = benchmarkColor;
+        benchmarkLabel.style.fontWeight = benchmarkFontWeight;
+      }
+
+      // Update navigation.
+      showMoreLeft.disabled = zoom.left == 0;
+      showLessLeft.disabled = zoom.left == zoom.right - 1;
+      showLessRight.disabled = zoom.left == zoom.right - 1;
+      showMoreRight.disabled = zoom.right == commits.length;
+
+      // Update hash.
+      window.location.hash =
+          Array.from(selectedBenchmarks)
+              .concat(
+                  selectedLegends.size == legends.size
+                      ? []
+                      : Array.from(selectedLegends))
+              .join(',');
+    }
+
+    // Create chart.
+    const myChart = new Chart(canvas, {
+      data: getData(),
+      options: options
+    });
+
+    // Hide disabled legends.
+    if (selectedLegends.size < legends.size) {
+      updateChart(false, true);
+    } else {
+      updateChart(false, false);
     }
   </script>
 </body>
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index bf50da3..d6226be 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -386,101 +386,10 @@
         'url': 'https://github.com/wikimedia/apps-android-wikipedia',
         'revision': '0fa7cad843c66313be8e25790ef084cf1a1fa67e',
         'folder': 'wikipedia',
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'androidx.compose.samples.crane',
-        'name': 'compose-crane',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/crane',
-        'golem_duration': 240
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'com.example.jetcaster',
-        'name': 'compose-jetcaster',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/jetcaster',
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'com.example.compose.jetchat',
-        'name': 'compose-jetchat',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/jetchat',
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'com.example.jetnews',
-        'name': 'compose-jetnews',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/jetnews',
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'com.example.jetsnack',
-        'name': 'compose-jetsnack',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/jetsnack',
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'com.example.compose.jetsurvey',
-        'name': 'compose-jetsurvey',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/jetsurvey',
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'com.example.owl',
-        'name': 'compose-owl',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/owl',
-    }),
-    # TODO(b/173167253): Check if monkey testing works.
-    App({
-        'id': 'com.example.compose.rally',
-        'name': 'compose-rally',
-        'collections': ['compose-samples'],
-        'dump_app': 'dump_app.zip',
-        'apk_app': 'app-release-unsigned.apk',
-        'url': 'https://github.com/android/compose-samples',
-        'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
-        'folder': 'android/compose-samples/rally',
-    }),
+    })
 ]
 
-APP_COLLECTIONS = [AppCollection({
-    'name': 'compose-samples',
-})]
+APP_COLLECTIONS = []
 
 
 def remove_print_lines(file):
diff --git a/tools/upload_benchmark_data_to_google_storage.py b/tools/upload_benchmark_data_to_google_storage.py
index 4fb9444..f293c2c 100755
--- a/tools/upload_benchmark_data_to_google_storage.py
+++ b/tools/upload_benchmark_data_to_google_storage.py
@@ -12,9 +12,9 @@
 
 import sys
 
-APPS = ['NowInAndroidApp', 'TiviApp']
+APPS = perf.APPS
 TARGETS = ['r8-full']
-NUM_COMMITS = 250
+NUM_COMMITS = 1000
 
 INDEX_HTML = os.path.join(utils.TOOLS_DIR, 'perf/index.html')
 
@@ -92,9 +92,9 @@
 
         # Write output files to public bucket.
         perf.ArchiveOutputFile(benchmark_data_file,
-                               'perf/benchmark_data.json',
+                               'benchmark_data.json',
                                header='Cache-Control:no-store')
-        perf.ArchiveOutputFile(INDEX_HTML, 'perf/index.html')
+        perf.ArchiveOutputFile(INDEX_HTML, 'index.html')
 
 
 def main():