Desugar invoke-custom for records

Bug: 169645628
Change-Id: I862f87a9498d246df03a2d3ee615e77ba9f88d05
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 56bdfda..30579d8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.RecordRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -309,6 +310,9 @@
       if (options.enableEnumUnboxing) {
         EnumUnboxingCfMethods.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
+      if (options.shouldDesugarRecords()) {
+        RecordRewriter.registerSynthesizedCodeReferences(appView.dexItemFactory());
+      }
       CfUtilityMethodsForCodeOptimizations.registerSynthesizedCodeReferences(
           appView.dexItemFactory());
 
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 2ce742a..1339701 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -631,6 +631,10 @@
   public final DexType callSiteType = createStaticallyKnownType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType =
       createStaticallyKnownType("Ljava/lang/invoke/MethodHandles$Lookup;");
+  public final DexType objectMethodsType =
+      createStaticallyKnownType("Ljava/lang/runtime/ObjectMethods;");
+  public final DexType typeDescriptorType =
+      createStaticallyKnownType("Ljava/lang/invoke/TypeDescriptor;");
   public final DexType iteratorType = createStaticallyKnownType("Ljava/util/Iterator;");
   public final DexType listIteratorType = createStaticallyKnownType("Ljava/util/ListIterator;");
   public final DexType enumerationType = createStaticallyKnownType("Ljava/util/Enumeration;");
@@ -642,6 +646,7 @@
       createStaticallyKnownType("Ljava/lang/invoke/StringConcatFactory;");
   public final DexType unsafeType = createStaticallyKnownType("Lsun/misc/Unsafe;");
 
+  public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
   public final StringConcatFactoryMembers stringConcatFactoryMembers =
       new StringConcatFactoryMembers();
@@ -1215,6 +1220,21 @@
     public final DexMethod toString = createMethod(recordType, createProto(stringType), "toString");
   }
 
