Reapply "Android U supports records and sealed classes"

This reverts commit b2c65396524b6f1c579458f1c1c549cc8fa0648c.

Original commit message:

Android U supports records and sealed classes

For API level is 34 and above generate code for native support of
records and sealed classes.

Fixes: b/293592205
Fixes: b/293987311
Bug: b/293591931
Change-Id: I62680cf074368a0d906446a2abbf8321da172d65
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5f11e97..79623e1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -64,7 +64,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
 import com.android.tools.r8.naming.ProguardMapMinifier;
-import com.android.tools.r8.naming.RecordInvokeDynamicRewriter;
+import com.android.tools.r8.naming.RecordInvokeDynamicInvokeCustomRewriter;
 import com.android.tools.r8.naming.RecordRewritingNamingLens;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.optimize.LegacyAccessModifier;
@@ -774,7 +774,8 @@
         new IdentifierMinifier(appView, NamingLens.getIdentityLens()).run(executorService);
         timing.end();
         timing.begin("RecordInvokeDynamicRewrite");
-        new RecordInvokeDynamicRewriter(appView, NamingLens.getIdentityLens()).run(executorService);
+        new RecordInvokeDynamicInvokeCustomRewriter(appView, NamingLens.getIdentityLens())
+            .run(executorService);
         timing.end();
       }
       appView.appInfo().notifyMinifierFinished();
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 3d05de8..78beee1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -583,38 +583,25 @@
       }
     }
 
-    if (appView.options().emitRecordAnnotationsExInDex) {
-      return new DexAnnotation(
-          VISIBILITY_SYSTEM,
-          new DexEncodedAnnotation(
-              factory.annotationRecord,
-              new DexAnnotationElement[] {
-                new DexAnnotationElement(
-                    factory.annotationRecordComponentNames, new DexValueArray(componentNames)),
-                new DexAnnotationElement(
-                    factory.annotationRecordComponentTypes, new DexValueArray(componentTypes)),
-                new DexAnnotationElement(
-                    factory.annotationRecordComponentSignatures,
-                    new DexValueArray(componentSignatures)),
-                new DexAnnotationElement(
-                    factory.annotationRecordComponentAnnotationVisibilities,
-                    new DexValueArray(componentAnnotationVisibilities)),
-                new DexAnnotationElement(
-                    factory.annotationRecordComponentAnnotations,
-                    new DexValueArray(componentAnnotations))
-              }));
-    } else {
-      return new DexAnnotation(
-          VISIBILITY_SYSTEM,
-          new DexEncodedAnnotation(
-              factory.annotationRecord,
-              new DexAnnotationElement[] {
-                new DexAnnotationElement(
-                    factory.annotationRecordComponentNames, new DexValueArray(componentNames)),
-                new DexAnnotationElement(
-                    factory.annotationRecordComponentTypes, new DexValueArray(componentTypes))
-              }));
-    }
+    return new DexAnnotation(
+        VISIBILITY_SYSTEM,
+        new DexEncodedAnnotation(
+            factory.annotationRecord,
+            new DexAnnotationElement[] {
+              new DexAnnotationElement(
+                  factory.annotationRecordComponentNames, new DexValueArray(componentNames)),
+              new DexAnnotationElement(
+                  factory.annotationRecordComponentTypes, new DexValueArray(componentTypes)),
+              new DexAnnotationElement(
+                  factory.annotationRecordComponentSignatures,
+                  new DexValueArray(componentSignatures)),
+              new DexAnnotationElement(
+                  factory.annotationRecordComponentAnnotationVisibilities,
+                  new DexValueArray(componentAnnotationVisibilities)),
+              new DexAnnotationElement(
+                  factory.annotationRecordComponentAnnotations,
+                  new DexValueArray(componentAnnotations))
+            }));
   }
 
   public static String getSignature(DexAnnotation signatureAnnotation) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 1e3e943..8d63fb1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
 import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
+import com.android.tools.r8.horizontalclassmerging.policies.NoRecords;
 import com.android.tools.r8.horizontalclassmerging.policies.NoResourceClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
 import com.android.tools.r8.horizontalclassmerging.policies.NoVerticallyMergedClasses;
@@ -174,7 +175,8 @@
         new NoInstanceFieldAnnotations(),
         new NoKotlinMetadata(),
         new NoNativeMethods(),
-        new NoServiceLoaders(appView));
+        new NoServiceLoaders(appView),
+        new NoRecords());
   }
 
   private static boolean verifySingleClassPoliciesIrrelevantForMergingSynthetics(
@@ -192,7 +194,8 @@
             new NoInstanceFieldAnnotations(),
             new NoKotlinMetadata(),
             new NoNativeMethods(),
-            new NoServiceLoaders(appView));
+            new NoServiceLoaders(appView),
+            new NoRecords());
     policies.stream().map(VerifySingleClassPolicyAlwaysSatisfied::new).forEach(builder::add);
     return true;
   }
@@ -208,7 +211,8 @@
             new NoInnerClasses(),
             new NoInstanceFieldAnnotations(),
             new NoKotlinMetadata(),
-            new NoNativeMethods());
+            new NoNativeMethods(),
+            new NoRecords());
     policies.stream().map(VerifySingleClassPolicyAlwaysSatisfied::new).forEach(builder::add);
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRecords.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRecords.java
new file mode 100644
index 0000000..d516fbb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRecords.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class NoRecords extends SingleClassPolicy {
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    // TODO(b/299094325): Allow merging record classes.
+    return !program.isRecord();
+  }
+
+  @Override
+  public String getName() {
+    return "NoRecords";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 0ee142f..710fb47 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -221,4 +221,11 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerCallSite(callSite);
   }
