Record field names minification

Bug: 201277582
Change-Id: I1d09fccc1dada55c39f4fe389e483acf5fd5932e
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
new file mode 100644
index 0000000..76c468a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.records;
+
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
+
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+
+/** Used to shrink records in Cf to Cf compilations */
+public class RecordCfToCfRewriter {
+
+  private final AppView<?> appView;
+
+  public static RecordCfToCfRewriter create(AppView<?> appView) {
+    if (appView.enableWholeProgramOptimizations()
+        && appView.options().isGeneratingClassFiles()
+        && appView.options().testing.enableRecordModeling) {
+      return new RecordCfToCfRewriter(appView);
+    }
+    return null;
+  }
+
+  private RecordCfToCfRewriter(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  // Called after final tree shaking, prune and minify field names and field values.
+  public CfInvokeDynamic rewriteRecordInvokeDynamic(
+      CfInvokeDynamic invokeDynamic, ProgramMethod context, NamingLens namingLens) {
+    if (!isInvokeDynamicOnRecord(invokeDynamic, appView, context)) {
+      return invokeDynamic;
+    }
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
+    DexString newFieldNames =
+        recordInvokeDynamic
+            .computeRecordFieldNamesComputationInfo()
+            .internalComputeNameFor(
+                recordInvokeDynamic.getRecordClass().type,
+                appView,
+                appView.graphLens(),
+                namingLens);
+    DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
+    return writeRecordInvokeDynamic(
+        recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
+  }
+
+  private DexField[] computePresentFields(
+      GraphLens graphLens, RecordInvokeDynamic recordInvokeDynamic) {
+    ArrayList<DexField> finalFields = new ArrayList<>();
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      DexEncodedField dexEncodedField =
+          recordInvokeDynamic
+              .getRecordClass()
+              .lookupInstanceField(graphLens.getRenamedFieldSignature(field));
+      if (dexEncodedField != null) {
+        finalFields.add(field);
+      }
+    }
+    DexField[] newFields = new DexField[finalFields.size()];
+    for (int i = 0; i < finalFields.size(); i++) {
+      newFields[i] = finalFields.get(i);
+    }
+    return newFields;
+  }
+
+  private CfInvokeDynamic writeRecordInvokeDynamic(RecordInvokeDynamic recordInvokeDynamic) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexMethodHandle bootstrapMethod =
+        new DexMethodHandle(
+            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
+    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
+    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordClass().type));
+    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      bootstrapArgs.add(
+          new DexValueMethodHandle(
+              new DexMethodHandle(MethodHandleType.INSTANCE_GET, field, false, null)));
+    }
+    return new CfInvokeDynamic(
+        factory.createCallSite(
+            recordInvokeDynamic.getMethodName(),
+            recordInvokeDynamic.getMethodProto(),
+            bootstrapMethod,
+            bootstrapArgs));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 0c1ee6e..3829aa0 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
@@ -6,9 +6,12 @@
 
 import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Dup;
 import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Swap;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
 
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -21,20 +24,14 @@
 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.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
 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;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
@@ -48,6 +45,7 @@
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
 import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider;
@@ -100,14 +98,15 @@
     CfCode cfCode = method.getDefinition().getCode().asCfCode();
     for (CfInstruction instruction : cfCode.getInstructions()) {
       if (instruction.isInvokeDynamic() && needsDesugaring(instruction, method)) {
-        prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), method, programAdditions);
+        prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), programAdditions, method);
       }
     }
   }
 
   private void prepareInvokeDynamicOnRecord(
-      CfInvokeDynamic invokeDynamic, ProgramMethod context, ProgramAdditions programAdditions) {
-    RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context);
+      CfInvokeDynamic invokeDynamic, ProgramAdditions programAdditions, ProgramMethod context) {
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
     if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName
         || recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
       ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions);
