Merge commit '501efac45091692d75c0ceef53e8a9e2368ca0d5' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index c867e02..58753de 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -181,6 +181,15 @@
 
   private static void run(AndroidApp inputApp, InternalOptions options, ExecutorService executor)
       throws IOException {
+    if (options.printMemory) {
+      // Run GC twice to remove objects with finalizers.
+      System.gc();
+      System.gc();
+      Runtime runtime = Runtime.getRuntime();
+      System.out.println("D8 is running with total memory:" + runtime.totalMemory());
+      System.out.println("D8 is running with free memory:" + runtime.freeMemory());
+      System.out.println("D8 is running with max memory:" + runtime.maxMemory());
+    }
     Timing timing = Timing.create("D8", options);
     try {
       // Disable global optimizations.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ec0f361..d68ff6b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterLibraryTypeSynthesizer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordFieldValuesRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -282,6 +283,15 @@
     }
     // Synthetic assertion to check that testing assertions works and can be enabled.
     assert forTesting(options, () -> !options.testing.testEnableTestAssertions);
+    if (options.printMemory) {
+      // Run GC twice to remove objects with finalizers.
+      System.gc();
+      System.gc();
+      Runtime runtime = Runtime.getRuntime();
+      System.out.println("R8 is running with total memory:" + runtime.totalMemory());
+      System.out.println("R8 is running with free memory:" + runtime.freeMemory());
+      System.out.println("R8 is running with max memory:" + runtime.maxMemory());
+    }
     try {
       AppView<AppInfoWithClassHierarchy> appView;
       {
@@ -688,6 +698,14 @@
 
       performFinalMainDexTracing(appView, executorService);
 
+      if (appView.appInfo().hasLiveness()) {
+        RecordFieldValuesRewriter recordFieldArrayRemover =
+            RecordFieldValuesRewriter.create(appView.withLiveness());
+        if (recordFieldArrayRemover != null) {
+          recordFieldArrayRemover.rewriteRecordFieldValues();
+        }
+      }
+
       // Remove unneeded visibility bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
       if (appView.appInfo().hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 480c9cb..98808c7 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfRecordFieldValues;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
@@ -254,6 +255,14 @@
     builder.append(name);
   }
 
+  public void print(CfRecordFieldValues recordFieldValues) {
+    String recordType =
+        recordFieldValues.getFields().length == 0
+            ? "empty"
+            : recordFieldValues.getFields()[0].holder.toString();
+    print("record_field_values(" + recordType + ")");
+  }
+
   public void print(CfNop nop) {
     print("nop");
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index ab73479..742a2be 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -110,6 +110,14 @@
     return true;
   }
 
+  public CfRecordFieldValues asRecordFieldValues() {
+    return null;
+  }
+
+  public boolean isRecordFieldValues() {
+    return false;
+  }
+
   public CfConstString asConstString() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
new file mode 100644
index 0000000..b5b2275
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -0,0 +1,118 @@
+// 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfRecordFieldValues extends CfInstruction {
+
+  private final DexField[] fields;
+
+  public CfRecordFieldValues(DexField[] fields) {
+    this.fields = fields;
+  }
+
+  private static void specify(StructuralSpecification<CfRecordFieldValues, ?> spec) {
+    spec.withItemArray(f -> f.fields);
+  }
+
+  @Override
+  public void write(
+      AppView<?> appView,
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public CfRecordFieldValues asRecordFieldValues() {
+    return this;
+  }
+
+  @Override
+  public boolean isRecordFieldValues() {
+    return true;
+  }
+
+  public DexField[] getFields() {
+    return fields;
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.RECORD_FIELD_VALUES_COMPARE_ID;
+  }
+
+  @Override
+  public int internalAcceptCompareTo(
+      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+    return visitor.visit(this, other.asRecordFieldValues(), CfRecordFieldValues::specify);
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int parameterCount = fields.length;
+    int[] registers = new int[parameterCount];
+    for (int i = parameterCount - 1; i >= 0; i--) {
+      Slot slot = state.pop();
+      registers[i] = slot.register;
+    }
+    builder.addRecordFieldValues(
+        fields,
+        IntArrayList.wrap(registers),
+        state.push(builder.dexItemFactory().objectArrayType).register);
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    return inliningConstraints.forRecordFieldValues();
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    for (DexField ignored : fields) {
+      frameBuilder.popInitialized(factory.objectType);
+    }
+    frameBuilder.push(factory.objectArrayType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/DexCompareHelper.java b/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
index 0587c08..130e69b 100644
--- a/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
+++ b/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
@@ -9,6 +9,7 @@
   // virtual instructions represented in our internal encoding.
   static final int INIT_CLASS_COMPARE_ID;
   static final int DEX_ITEM_CONST_STRING_COMPARE_ID;
+  static final int DEX_RECORD_FIELD_VALUES_COMPARE_ID;
 
   private static int HIGHEST_DEX_OPCODE = 0xFF;
 
@@ -16,6 +17,7 @@
     int lastId = HIGHEST_DEX_OPCODE;
     INIT_CLASS_COMPARE_ID = ++lastId;
     DEX_ITEM_CONST_STRING_COMPARE_ID = ++lastId;
+    DEX_RECORD_FIELD_VALUES_COMPARE_ID = ++lastId;
   }
 
   // Helper to signal that the concrete instruction is uniquely determined by its ID/opcode.
diff --git a/src/main/java/com/android/tools/r8/code/DexRecordFieldValues.java b/src/main/java/com/android/tools/r8/code/DexRecordFieldValues.java
new file mode 100644
index 0000000..13cde75
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/DexRecordFieldValues.java
@@ -0,0 +1,147 @@
+// 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.code;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+
+public class DexRecordFieldValues extends Instruction {
+
+  public static final String NAME = "RecordFieldValues";
+  public static final String SMALI_NAME = "record-field-values*";
+
+  private final int outRegister;
+  private final int[] arguments;
+  private final DexField[] fields;
+
+  public DexRecordFieldValues(int outRegister, int[] arguments, DexField[] fields) {
+    this.outRegister = outRegister;
+    this.arguments = arguments;
+    this.fields = fields;
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    for (DexField field : fields) {
+      field.collectIndexedItems(indexedItems);
+    }
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+
+  @Override
+  public String getSmaliName() {
+    return SMALI_NAME;
+  }
+
+  @Override
+  public int getOpcode() {
+    throw new Unreachable(
+        "DexRecordFieldValues instructions should always be rewritten into NewArray");
+  }
+
+  @Override
+  public int getSize() {
+    return 2;
+  }
+
+  @Override
+  int getCompareToId() {
+    return DexCompareHelper.DEX_RECORD_FIELD_VALUES_COMPARE_ID;
+  }
+
+  @Override
+  int internalAcceptCompareTo(Instruction other, CompareToVisitor visitor) {
+    return visitor.visit(this, (DexRecordFieldValues) other, DexRecordFieldValues::specify);
+  }
+
+  private static void specify(StructuralSpecification<DexRecordFieldValues, ?> spec) {
+    spec.withItemArray(i -> i.fields);
+  }
+
+  private void appendArguments(StringBuilder builder) {
+    builder.append("{ ");
+    for (int i = 0; i < arguments.length; i++) {
+      if (i != 0) {
+        builder.append(",");
+      }
+      builder.append("v").append(arguments[i]);
+    }
+    builder.append(" }");
+  }
+
+  @Override
+  public String toString(ClassNameMapper naming) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("v").append(outRegister).append(" ");
+    appendArguments(sb);
+    return formatString(sb.toString());
+  }
+
+  @Override
+  public String toSmaliString(ClassNameMapper naming) {
+    return toString(naming);
+  }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    throw new Unreachable(
+        "DexRecordFieldValues instructions should always be rewritten into NewArray");
+  }
+
+  @Override
+  public boolean isRecordFieldValues() {
+    return true;
+  }
+
+  @Override
+  public void registerUse(UseRegistry registry) {
+    registry.registerRecordFieldValues(fields);
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder) {
+    IntList parameters = new IntArrayList();
+    for (int i = 0; i < arguments.length; i++) {
+      parameters.add(arguments[i]);
+    }
+    builder.addRecordFieldValues(fields, parameters, outRegister);
+  }
+
+  @Override
+  public int hashCode() {
+    return 31 * getClass().hashCode() + Arrays.hashCode(fields);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 5a10577..03d7a3d 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -183,6 +183,10 @@
     return false;
   }
 
+  public boolean isRecordFieldValues() {
+    return false;
+  }
+
   public DexItemBasedConstString asDexItemBasedConstString() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
index d3c3351..063123a 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
@@ -30,6 +30,7 @@
   public static final int INIT_CLASS_COMPARE_ID;
   public static final int LABEL_COMPARE_ID;
   public static final int POSITION_COMPARE_ID;
+  public static final int RECORD_FIELD_VALUES_COMPARE_ID;
 
   static {
     int lastId = Opcodes.IFNONNULL;
@@ -44,6 +45,7 @@
     INIT_CLASS_COMPARE_ID = ++lastId;
     LABEL_COMPARE_ID = ++lastId;
     POSITION_COMPARE_ID = ++lastId;
+    RECORD_FIELD_VALUES_COMPARE_ID = ++lastId;
   }
 
   // Helper to signal that the concrete instruction is uniquely determined by its ID/opcode.
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 22eaec8..c1c3fb7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1329,7 +1329,7 @@
   }
 
   public class RecordMembers {
-    public final DexMethod init = createMethod(recordType, createProto(voidType), "<init>");
+    public final DexMethod constructor = createMethod(recordType, createProto(voidType), "<init>");
     public final DexMethod equals =
         createMethod(recordType, createProto(booleanType, objectType), "equals");
     public final DexMethod hashCode = createMethod(recordType, createProto(intType), "hashCode");
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index bea5b47..2cd2be0 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -42,6 +42,8 @@
 
   boolean isReadFromAnnotation();
 
+  boolean isReadFromRecordInvokeDynamic();
+
   boolean isReadFromMethodHandle();
 
   boolean isWritten();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 4edec8a..0a7d218 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -28,6 +28,7 @@
   public static int FLAG_IS_READ_FROM_METHOD_HANDLE = 1 << 1;
   public static int FLAG_IS_WRITTEN_FROM_METHOD_HANDLE = 1 << 2;
   public static int FLAG_HAS_REFLECTIVE_ACCESS = 1 << 3;
+  public static int FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC = 1 << 4;
 
   // A direct reference to the definition of the field.
   private DexField field;
@@ -189,7 +190,10 @@
   /** Returns true if this field is read by the program. */
   @Override
   public boolean isRead() {
-    return !readsWithContexts.isEmpty() || isReadFromAnnotation() || isReadFromMethodHandle();
+    return !readsWithContexts.isEmpty()
+        || isReadFromAnnotation()
+        || isReadFromMethodHandle()
+        || isReadFromRecordInvokeDynamic();
   }
 
   @Override
@@ -206,10 +210,23 @@
     return (flags & FLAG_IS_READ_FROM_METHOD_HANDLE) != 0;
   }
 
+  @Override
+  public boolean isReadFromRecordInvokeDynamic() {
+    return (flags & FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC) != 0;
+  }
+
   public void setReadFromMethodHandle() {
     flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
   }
 
+  public void setReadFromRecordInvokeDynamic() {
+    flags |= FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
+  }
+
+  public void clearReadFromRecordInvokeDynamic() {
+    flags &= ~FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
+  }
+
   /** Returns true if this field is written by the program. */
   @Override
   public boolean isWritten() {
@@ -279,7 +296,11 @@
   }
 
   public void clearReads() {
+    assert !hasReflectiveAccess();
+    assert !isReadFromAnnotation();
+    assert !isReadFromMethodHandle();
     readsWithContexts = AbstractAccessContexts.empty();
+    clearReadFromRecordInvokeDynamic();
   }
 
   public void clearWrites() {
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index c3912ca..4dd921c 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -51,6 +51,10 @@
     return continuation;
   }
 
+  public void registerRecordFieldValues(DexField[] fields) {
+    registerTypeReference(appView.dexItemFactory().objectArrayType);
+  }
+
   public abstract void registerInitClass(DexType type);
 
   public abstract void registerInvokeVirtual(DexMethod method);
@@ -178,7 +182,7 @@
     }
   }
 
-  public void registerCallSite(DexCallSite callSite) {
+  protected void registerCallSiteExceptBootstrapArgs(DexCallSite callSite) {
     boolean isLambdaMetaFactory =
         dexItemFactory().isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
 
@@ -190,10 +194,17 @@
     // Lambda metafactory will use this type as the main SAM
     // interface for the dynamically created lambda class.
     registerTypeReference(callSite.methodProto.returnType);
+  }
 
+  protected void registerCallSiteBootstrapArgs(DexCallSite callSite, int start, int end) {
+    boolean isLambdaMetaFactory =
+        appView.dexItemFactory().isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
     // Register bootstrap method arguments.
     // Only Type, MethodHandle, and MethodType need to be registered.
-    for (DexValue arg : callSite.bootstrapArgs) {
+    assert start >= 0;
+    assert end <= callSite.bootstrapArgs.size();
+    for (int i = start; i < end; i++) {
+      DexValue arg = callSite.bootstrapArgs.get(i);
       switch (arg.getValueKind()) {
         case METHOD_HANDLE:
           DexMethodHandle handle = arg.asDexValueMethodHandle().value;
@@ -219,6 +230,11 @@
     }
   }
 
+  public void registerCallSite(DexCallSite callSite) {
+    registerCallSiteExceptBootstrapArgs(callSite);
+    registerCallSiteBootstrapArgs(callSite, 0, callSite.bootstrapArgs.size());
+  }
+
   public void registerProto(DexProto proto) {
     registerTypeReference(proto.returnType);
     for (DexType type : proto.parameters.values) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 2e237a2..7e6d5d3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -258,6 +258,7 @@
 
     if (fieldAccessInfo.hasReflectiveAccess()
         || fieldAccessInfo.isAccessedFromMethodHandle()
+        || fieldAccessInfo.isReadFromRecordInvokeDynamic()
         || fieldAccessInfo.isReadFromAnnotation()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index dd29130..30060d9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -20,11 +20,11 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
 import com.android.tools.r8.ir.analysis.value.NullOrAbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.EnumValuesObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index ec51f91..0b9a6cd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.google.common.collect.ImmutableMap;
 
 public abstract class StaticFieldValues {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
index f9a2e25..9a61590 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
@@ -32,12 +31,6 @@
     // Already handled above.
     assert !appView.dexItemFactory().classMethods.isReflectiveNameLookup(invokedMethod);
 
-    // Modeling of other library methods.
-    DexType holder = invokedMethod.holder;
-    if (holder == appView.dexItemFactory().objectType
-        && invokedMethod == appView.dexItemFactory().objectMembers.constructor) {
-      return EmptyFieldSet.getInstance();
-    }
     return UnknownFieldSet.getInstance();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
index f8c2bf0..5cbf665 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 09c661c..e1cdb6c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import java.util.concurrent.ConcurrentHashMap;
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index a5d76c3..71a6dbf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.StaticGet;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
index e9b7fde..d14f5a9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import java.util.Objects;
 
 public class SingleStatefulFieldValue extends SingleFieldValue {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
index 80de8a3..809b365 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 
 public class SingleStatelessFieldValue extends SingleFieldValue {
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
similarity index 83%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
index f8056dc..1b94542 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
@@ -1,13 +1,15 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
 
 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.GraphLens;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.function.BiConsumer;
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
index 13686eb..397e79f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
@@ -1,13 +1,15 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
 
 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.GraphLens;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Arrays;
 import java.util.Objects;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
index 567449e..559fa77 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
@@ -1,13 +1,15 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
 
 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.GraphLens;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.IdentityHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
index 012a204..f709c59 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
@@ -1,14 +1,16 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
 
 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.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.IdentityHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
index f8793ab..64b59e4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -290,6 +290,11 @@
   }
 
   @Override
+  public T visit(RecordFieldValues instruction) {
+    return null;
+  }
+
+  @Override
   public T visit(Rem instruction) {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 9bccbfc..090fb45 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -700,6 +700,14 @@
     return this;
   }
 
+  public boolean isRecordFieldValues() {
+    return false;
+  }
+
+  public RecordFieldValues asRecordFieldValues() {
+    return null;
+  }
+
   public boolean isArrayAccess() {
     return false;
   }
@@ -1400,7 +1408,8 @@
     return isNewArrayEmpty()
         || isNewArrayFilledData()
         || isInvokeNewArray()
-        || isInvokeMultiNewArray();
+        || isInvokeMultiNewArray()
+        || isRecordFieldValues();
   }
 
   public boolean isCreatingInstanceOrArray() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index 8aa631d..ddb611f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -118,6 +118,8 @@
 
   T visit(Pop instruction);
 
+  T visit(RecordFieldValues instruction);
+
   T visit(Rem instruction);
 
   T visit(Return instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index d2c79d4..f1527a6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -75,4 +75,5 @@
   int USHR = 66;
   int XOR = 67;
   int UNINITIALIZED_THIS_LOCAL_READ = 68;
+  int RECORD_FIELD_VALUES = 69;
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
new file mode 100644
index 0000000..00df561
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
@@ -0,0 +1,119 @@
+// 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.code;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.code.CfRecordFieldValues;
+import com.android.tools.r8.code.DexRecordFieldValues;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import java.util.Arrays;
+import java.util.List;
+
+public class RecordFieldValues extends Instruction {
+
+  private final DexField[] fields;
+
+  public RecordFieldValues(DexField[] fields, Value outValue, List<Value> fieldValues) {
+    super(outValue, fieldValues);
+    this.fields = fields;
+  }
+
+  public DexField[] getFields() {
+    return fields;
+  }
+
+  @Override
+  public int opcode() {
+    return Opcodes.RECORD_FIELD_VALUES;
+  }
+
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
+  public RecordFieldValues asRecordFieldValues() {
+    return this;
+  }
+
+  @Override
+  public boolean isRecordFieldValues() {
+    return true;
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    // There are no restrictions on the registers since this instruction eventually has to be
+    // removed through IR.
+    int[] arguments = new int[inValues().size()];
+    for (int i = 0; i < inValues().size(); i++) {
+      arguments[i] = builder.allocatedRegister(inValues().get(i), getNumber());
+    }
+    int dest = builder.allocatedRegister(outValue(), getNumber());
+    builder.add(this, new DexRecordFieldValues(dest, arguments, fields));
+  }
+
+  @Override
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(
+        appView.dexItemFactory().objectArrayType, Nullability.definitelyNotNull(), appView);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfRecordFieldValues(fields));
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    if (!other.isRecordFieldValues()) {
+      return false;
+    }
+    RecordFieldValues o = other.asRecordFieldValues();
+    return Arrays.equals(o.fields, fields);
+  }
+
+  @Override
+  public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
+    return false;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    return Constants.U16BIT_MAX;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    return Constants.U16BIT_MAX;
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, ProgramMethod context) {
+    return inliningConstraints.forRecordFieldValues();
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.loadInValues(this, it);
+    helper.storeOutValue(this, it);
+  }
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 8f271a1..40c7bb9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -104,6 +104,7 @@
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Phi.StackMapPhi;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.RecordFieldValues;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.SafeCheckCast;
@@ -1557,6 +1558,20 @@
     add(instruction);
   }
 
+  public void addRecordFieldValues(DexField[] fields, IntList registers, int outValue) {
+    List<Value> arguments = new ArrayList<>();
+    for (int register : registers) {
+      arguments.add(readRegister(register, ValueTypeConstraint.OBJECT));
+    }
+    Value out =
+        writeRegister(
+            outValue,
+            TypeElement.fromDexType(
+                appView.dexItemFactory().objectArrayType, definitelyNotNull(), appView),
+            ThrowingInfo.CAN_THROW);
+    add(new RecordFieldValues(fields, out, arguments));
+  }
+
   public void addInvoke(
       Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf) {
     if (type == Type.POLYMORPHIC) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 293635b..07ba0ce 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -189,7 +189,7 @@
     this.appView = appView;
     this.options = appView.options();
     this.printer = printer;
-    this.codeRewriter = new CodeRewriter(appView, this);
+    this.codeRewriter = new CodeRewriter(appView);
     this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
     this.classInitializerDefaultsOptimization =
         new ClassInitializerDefaultsOptimization(appView, this);
@@ -1141,7 +1141,7 @@
     String previous = printMethod(code, "Initial IR (SSA)", null);
 
     if (options.testing.irModifier != null) {
-      options.testing.irModifier.accept(code);
+      options.testing.irModifier.accept(code, appView);
     }
 
     if (options.canHaveArtStringNewInitBug()) {
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
index 76c468a6..b5e08d5 100644
--- 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
@@ -55,10 +55,7 @@
         recordInvokeDynamic
             .computeRecordFieldNamesComputationInfo()
             .internalComputeNameFor(
-                recordInvokeDynamic.getRecordClass().type,
-                appView,
-                appView.graphLens(),
-                namingLens);
+                recordInvokeDynamic.getRecordType(), appView, appView.graphLens(), namingLens);
     DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
     return writeRecordInvokeDynamic(
         recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
@@ -89,7 +86,7 @@
         new DexMethodHandle(
             MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
     ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
-    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordClass().type));
+    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordType()));
     bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
     for (DexField field : recordInvokeDynamic.getFields()) {
       bootstrapArgs.add(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 3829aa0..9a7c53c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -301,8 +301,7 @@
       RecordInstructionDesugaringEventConsumer eventConsumer,
       MethodProcessingContext methodProcessingContext) {
     localStackAllocator.allocateLocalStack(1);
-    DexMethod getFieldsAsObjects =
-        getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordClass().type);
+    DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
     assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null;
     ArrayList<CfInstruction> instructions = new ArrayList<>();
     instructions.add(new CfStackInstruction(Dup));
@@ -320,7 +319,7 @@
   }
 
   private List<CfInstruction> desugarInvokeRecordEquals(RecordInvokeDynamic recordInvokeDynamic) {
-    DexMethod equalsRecord = equalsRecordMethod(recordInvokeDynamic.getRecordClass().type);
+    DexMethod equalsRecord = equalsRecordMethod(recordInvokeDynamic.getRecordType());
     assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(equalsRecord) != null;
     return Collections.singletonList(new CfInvoke(Opcodes.INVOKESPECIAL, equalsRecord, false));
   }
@@ -331,17 +330,17 @@
       RecordInstructionDesugaringEventConsumer eventConsumer,
       MethodProcessingContext methodProcessingContext) {
     localStackAllocator.allocateLocalStack(2);
-    DexMethod getFieldsAsObjects =
-        getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordClass().type);
-    assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null;
+    DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
+    assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects)
+        != null;
     ArrayList<CfInstruction> instructions = new ArrayList<>();
     instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
-    instructions.add(new CfConstClass(recordInvokeDynamic.getRecordClass().type, true));
+    instructions.add(new CfConstClass(recordInvokeDynamic.getRecordType(), true));
     if (appView.options().testing.enableRecordModeling
         && appView.enableWholeProgramOptimizations()) {
       instructions.add(
           new CfDexItemBasedConstString(
-              recordInvokeDynamic.getRecordClass().type,
+              recordInvokeDynamic.getRecordType(),
               recordInvokeDynamic.computeRecordFieldNamesComputationInfo()));
     } else {
       instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
@@ -465,7 +464,7 @@
             Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
     DexEncodedMethod init =
         DexEncodedMethod.syntheticBuilder()
-            .setMethod(factory.recordMembers.init)
+            .setMethod(factory.recordMembers.constructor)
             .setAccessFlags(methodAccessFlags)
             .setCode(null)
             // Will be traced by the enqueuer.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
new file mode 100644
index 0000000..4689b6d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -0,0 +1,174 @@
+// 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 com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.RecordFieldValues;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+/** Used to shrink record field arrays in dex compilations */
+public class RecordFieldValuesRewriter {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final IRConverter irConverter;
+
+  public static RecordFieldValuesRewriter create(AppView<AppInfoWithLiveness> appView) {
+    if (appView.enableWholeProgramOptimizations()
+        && appView.options().isGeneratingDex()
+        && appView.options().testing.enableRecordModeling) {
+      return new RecordFieldValuesRewriter(appView);
+    }
+    return null;
+  }
+
+  private RecordFieldValuesRewriter(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    irConverter = new IRConverter(appView, Timing.empty());
+  }
+
+  // Called after final tree shaking, prune and minify field names and field values.
+  // At least one instruction is a newRecordFieldArray.
+  public void rewriteRecordFieldValues() {
+    for (DexMethod recordFieldValuesReference : appView.appInfo().recordFieldValuesReferences) {
+      DexClass dexClass =
+          appView.contextIndependentDefinitionFor(recordFieldValuesReference.getHolderType());
+      assert dexClass.isProgramClass();
+      ProgramMethod programMethod =
+          dexClass.asProgramClass().lookupProgramMethod(recordFieldValuesReference);
+      assert programMethod != null;
+      rewriteRecordFieldValues(programMethod);
+    }
+  }
+
+  public void rewriteRecordFieldValues(ProgramMethod programMethod) {
+    IRCode irCode =
+        programMethod
+            .getDefinition()
+            .getCode()
+            .buildIR(programMethod, appView, programMethod.getOrigin());
+    boolean done = false;
+    ListIterator<BasicBlock> blockIterator = irCode.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator iterator = block.listIterator(irCode);
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        if (instruction.isRecordFieldValues()) {
+          rewriteRecordFieldArray(
+              instruction.asRecordFieldValues(), irCode, blockIterator, iterator);
+          done = true;
+        }
+      }
+    }
+    assert done;
+    irConverter.removeDeadCodeAndFinalizeIR(
+        irCode, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+  }
+
+  public void rewriteRecordFieldArray(
+      RecordFieldValues recordFieldArray,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      InstructionListIterator iterator) {
+    List<Value> newInValues = computePresentFields(recordFieldArray, code.context());
+    ConstNumber arrayLengthIntConstant = code.createIntConstant(newInValues.size());
+    Position constantPosition =
+        appView.options().debug ? Position.none() : recordFieldArray.getPosition();
+    arrayLengthIntConstant.setPosition(constantPosition);
+    iterator.previous();
+    iterator.add(arrayLengthIntConstant);
+    iterator.next();
+    NewArrayEmpty newArrayEmpty =
+        new NewArrayEmpty(
+            recordFieldArray.outValue(),
+            arrayLengthIntConstant.outValue(),
+            appView.dexItemFactory().objectArrayType);
+    newArrayEmpty.setPosition(recordFieldArray.getPosition());
+    iterator.replaceCurrentInstruction(newArrayEmpty);
+    for (int i = 0; i < newInValues.size(); i++) {
+      ConstNumber intConstantI = code.createIntConstant(i);
+      intConstantI.setPosition(constantPosition);
+      iterator.add(intConstantI);
+      ArrayPut arrayPut =
+          new ArrayPut(
+              MemberType.OBJECT,
+              newArrayEmpty.outValue(),
+              intConstantI.outValue(),
+              newInValues.get(i));
+      iterator.add(arrayPut);
+      arrayPut.setPosition(recordFieldArray.getPosition());
+    }
+    if (newArrayEmpty.getBlock().hasCatchHandlers()) {
+      splitIfCatchHandlers(code, newArrayEmpty.getBlock(), blockIterator);
+    }
+  }
+
+  private void splitIfCatchHandlers(
+      IRCode code,
+      BasicBlock blockWithIncorrectThrowingInstructions,
+      ListIterator<BasicBlock> blockIterator) {
+    InstructionListIterator instructionsIterator =
+        blockWithIncorrectThrowingInstructions.listIterator(code);
+    BasicBlock currentBlock = blockWithIncorrectThrowingInstructions;
+    while (currentBlock != null && instructionsIterator.hasNext()) {
+      Instruction throwingInstruction =
+          instructionsIterator.nextUntil(Instruction::instructionTypeCanThrow);
+      BasicBlock nextBlock;
+      if (throwingInstruction != null) {
+        nextBlock = instructionsIterator.split(code, blockIterator);
+        // Back up to before the split before inserting catch handlers.
+        blockIterator.previous();
+        nextBlock.copyCatchHandlers(code, blockIterator, currentBlock, appView.options());
+        BasicBlock b = blockIterator.next();
+        assert b == nextBlock;
+        // Switch iteration to the split block.
+        instructionsIterator = nextBlock.listIterator(code);
+        currentBlock = nextBlock;
+      } else {
+        assert !instructionsIterator.hasNext();
+        instructionsIterator = null;
+        currentBlock = null;
+      }
+    }
+  }
+
+  private List<Value> computePresentFields(
+      RecordFieldValues recordFieldValues, ProgramMethod context) {
+    List<Value> inValues = recordFieldValues.inValues();
+    DexField[] fields = recordFieldValues.getFields();
+    assert inValues.size() == fields.length;
+    List<Value> newInValues = new ArrayList<>();
+    for (int index = 0; index < fields.length; index++) {
+      FieldResolutionResult resolution =
+          appView
+              .appInfo()
+              .resolveField(appView.graphLens().getRenamedFieldSignature(fields[index]), context);
+      if (resolution.isSuccessfulResolution()) {
+        newInValues.add(inValues.get(index));
+      }
+    }
+    return newInValues;
+  }
+}
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 dc589e2..3eb87cb 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
@@ -14,6 +14,7 @@
 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.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
@@ -24,8 +25,12 @@
 
   public static boolean isInvokeDynamicOnRecord(
       CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
+    return isInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView, context);
+  }
+
+  public static boolean isInvokeDynamicOnRecord(
+      DexCallSite callSite, AppView<?> appView, ProgramMethod context) {
     DexItemFactory factory = appView.dexItemFactory();
-    DexCallSite callSite = invokeDynamic.getCallSite();
     // 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
     DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
     if (!bootstrapMethod.type.isInvokeStatic()) {
@@ -68,7 +73,7 @@
     DexString fieldNames = valueString.getValue();
     assert fieldNames.toString().isEmpty()
         || (fieldNames.toString().split(";").length == callSite.bootstrapArgs.size() - 2);
-    assert recordClass.instanceFields().size() == callSite.bootstrapArgs.size() - 2;
+    assert recordClass.instanceFields().size() <= callSite.bootstrapArgs.size() - 2;
     for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
       DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
       if (handle == null
@@ -143,6 +148,10 @@
       return fields;
     }
 
+    DexType getRecordType() {
+      return recordClass.getType();
+    }
+
     DexProgramClass getRecordClass() {
       return recordClass;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 71a8249..cdc2c6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -85,7 +85,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
-import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
@@ -156,15 +155,12 @@
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
   private static final int SELF_RECURSION_LIMIT = 4;
 
-  public final IRConverter converter;
-
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
   private final InternalOptions options;
 
-  public CodeRewriter(AppView<?> appView, IRConverter converter) {
+  public CodeRewriter(AppView<?> appView) {
     this.appView = appView;
-    this.converter = converter;
     this.options = appView.options();
     this.dexItemFactory = appView.dexItemFactory();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index dc88779..4cf1c8c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -287,6 +287,10 @@
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
+  public ConstraintWithTarget forRecordFieldValues() {
+    return ConstraintWithTarget.ALWAYS;
+  }
+
   public ConstraintWithTarget forNewArrayFilledData() {
     return ConstraintWithTarget.ALWAYS;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 7092012..e42266b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -16,9 +16,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -308,6 +308,7 @@
                     || instruction.isSwitch()
                     || instruction.isThrow()
                     || instruction.isUnop()
+                    || instruction.isRecordFieldValues()
                 : "Unexpected instruction of type " + instruction.getClass().getTypeName();
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 228c259..487d677 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,8 +28,8 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleConstValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 import com.android.tools.r8.ir.code.BasicBlock;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
index 0ceb39e..645a4bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
index 4b8a513..d629d45 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index ebe34b8..8b44af5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
index 09a482d..5403b86 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleConstValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsages;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 40e53fb..40627da 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -48,8 +48,8 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.EnumValuesObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.ArrayPut;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index ab74fb6..bc6f506 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -81,6 +81,7 @@
         .add(dexItemFactory.longMembers.toString)
         .add(dexItemFactory.npeMethods.init)
         .add(dexItemFactory.npeMethods.initWithMessage)
+        .add(dexItemFactory.recordMembers.constructor)
         .add(dexItemFactory.objectMembers.constructor)
         .add(dexItemFactory.objectMembers.getClass)
         .add(dexItemFactory.shortMembers.toString)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index b986800..ca99313 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
 import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
 import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index e9fc30b..46124b9 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfRecordFieldValues;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.graph.AppView;
@@ -75,6 +76,13 @@
       // 0 : receiver (the record instance)
       // 1 : the array to return
       // 2+: spills
+      return appView.enableWholeProgramOptimizations()
+              && appView.options().testing.enableRecordModeling
+          ? generateCfCodeWithRecordModeling()
+          : generateCfCodeWithArray();
+    }
+
+    private CfCode generateCfCodeWithArray() {
       DexItemFactory factory = appView.dexItemFactory();
       List<CfInstruction> instructions = new ArrayList<>();
       // Object[] fields = new Object[*length*];
@@ -86,23 +94,7 @@
         DexField field = fields[i];
         instructions.add(new CfLoad(ValueType.OBJECT, 1));
         instructions.add(new CfConstNumber(i, ValueType.INT));
-        instructions.add(new CfLoad(ValueType.OBJECT, 0));
-        instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
-        if (field.type.isPrimitiveType()) {
-          factory.primitiveToBoxed.forEach(
-              (primitiveType, boxedType) -> {
-                if (primitiveType == field.type) {
-                  instructions.add(
-                      new CfInvoke(
-                          Opcodes.INVOKESTATIC,
-                          factory.createMethod(
-                              boxedType,
-                              factory.createProto(boxedType, primitiveType),
-                              factory.valueOfMethodName),
-                          false));
-                }
-              });
-        }
+        loadFieldAsObject(instructions, field);
         instructions.add(new CfArrayStore(MemberType.OBJECT));
       }
       // return fields;
@@ -110,6 +102,39 @@
       instructions.add(new CfReturn(ValueType.OBJECT));
       return standardCfCodeFromInstructions(instructions);
     }
+
+    private CfCode generateCfCodeWithRecordModeling() {
+      List<CfInstruction> instructions = new ArrayList<>();
+      // fields[*i*] = this.*field* || *PrimitiveWrapper*.valueOf(this.*field*);
+      for (DexField field : fields) {
+        loadFieldAsObject(instructions, field);
+      }
+      // return recordFieldValues(fields);
+      instructions.add(new CfRecordFieldValues(fields));
+      instructions.add(new CfReturn(ValueType.OBJECT));
+      return standardCfCodeFromInstructions(instructions);
+    }
+
+    private void loadFieldAsObject(List<CfInstruction> instructions, DexField field) {
+      DexItemFactory factory = appView.dexItemFactory();
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      if (field.type.isPrimitiveType()) {
+        factory.primitiveToBoxed.forEach(
+            (primitiveType, boxedType) -> {
+              if (primitiveType == field.type) {
+                instructions.add(
+                    new CfInvoke(
+                        Opcodes.INVOKESTATIC,
+                        factory.createMethod(
+                            boxedType,
+                            factory.createProto(boxedType, primitiveType),
+                            factory.valueOfMethodName),
+                        false));
+              }
+            });
+      }
+    }
   }
 
   public static class RecordEqualsCfCodeProvider extends SyntheticCfCodeProvider {
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 696bce1..13d7fc6 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -49,8 +49,7 @@
     if (renaming != null) {
       return rewriteTo(renaming);
     }
-    if (appView.options().isMinifying()) {
-      // TODO(b/202367773): This should also apply if optimizing.
+    if (appView.options().isMinifying() || appView.options().isOptimizing()) {
       return rewriteToDefaultSourceFile();
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
index 17ac83a..5ad5e27 100644
--- a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
@@ -128,10 +128,6 @@
           recordClass.lookupInstanceField(graphLens.getRenamedFieldSignature(fields[i]));
       if (recordField != null) {
         names.add(nameSupplier.apply(i));
-      } else {
-        // TODO(b/201277582): Temporarily work-around as long as the field values are not also
-        //  shrunk in dex.
-        names.add("<pruned>");
       }
     }
     return dexStringFromFieldNames(names, definitions.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
index cd1e030..ed2596a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
@@ -208,7 +208,8 @@
   }
 
   private void promoteToFinalIfPossible(DexProgramClass clazz) {
-    if (!clazz.isAbstract()
+    if (!appView.testing().disableMarkingClassesFinal
+        && !clazz.isAbstract()
         && !clazz.isInterface()
         && appView.getKeepInfo(clazz).isOptimizationAllowed(appView.options())) {
       clazz.getAccessFlags().promoteToFinal();
@@ -216,7 +217,8 @@
   }
 
   private void promoteToFinalIfPossible(ProgramMethod method, VirtualRootMethod virtualRootMethod) {
-    if (!method.getHolder().isInterface()
+    if (!appView.testing().disableMarkingMethodsFinal
+        && !method.getHolder().isInterface()
         && !method.getAccessFlags().isAbstract()
         && !virtualRootMethod.hasOverrides()
         && appView.getKeepInfo(method).isOptimizationAllowed(appView.options())) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 6f3e907..b0f3ea9 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -181,6 +181,10 @@
    */
   public final Map<DexType, Visibility> initClassReferences;
   /**
+   * Set of all methods including a RecordFieldValues instruction. Set only in final tree shaking.
+   */
+  public final Set<DexMethod> recordFieldValuesReferences;
+  /**
    * All methods and fields whose value *must* never be propagated due to a configuration directive.
    * (testing only).
    */
@@ -240,7 +244,8 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Set<DexType> lockCandidates,
-      Map<DexType, Visibility> initClassReferences) {
+      Map<DexType, Visibility> initClassReferences,
+      Set<DexMethod> recordFieldValuesReferences) {
     super(syntheticItems, classToFeatureSplitMap, mainDexInfo, missingClasses);
     this.deadProtoTypes = deadProtoTypes;
     this.liveTypes = liveTypes;
@@ -278,6 +283,7 @@
     this.switchMaps = switchMaps;
     this.lockCandidates = lockCandidates;
     this.initClassReferences = initClassReferences;
+    this.recordFieldValuesReferences = recordFieldValuesReferences;
     assert verify();
   }
 
@@ -322,7 +328,8 @@
         previous.prunedTypes,
         previous.switchMaps,
         previous.lockCandidates,
-        previous.initClassReferences);
+        previous.initClassReferences,
+        previous.recordFieldValuesReferences);
   }
 
   private AppInfoWithLiveness(
@@ -374,7 +381,8 @@
             : previous.prunedTypes,
         previous.switchMaps,
         pruneClasses(previous.lockCandidates, prunedItems, executorService, futures),
-        pruneMapFromClasses(previous.initClassReferences, prunedItems, executorService, futures));
+        pruneMapFromClasses(previous.initClassReferences, prunedItems, executorService, futures),
+        previous.recordFieldValuesReferences);
   }
 
   private static Set<DexType> pruneClasses(
@@ -578,7 +586,8 @@
         prunedTypes,
         switchMaps,
         lockCandidates,
-        initClassReferences);
+        initClassReferences,
+        recordFieldValuesReferences);
   }
 
   private static KeepInfoCollection extendPinnedItems(
@@ -661,6 +670,7 @@
     this.switchMaps = switchMaps;
     this.lockCandidates = previous.lockCandidates;
     this.initClassReferences = previous.initClassReferences;
+    this.recordFieldValuesReferences = previous.recordFieldValuesReferences;
     previous.markObsolete();
     assert verify();
   }
@@ -1284,7 +1294,8 @@
         prunedTypes,
         lens.rewriteFieldKeys(switchMaps),
         lens.rewriteReferences(lockCandidates),
-        rewriteInitClassReferences(lens));
+        rewriteInitClassReferences(lens),
+        lens.rewriteReferences(recordFieldValuesReferences));
   }
 
   public Map<DexType, Visibility> rewriteInitClassReferences(GraphLens lens) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 33288f7..d0bf27c 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -54,6 +57,12 @@
   }
 
   @Override