+
+  @Override
+  protected boolean needsRangedInvoke(DexBuilder builder) {
+    return builder.getOptions().testing.forceInvokeRangeForInvokeCustom
+        ? true
+        : super.needsRangedInvoke(builder);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
deleted file mode 100644
index 14c854b..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar.records;
-
-import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
-import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
-
-import com.android.tools.r8.cf.code.CfInvokeDynamic;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
-import com.android.tools.r8.naming.NamingLens;
-import java.util.ArrayList;
-
-/** Used to shrink records in Cf to Cf compilations */
-public class RecordCfToCfRewriter {
-
-  private final AppView<?> appView;
-
-  public static RecordCfToCfRewriter create(AppView<?> appView) {
-    if (appView.enableWholeProgramOptimizations() && appView.options().isGeneratingClassFiles()) {
-      return new RecordCfToCfRewriter(appView);
-    }
-    return null;
-  }
-
-  private RecordCfToCfRewriter(AppView<?> appView) {
-    this.appView = appView;
-  }
-
-  // Called after final tree shaking, prune and minify field names and field values.
-  public CfInvokeDynamic rewriteRecordInvokeDynamic(
-      CfInvokeDynamic invokeDynamic, ProgramMethod context, NamingLens namingLens) {
-    if (!isInvokeDynamicOnRecord(invokeDynamic, appView, context)) {
-      return invokeDynamic;
-    }
-    RecordInvokeDynamic recordInvokeDynamic =
-        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
-    DexString newFieldNames =
-        recordInvokeDynamic
-            .computeRecordFieldNamesComputationInfo()
-            .internalComputeNameFor(
-                recordInvokeDynamic.getRecordType(), appView, appView.graphLens(), namingLens);
-    DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
-    return writeRecordInvokeDynamic(
-        recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
-  }
-
-  private DexField[] computePresentFields(
-      GraphLens graphLens, RecordInvokeDynamic recordInvokeDynamic) {
-    ArrayList<DexField> finalFields = new ArrayList<>();
-    for (DexField field : recordInvokeDynamic.getFields()) {
-      DexEncodedField dexEncodedField =
-          recordInvokeDynamic
-              .getRecordClass()
-              .lookupInstanceField(graphLens.getRenamedFieldSignature(field));
-      if (dexEncodedField != null) {
-        finalFields.add(field);
-      }
-    }
-    DexField[] newFields = new DexField[finalFields.size()];
-    for (int i = 0; i < finalFields.size(); i++) {
-      newFields[i] = finalFields.get(i);
-    }
-    return newFields;
-  }
-
-  private CfInvokeDynamic writeRecordInvokeDynamic(RecordInvokeDynamic recordInvokeDynamic) {
-    DexItemFactory factory = appView.dexItemFactory();
-    DexMethodHandle bootstrapMethod =
-        new DexMethodHandle(
-            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
-    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
-    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordType()));
-    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
-    for (DexField field : recordInvokeDynamic.getFields()) {
-      bootstrapArgs.add(
-          new DexValueMethodHandle(
-              new DexMethodHandle(MethodHandleType.INSTANCE_GET, field, false, null)));
-    }
-    return new CfInvokeDynamic(
-        factory.createCallSite(
-            recordInvokeDynamic.getMethodName(),
-            recordInvokeDynamic.getMethodProto(),
-            bootstrapMethod,
-            bootstrapArgs));
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
new file mode 100644
index 0000000..7cd44a8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.records;
+
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeCustomOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeCustomOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
+
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.dex.code.DexInstruction;
+import com.android.tools.r8.dex.code.DexInvokeCustom;
+import com.android.tools.r8.dex.code.DexInvokeCustomRange;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+
+/** Used to rewrite invokedynamic/invoke-custom when shrinking and minifying records. */
+public class RecordRewriter {
+
+  private final AppView<?> appView;
+
+  public static RecordRewriter create(AppView<?> appView) {
+    if (appView.enableWholeProgramOptimizations()) {
+      return new RecordRewriter(appView);
+    }
+    return null;
+  }
+
+  private RecordRewriter(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  // Called after final tree shaking, prune and minify field names and field values.
+  public CfInvokeDynamic rewriteRecordInvokeDynamic(
+      CfInvokeDynamic invokeDynamic, ProgramMethod context, NamingLens namingLens) {
+    if (!isInvokeDynamicOnRecord(invokeDynamic, appView, context)) {
+      return invokeDynamic;
+    }
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
+    DexString newFieldNames =
+        recordInvokeDynamic
+            .computeRecordFieldNamesComputationInfo()
+            .internalComputeNameFor(
+                recordInvokeDynamic.getRecordType(), appView, appView.graphLens(), namingLens);
+    DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
+    return writeRecordInvokeDynamic(
+        recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
+  }
+
+  public DexInstruction rewriteRecordInvokeCustom(
+      DexInstruction invokeCustom, ProgramMethod context, NamingLens namingLens) {
+    if (!isInvokeCustomOnRecord(invokeCustom, appView, context)) {
+      return invokeCustom;
+    }
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeCustomOnRecord(invokeCustom, appView, context);
+    DexString newFieldNames =
+        recordInvokeDynamic
+            .computeRecordFieldNamesComputationInfo()
+            .internalComputeNameFor(
+                recordInvokeDynamic.getRecordType(), appView, appView.graphLens(), namingLens);
+    DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
+    return writeRecordInvokeCustom(
+        invokeCustom, recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
+  }
+
+  private DexField[] computePresentFields(
+      GraphLens graphLens, RecordInvokeDynamic recordInvokeDynamic) {
+    ArrayList<DexField> finalFields = new ArrayList<>();
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      DexEncodedField dexEncodedField =
+          recordInvokeDynamic
+              .getRecordClass()
+              .lookupInstanceField(graphLens.getRenamedFieldSignature(field));
+      if (dexEncodedField != null) {
+        finalFields.add(field);
+      }
+    }
+    DexField[] newFields = new DexField[finalFields.size()];
+    for (int i = 0; i < finalFields.size(); i++) {
+      newFields[i] = finalFields.get(i);
+    }
+    return newFields;
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  private CfInvokeDynamic writeRecordInvokeDynamic(RecordInvokeDynamic recordInvokeDynamic) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexMethodHandle bootstrapMethod =
+        factory.createMethodHandle(
+            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
+    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
+    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordType()));
+    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      assert recordInvokeDynamic.getRecordCodeType() == field.getHolderType();
+      bootstrapArgs.add(
+          new DexValueMethodHandle(
+              new DexMethodHandle(MethodHandleType.INSTANCE_GET, field, false, null)));
+    }
+    return new CfInvokeDynamic(
+        factory.createCallSite(
+            recordInvokeDynamic.getMethodName(),
+            recordInvokeDynamic.getMethodProto(),
+            bootstrapMethod,
+            bootstrapArgs));
+  }
+
+  private DexInstruction writeRecordInvokeCustom(
+      DexInstruction invokeCustom, RecordInvokeDynamic recordInvokeDynamic) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexMethodHandle bootstrapMethod =
+        factory.createMethodHandle(
+            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
+    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
+    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordCodeType()));
+    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      // Rewrite using the code type of the field.
+      DexField codeField =
+          factory.createField(recordInvokeDynamic.getRecordCodeType(), field.type, field.name);
+      bootstrapArgs.add(
+          new DexValueMethodHandle(
+              factory.createMethodHandle(MethodHandleType.INSTANCE_GET, codeField, false, null)));
+    }
+    if (invokeCustom instanceof DexInvokeCustom) {
+      DexInvokeCustom current = (DexInvokeCustom) invokeCustom;
+      DexInvokeCustom rewritten =
+          new DexInvokeCustom(
+              current.A,
+              factory.createCallSite(
+                  recordInvokeDynamic.getMethodName(),
+                  recordInvokeDynamic.getMethodProto(),
+                  bootstrapMethod,
+                  bootstrapArgs),
+              current.C,
+              current.D,
+              current.E,
+              current.F,
+              current.G);
+      rewritten.setOffset(current.getOffset());
+      return rewritten;
+    } else {
+      DexInvokeCustomRange current = (DexInvokeCustomRange) invokeCustom;
+      DexInvokeCustomRange rewritten =
+          new DexInvokeCustomRange(
+              current.CCCC,
+              current.AA,
+              factory.createCallSite(
+                  recordInvokeDynamic.getMethodName(),
+                  recordInvokeDynamic.getMethodProto(),
+                  bootstrapMethod,
+                  bootstrapArgs));
+      rewritten.setOffset(current.getOffset());
+      return rewritten;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
index 50c6963..f465a98 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.ir.desugar.records;
 
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.dex.code.DexInstruction;
+import com.android.tools.r8.dex.code.DexInvokeCustom;
+import com.android.tools.r8.dex.code.DexInvokeCustomRange;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -28,6 +31,12 @@
     return isInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView, context);
   }
 
+  public static boolean isInvokeCustomOnRecord(
+      DexInstruction invokeCustom, AppView<?> appView, ProgramMethod context) {
+    assert invokeCustom instanceof DexInvokeCustom || invokeCustom instanceof DexInvokeCustomRange;
+    return isInvokeDynamicOnRecord(invokeCustom.getCallSite(), appView, context);
+  }
+
   @SuppressWarnings("ReferenceEquality")
   public static boolean isInvokeDynamicOnRecord(
       DexCallSite callSite, AppView<?> appView, ProgramMethod context) {
@@ -61,7 +70,10 @@
       assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
       return false;
     }
-    DexClass recordClass = appView.definitionFor(recordType.getValue(), context);
+    DexType codeRecordType = recordType.getValue();
+    DexClass recordClass =
+        appView.definitionFor(
+            appView.graphLens().lookupType(codeRecordType, appView.codeLens()), context);
     if (recordClass == null || recordClass.isNotProgramClass()) {
       return false;
     }
@@ -87,16 +99,16 @@
     }
     // 3. Check it matches one of the 3 invokeDynamicOnRecord instruction.
     if (callSite.methodName == factory.toStringMethodName) {
-      assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
+      assert callSite.methodProto == factory.createProto(factory.stringType, codeRecordType);
       return true;
     }
     if (callSite.methodName == factory.hashCodeMethodName) {
-      assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
+      assert callSite.methodProto == factory.createProto(factory.intType, codeRecordType);
       return true;
     }
     if (callSite.methodName == factory.equalsMethodName) {
       assert callSite.methodProto
-          == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
+          == factory.createProto(factory.booleanType, codeRecordType, factory.objectType);
       return true;
     }
     return false;