+  public class ObjectMethodsMembers {
+    public final DexMethod bootstrap =
+        createMethod(
+            objectMethodsType,
+            createProto(
+                objectType,
+                lookupType,
+                stringType,
+                typeDescriptorType,
+                classType,
+                stringType,
+                createArrayType(1, methodHandleType)),
+            "bootstrap");
+  }
+
   public class ObjectMembers {
 
     /**
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index bb8e12a..4ba4555 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -95,6 +95,9 @@
       // The non-synthetic holder is not scheduled. It will be processed once holder is scheduled.
       return;
     }
+    if (method.getDefinition().isAbstract()) {
+      return;
+    }
     terminalFutures.add(
         ThreadUtils.processAsynchronously(
             () ->
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
index d5acaa7..9b02ab5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 
 public abstract class CfClassDesugaringEventConsumer implements RecordDesugaringEventConsumer {
@@ -25,6 +26,11 @@
     public void acceptRecordClass(DexProgramClass recordClass) {
       methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
     }
+
+    @Override
+    public void acceptRecordMethod(ProgramMethod method) {
+      assert false;
+    }
   }
 
   // TODO(b/): Implement R8CfClassDesugaringEventConsumer
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index a95569e..a0c4c64 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -64,6 +64,11 @@
       }
 
       @Override
+      public void acceptRecordMethod(ProgramMethod method) {
+        assert false;
+      }
+
+      @Override
       public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
         assert false;
       }
@@ -119,6 +124,11 @@
     }
 
     @Override
+    public void acceptRecordMethod(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
     public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
       synchronized (pendingInvokeSpecialBridges) {
         assert !pendingInvokeSpecialBridges.containsKey(info.getNewDirectMethod().getReference());
@@ -242,6 +252,11 @@
     }
 
     @Override
+    public void acceptRecordMethod(ProgramMethod method) {
+      assert false : "TODO(b/179146128): To be implemented";
+    }
+
+    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
       // present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java
new file mode 100644
index 0000000..85a7982
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java
@@ -0,0 +1,454 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateRecordMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+
+public final class RecordCfMethods {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType(
+        "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;");
+    factory.createSynthesizedType("Ljava/lang/Record;");
+    factory.createSynthesizedType("Ljava/util/Arrays;");
+    factory.createSynthesizedType("[Ljava/lang/Object;");
+    factory.createSynthesizedType("[Ljava/lang/String;");
+  }
+
+  public static CfCode RecordMethods_equals(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.classType),
+                    options.itemFactory.createString("getClass")),
+                false),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.classType),
+                    options.itemFactory.createString("getClass")),
+                false),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label3),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfCheckCast(options.itemFactory.createType("Ljava/lang/Record;")),
+            label1,
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            label2,
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.booleanType,
+                        options.itemFactory.createType("[Ljava/lang/Object;"),
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("equals")),
+                false),
+            new CfIf(If.Type.EQ, ValueType.INT, label3),
+            new CfConstNumber(1, ValueType.INT),
+            new CfGoto(label4),
+            label3,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfConstNumber(0, ValueType.INT),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
+            new CfReturn(ValueType.INT),
+            label5),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode RecordMethods_hashCode(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfConstNumber(31, ValueType.INT),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("hashCode")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
+            new CfLoad(ValueType.OBJECT, 0),
+            label1,
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.classType),
+                    options.itemFactory.createString("getClass")),
+                false),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.intType),
+                    options.itemFactory.createString("hashCode")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            label2,
+            new CfReturn(ValueType.INT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode RecordMethods_toString(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    CfLabel label13 = new CfLabel();
+    CfLabel label14 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        7,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringType,
+                    options.itemFactory.createProto(options.itemFactory.booleanType),
+                    options.itemFactory.createString("isEmpty")),
+                false),
+            new CfIf(If.Type.EQ, ValueType.INT, label1),
+            new CfConstNumber(0, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/String;")),
+            new CfGoto(label2),
+            label1,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfConstString(options.itemFactory.createString(";")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/String;"),
+                        options.itemFactory.stringType),
+                    options.itemFactory.createString("split")),
+                false),
+            label2,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType("[Ljava/lang/String;"))))),
+            new CfStore(ValueType.OBJECT, 3),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            new CfStore(ValueType.OBJECT, 4),
+            label4,
+            new CfNew(options.itemFactory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfStore(ValueType.OBJECT, 5),
+            label5,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfConstString(options.itemFactory.createString("[")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label6,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 6),
+            label7,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5, 6},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.INT, 6),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfArrayLength(),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label12),
+            label8,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfLoad(ValueType.INT, 6),
+            new CfArrayLoad(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfConstString(options.itemFactory.createString("=")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfLoad(ValueType.INT, 6),
+            new CfArrayLoad(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.objectType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label9,
+            new CfLoad(ValueType.INT, 6),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfArrayLength(),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfIfCmp(If.Type.EQ, ValueType.INT, label11),
+            label10,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfConstString(options.itemFactory.createString(", ")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label11,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5, 6},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfIinc(6, 1),
+            new CfGoto(label7),
+            label12,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initialized(options.itemFactory.stringBuilderType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfConstString(options.itemFactory.createString("]")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label13,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label14),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
index 19d9ae3..61eb249 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
@@ -5,8 +5,11 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 
 public interface RecordDesugaringEventConsumer {
 
   void acceptRecordClass(DexProgramClass recordClass);
+
+  void acceptRecordMethod(ProgramMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
index 67e2c90..d94a747 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -4,50 +4,78 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.cf.code.CfConstNull;
-import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.cf.code.CfTypeInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+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.DexAnnotationSet;
+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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.RecordGetFieldsAsObjectsCfCodeProvider;
+import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
+import java.util.function.BiFunction;
+import org.objectweb.asm.Opcodes;
 
 public class RecordRewriter implements CfInstructionDesugaring, CfClassDesugaring {
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
+  private final DexProto recordToStringHelperProto;
+  private final DexProto recordEqualsHelperProto;
+  private final DexProto recordHashCodeHelperProto;
+
+  public static final String GET_FIELDS_AS_OBJECTS_METHOD_NAME = "$record$getFieldsAsObjects";
 
   public static RecordRewriter create(AppView<?> appView) {
     return appView.options().shouldDesugarRecords() ? new RecordRewriter(appView) : null;
   }
 
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    RecordCfMethods.registerSynthesizedCodeReferences(factory);
+    RecordGetFieldsAsObjectsCfCodeProvider.registerSynthesizedCodeReferences(factory);
+  }
+
   private RecordRewriter(AppView<?> appView) {
     this.appView = appView;
     factory = appView.dexItemFactory();
+    recordToStringHelperProto =
+        factory.createProto(
+            factory.stringType, factory.recordType, factory.stringType, factory.stringType);
+    recordEqualsHelperProto =
+        factory.createProto(factory.booleanType, factory.recordType, factory.objectType);
+    recordHashCodeHelperProto = factory.createProto(factory.intType, factory.recordType);
   }
 
   public void scan(
@@ -97,41 +125,168 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
-
-    // TODO(b/179146128): This is a temporary work-around to test desugaring of records
-    // without rewriting the record invoke-custom. This should be removed when the record support
-    // is complete.
-    if (instruction.isInvokeDynamic()
-        && context.getHolder().superType == factory.recordType
-        && (context.getReference().match(factory.recordMembers.toString)
-            || context.getReference().match(factory.recordMembers.hashCode)
-            || context.getReference().match(factory.recordMembers.equals))) {
-      requiresRecordClass(eventConsumer);
-      CfInstruction constant =
-          context.getReference().match(factory.recordMembers.toString)
-              ? new CfConstNull()
-              : new CfConstNumber(0, ValueType.INT);
-      return ImmutableList.of(new CfStackInstruction(CfStackInstruction.Opcode.Pop), constant);
-    }
-
-    CfInstruction desugaredInstruction = desugarInstruction(instruction, context);
-    return desugaredInstruction == null ? null : Collections.singletonList(desugaredInstruction);
-  }
-
-  private CfInstruction desugarInstruction(CfInstruction instruction, ProgramMethod context) {
     assert !instruction.isInitClass();
-    // TODO(b/179146128): Rewrite record invoke-dynamic here.
+    if (instruction.isInvokeDynamic() && needsDesugaring(instruction.asInvokeDynamic(), context)) {
+      return desugarInvokeDynamicOnRecord(
+          instruction.asInvokeDynamic(), context, eventConsumer, methodProcessingContext);
+    }
     if (instruction.isInvoke()) {
       CfInvoke cfInvoke = instruction.asInvoke();
       DexMethod newMethod =
           rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()));
       if (newMethod != cfInvoke.getMethod()) {
-        return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface());
+        return Collections.singletonList(
+            new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface()));
       }
     }
     return null;
   }
 
+  public List<CfInstruction> desugarInvokeDynamicOnRecord(
+      CfInvokeDynamic invokeDynamic,
+      ProgramMethod context,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    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();
+    if (callSite.methodName == factory.toStringMethodName) {
+      DexString simpleName =
+          ClassNameComputationInfo.ClassNameMapping.SIMPLE_NAME.map(
+              recordValueType.getValue().toDescriptorString(), context.getHolder(), factory);
+      return desugarInvokeRecordToString(
+          recordClass, fieldNames, fields, simpleName, eventConsumer, methodProcessingContext);
+    }
+    if (callSite.methodName == factory.hashCodeMethodName) {
+      return desugarInvokeRecordHashCode(
+          recordClass, fields, eventConsumer, methodProcessingContext);
+    }
+    if (callSite.methodName == factory.equalsMethodName) {
+      return desugarInvokeRecordEquals(recordClass, fields, eventConsumer, methodProcessingContext);
+    }
+    throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared.");
+  }
+
+  private ProgramMethod synthesizeGetFieldsAsObjectsMethod(
+      DexProgramClass clazz, DexField[] fields, DexMethod method) {
+    MethodAccessFlags methodAccessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
+    DexEncodedMethod encodedMethod =
+        new DexEncodedMethod(
+            method,
+            methodAccessFlags,
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            null,
+            true);
+    encodedMethod.setCode(
+        new RecordGetFieldsAsObjectsCfCodeProvider(appView, factory.r8RecordType, fields)
+            .generateCfCode(),
+        appView);
+    return new ProgramMethod(clazz, encodedMethod);
+  }
+
+  private void ensureGetFieldsAsObjects(
+      DexProgramClass clazz, DexField[] fields, RecordDesugaringEventConsumer eventConsumer) {
+    DexMethod method = getFieldsAsObjectsMethod(clazz.type);
+    synchronized (clazz.getMethodCollection()) {
+      ProgramMethod getFieldsAsObjects = clazz.lookupProgramMethod(method);
+      if (getFieldsAsObjects == null) {
+        getFieldsAsObjects = synthesizeGetFieldsAsObjectsMethod(clazz, fields, method);
+        clazz.addVirtualMethod(getFieldsAsObjects.getDefinition());
+        if (eventConsumer != null) {
+          eventConsumer.acceptRecordMethod(getFieldsAsObjects);
+        }
+      }
+    }
+  }
+
+  private DexMethod getFieldsAsObjectsMethod(DexType holder) {
+    return factory.createMethod(
+        holder, factory.createProto(factory.objectArrayType), GET_FIELDS_AS_OBJECTS_METHOD_NAME);
+  }
+
+  private ProgramMethod synthesizeRecordHelper(
+      DexProto helperProto,
+      BiFunction<InternalOptions, DexMethod, CfCode> codeGenerator,
+      MethodProcessingContext methodProcessingContext) {
+    return appView
+        .getSyntheticItems()
+        .createMethod(
+            SyntheticNaming.SyntheticKind.RECORD_HELPER,
+            methodProcessingContext.createUniqueContext(),
+            factory,
+            builder ->
+                builder
+                    .setProto(helperProto)
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(methodSig -> codeGenerator.apply(appView.options(), methodSig)));
+  }
+
+  private List<CfInstruction> desugarInvokeRecordHashCode(
+      DexProgramClass recordClass,
+      DexField[] fields,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    ensureGetFieldsAsObjects(recordClass, fields, eventConsumer);
+    ProgramMethod programMethod =
+        synthesizeRecordHelper(
+            recordHashCodeHelperProto,
+            RecordCfMethods::RecordMethods_hashCode,
+            methodProcessingContext);
+    eventConsumer.acceptRecordMethod(programMethod);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+  }
+
+  private List<CfInstruction> desugarInvokeRecordEquals(
+      DexProgramClass recordClass,
+      DexField[] fields,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    ensureGetFieldsAsObjects(recordClass, fields, eventConsumer);
+    ProgramMethod programMethod =
+        synthesizeRecordHelper(
+            recordEqualsHelperProto,
+            RecordCfMethods::RecordMethods_equals,
+            methodProcessingContext);
+    eventConsumer.acceptRecordMethod(programMethod);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+  }
+
+  private List<CfInstruction> desugarInvokeRecordToString(
+      DexProgramClass recordClass,
+      DexString fieldNames,
+      DexField[] fields,
+      DexString simpleName,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    ensureGetFieldsAsObjects(recordClass, fields, eventConsumer);
+    ArrayList<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfConstString(simpleName));
+    instructions.add(new CfConstString(fieldNames));
+    ProgramMethod programMethod =
+        synthesizeRecordHelper(
+            recordToStringHelperProto,
+            RecordCfMethods::RecordMethods_toString,
+            methodProcessingContext);
+    eventConsumer.acceptRecordMethod(programMethod);
+    instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+    return instructions;
+  }
+
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
     assert !instruction.isInitClass();
@@ -210,14 +365,85 @@
     return rewriteMethod(method, isSuper) != method;
   }
 
+  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;
+  }
+
   @SuppressWarnings("ConstantConditions")
   private DexMethod rewriteMethod(DexMethod method, boolean isSuper) {
-    if (method.holder != factory.recordType || method.isInstanceInitializer(factory)) {
+    if (!(method == factory.recordMembers.equals
+        || method == factory.recordMembers.hashCode
+        || method == factory.recordMembers.toString)) {
       return method;
     }
-    assert method == factory.recordMembers.equals
-        || method == factory.recordMembers.hashCode
-        || method == factory.recordMembers.toString;
     if (isSuper) {
       // TODO(b/179146128): Support rewriting invoke-super to a Record method.
       throw new CompilationError("Rewrite invoke-super to abstract method error.");
@@ -239,17 +465,45 @@
     if (recordClass != null && recordClass.isProgramClass()) {
       return null;
     }
-    assert recordClass == null || recordClass.isLibraryClass();
+    return synchronizedSynthesizeR8Record();
+  }
+
+  private synchronized DexProgramClass synchronizedSynthesizeR8Record() {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexClass recordClass =
+        appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType);
+    if (recordClass != null && recordClass.isProgramClass()) {
+      return null;
+    }
     DexEncodedMethod init = synthesizeRecordInitMethod();
-    // TODO(b/179146128): We may want to remove here the class from the library classes if present
-    //  in cf to cf.
+    DexEncodedMethod abstractGetFieldsAsObjectsMethod =
+        synthesizeAbstractGetFieldsAsObjectsMethod();
     return appView
         .getSyntheticItems()
         .createFixedClassFromType(
             SyntheticNaming.SyntheticKind.RECORD_TAG,
             factory.recordType,
             factory,
-            builder -> builder.setAbstract().setDirectMethods(Collections.singletonList(init)));
+            builder ->
+                builder
+                    .setAbstract()
+                    .setVirtualMethods(ImmutableList.of(abstractGetFieldsAsObjectsMethod))
+                    .setDirectMethods(ImmutableList.of(init)));
+  }
+
+  private DexEncodedMethod synthesizeAbstractGetFieldsAsObjectsMethod() {
+    MethodAccessFlags methodAccessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT, false);
+    DexMethod fieldsAsObjectsMethod = getFieldsAsObjectsMethod(factory.recordType);
+    return new DexEncodedMethod(
+        fieldsAsObjectsMethod,
+        methodAccessFlags,
+        MethodTypeSignature.noSignature(),
+        DexAnnotationSet.empty(),
+        ParameterAnnotationsList.empty(),
+        null,
+        true);
   }
 
   private DexEncodedMethod synthesizeRecordInitMethod() {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java
new file mode 100644
index 0000000..4f534da
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Generates a method which answers all field values as an array of objects. If the field value is a
+ * primitive type, it uses the primitive wrapper to wrap it.
+ *
+ * <p>The fields in parameters are in the order where they should be in the array generated by the
+ * method, which is not necessarily the class instanceFields order.
+ *
+ * <p>Example: <code>record Person{ int age; String name;}</code>
+ *
+ * <p><code>Object[] getFieldsAsObjects() {
+ * Object[] fields = new Object[2];
+ * fields[0] = name;
+ * fields[1] = Integer.valueOf(age);
+ * return fields;</code>
+ */
+public class RecordGetFieldsAsObjectsCfCodeProvider extends SyntheticCfCodeProvider {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("[Ljava/lang/Object;");
+    factory.primitiveToBoxed.forEach(
+        (primitiveType, boxedType) -> {
+          factory.createSynthesizedType(primitiveType.toDescriptorString());
+          factory.createSynthesizedType(boxedType.toDescriptorString());
+        });
+  }
+
+  private final DexField[] fields;
+
+  public RecordGetFieldsAsObjectsCfCodeProvider(
+      AppView<?> appView, DexType holder, DexField[] fields) {
+    super(appView, holder);
+    this.fields = fields;
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    // Stack layout:
+    // 0 : receiver (the record instance)
+    // 1 : the array to return
+    // 2+: spills
+    DexItemFactory factory = appView.dexItemFactory();
+    List<CfInstruction> instructions = new ArrayList<>();
+    // Object[] fields = new Object[*length*];
+    instructions.add(new CfConstNumber(fields.length, ValueType.INT));
+    instructions.add(new CfNewArray(factory.objectArrayType));
+    instructions.add(new CfStore(ValueType.OBJECT, 1));
+    // fields[*i*] = this.*field* || *PrimitiveWrapper*.valueOf(this.*field*);
+    for (int i = 0; i < fields.length; i++) {
+      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));
+              }
+            });
+      }
+      instructions.add(new CfArrayStore(MemberType.OBJECT));
+    }
+    // return fields;
+    instructions.add(new CfLoad(ValueType.OBJECT, 1));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 7148738..cb1e91b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -31,6 +31,7 @@
     HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", false, true),
     // Method synthetics.
