Introduce RecordFieldValues instruction

Bug: 201277582
Change-Id: Ic3252a337f9850d7f070830ba93470619cbcad1f
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ec0f361..7ad9b39 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;
@@ -688,6 +689,12 @@
 
       performFinalMainDexTracing(appView, executorService);
 
+      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/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 9319c67..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);
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/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/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..a7f5846 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
@@ -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/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/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 3502922..d0bf27c 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -57,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());
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 fefa1d7..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();
 
@@ -3745,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/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
index 8f915a2..0e20d4b 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
@@ -29,11 +29,7 @@
   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_INVALID_RESULT_R8_ADVANCED_DEX =
-      StringUtils.lines("a[a=-1]", "a[a=-1]");
-  private static final String EXPECTED_RESULT_R8_ADVANCED_CF =
+  private static final String EXPECTED_RESULT_R8_RECORD_MODELING =
       StringUtils.lines("a[a=Jane Doe]", "a[a=Bob]");
 
   private final TestParameters parameters;
@@ -99,7 +95,7 @@
         .compile()
         .inspect(this::assertSingleField)
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_INVALID_RESULT_R8_ADVANCED_DEX);
+        .assertSuccessWithOutput(EXPECTED_RESULT_R8_RECORD_MODELING);
   }
 
   @Test
@@ -123,7 +119,7 @@
         .compile()
         .inspect(this::assertSingleField)
         .run(parameters.getRuntime(), MAIN_TYPE)
-        .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_CF);
+        .assertSuccessWithOutput(EXPECTED_RESULT_R8_RECORD_MODELING);
   }
 
   @Test