@@ -105,7 +117,17 @@
   public static RecordInvokeDynamic parseInvokeDynamicOnRecord(
       CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
     assert isInvokeDynamicOnRecord(invokeDynamic, appView, context);
-    DexCallSite callSite = invokeDynamic.getCallSite();
+    return parseInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView, context);
+  }
+
+  public static RecordInvokeDynamic parseInvokeCustomOnRecord(
+      DexInstruction invokeCustom, AppView<?> appView, ProgramMethod context) {
+    assert isInvokeCustomOnRecord(invokeCustom, appView, context);
+    return parseInvokeDynamicOnRecord(invokeCustom.getCallSite(), appView, context);
+  }
+
+  public static RecordInvokeDynamic parseInvokeDynamicOnRecord(
+      DexCallSite callSite, AppView<?> appView, ProgramMethod context) {
     DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
     DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
     DexString fieldNames = valueString.getValue();
@@ -114,10 +136,11 @@
       DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
       fields[i - 2] = handle.value.member.asDexField();
     }
+    DexType recordCodeType = recordValueType.getValue();
     DexProgramClass recordClass =
-        appView.definitionFor(recordValueType.getValue()).asProgramClass();
+        appView.definitionFor(appView.graphLens().lookupType(recordCodeType)).asProgramClass();
     return new RecordInvokeDynamic(
-        callSite.methodName, callSite.methodProto, fieldNames, fields, recordClass);
+        callSite.methodName, callSite.methodProto, fieldNames, fields, recordClass, recordCodeType);
   }
 
   static class RecordInvokeDynamic {
@@ -127,22 +150,26 @@
     private final DexString fieldNames;
     private final DexField[] fields;
     private final DexProgramClass recordClass;
+    private final DexType recordCodeType;
 
     private RecordInvokeDynamic(
         DexString methodName,
         DexProto methodProto,
         DexString fieldNames,
         DexField[] fields,
-        DexProgramClass recordClass) {
+        DexProgramClass recordClass,
+        DexType recordCodeType) {
       this.methodName = methodName;
       this.methodProto = methodProto;
       this.fieldNames = fieldNames;
       this.fields = fields;
       this.recordClass = recordClass;
+      this.recordCodeType = recordCodeType;
     }
 
     RecordInvokeDynamic withFieldNamesAndFields(DexString fieldNames, DexField[] fields) {
-      return new RecordInvokeDynamic(methodName, methodProto, fieldNames, fields, recordClass);
+      return new RecordInvokeDynamic(
+          methodName, methodProto, fieldNames, fields, recordClass, recordCodeType);
     }
 
     DexField[] getFields() {
@@ -153,6 +180,10 @@
       return recordClass.getType();
     }
 
+    DexType getRecordCodeType() {
+      return recordCodeType;
+    }
+
     DexProgramClass getRecordClass() {
       return recordClass;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 40490d4..74c76d1 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -87,7 +87,7 @@
     timing.end();
 
     timing.begin("RecordInvokeDynamicRewrite");
-    new RecordInvokeDynamicRewriter(appView, lens).run(executorService);
+    new RecordInvokeDynamicInvokeCustomRewriter(appView, lens).run(executorService);
     timing.end();
 
     appView.notifyOptimizationFinishedForTesting();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 423978d..24fb83a 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -157,7 +157,7 @@
     timing.end();
 
     timing.begin("RecordInvokeDynamicRewrite");
-    new RecordInvokeDynamicRewriter(appView, lens).run(executorService);
+    new RecordInvokeDynamicInvokeCustomRewriter(appView, lens).run(executorService);
     timing.begin("MinifyIdentifiers");
 
     appView.notifyOptimizationFinishedForTesting();
diff --git a/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicInvokeCustomRewriter.java b/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicInvokeCustomRewriter.java
new file mode 100644
index 0000000..78eb905
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicInvokeCustomRewriter.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.dex.code.DexInstruction;
+import com.android.tools.r8.dex.code.DexInvokeCustom;
+import com.android.tools.r8.dex.code.DexInvokeCustomRange;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordRewriter;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/** Rewrites the record invokedynamic/invoke-custom in hashCode, equals and toString. */
+public class RecordInvokeDynamicInvokeCustomRewriter {
+
+  private final AppView<?> appView;
+  private final RecordRewriter recordRewriter;
+  private final NamingLens lens;
+
+  public RecordInvokeDynamicInvokeCustomRewriter(AppView<?> appView, NamingLens lens) {
+    this.appView = appView;
+    this.recordRewriter = RecordRewriter.create(appView);
+    this.lens = lens;
+  }
+
+  public void run(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          clazz.forEachProgramMethodMatching(
+              DexEncodedMethod::hasCode, this::rewriteRecordInvokeDynamicInMethod);
+        },
+        executorService);
+  }
+
+  private void rewriteRecordInvokeDynamicInMethod(ProgramMethod programMethod) {
+    if (recordRewriter == null) {
+      return;
+    }
+    if (!programMethod.getHolder().isRecord()) {
+      return;
+    }
+    Code code = programMethod.getDefinition().getCode();
+    assert code != null;
+    if (code.isDexCode()) {
+      DexInstruction[] instructions = code.asDexCode().instructions;
+      DexInstruction[] newInstructions =
+          ArrayUtils.map(
+              instructions,
+              (DexInstruction instruction) -> {
+                if (instruction instanceof DexInvokeCustom
+                    || instruction instanceof DexInvokeCustomRange) {
+                  return recordRewriter.rewriteRecordInvokeCustom(instruction, programMethod, lens);
+                }
+                return instruction;
+              },
+              DexInstruction.EMPTY_ARRAY);
+      if (newInstructions != instructions) {
+        programMethod.setCode(code.asDexCode().withNewInstructions(newInstructions), appView);
+      }
+    } else if (code.isCfCode()) {
+      List<CfInstruction> instructions = code.asCfCode().getInstructions();
+      List<CfInstruction> newInstructions =
+          ListUtils.mapOrElse(
+              instructions,
+              (int index, CfInstruction instruction) -> {
+                if (instruction.isInvokeDynamic()) {
+                  return recordRewriter.rewriteRecordInvokeDynamic(
+                      instruction.asInvokeDynamic(), programMethod, lens);
+                }
+                return instruction;
+              },
+              instructions);
+      code.asCfCode().setInstructions(newInstructions);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicRewriter.java b/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicRewriter.java
deleted file mode 100644
index 80b2e33..0000000
--- a/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicRewriter.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.naming;
-
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.records.RecordCfToCfRewriter;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.ThreadUtils;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-/** Rewrites the record invokedynamic in hashCode, equals and toString. */
-public class RecordInvokeDynamicRewriter {
-
-  private final AppView<?> appView;
-  private final RecordCfToCfRewriter recordCfToCfRewriter;
-  private final NamingLens lens;
-
-  public RecordInvokeDynamicRewriter(AppView<?> appView, NamingLens lens) {
-    this.appView = appView;
-    this.recordCfToCfRewriter = RecordCfToCfRewriter.create(appView);
-    this.lens = lens;
-  }
-
-  public void run(ExecutorService executorService) throws ExecutionException {
-    ThreadUtils.processItems(
-        appView.appInfo().classes(),
-        clazz -> {
-          clazz.forEachProgramMethodMatching(
-              DexEncodedMethod::hasCode, this::rewriteRecordInvokeDynamicInMethod);
-        },
-        executorService);
-  }
-
-  private void rewriteRecordInvokeDynamicInMethod(ProgramMethod programMethod) {
-    if (recordCfToCfRewriter == null) {
-      return;
-    }
-    if (!programMethod.getHolder().isRecord()) {
-      return;
-    }
-    Code code = programMethod.getDefinition().getCode();
-    assert code != null;
-    if (!code.isCfCode()) {
-      return;
-    }
-    List<CfInstruction> instructions = code.asCfCode().getInstructions();
-    List<CfInstruction> newInstructions =
-        ListUtils.mapOrElse(
-            instructions,
-            (int index, CfInstruction instruction) -> {
-              if (instruction.isInvokeDynamic()) {
-                return recordCfToCfRewriter.rewriteRecordInvokeDynamic(
-                    instruction.asInvokeDynamic(), programMethod, lens);
-              }
-              return instruction;
-            },
-            instructions);
-    code.asCfCode().setInstructions(newInstructions);
-  }
-}
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 6c81818..de7a1b5 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -468,18 +468,18 @@
   public boolean createSingletonsForStatelessLambdas =
       System.getProperty("com.android.tools.r8.createSingletonsForStatelessLambdas") != null;
 
-  // Flag to allow record annotations in DEX. See b/231930852 for context.
-  public boolean emitRecordAnnotationsInDex =
+  // TODO(b/293591931): Remove this flag.
+  //  Flag to allow record annotations in DEX. See b/231930852 for context.
+  private final boolean emitRecordAnnotationsInDex =
       System.getProperty("com.android.tools.r8.emitRecordAnnotationsInDex") != null;
-  public boolean emitRecordAnnotationsExInDex =
-      System.getProperty("com.android.tools.r8.emitRecordAnnotationsExInDex") != null;
 
   // Flag to allow nest annotations in DEX. See b/231930852 for context.
   public boolean emitNestAnnotationsInDex =
       System.getProperty("com.android.tools.r8.emitNestAnnotationsInDex") != null;
 
+  // TODO(b/293591931): Remove this flag.
   // Flag to allow permitted subclasses annotations in DEX. See b/231930852 for context.
-  public boolean emitPermittedSubclassesAnnotationsInDex =
+  private final boolean emitPermittedSubclassesAnnotationsInDex =
       System.getProperty("com.android.tools.r8.emitPermittedSubclassesAnnotationsInDex") != null;
 
   private DumpInputFlags dumpInputFlags = DumpInputFlags.getDefault();
@@ -2249,6 +2249,8 @@
 
     public boolean enableBinopOptimization = true;
 
+    public boolean forceInvokeRangeForInvokeCustom = false;
+
     private DeterminismChecker getDeterminismChecker() {
       // Lazily read the env-var so that it can be set after options init.
       if (determinismChecker == null && !hasReadCheckDeterminism) {
@@ -2645,11 +2647,11 @@
   }
 
   public boolean canUseRecords() {
-    return hasFeaturePresentFrom(null) || emitRecordAnnotationsInDex;
+    return hasFeaturePresentFrom(AndroidApiLevel.U) || emitRecordAnnotationsInDex;
   }
 
   public boolean canUseSealedClasses() {
-    return hasFeaturePresentFrom(null) || emitPermittedSubclassesAnnotationsInDex;
+    return hasFeaturePresentFrom(AndroidApiLevel.U) || emitPermittedSubclassesAnnotationsInDex;
   }
 
   public boolean canLeaveStaticInterfaceMethodInvokes() {
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index a44a64e..5d74954 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -101,6 +101,17 @@
   }
 
   @SuppressWarnings("UnnecessaryParentheses")
+  public static boolean containsEntry(Path zipfile, String name) throws IOException {
+    BooleanBox result = new BooleanBox();
+    ZipUtils.iter(
+        zipfile,
+        (entry, stream) -> {
+          result.computeIfNotSet(() -> entry.getName().equals(name));
+        });
+    return result.get();
+  }
+
+  @SuppressWarnings("UnnecessaryParentheses")
   public static Path map(
       Path zipFilePath, Path mappedFilePath, BiFunction<ZipEntry, byte[], byte[]> map)
       throws IOException {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d6032c7..6b4518d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1701,6 +1701,11 @@
             .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport());
   }
 
+  public static boolean runtimeWithRecordsSupport(TestRuntime runtime) {
+    return (runtime.isCf() && runtime.asCf().hasRecordsSupport())
+        || (runtime.isDex() && runtime.asDex().hasRecordsSupport());
+  }
+
   public static AndroidApiLevel apiLevelWithStaticInterfaceMethodsSupport() {
     return AndroidApiLevel.N;
   }
@@ -1737,6 +1742,10 @@
     return AndroidApiLevel.O;
   }
 
+  public static boolean canUseNativeRecords(TestParameters parameters) {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
+  }
+
   public static boolean canUseJavaUtilObjects(TestParameters parameters) {
     return parameters.isCfRuntime()
         || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 7ca196a..892c68f 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -101,17 +101,6 @@
     return false;
   }
 
-  public boolean canUseRecords() {
-    assert isCfRuntime() || isDexRuntime();
-    return isCfRuntime() && asCfRuntime().isNewerThanOrEqual(CfVm.JDK14);
-  }
-
-  public boolean canUseRecordsWhenDesugaring() {
-    assert isCfRuntime() || isDexRuntime();
-    assert apiLevel != null;
-    return false;
-  }
-
   public boolean canUseFilledNewArrayOnNonStringObjects() {
     return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
   }
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 3669e77..77e4e22 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -84,6 +84,12 @@
     public static CfVm getMinimumSystemVersion() {
       return JDK11;
     }
+
+    // Records was experimental from JDK-15 (requiring turning on experimental feaures), and GA
+    // in JDK-17.
+    public boolean hasRecordsSupport() {
+      return isGreaterThanOrEqualTo(JDK17);
+    }
   }
 
   private static final Path JDK8_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk8");
@@ -308,6 +314,10 @@
     public AndroidApiLevel getMinApiLevel() {
       return ToolHelper.getMinApiLevelForDexVm(vm);
     }
+
+    public boolean hasRecordsSupport() {
+      return getVersion().hasRecordsSupport();
+    }
   }
 
   // Wrapper for the CF runtimes.