@@ -178,8 +177,8 @@
       return desugarInvokeDynamicOnRecord(
           instruction.asInvokeDynamic(),
           localStackAllocator,
-          context,
           eventConsumer,
+          context,
           methodProcessingContext);
     }
     assert instruction.isInvoke();
@@ -191,71 +190,17 @@
         new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface()));
   }
 
-  static class RecordInvokeDynamic {
-
-    private final DexString methodName;
-    private final DexString fieldNames;
-    private final DexField[] fields;
-    private final DexProgramClass recordClass;
-
-    private RecordInvokeDynamic(
-        DexString methodName,
-        DexString fieldNames,
-        DexField[] fields,
-        DexProgramClass recordClass) {
-      this.methodName = methodName;
-      this.fieldNames = fieldNames;
-      this.fields = fields;
-      this.recordClass = recordClass;
-    }
-
-    DexField[] getFields() {
-      return fields;
-    }
-
-    DexProgramClass getRecordClass() {
-      return recordClass;
-    }
-
-    DexString getFieldNames() {
-      return fieldNames;
-    }
-
-    DexString getMethodName() {
-      return methodName;
-    }
-  }
-
-  private RecordInvokeDynamic parseInvokeDynamicOnRecord(
-      CfInvokeDynamic invokeDynamic, ProgramMethod context) {
-    assert needsDesugaring(invokeDynamic, context);
-    DexCallSite callSite = invokeDynamic.getCallSite();
-    DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
-    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
-    DexString fieldNames = valueString.getValue();
-    DexField[] fields = new DexField[callSite.bootstrapArgs.size() - 2];
-    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
-      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
-      fields[i - 2] = handle.value.member.asDexField();
-    }
-    DexProgramClass recordClass =
-        appView.definitionFor(recordValueType.getValue()).asProgramClass();
-    return new RecordInvokeDynamic(callSite.methodName, fieldNames, fields, recordClass);
-  }
-
   private List<CfInstruction> desugarInvokeDynamicOnRecord(
       CfInvokeDynamic invokeDynamic,
       LocalStackAllocator localStackAllocator,
-      ProgramMethod context,
       CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
-    RecordInvokeDynamic recordInvokeDynamic = parseInvokeDynamicOnRecord(invokeDynamic, context);
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
     if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName) {
       return desugarInvokeRecordToString(
-          recordInvokeDynamic,
-          localStackAllocator,
-          eventConsumer,
-          methodProcessingContext);
+          recordInvokeDynamic, localStackAllocator, eventConsumer, methodProcessingContext);
     }
     if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
       return desugarInvokeRecordHashCode(
@@ -392,7 +337,15 @@
     ArrayList<CfInstruction> instructions = new ArrayList<>();
     instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
     instructions.add(new CfConstClass(recordInvokeDynamic.getRecordClass().type, true));
-    instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
+    if (appView.options().testing.enableRecordModeling
+        && appView.enableWholeProgramOptimizations()) {
+      instructions.add(
+          new CfDexItemBasedConstString(
+              recordInvokeDynamic.getRecordClass().type,
+              recordInvokeDynamic.computeRecordFieldNamesComputationInfo()));
+    } else {
+      instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
+    }
     ProgramMethod programMethod =
         synthesizeRecordHelper(
             recordToStringHelperProto,
@@ -426,9 +379,7 @@
             appView,
             builder -> {
               DexEncodedMethod init = synthesizeRecordInitMethod();
-              builder
-                  .setAbstract()
-                  .setDirectMethods(ImmutableList.of(init));
+              builder.setAbstract().setDirectMethods(ImmutableList.of(init));
             },
             eventConsumer::acceptRecordClass);
   }