+  public void registerRecordFieldValues(DexField[] fields) {
+    super.registerRecordFieldValues(fields);
+    enqueuer.traceRecordFieldValues(fields, getContext());
+  }
+
+  @Override
   public void registerInvokeVirtual(DexMethod invokedMethod) {
     setMaxApiReferenceLevel(invokedMethod);
     enqueuer.traceInvokeVirtual(invokedMethod, getContext());
@@ -95,6 +104,11 @@
     enqueuer.traceInstanceFieldReadFromMethodHandle(field, getContext());
   }
 
+  private void registerInstanceFieldReadFromRecordMethodHandle(DexField field) {
+    setMaxApiReferenceLevel(field);
+    enqueuer.traceInstanceFieldReadFromRecordMethodHandle(field, getContext());
+  }
+
   @Override
   public void registerInstanceFieldWrite(DexField field) {
     setMaxApiReferenceLevel(field);
@@ -178,10 +192,33 @@
 
   @Override
   public void registerCallSite(DexCallSite callSite) {
-    super.registerCallSite(callSite);
+    super.registerCallSiteExceptBootstrapArgs(callSite);
+    if (isInvokeDynamicOnRecord(callSite, appView, getContext())
+        && appView.options().testing.enableRecordModeling) {
+      registerRecordCallSiteBootstrapArgs(callSite);
+    } else {
+      super.registerCallSiteBootstrapArgs(callSite, 0, callSite.bootstrapArgs.size());
+    }
     enqueuer.traceCallSite(callSite, getContext());
   }
 
+  private void registerRecordCallSiteBootstrapArgs(DexCallSite callSite) {
+    // The Instance Get method handle in invokeDynamicOnRecord are considered:
+    // - a record use if not a constant value,
+    // - unused if a constant value.
+    registerCallSiteBootstrapArgs(callSite, 0, 2);
+    for (int i = 2; i < callSite.getBootstrapArgs().size(); i++) {
+      DexField field = callSite.getBootstrapArgs().get(i).asDexValueMethodHandle().value.asField();
+      DexEncodedField encodedField =
+          appView.appInfo().resolveField(field, getContext()).getResolvedField();
+      // Member value propagation does not rewrite method handles, special handling for this
+      // method handle access is done after the final tree shaking.
+      if (!encodedField.getOptimizationInfo().isDead()) {
+        registerInstanceFieldReadFromRecordMethodHandle(field);
+      }
+    }
+  }
+
   private void setMaxApiReferenceLevel(DexReference reference) {
     if (reference.isDexMember()) {
       maxApiReferenceLevel =
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index a7b0a76..5b0ff74 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -406,6 +406,12 @@
   private final Map<DexType, Visibility> initClassReferences = new IdentityHashMap<>();
 
   /**
+   * A map from seen init-class references to the minimum required visibility of the corresponding
+   * static field.
+   */
+  private final Set<DexMethod> recordFieldValuesReferences = Sets.newIdentityHashSet();
+
+  /**
    * A map from annotation classes to annotations that need to be processed should the classes ever
    * become live.
    */
@@ -1121,6 +1127,14 @@
     }
   }
 
+  void traceRecordFieldValues(DexField[] fields, ProgramMethod currentMethod) {
+    // TODO(b/203377129): Consider adding an enqueuer extension instead of growing the
+    //  number of fields in appInfoWithLiveness.
+    if (mode.isFinalTreeShaking()) {
+      recordFieldValuesReferences.add(currentMethod.getReference());
+    }
+  }
+
   void traceInitClass(DexType type, ProgramMethod currentMethod) {
     assert type.isClassType();
 
@@ -1406,15 +1420,25 @@
   }
 
   void traceInstanceFieldRead(DexField field, ProgramMethod currentMethod) {
-    traceInstanceFieldRead(field, currentMethod, false);
+    traceInstanceFieldRead(field, currentMethod, FieldReadType.READ);
   }
 
   void traceInstanceFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
-    traceInstanceFieldRead(field, currentMethod, true);
+    traceInstanceFieldRead(field, currentMethod, FieldReadType.READ_FROM_METHOD_HANDLE);
+  }
+
+  void traceInstanceFieldReadFromRecordMethodHandle(DexField field, ProgramMethod currentMethod) {
+    traceInstanceFieldRead(field, currentMethod, FieldReadType.READ_FROM_RECORD_METHOD_HANDLE);
+  }
+
+  private enum FieldReadType {
+    READ,
+    READ_FROM_METHOD_HANDLE,
+    READ_FROM_RECORD_METHOD_HANDLE
   }
 
   private void traceInstanceFieldRead(
-      DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
+      DexField fieldReference, ProgramMethod currentMethod, FieldReadType readType) {
     if (!registerFieldRead(fieldReference, currentMethod)) {
       return;
     }
@@ -1440,8 +1464,10 @@
             + "` to field marked dead: "
             + field.getReference().toSourceString();
 
-    if (fromMethodHandle) {
+    if (readType == FieldReadType.READ_FROM_METHOD_HANDLE) {
       fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
+    } else if (readType == FieldReadType.READ_FROM_RECORD_METHOD_HANDLE) {
+      fieldAccessInfoCollection.get(field.getReference()).setReadFromRecordInvokeDynamic();
     }
 
     if (Log.ENABLED) {
@@ -3733,7 +3759,8 @@
             emptySet(),
             Collections.emptyMap(),
             lockCandidates,
-            initClassReferences);
+            initClassReferences,
+            recordFieldValuesReferences);
     appInfo.markObsolete();
     if (options.testing.enqueuerInspector != null) {
       options.testing.enqueuerInspector.accept(appInfoWithLiveness, mode);
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 88c96ef..bc0344a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1647,7 +1647,7 @@
     public boolean noLocalsTableOnInput = false;
     public boolean forceNameReflectionOptimization = false;
     public boolean enableNarrowAndWideningingChecksInD8 = false;
-    public Consumer<IRCode> irModifier = null;
+    public BiConsumer<IRCode, AppView<?>> irModifier = null;
     public Consumer<IRCode> inlineeIrModifier = null;
     public int basicBlockMuncherIterationLimit = NO_LIMIT;
     public boolean dontReportFailingCheckDiscarded = false;
@@ -1671,12 +1671,16 @@
     public Set<String> allowedUnusedDontWarnPatterns = new HashSet<>();
     public boolean enableTestAssertions =
         System.getProperty("com.android.tools.r8.enableTestAssertions") != null;
+    public boolean disableMarkingMethodsFinal =
+        System.getProperty("com.android.tools.r8.disableMarkingMethodsFinal") != null;
+    public boolean disableMarkingClassesFinal =
+        System.getProperty("com.android.tools.r8.disableMarkingClassesFinal") != null;
     public boolean testEnableTestAssertions = false;
     public boolean keepMetadataInR8IfNotRewritten = true;
 
     // If set, pruned record fields are not used in hashCode/equals/toString and toString prints
     // minified field names instead of original field names.
-    public boolean enableRecordModeling = false;
+    public boolean enableRecordModeling = true;
 
     public boolean allowConflictingSyntheticTypes = false;
 
diff --git a/src/test/examplesJava17/records/RecordShrinkField.java b/src/test/examplesJava17/records/RecordShrinkField.java
index b3cd366..2a21904 100644
--- a/src/test/examplesJava17/records/RecordShrinkField.java
+++ b/src/test/examplesJava17/records/RecordShrinkField.java
@@ -6,9 +6,9 @@
 
 public class RecordShrinkField {
 
-  record Person(String name, int age, int unused) {
+  record Person(int unused, String name, int age) {
     Person(String name, int age) {
-      this(name, age, -1);
+      this(-1, name, age);
     }
   }
 
diff --git a/src/test/examplesJava17/records/RecordWithConstClass.java b/src/test/examplesJava17/records/RecordWithConstClass.java
new file mode 100644
index 0000000..db06f51
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordWithConstClass.java
@@ -0,0 +1,18 @@
+// 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 records;
+
+import records.differentpackage.PrivateConstClass;
+
+public class RecordWithConstClass {
+
+  record MyRecordWithConstClass(Class<?> theClass) { }
+
+  public static void main(String[] args) {
+    MyRecordWithConstClass inst =
+        new MyRecordWithConstClass(PrivateConstClass.getPrivateConstClass());
+    System.out.println(inst);
+  }
+}
diff --git a/src/test/examplesJava17/records/differentpackage/PrivateConstClass.java b/src/test/examplesJava17/records/differentpackage/PrivateConstClass.java
new file mode 100644
index 0000000..fbf93c6
--- /dev/null
+++ b/src/test/examplesJava17/records/differentpackage/PrivateConstClass.java
@@ -0,0 +1,14 @@
+// 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 records.differentpackage;
+
+public class PrivateConstClass {
+
+  private static class PrivateClass {}
+
+  public static Class<?> getPrivateConstClass() {
+    return PrivateClass.class;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 08bb64b..b3bcdb4 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -146,7 +146,8 @@
                   .setMode(mode)
                   .setDisableTreeShaking(true)
                   .setDisableMinification(true)
-                  .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
+                  .addProguardConfiguration(
+                      ImmutableList.of("-keepattributes *", "-dontoptimize"), Origin.unknown())
                   .build();
           ToolHelper.runR8(command, this::configure);
         break;
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 aec1329..8f5dc82 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -84,7 +85,7 @@
     assert instructions.get(index + 4) instanceof CfStackInstruction;
   }
 
-  private void processIR(IRCode code) {
+  private void processIR(IRCode code, AppView<?> appView) {
     if (!code.method().qualifiedName().equals(TryRangeTestLimitRange.class.getName() + ".main")) {
       return;
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterClassMergingTest.java
new file mode 100644
index 0000000..8093d0c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterClassMergingTest.java
@@ -0,0 +1,117 @@
+// 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtending;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 NonPublicOverrideOfPublicMethodAfterClassMergingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject iClassSubject = inspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+              assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(aClassSubject, isExtending(iClassSubject));
+              assertThat(
+                  aClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.m()", "B.m()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().m();
+      (System.currentTimeMillis() > 0 ? new B() : new C()).m();
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  abstract static class I {}
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  abstract static class J {
+
+    public abstract void m();
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class A extends I {
+
+    @NeverInline
+    void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B extends J {
+
+    @Override
+    public void m() {
+      System.out.println("B.m()");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class C extends J {
+
+    @Override
+    public void m() {
+      System.out.println("C.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
new file mode 100644
index 0000000..de26733
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
@@ -0,0 +1,132 @@
+// 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.classmerging.horizontal.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 IllegalOverrideAfterInterfaceMergingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject iClassSubject = inspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+              assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(aClassSubject, isImplementing(iClassSubject));
+              // TODO(b/203446070): Package private A.m() should not override public I.m().
+              assertThat(
+                  aClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/203446070): Should always succeed.
+        .applyIf(
+            parameters.isCfRuntime(),
+            runResult -> runResult.assertSuccessWithOutputLines("A.m()", "B.m()"),
+            runResult ->
+                runResult.applyIf(
+                    parameters.getDexRuntimeVersion().isDalvik(),
+                    ignore ->
+                        runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+                    ignore ->
+                        runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class)));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().m();
+      (System.currentTimeMillis() > 0 ? new B() : new C()).m();
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {}
+
+  // Should not be merged into I, since I has a subclass with a package private method signature
+  // `void m()`.
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+
+    void m();
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class A implements I {
+
+    // Intentionally package private. If J is merged into I then this is an illegal override of
+    // I.m().
+    @NeverInline
+    void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B implements J {
+
+    @Override
+    public void m() {
+      System.out.println("B.m()");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class C implements J {
+
+    @Override
+    public void m() {
+      System.out.println("C.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
new file mode 100644
index 0000000..a75fd7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
@@ -0,0 +1,140 @@
+// 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.classmerging.horizontal.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtending;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 IllegalSiblingAfterInterfaceMergingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject iClassSubject = inspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+              assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+
+              ClassSubject a0ClassSubject = inspector.clazz(A0.class);
+              assertThat(a0ClassSubject, isPresent());
+              assertThat(
+                  a0ClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(aClassSubject, isExtending(a0ClassSubject));
+              assertThat(aClassSubject, isImplementing(iClassSubject));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/203446070): Should always succeed.
+        .applyIf(
+            parameters.isCfRuntime(),
+            runResult -> runResult.assertSuccessWithOutputLines("A.m()", "B.m()"),
+            runResult ->
+                runResult.applyIf(
+                    parameters.getDexRuntimeVersion().isDalvik(),
+                    ignore ->
+                        runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+                    ignore ->
+                        runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class)));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().m();
+      (System.currentTimeMillis() > 0 ? new B() : new C()).m();
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {}
+
+  // Should not be merged into I, since I has a subclass with a package private method signature
+  // `void m()`.
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+
+    void m();
+  }
+
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  static class A0 {
+
+    // Intentionally package private. If J is merged into I then this is an illegal override of
+    // I.m().
+    @NeverInline
+    void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class A extends A0 implements I {}
+
+  @NoHorizontalClassMerging
+  static class B implements J {
+
+    @Override
+    public void m() {
+      System.out.println("B.m()");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class C implements J {
+
+    @Override
+    public void m() {
+      System.out.println("C.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index 05bbb73..ef9112c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -25,7 +25,7 @@
 public class DebugInfoWhenInliningTest extends DebugTestBase {
 
   private static final String CLASS_NAME = "Inlining1";
-  private static final String SOURCE_FILE = "Inlining1.java";
+  private static final String SOURCE_FILE = "SourceFile";
 
   private DebugTestConfig makeConfig(
       LineNumberOptimization lineNumberOptimization, boolean writeProguardMap) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 228275b..7207f6d 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -34,10 +34,11 @@
           "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", "a", "b");
 
   private final TestParameters parameters;
 
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
new file mode 100644
index 0000000..49df3e2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
@@ -0,0 +1,113 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+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;
+
+@RunWith(Parameterized.class)
+public class RecordKeepRulesTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordShrinkField";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+
+  private static final String KEEP_RULE_CLASS_NAME =
+      "-keep,allowshrinking,allowoptimization class records.RecordShrinkField$Person";
+  private static final String KEEP_RULE_FIELD_NAMES =
+      "-keepclassmembers,allowshrinking,allowoptimization class records.RecordShrinkField$Person {"
+          + " <fields>; }";
+  private static final String KEEP_RULE_FIELDS_NO_NAMES =
+      "-keepclassmembers,allowobfuscation class records.RecordShrinkField$Person { <fields>; }";
+  private static final String KEEP_RULE_ALL =
+      "-keep class records.RecordShrinkField$Person { <fields>; }";
+
+  private static final String EXPECTED_RESULT_R8_WITH_CLASS_NAME =
+      StringUtils.lines("RecordShrinkField$Person[a=Jane Doe]", "RecordShrinkField$Person[a=Bob]");
+  private static final String EXPECTED_RESULT_R8_WITH_FIELD_NAMES =
+      StringUtils.lines("a[name=Jane Doe]", "a[name=Bob]");
+  private static final String EXPECTED_RESULT_R8_WITH_FIELD_NO_NAMES =
+      StringUtils.lines("a[a=-1, b=Jane Doe, c=42]", "a[a=-1, b=Bob, c=42]");
+  private static final String EXPECTED_RESULT_R8_WITH_ALL =
+      StringUtils.lines(
+          "RecordShrinkField$Person[unused=-1, name=Jane Doe, age=42]",
+          "RecordShrinkField$Person[unused=-1, name=Bob, age=42]");
+
+  private final TestParameters parameters;
+  private final boolean proguardCompatibility;
+
+  public RecordKeepRulesTest(TestParameters parameters, boolean proguardCompatibility) {
+    this.parameters = parameters;
+    this.proguardCompatibility = proguardCompatibility;
+  }
+
+  @Parameterized.Parameters(name = "{0}; proguardCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  @Test
+  public void testR8KeepRuleClassName() throws Exception {
+    testR8FieldNames(KEEP_RULE_CLASS_NAME, EXPECTED_RESULT_R8_WITH_CLASS_NAME);
+    testR8CfThenDexFieldNames(KEEP_RULE_CLASS_NAME, EXPECTED_RESULT_R8_WITH_CLASS_NAME);
+  }
+
+  @Test
+  public void testR8KeepRuleFieldNames() throws Exception {
+    testR8FieldNames(KEEP_RULE_FIELD_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NAMES);
+    testR8CfThenDexFieldNames(KEEP_RULE_FIELD_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NAMES);
+  }
+
+  @Test
+  public void testR8KeepRuleFieldsNoNames() throws Exception {
+    testR8FieldNames(KEEP_RULE_FIELDS_NO_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NO_NAMES);
+    testR8CfThenDexFieldNames(KEEP_RULE_FIELDS_NO_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NO_NAMES);
+  }
+
+  @Test
+  public void testR8KeepRuleAll() throws Exception {
+    testR8FieldNames(KEEP_RULE_ALL, EXPECTED_RESULT_R8_WITH_ALL);
+    testR8CfThenDexFieldNames(KEEP_RULE_ALL, EXPECTED_RESULT_R8_WITH_ALL);
+  }
+
+  private void testR8FieldNames(String keepRules, String expectedOutput) throws Exception {
+    testForR8Compat(parameters.getBackend(), proguardCompatibility)
+        .addProgramClassFileData(PROGRAM_DATA)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_TYPE)
+        .addKeepRules(keepRules)
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  private void testR8CfThenDexFieldNames(String keepRules, String expectedOutput) throws Exception {
+    Path desugared =
+        testForR8Compat(Backend.CF, proguardCompatibility)
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(MAIN_TYPE)
+            .addKeepRules(keepRules)
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile()
+            .writeToZip();
+    testForD8(parameters.getBackend())
+        .addProgramFiles(desugared)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
index df6be44..8dc4a82 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
@@ -25,17 +25,10 @@
   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 =
-      StringUtils.lines("%s[name=Jane Doe, age=42, unused=-1]", "%s[name=Bob, age=42, unused=-1]");
+      StringUtils.lines("%s[unused=-1, name=Jane Doe, age=42]", "%s[unused=-1, name=Bob, age=42]");
   private static final String EXPECTED_RESULT_D8 =
       String.format(EXPECTED_RESULT, "Person", "Person");
-  private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT, "a", "a");
-  // TODO(b/201277582): These results are temporary while we transition into pruned minified record
-  //  fields.
-  private static final String EXPECTED_RESULT_R8_ADVANCED_DEX =
-      StringUtils.lines(
-          "a[a=Jane Doe, <pruned>=42, <pruned>=-1]", "a[a=Bob, <pruned>=42, <pruned>=-1]");
-  private static final String EXPECTED_RESULT_R8_ADVANCED_CF =
-      StringUtils.lines("a[a=Jane Doe, b=42, c=-1]", "a[a=Bob, b=42, c=-1]");
+  private static final String EXPECTED_RESULT_R8 = StringUtils.lines("a[a=Jane Doe]", "a[a=Bob]");
 
   private final TestParameters parameters;
 
@@ -74,59 +67,6 @@
   }
 
   @Test
-  public void testR8Compat() throws Exception {
-    testForR8Compat(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(MAIN_TYPE)
-        .addKeepRules(
-            "-keepclassmembers,allowshrinking,allowoptimization class"
-                + " records.RecordShrinkField$Person { <fields>; }")
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .compile()
-        .inspect(this::assertSingleField)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_R8);
-  }
-
-  @Test
-  public void testR8AdvancedShrinking() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClassFileData(PROGRAM_DATA)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(MAIN_TYPE)
-        .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .compile()
-        .inspect(this::assertSingleField)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_DEX);
-  }
-
-  @Test
-  public void testR8CfThenDexAdvancedShrinking() throws Exception {
-    Path desugared =
-        testForR8(Backend.CF)
-            .addProgramClassFileData(PROGRAM_DATA)
-            .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE)
-            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-            .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .compile()
-            .writeToZip();
-    testForR8(parameters.getBackend())
-        .addProgramFiles(desugared)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(MAIN_TYPE)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .compile()
-        .inspect(this::assertSingleField)
-        .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_CF);
-  }
-
-  @Test
   public void testR8CfThenDex() throws Exception {
     Path desugared =
         testForR8(Backend.CF)
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
new file mode 100644
index 0000000..f76b137
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
@@ -0,0 +1,102 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+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;
+
+@RunWith(Parameterized.class)
+public class RecordWithNonMaterializableConstClassTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordWithConstClass";
+  private static final String PRIVATE_CLASS_NAME =
+      "records.differentpackage.PrivateConstClass$PrivateClass";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final byte[][] EXTRA_DATA =
+      RecordTestUtils.getProgramData("differentpackage/PrivateConstClass");
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT_FORMAT =
+      StringUtils.lines("%s[%s=class " + PRIVATE_CLASS_NAME + "]");
+  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 final TestParameters parameters;
+
+  public RecordWithNonMaterializableConstClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  @Test
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .addProgramClassFileData(EXTRA_DATA)
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT_D8);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addProgramClassFileData(EXTRA_DATA)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT_D8);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addProgramClassFileData(EXTRA_DATA)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_TYPE)
+        .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT_R8);
+  }
+
+  @Test
+  public void testR8CfThenDex() throws Exception {
+    Path desugared =
+        testForR8(Backend.CF)
+            .addProgramClassFileData(PROGRAM_DATA)
+            .addProgramClassFileData(EXTRA_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(MAIN_TYPE)
+            .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile()
+            .writeToZip();
+    testForR8(parameters.getBackend())
+        .addProgramFiles(desugared)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_TYPE)
+        .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT_R8);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
index 6001549..5cea9fa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -86,6 +87,10 @@
             });
   }
 
+  private void modifyIr(IRCode irCode, AppView<?> appView) {
+    modifyIr(irCode);
+  }
+
   private void modifyIr(IRCode irCode) {
     if (irCode.context().getReference().qualifiedName().equals(A.class.getTypeName() + ".foo")) {
       assertEquals(7, irCode.blocks.size());
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
index 87ae7f4..5a7be26 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.arrays;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -17,15 +21,12 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
 @RunWith(Parameterized.class)
 public class ArrayLengthRewriteTest extends TestBase {
   @Parameters(name = "{0}, debug = {1}")
   public static Iterable<?> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   private final TestParameters parameters;
@@ -62,7 +63,7 @@
     assumeTrue(parameters.isDexRuntime());
 
     testForD8()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .setMode(debugMode ? CompilationMode.DEBUG : CompilationMode.RELEASE)
         .addProgramClasses(Main.class)
         .run(parameters.getRuntime(), Main.class)
@@ -72,7 +73,7 @@
 
   @Test public void r8() throws Exception {
     testForR8(parameters.getBackend())
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .setMode(debugMode ? CompilationMode.DEBUG : CompilationMode.RELEASE)
         .addProgramClasses(Main.class)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
new file mode 100644
index 0000000..4a76a6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -0,0 +1,85 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArrayWithDataLengthRewriteTest extends TestBase {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  private final TestParameters parameters;
+
+  public ArrayWithDataLengthRewriteTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final String[] expectedOutput = {"3"};
+
+  @Test
+  public void d8() throws Exception {
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .setMode(CompilationMode.RELEASE)
+        .addProgramClasses(Main.class)
+        .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(expectedOutput)
+        .inspect(this::assertNoArrayLength);
+  }
+
+  @Test
+  public void r8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getRuntime())
+        .addProgramClasses(Main.class)
+        .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(expectedOutput)
+        .inspect(this::assertNoArrayLength);
+  }
+
+  private void transformArray(IRCode irCode, AppView<?> appView) {
+    if (irCode.context().getReference().getName().toString().contains("main")) {
+      new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+      assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
+    }
+  }
+
+  private void assertNoArrayLength(CodeInspector inspector) {
+    ClassSubject mainClass = inspector.clazz(Main.class);
+    assertTrue(mainClass.isPresent());
+    assertTrue(
+        mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+  }
+
+  public static final class Main {
+    public static void main(String[] args) {
+      int[] ints = new int[3];
+      ints[0] = 5;
+      ints[1] = 6;
+      ints[2] = 1;
+      System.out.println(ints.length);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 2d74926..7f35d90 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -90,6 +90,11 @@
   }
 
   @Override
+  public boolean isExtending(ClassSubject subject) {
+    throw new Unreachable("Cannot determine if an absent class is extending a given class");
+  }
+
+  @Override
   public boolean isImplementing(ClassSubject subject) {
     throw new Unreachable("Cannot determine if an absent class is implementing a given interface");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 9c057e6..04f0d52 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -180,6 +180,8 @@
 
   public abstract boolean isAnnotation();
 
+  public abstract boolean isExtending(ClassSubject subject);
+
   public abstract boolean isImplementing(ClassSubject subject);
 
   public abstract boolean isImplementing(Class<?> clazz);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index a0839d8..69ebe80 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -294,6 +294,11 @@
   }
 
   @Override
+  public boolean isExtending(ClassSubject subject) {
+    return getSuperClass().getDexProgramClass().getType() == subject.getDexProgramClass().getType();
+  }
+
+  @Override
   public boolean isInterface() {
     return dexClass.isInterface();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 6e83620..db174e0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -505,6 +505,22 @@
     };
   }
 
+  public static Matcher<ClassSubject> isExtending(ClassSubject superSubject) {
+    assertThat(superSubject, isPresent());
+    assertThat(superSubject, not(isInterface()));
+    return new TypeSafeMatcher<ClassSubject>() {
+      @Override
+      public boolean matchesSafely(ClassSubject subject) {
+        return subject.isPresent() && subject.isExtending(superSubject);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("extends ").appendText(superSubject.getOriginalName());
+      }
+    };
+  }
+
   public static Matcher<FieldSubject> isFieldOfType(DexType type) {
     return new TypeSafeMatcher<FieldSubject>() {
       @Override
diff --git a/tools/retrace.py b/tools/retrace.py
index 0aa5af2..14593ab 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -151,7 +151,7 @@
 
   retrace_args += [
     '-cp',
-    utils.R8_JAR if no_r8lib else utils.R8LIB_JAR,
+    utils.R8_JAR if no_r8lib else utils.R8RETRACE_JAR,
     'com.android.tools.r8.retrace.Retrace',
     map_path
   ]