@@ -384,6 +394,10 @@
     public boolean isNewerThanOrEqual(CfVm version) {
       return vm == version || !vm.lessThanOrEqual(version);
     }
+
+    public boolean hasRecordsSupport() {
+      return getVm().hasRecordsSupport();
+    }
   }
 
   public <T> T match(Function<CfRuntime, T> onCf, BiFunction<DexRuntime, DexVm.Version, T> onDex) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d93ed8c..21147f8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -396,6 +396,10 @@
         return isNewerThanOrEqual(start) && isOlderThanOrEqual(end);
       }
 
+      public boolean hasRecordsSupport() {
+        return isNewerThanOrEqual(V14_0_0);
+      }
+
       public String toString() {
         return shortName;
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
index 874a60c..c2846c5 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
@@ -5,12 +5,10 @@
 package com.android.tools.r8.desugar.records;
 
 
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -22,9 +20,9 @@
   private static final String RECORD_NAME = "EmptyRecordAnnotation";
   private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
   private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
-  private static final String EXPECTED_RESULT_CF =
+  private static final String EXPECTED_RESULT_NATIVE_RECORD =
       StringUtils.lines("class java.lang.Record", "class records.EmptyRecordAnnotation$Empty");
-  private static final String EXPECTED_RESULT_DEX =
+  private static final String EXPECTED_RESULT_DESUGARED_RECORD =
       StringUtils.lines(
           "class com.android.tools.r8.RecordTag", "class records.EmptyRecordAnnotation$Empty");
 
@@ -43,50 +41,49 @@
         .build();
   }
 