@@ -484,75 +435,7 @@
   }
 
   private boolean needsDesugaring(CfInvokeDynamic invokeDynamic, ProgramMethod context) {
-    DexCallSite callSite = invokeDynamic.getCallSite();
-    // 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
-    DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
-    if (!bootstrapMethod.type.isInvokeStatic()) {
-      return false;
-    }
-    if (bootstrapMethod.member != factory.objectMethodsMembers.bootstrap) {
-      return false;
-    }
-    // From there on we assume in the assertions that the invoke to the library method is
-    // well-formed. If the invoke is not well formed assertions will fail but the execution is
-    // correct.
-    if (bootstrapMethod.isInterface) {
-      assert false
-          : "Invoke-dynamic invoking non interface method ObjectMethods#bootstrap as an interface"
-              + " method.";
-      return false;
-    }
-    // 2. Validate the bootstrapArgs include the record type, the instance field names and
-    // the corresponding instance getters.
-    if (callSite.bootstrapArgs.size() < 2) {
-      assert false
-          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with less than 2 parameters.";
-      return false;
-    }
-    DexValueType recordType = callSite.bootstrapArgs.get(0).asDexValueType();
-    if (recordType == null) {
-      assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
-      return false;
-    }
-    DexClass recordClass = appView.definitionFor(recordType.getValue());
-    if (recordClass == null || recordClass.isNotProgramClass()) {
-      return false;
-    }
-    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
-    if (valueString == null) {
-      assert false
-          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid field names.";
-      return false;
-    }
-    DexString fieldNames = valueString.getValue();
-    assert fieldNames.toString().isEmpty()
-        || (fieldNames.toString().split(";").length == 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
-          || !handle.value.type.isInstanceGet()
-          || !handle.value.member.isDexField()) {
-        assert false
-            : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid getters.";
-        return false;
-      }
-    }
-    // 3. Create the invoke-record instruction.
-    if (callSite.methodName == factory.toStringMethodName) {
-      assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
-      return true;
-    }
-    if (callSite.methodName == factory.hashCodeMethodName) {
-      assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
-      return true;
-    }
-    if (callSite.methodName == factory.equalsMethodName) {
-      assert callSite.methodProto
-          == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
-      return true;
-    }
-    return false;
+    return isInvokeDynamicOnRecord(invokeDynamic, appView, context);
   }
 
   @SuppressWarnings("ConstantConditions")
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
new file mode 100644
index 0000000..dc589e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
@@ -0,0 +1,166 @@
+// 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.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.naming.dexitembasedstring.RecordFieldNamesComputationInfo;
+
+public class RecordRewriterHelper {
+
+  public static boolean isInvokeDynamicOnRecord(
+      CfInvokeDynamic invokeDynamic, 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()) {
+      return false;
+    }
+    if (bootstrapMethod.member != factory.objectMethodsMembers.bootstrap) {
+      return false;
+    }
+    // From there on we assume in the assertions that the invoke to the library method is
+    // well-formed. If the invoke is not well formed assertions will fail but the execution is
+    // correct.
+    if (bootstrapMethod.isInterface) {
+      assert false
+          : "Invoke-dynamic invoking non interface method ObjectMethods#bootstrap as an interface"
+              + " method.";
+      return false;
+    }
+    // 2. Validate the bootstrapArgs include the record type, the instance field names and
+    // the corresponding instance getters.
+    if (callSite.bootstrapArgs.size() < 2) {
+      assert false
+          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with less than 2 parameters.";
+      return false;
+    }
+    DexValueType recordType = callSite.bootstrapArgs.get(0).asDexValueType();
+    if (recordType == null) {
+      assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
+      return false;
+    }
+    DexClass recordClass = appView.definitionFor(recordType.getValue(), context);
+    if (recordClass == null || recordClass.isNotProgramClass()) {
+      return false;
+    }
+    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+    if (valueString == null) {
+      assert false
+          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid field names.";
+      return false;
+    }
+    DexString fieldNames = valueString.getValue();
+    assert fieldNames.toString().isEmpty()
+        || (fieldNames.toString().split(";").length == 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
+          || !handle.value.type.isInstanceGet()
+          || !handle.value.member.isDexField()) {
+        assert false
+            : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid getters.";
+        return false;
+      }
+    }
+    // 3. Check it matches one of the 3 invokeDynamicOnRecord instruction.
+    if (callSite.methodName == factory.toStringMethodName) {
+      assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
+      return true;
+    }
+    if (callSite.methodName == factory.hashCodeMethodName) {
+      assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
+      return true;
+    }
+    if (callSite.methodName == factory.equalsMethodName) {
+      assert callSite.methodProto
+          == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
+      return true;
+    }
+    return false;
+  }
+
+  public static RecordInvokeDynamic parseInvokeDynamicOnRecord(
+      CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
+    assert isInvokeDynamicOnRecord(invokeDynamic, appView, context);
+    DexCallSite callSite = invokeDynamic.getCallSite();
+    DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
+    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+    DexString fieldNames = valueString.getValue();
+    DexField[] fields = new DexField[callSite.bootstrapArgs.size() - 2];
+    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
+      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
+      fields[i - 2] = handle.value.member.asDexField();
+    }
+    DexProgramClass recordClass =
+        appView.definitionFor(recordValueType.getValue()).asProgramClass();
+    return new RecordInvokeDynamic(
+        callSite.methodName, callSite.methodProto, fieldNames, fields, recordClass);
+  }
+
+  static class RecordInvokeDynamic {
+
+    private final DexString methodName;
+    private final DexProto methodProto;
+    private final DexString fieldNames;
+    private final DexField[] fields;
+    private final DexProgramClass recordClass;
+
+    private RecordInvokeDynamic(
+        DexString methodName,
+        DexProto methodProto,
+        DexString fieldNames,
+        DexField[] fields,
+        DexProgramClass recordClass) {
+      this.methodName = methodName;
+      this.methodProto = methodProto;
+      this.fieldNames = fieldNames;
+      this.fields = fields;
+      this.recordClass = recordClass;
+    }
+
+    RecordInvokeDynamic withFieldNamesAndFields(DexString fieldNames, DexField[] fields) {
+      return new RecordInvokeDynamic(methodName, methodProto, fieldNames, fields, recordClass);
+    }
+
+    DexField[] getFields() {
+      return fields;
+    }
+
+    DexProgramClass getRecordClass() {
+      return recordClass;
+    }
+
+    DexString getFieldNames() {
+      return fieldNames;
+    }
+
+    DexString getMethodName() {
+      return methodName;
+    }
+
+    DexProto getMethodProto() {
+      return methodProto;
+    }
+
+    RecordFieldNamesComputationInfo computeRecordFieldNamesComputationInfo() {
+      return RecordFieldNamesComputationInfo.forFieldNamesAndFields(getFieldNames(), getFields());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index a775a72..a22a766 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -19,10 +19,12 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.records.RecordCfToCfRewriter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -35,11 +37,13 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final ProguardClassFilter adaptClassStrings;
+  private final RecordCfToCfRewriter recordCfToCfRewriter;
   private final NamingLens lens;
 
   IdentifierMinifier(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     this.appView = appView;
     this.adaptClassStrings = appView.options().getProguardConfiguration().getAdaptClassStrings();
+    this.recordCfToCfRewriter = RecordCfToCfRewriter.create(appView);
     this.lens = lens;
   }
 
@@ -117,12 +121,10 @@
           for (DexEncodedField field : clazz.staticFields()) {
             replaceDexItemBasedConstStringInStaticField(field);
           }
-          clazz
-              .methods(DexEncodedMethod::hasCode)
-              .forEach(this::replaceDexItemBasedConstStringInMethod);
+          clazz.forEachProgramMethodMatching(
+              DexEncodedMethod::hasCode, this::replaceDexItemBasedConstStringInMethod);
         },
-        executorService
-    );
+        executorService);
   }
 
   private void replaceDexItemBasedConstStringInStaticField(DexEncodedField encodedField) {
@@ -137,8 +139,8 @@
     }
   }
 
