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();
+ }
+}