-  private boolean isDefaultCfParameters() {
-    return parameters.isCfRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addProgramClassFileData(PROGRAM_DATA)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_RECORD);
   }
 
   @Test
-  public void testD8AndJvm() throws Exception {
-    if (isDefaultCfParameters()) {
-      testForJvm(parameters)
-          .addProgramClassFileData(PROGRAM_DATA)
-          .run(parameters.getRuntime(), MAIN_TYPE)
-          .assertSuccessWithOutput(EXPECTED_RESULT_CF);
-    }
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_DEX);
+        .applyIf(
+            canUseNativeRecords(parameters),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_RECORD),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD));
   }
 
   @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
-    R8FullTestBuilder builder =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(PROGRAM_DATA)
-            .setMinApi(parameters)
-            .addKeepRules("-keep class records.EmptyRecordAnnotation { *; }")
-            .addKeepRules("-keepattributes *Annotation*")
-            .addKeepRules("-keep class records.EmptyRecordAnnotation$Empty")
-            .addKeepMainRule(MAIN_TYPE);
-    if (parameters.isCfRuntime()) {
-      builder
-          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-          .compile()
-          .inspect(RecordTestUtils::assertRecordsAreRecords)
-          .enableJVMPreview()
-          .run(parameters.getRuntime(), MAIN_TYPE)
-          .assertSuccessWithOutput(EXPECTED_RESULT_CF);
-      return;
-    }
-    builder
-        .addKeepRules("-keep class java.lang.Record")
+    boolean willDesugarRecords = parameters.isDexRuntime() && !canUseNativeRecords(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+        .addProgramClassFileData(PROGRAM_DATA)
+        .setMinApi(parameters)
+        .addKeepRules("-keep class records.EmptyRecordAnnotation { *; }")
+        .addKeepRules("-keepattributes *Annotation*")
+        .addKeepRules("-keep class records.EmptyRecordAnnotation$Empty")
+        .addKeepMainRule(MAIN_TYPE)
+        // This is used to avoid renaming com.android.tools.r8.RecordTag.
+        .applyIf(willDesugarRecords, b -> b.addKeepRules("-keep class java.lang.Record"))
+        .compile()
+        .applyIf(parameters.isCfRuntime(), r -> r.inspect(RecordTestUtils::assertRecordsAreRecords))
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_DEX);
+        .applyIf(
+            !willDesugarRecords,
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_RECORD),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
index bcc90c5..0fcb6da 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableMap;
-import java.nio.file.Path;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import org.junit.Test;
@@ -42,12 +41,12 @@
                   + CLASS
                   + " { <fields>; }",
               "a[name=%s]")