+    RECORD_HELPER("Record", true),
     BACKPORT("Backport", true),
     STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
     TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 2e97d1e..199869e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,20 +36,37 @@
   public static List<Object[]> data() {
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevels()
+            .build());
   }
 
   @Test
-  public void testJvm() throws Exception {
-    testForJvm()
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      // TODO(b/179146128): Add a test for D8 cf to cf.
+      return;
+    }
+    testForD8()
         .addProgramClassFileData(PROGRAM_DATA)
-        .enablePreview()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8Cf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     Path output =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
new file mode 100644
index 0000000..53d6201
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.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.desugar.records;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfTypeInstruction;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.RecordRewriter;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+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 GenerateRecordMethods extends MethodGenerationBase {
+  private final DexType GENERATED_TYPE =
+      factory.createType("Lcom/android/tools/r8/ir/desugar/RecordCfMethods;");
+  private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of(RecordMethods.class);
+
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+  }
+
+  public GenerateRecordMethods(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected DexType getGeneratedType() {
+    return GENERATED_TYPE;
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return METHOD_TEMPLATE_CLASSES;
+  }
+
+  @Override
+  protected int getYear() {
+    return 2021;
+  }
+
+  @Override
+  protected CfCode getCode(String holderName, String methodName, CfCode code) {
+    DexType recordStubType =
+        factory.createType("Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;");
+    code.setInstructions(
+        code.getInstructions().stream()
+            .map(instruction -> rewriteRecordStub(factory, instruction, recordStubType))
+            .collect(Collectors.toList()));
+    return code;
+  }
+
+  private CfInstruction rewriteRecordStub(
+      DexItemFactory factory, CfInstruction instruction, DexType recordStubType) {
+    if (instruction.isTypeInstruction()) {
+      CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
+      return typeInstruction.withType(
+          rewriteType(factory, recordStubType, typeInstruction.getType()));
+    }
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      DexMethod method = cfInvoke.getMethod();
+      DexMethod newMethod =
+          factory.createMethod(
+              rewriteType(factory, recordStubType, method.holder),
+              method.proto,
+              rewriteName(method.name));
+      return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface());
+    }
+    return instruction;
+  }
+
+  private String rewriteName(DexString name) {
+    return name.toString().equals("getFieldsAsObjects")
+        ? RecordRewriter.GET_FIELDS_AS_OBJECTS_METHOD_NAME
+        : name.toString();
+  }
+
+  private DexType rewriteType(DexItemFactory factory, DexType recordStubType, DexType type) {
+    return type == recordStubType ? factory.recordType : type;
+  }
+
+  @Test
+  public void testRecordMethodsGenerated() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+    sorted.sort(Comparator.comparing(Class::getTypeName));
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+    assertEquals(
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new GenerateRecordMethods(null).generateMethodsAndWriteThemToFile();
+  }
+}
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 071af85..7a305bd 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -48,20 +49,37 @@
   public static List<Object[]> data() {
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevels()
+            .build());
   }
 
   @Test