-  private void replaceDexItemBasedConstStringInMethod(DexEncodedMethod encodedMethod) {
-    Code code = encodedMethod.getCode();
+  private void replaceDexItemBasedConstStringInMethod(ProgramMethod programMethod) {
+    Code code = programMethod.getDefinition().getCode();
     assert code != null;
     if (code.isDexCode()) {
       Instruction[] instructions = code.asDexCode().instructions;
@@ -157,23 +159,23 @@
     } else {
       assert code.isCfCode();
       List<CfInstruction> instructions = code.asCfCode().getInstructions();
-      List<CfInstruction> newInstructions = null;
-      for (int i = 0; i < instructions.size(); ++i) {
-        CfInstruction instruction = instructions.get(i);
-        if (instruction.isDexItemBasedConstString()) {
-          CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-          DexString replacement =
-              cnst.getNameComputationInfo()
-                  .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens);
-          if (newInstructions == null) {
-            newInstructions = new ArrayList<>(instructions);
-          }
-          newInstructions.set(i, new CfConstString(replacement));
-        }
-      }
-      if (newInstructions != null) {
-        code.asCfCode().setInstructions(newInstructions);
-      }
+      List<CfInstruction> newInstructions =
+          ListUtils.mapOrElse(
+              instructions,
+              (int i, CfInstruction instruction) -> {
+                if (instruction.isDexItemBasedConstString()) {
+                  CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
+                  return new CfConstString(
+                      cnst.getNameComputationInfo()
+                          .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens));
+                } else if (recordCfToCfRewriter != null && instruction.isInvokeDynamic()) {
+                  return recordCfToCfRewriter.rewriteRecordInvokeDynamic(
+                      instruction.asInvokeDynamic(), programMethod, lens);
+                }
+                return instruction;
+              },
+              instructions);
+      code.asCfCode().setInstructions(newInstructions);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
index 67e574c..8a16fb0 100644
--- a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
@@ -27,6 +27,10 @@
         return asClassNameComputationInfo()
             .internalComputeNameFor(rewritten.asDexType(), definitions, namingLens);
       }