+          .put("-keep class " + CLASS + " { <fields>; }", "RecordBlog$Person[name=%s, age=42]")
           .put(
               "-keepclassmembers,allowobfuscation,allowoptimization class "
                   + CLASS
                   + " { <fields>; }",
               "a[a=%s, b=42]")
-          .put("-keep class " + CLASS + " { <fields>; }", "RecordBlog$Person[name=%s, age=42]")
           .build();
 
   @Parameter(0)
@@ -83,14 +82,16 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters)
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(computeOutput(REFERENCE_OUTPUT_FORMAT));
+        .applyIf(
+            canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
+            r -> r.assertSuccessWithOutput(computeOutput(REFERENCE_OUTPUT_FORMAT)));
   }
 
   @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
-    Path[] jdk15LibraryFiles = RecordTestUtils.getJdk15LibraryFiles(temp);
     Map<String, String> results = new IdentityHashMap<>();
     KEEP_RULE_TO_OUTPUT_FORMAT.forEach(
         (kr, outputFormat) -> {
@@ -105,7 +106,7 @@
             if (parameters.isCfRuntime()) {
               res =
                   builder
-                      .addLibraryFiles(jdk15LibraryFiles)
+                      .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
                       .run(parameters.getRuntime(), MAIN_TYPE)
                       .getStdOut();
             } else {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
index 14485c9..8ded32a 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
@@ -231,16 +231,7 @@
         parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
     boolean runtimeWithNativeRecordSupport =
         parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V14_0_0);
-    testForDesugaring(
-            parameters,
-            options -> {
-              if (compilingForNativeRecordSupport) {
-                // TODO(b/231930852): When Art 14 support records this will be controlled by API
-                // level.
-                options.emitRecordAnnotationsInDex = true;
-                options.emitRecordAnnotationsExInDex = true;
-              }
-            })
+    testForDesugaring(parameters)
         .addProgramClassFileData(PROGRAM_DATA)
         .run(parameters.getRuntime(), MAIN_TYPE)
         .applyIf(
@@ -340,12 +331,6 @@
             "records.RecordWithAnnotations$AnnotationRecordComponentOnly")
         .applyIf(keepAnnotations, TestShrinkerBuilder::addKeepRuntimeVisibleAnnotations)
         .setMinApi(parameters)
-        .applyIf(
-            compilingForNativeRecordSupport,
-            // TODO(b/231930852): When Art 14 support records this will be controlled by API level.
-            b ->
-                b.addOptionsModification(options -> options.emitRecordAnnotationsInDex = true)
-                    .addOptionsModification(options -> options.emitRecordAnnotationsExInDex = true))
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
index db46699..ff6732a 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
@@ -98,16 +98,7 @@
     boolean runningWithNativeRecordSupport =
         parameters.getRuntime().isDex()
             && parameters.getRuntime().asDex().getVersion().isNewerThanOrEqual(Version.V14_0_0);
-    testForDesugaring(
-            parameters,
-            options -> {
-              if (compilingForNativeRecordSupport) {
-                // TODO(b/231930852): When Art 14 support records this will be controlled by API
-                // level.
-                options.emitRecordAnnotationsInDex = true;
-                options.emitRecordAnnotationsExInDex = true;
-              }
-            })
+    testForDesugaring(parameters)
         .addProgramClassFileData(PROGRAM_DATA)
         .run(parameters.getRuntime(), MAIN_TYPE)
         .applyIf(
@@ -175,12 +166,6 @@
         .addKeepMainRule(MAIN_TYPE)
         .applyIf(keepSignatures, TestShrinkerBuilder::addKeepAttributeSignature)
         .setMinApi(parameters)
-        .applyIf(
-            compilingForNativeRecordSupport,
-            // TODO(b/231930852): When Art 14 support records this will be controlled by API level.
-            b ->
-                b.addOptionsModification(options -> options.emitRecordAnnotationsInDex = true)
-                    .addOptionsModification(options -> options.emitRecordAnnotationsExInDex = true))
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
index 2136f70..0c63278 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.records;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.GlobalSyntheticsConsumer;
@@ -64,7 +65,10 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters)
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
+        .applyIf(
+            canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
   }
 
   @Test
@@ -75,7 +79,9 @@
     Path path = compileIntermediate(globals);
     testForD8()
         .addProgramFiles(path)
-        .apply(
+        .applyIf(
+            canUseNativeRecords(parameters),
+            b -> assertFalse(globals.hasGlobals()),
             b ->
                 b.getBuilder()
                     .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
@@ -97,7 +103,9 @@
     Path path = compileIntermediate(globals);
     testForD8()
         .addProgramFiles(path)
-        .apply(
+        .applyIf(
+            canUseNativeRecords(parameters),
+            b -> assertFalse(globals.hasGlobals()),
             b ->
                 b.getBuilder()
                     .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
index 8f33d07..214afea 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
@@ -4,17 +4,14 @@
 
 package com.android.tools.r8.desugar.records;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.errors.DuplicateTypeInProgramAndLibraryDiagnostic;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import java.nio.file.Path;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,10 +36,13 @@
           "true",
           "false",
           "false",
-          "%s[name=Jane Doe, age=42]");
+          "%s[%s=Jane Doe, %s=42]");
   private static final String EXPECTED_RESULT_D8 =
-      String.format(EXPECTED_RESULT, "Empty", "Person");
-  private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT, "a", "b");
+      String.format(EXPECTED_RESULT, "Empty", "Person", "name", "age");
+  private static final String EXPECTED_RESULT_R8 =
+      String.format(EXPECTED_RESULT, "a", "b", "name", "age");
+  private static final String EXPECTED_RESULT_R8_2 =
+      String.format(EXPECTED_RESULT, "a", "b", "a", "b");
 
   private final TestParameters parameters;
 
@@ -80,32 +80,17 @@
             .setMinApi(parameters)
             .compile()
             .writeToZip();
+    if (canUseNativeRecords(parameters)) {
+      assertFalse(ZipUtils.containsEntry(desugared, "com/android/tools/r8/RecordTag.class"));
+    } else {
+      assertTrue(ZipUtils.containsEntry(desugared, "com/android/tools/r8/RecordTag.class"));
+    }
     testForR8(parameters.getBackend())
         .addProgramFiles(desugared)
         .setMinApi(parameters)
         .addKeepMainRule(MAIN_TYPE)
-        .applyIf(
-            parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U),
-            b -> b.allowDiagnosticMessages())
-        .compileWithExpectedDiagnostics(
-            // Type com.android.tools.r8.RecordTag from desugared code will be converted to
-            // java.lang.Record during reading causing duplicate java.lang.Record class.
-            parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U)
-                ? diagnostics ->
-                    diagnostics
-                        .assertWarningsMatch(
-                            diagnosticMessage(
-                                containsString(
-                                    "The following library types, prefixed by java., are present"
-                                        + " both as library and non library classes:"
-                                        + " java.lang.Record.")))
-                        .assertInfosMatch(
-                            allOf(
-                                diagnosticType(DuplicateTypeInProgramAndLibraryDiagnostic.class),
-                                diagnosticMessage(containsString("java.lang.Record"))))
-                        .assertNoErrors()
-                : diagnostics -> diagnostics.assertNoMessages())
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_R8);
+        .assertSuccessWithOutput(
+            canUseNativeRecords(parameters) ? EXPECTED_RESULT_R8_2 : EXPECTED_RESULT_R8);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index dfa8cd5..38954bf 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -6,12 +6,13 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 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.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
@@ -67,20 +68,25 @@
   }
 
   @Test