-  public void testJvm() throws Exception {
-    testForJvm()
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      // TODO(b/179146128): Add a test for D8 cf to cf.
+      return;
+    }
+    testForD8()
         .addProgramClassFileData(PROGRAM_DATA)
-        .enablePreview()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8Cf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     Path output =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
new file mode 100644
index 0000000..7b1b2e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
@@ -0,0 +1,43 @@
+// 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 java.util.Arrays;
+
+// This class implements support methods for record desugaring. The RecordRewriter
+// rewrites relevant calls to one of the following methods.
+public class RecordMethods {
+
+  public static String toString(RecordStub recordInstance, String simpleName, String fieldNames) {
+    // Example: "Person[name=Jane Doe, age=42]"
+    String[] fieldNamesSplit = fieldNames.isEmpty() ? new String[0] : fieldNames.split(";");
+    Object[] fields = recordInstance.getFieldsAsObjects();
+    StringBuilder builder = new StringBuilder();
+    builder.append(simpleName).append("[");
+    for (int i = 0; i < fieldNamesSplit.length; i++) {
+      builder.append(fieldNamesSplit[i]).append("=").append(fields[i]);
+      if (i != fieldNamesSplit.length - 1) {
+        builder.append(", ");
+      }
+    }
+    builder.append("]");
+    return builder.toString();
+  }
+
+  public static int hashCode(RecordStub recordInstance) {
+    return 31 * Arrays.hashCode(recordInstance.getFieldsAsObjects())
+        + recordInstance.getClass().hashCode();
+  }
+
+  public static boolean equals(RecordStub recordInstance, Object other) {
+    return recordInstance.getClass() == other.getClass()
+        && Arrays.equals(
+            ((RecordStub) other).getFieldsAsObjects(), recordInstance.getFieldsAsObjects());
+  }
+
+  public abstract static class RecordStub {
+    abstract Object[] getFieldsAsObjects();
+  }
+}