+      if (isRecordFieldNamesComputationInfo()) {
+        return asRecordFieldNamesComputationInfo()
+            .internalComputeNameFor(rewritten.asDexType(), definitions, graphLens, namingLens);
+      }
     }
     return namingLens.lookupName(rewritten, definitions.dexItemFactory());
   }
@@ -53,4 +57,12 @@
   public ClassNameComputationInfo asClassNameComputationInfo() {
     return null;
   }
+
+  public boolean isRecordFieldNamesComputationInfo() {
+    return false;
+  }
+
+  public RecordFieldNamesComputationInfo asRecordFieldNamesComputationInfo() {
+    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
new file mode 100644
index 0000000..17ac83a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
@@ -0,0 +1,168 @@
+// 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.naming.dexitembasedstring;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.IntFunction;
+
+/**
+ * Specific subclass for Record field names. Record field names are specific DexString instances
+ * used at the Java class file level for Records which encode the record field names in a single
+ * String separated by semi-colon. The computation is able to minify and prune such names.
+ *
+ * <p>Example: record Person(String name, int age) {} The string is: name;age
+ *
+ * <p>The JVM normally generates the Record field names string such as it includes each field name
+ * in the string. However, bytecode manipulation tools may rewrite the field name and not the
+ * string. For this reason we have two subclasses. - MatchingRecordFieldNamesComputationInfo is used
+ * when the name in the string matches the field names, and the R8 compilation is able to minify and
+ * prune the string based on the fields. - MissMatchingRecordFieldNamesComputationInfo is used when
+ * the name in the string does not match the field names, and the R8 compilation is then only able
+ * to prune the fields while maintaining the non-matching field names.
+ */
+public abstract class RecordFieldNamesComputationInfo extends NameComputationInfo<DexType> {
+
+  final DexField[] fields;
+
+  protected RecordFieldNamesComputationInfo(DexField[] fields) {
+    this.fields = fields;
+  }
+
+  private static class MissMatchingRecordFieldNamesComputationInfo
+      extends RecordFieldNamesComputationInfo {
+
+    private final String[] fieldNames;
+
+    private MissMatchingRecordFieldNamesComputationInfo(String[] fieldNames, DexField[] fields) {
+      super(fields);
+      this.fieldNames = fieldNames;
+    }
+
+    @Override
+    public DexString internalComputeNameFor(
+        DexType type,
+        DexDefinitionSupplier definitions,
+        GraphLens graphLens,
+        NamingLens namingLens) {
+      return internalComputeNameFor(type, definitions, graphLens, i -> fieldNames[i]);
+    }
+  }
+
+  private static class MatchingRecordFieldNamesComputationInfo
+      extends RecordFieldNamesComputationInfo {
+
+    public MatchingRecordFieldNamesComputationInfo(DexField[] fields) {
+      super(fields);
+    }
+
+    @Override
+    public DexString internalComputeNameFor(
+        DexType type,
+        DexDefinitionSupplier definitions,
+        GraphLens graphLens,
+        NamingLens namingLens) {
+      return internalComputeNameFor(
+          type,
+          definitions,
+          graphLens,
+          i ->
+              namingLens
+                  .lookupField(
+                      graphLens.getRenamedFieldSignature(fields[i]), definitions.dexItemFactory())
+                  .name
+                  .toString());
+    }
+  }
+
+  static DexString dexStringFromFieldNames(List<String> fieldNames, DexItemFactory factory) {
+    return factory.createString(StringUtils.join(";", fieldNames));
+  }
+
+  public static RecordFieldNamesComputationInfo forFieldNamesAndFields(
+      DexString fieldNames, DexField[] fields) {
+    String fieldNamesString = fieldNames.toString();
+    String[] fieldNamesSplit =
+        fieldNamesString.isEmpty() ? new String[0] : fieldNamesString.split(";");
+    assert fieldNamesSplit.length == fields.length;
+    if (fieldsMatchNames(fieldNamesSplit, fields)) {
+      return new MatchingRecordFieldNamesComputationInfo(fields);
+    }
+    return new MissMatchingRecordFieldNamesComputationInfo(fieldNamesSplit, fields);
+  }
+
+  private static boolean fieldsMatchNames(String[] fieldNames, DexField[] fields) {
+    for (int i = 0; i < fieldNames.length; i++) {
+      if (!(fields[i].name.toString().equals(fieldNames[i]))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public DexString internalComputeNameFor(
+      DexType type,
+      DexDefinitionSupplier definitions,
+      GraphLens graphLens,
+      IntFunction<String> nameSupplier) {
+    assert Arrays.stream(fields).allMatch(f -> f.holder == type);
+    DexClass recordClass = definitions.contextIndependentDefinitionFor(type);
+    assert recordClass != null;
+    List<String> names = new ArrayList<>(fields.length);
+    for (int i = 0; i < fields.length; i++) {
+      DexEncodedField recordField =
+          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());
+  }
+
+  @Override
+  DexString internalComputeNameFor(
+      DexType reference, DexDefinitionSupplier definitions, NamingLens namingLens) {
+    throw new Unreachable();
+  }
+
+  public abstract DexString internalComputeNameFor(
+      DexType type, DexDefinitionSupplier definitions, GraphLens graphLens, NamingLens namingLens);
+
+  @Override
+  public boolean needsToComputeName() {
+    return true;
+  }
+
+  @Override
+  public boolean needsToRegisterReference() {
+    return false;
+  }
+
+  @Override
+  public boolean isRecordFieldNamesComputationInfo() {
+    return true;
+  }
+
+  @Override
+  public RecordFieldNamesComputationInfo asRecordFieldNamesComputationInfo() {
+    return this;
+  }
+}
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 ff3878e..24e9259 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1590,6 +1590,10 @@
     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 allowConflictingSyntheticTypes = false;
 
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
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 7b653ed..df6be44 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,6 +29,13 @@
   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 final TestParameters parameters;
 
@@ -83,6 +90,43 @@
   }
 
   @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)