-  public void testFailureWithoutGlobalSyntheticsConsumer() {
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(PROGRAM_DATA_1)
-                .setMinApi(parameters)
-                .setIntermediate(true)
-                .compileWithExpectedDiagnostics(
-                    diagnostics ->
-                        diagnostics
-                            .assertOnlyErrors()
-                            .assertErrorsMatch(
-                                diagnosticType(MissingGlobalSyntheticsConsumerDiagnostic.class))));
+  public void testNoGlobalSyntheticsConsumer() throws Exception {
+    D8TestBuilder builder =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA_1)
+            .setMinApi(parameters)
+            .setIntermediate(true);
+    if (canUseNativeRecords(parameters)) {
+      builder.compile();
+    } else {
+      assertThrows(
+          CompilationFailedException.class,
+          () ->
+              builder.compileWithExpectedDiagnostics(
+                  diagnostics ->
+                      diagnostics
+                          .assertOnlyErrors()
+                          .assertErrorsMatch(
+                              diagnosticType(MissingGlobalSyntheticsConsumerDiagnostic.class))));
+    }
   }
 
   @Test
@@ -123,8 +129,8 @@
             .inspect(this::assertDoesNotHaveRecordTag)
             .writeToZip();
 
-    assertTrue(globals1.hasGlobals());
-    assertTrue(globals2.hasGlobals());
+    assertTrue(canUseNativeRecords(parameters) ^ globals1.hasGlobals());
+    assertTrue(canUseNativeRecords(parameters) ^ globals2.hasGlobals());
 
     D8TestCompileResult result =
         testForD8(parameters.getBackend())
@@ -138,8 +144,18 @@
             .compile()
             .inspect(this::assertHasRecordTag);
 
-    result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
-    result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
+    result
+        .run(parameters.getRuntime(), MAIN_TYPE_1)
+        .applyIf(
+            canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1));
+    result
+        .run(parameters.getRuntime(), MAIN_TYPE_2)
+        .applyIf(
+            canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2));
   }
 
   @Test
@@ -162,8 +178,18 @@
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters)
             .compile();
-    result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
-    result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
+    result
+        .run(parameters.getRuntime(), MAIN_TYPE_1)
+        .applyIf(
+            canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1));
+    result
+        .run(parameters.getRuntime(), MAIN_TYPE_2)
+        .applyIf(
+            canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2));
   }
 
   @Test
@@ -184,22 +210,44 @@
             .inspect(this::assertHasRecordTag)
             .writeToZip();
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramFiles(output1, output2)
-                .setMinApi(parameters)
-                .compileWithExpectedDiagnostics(
-                    diagnostics ->
-                        diagnostics
-                            .assertOnlyErrors()
-                            .assertErrorsMatch(diagnosticType(DuplicateTypesDiagnostic.class))));
+    if (canUseNativeRecords(parameters)) {
+      D8TestCompileResult result =
+          testForD8(parameters.getBackend())
+              .addProgramFiles(output1, output2)
+              .setMinApi(parameters)
+              .compile();
+      result
+          .run(parameters.getRuntime(), MAIN_TYPE_1)
+          .applyIf(
+              canUseNativeRecords(parameters)
+                  && !runtimeWithRecordsSupport(parameters.getRuntime()),
+              r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+              r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1));
+      result
+          .run(parameters.getRuntime(), MAIN_TYPE_2)
+          .applyIf(
+              canUseNativeRecords(parameters)
+                  && !runtimeWithRecordsSupport(parameters.getRuntime()),
+              r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+              r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2));
+    } else {
+      assertThrows(
+          CompilationFailedException.class,
+          () ->
+              testForD8(parameters.getBackend())
+                  .addProgramFiles(output1, output2)
+                  .setMinApi(parameters)
+                  .compileWithExpectedDiagnostics(
+                      diagnostics ->
+                          diagnostics
+                              .assertOnlyErrors()
+                              .assertErrorsMatch(diagnosticType(DuplicateTypesDiagnostic.class))));
+    }
   }
 
   private void assertHasRecordTag(CodeInspector inspector) {
     // Note: this should be asserting on record tag.
-    assertThat(inspector.clazz("java.lang.Record"), isPresent());
+    assertThat(inspector.clazz("java.lang.Record"), isPresentIf(!canUseNativeRecords(parameters)));
   }
 
   private void assertDoesNotHaveRecordTag(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
index 0262140..428563a 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
@@ -34,6 +34,8 @@
   private static final String EXPECTED_RESULT_D8 =
       String.format(EXPECTED_RESULT_FORMAT, "MyRecordWithConstClass", "theClass");
   private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT_FORMAT, "a", "a");
+  private static final String EXPECTED_RESULT_R8_ART14 =
+      String.format(EXPECTED_RESULT_FORMAT, "a", "theClass");
 
   @Parameter(0)
   public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index e208949..5ed96ab 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -4,18 +4,21 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.GlobalSyntheticsConsumer;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,9 +48,14 @@
   @Parameter(0)
   public TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  @Parameter(1)
+  public boolean forceInvokeRangeForInvokeCustom;
+
+  @Parameters(name = "{0}, forceInvokeRangeForInvokeCustom: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
   }
 
   private boolean isCfRuntimeWithNativeRecordSupport() {
@@ -59,6 +67,7 @@
   @Test
   public void testReference() throws Exception {
     assumeTrue(isCfRuntimeWithNativeRecordSupport());
+    assumeFalse(forceInvokeRangeForInvokeCustom);
     testForJvm(parameters)
         .addProgramClassFileData(PROGRAM_DATA)
         .run(parameters.getRuntime(), MAIN_TYPE)
@@ -67,6 +76,8 @@
 
   @Test
   public void testD8() throws Exception {
+    assumeFalse(forceInvokeRangeForInvokeCustom);
+    boolean runningWithNativeRecordSupport = runtimeWithRecordsSupport(parameters.getRuntime());
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters)
@@ -75,17 +86,24 @@
             RecordTestUtils::assertNoJavaLangRecord,
             options -> options.testing.disableRecordApplicationReaderMap = true)
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
+        .applyIf(
+            canUseNativeRecords(parameters) && !runningWithNativeRecordSupport,
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
+    ;
   }
 
   @Test
   public void testD8Intermediate() throws Exception {
     assumeTrue(parameters.isDexRuntime());
+    assumeFalse(forceInvokeRangeForInvokeCustom);
     GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
     Path path = compileIntermediate(globals);
     testForD8()
         .addProgramFiles(path)
-        .apply(
+        .applyIf(
+            parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U),
+            b -> assertFalse(globals.hasGlobals()),
             b ->
                 b.getBuilder()
                     .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
@@ -98,12 +116,15 @@
   @Test
   public void testD8IntermediateNoDesugaringInStep2() throws Exception {
     assumeTrue(parameters.isDexRuntime());
+    assumeFalse(forceInvokeRangeForInvokeCustom);
     GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
     Path path = compileIntermediate(globals);
     // In Android Studio they disable desugaring at this point to improve build speed.
     testForD8()
         .addProgramFiles(path)
-        .apply(
+        .applyIf(
+            parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U),
+            b -> assertFalse(globals.hasGlobals()),
             b ->
                 b.getBuilder()
                     .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
@@ -130,8 +151,13 @@
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+    assumeTrue(forceInvokeRangeForInvokeCustom || !parameters.isDexRuntime());
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
+            .addOptionsModification(
+                opptions ->
+                    opptions.testing.forceInvokeRangeForInvokeCustom =
+                        forceInvokeRangeForInvokeCustom)
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters)
             .addKeepMainRule(MAIN_TYPE);
@@ -140,6 +166,10 @@
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
           .compile()
           .inspect(RecordTestUtils::assertRecordsAreRecords)
+          .inspect(
+              inspector -> {
+                inspector.clazz("records.SimpleRecord$Person").isRenamed();
+              })
           .run(parameters.getRuntime(), MAIN_TYPE)
           .assertSuccessWithOutput(EXPECTED_RESULT);
       return;
@@ -157,6 +187,7 @@
   public void testR8NoMinification() throws Exception {
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+    assumeTrue(forceInvokeRangeForInvokeCustom || !parameters.isDexRuntime());
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
index cc01489..4934f16 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
@@ -39,6 +39,7 @@
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
+  private static final String EXPECTED_OUTPUT_PRE_34 = StringUtils.lines("false");
   private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
 
   @Test
@@ -56,9 +57,12 @@
 
   private void inspect(CodeInspector inspector) {
     assertEquals(
-        ImmutableList.of(
-            inspector.clazz(Sub1.class).asTypeSubject(),
-            inspector.clazz(Sub2.class).asTypeSubject()),
+        parameters.getBackend().isDex()
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U)
+            ? ImmutableList.of(
+                inspector.clazz(Sub1.class).asTypeSubject(),
+                inspector.clazz(Sub2.class).asTypeSubject())
+            : ImmutableList.of(),
         inspector.clazz(C.class).getFinalPermittedSubclassAttributes());
   }
 
@@ -69,14 +73,17 @@
         .addProgramClassFileData(getTransformedClasses())
         .addProgramClasses(Sub1.class, Sub2.class)
         .setMinApi(parameters)
-        .addOptionsModification(options -> options.emitPermittedSubclassesAnnotationsInDex = true)
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
             // TODO(b/270941147): Partial DEX support in Android U DP1 (reflective APIs).
             parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V14_0_0),
-            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+            r ->
+                r.assertSuccessWithOutput(
+                    parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U)
+                        ? EXPECTED_OUTPUT
+                        : EXPECTED_OUTPUT_PRE_34),
             r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
   }
 
@@ -135,10 +142,12 @@
 
     public static void main(String[] args) {
       System.out.println(AdditionalClassAPIs.isSealed(C.class));
-      System.out.println(
-          sameArrayContent(
-              new Class<?>[] {Sub1.class, Sub2.class},
-              AdditionalClassAPIs.getPermittedSubclasses(C.class)));
+      if (AdditionalClassAPIs.isSealed(C.class)) {
+        System.out.println(
+            sameArrayContent(
+                new Class<?>[] {Sub1.class, Sub2.class},
+                AdditionalClassAPIs.getPermittedSubclasses(C.class)));
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
index 592281a..31b779c 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -19,6 +19,7 @@
 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.desugar.records.RecordTestUtils;
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
 import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
@@ -78,7 +79,7 @@
   @Test
   public void testReference() throws Exception {
     parameters.assumeJvmTestParameters();
-    assumeTrue(parameters.canUseRecords());
+    assumeTrue(runtimeWithRecordsSupport(parameters.getRuntime()));
     testForJvm(parameters)
         .addProgramClassFileData(PROGRAM_DATA)
         .run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
@@ -100,13 +101,16 @@
                     inspector -> inspectD8(profileInspector, inspector),
                     options -> options.testing.disableRecordApplicationReaderMap = true))
         .run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
-        .assertSuccessWithOutput(EXPECTED_RESULT);
+        .applyIf(
+            canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
   }
 
   @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
-    assumeTrue(parameters.canUseRecords() || parameters.isDexRuntime());
+    assumeTrue(runtimeWithRecordsSupport(parameters.getRuntime()) || parameters.isDexRuntime());
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
@@ -162,7 +166,7 @@
         SyntheticItemsTestUtils.syntheticRecordTagClass(),
         false,
         parameters.canUseNestBasedAccessesWhenDesugaring(),
-        parameters.canUseRecordsWhenDesugaring());
+        canUseNativeRecords(parameters));
   }
 
   private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) {
@@ -172,7 +176,7 @@
         RECORD_REFERENCE,
         parameters.canHaveNonReboundConstructorInvoke(),
         parameters.canUseNestBasedAccesses(),
-        parameters.canUseRecords());
+        canUseNativeRecords(parameters) || parameters.isCfRuntime());
   }
 
   private void inspect(
@@ -207,7 +211,15 @@
             ? inspector.getTypeSubject(RECORD_REFERENCE.getTypeName())
             : recordTagClassSubject.asTypeSubject(),
         personRecordClassSubject.getSuperType());
-    assertEquals(canUseRecords ? 6 : 10, personRecordClassSubject.allMethods().size());
+    assertEquals(
+        canUseRecords
+            ? (parameters.isCfRuntime()
+                    && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)
+                    && !canUseNativeRecords(parameters)
+                ? 6
+                : 8)
+            : 10,
+        personRecordClassSubject.allMethods().size());
 
     MethodSubject personInstanceInitializerSubject =
         personRecordClassSubject.uniqueInstanceInitializer();