Introduce RecordFieldValues instruction
Bug: 201277582
Change-Id: Ic3252a337f9850d7f070830ba93470619cbcad1f
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ec0f361..7ad9b39 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -51,6 +51,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterLibraryTypeSynthesizer;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.records.RecordFieldValuesRewriter;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.NestReducer;
@@ -688,6 +689,12 @@
performFinalMainDexTracing(appView, executorService);
+ RecordFieldValuesRewriter recordFieldArrayRemover =
+ RecordFieldValuesRewriter.create(appView.withLiveness());
+ if (recordFieldArrayRemover != null) {
+ recordFieldArrayRemover.rewriteRecordFieldValues();
+ }
+
// Remove unneeded visibility bridges that have been inserted for member rebinding.
// This can only be done if we have AppInfoWithLiveness.
if (appView.appInfo().hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 480c9cb..98808c7 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -42,6 +42,7 @@
import com.android.tools.r8.cf.code.CfNop;
import com.android.tools.r8.cf.code.CfNumberConversion;
import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfRecordFieldValues;
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfStackInstruction;
@@ -254,6 +255,14 @@
builder.append(name);
}
+ public void print(CfRecordFieldValues recordFieldValues) {
+ String recordType =
+ recordFieldValues.getFields().length == 0
+ ? "empty"
+ : recordFieldValues.getFields()[0].holder.toString();
+ print("record_field_values(" + recordType + ")");
+ }
+
public void print(CfNop nop) {
print("nop");
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index ab73479..742a2be 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -110,6 +110,14 @@
return true;
}
+ public CfRecordFieldValues asRecordFieldValues() {
+ return null;
+ }
+
+ public boolean isRecordFieldValues() {
+ return false;
+ }
+
public CfConstString asConstString() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
new file mode 100644
index 0000000..b5b2275
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfRecordFieldValues extends CfInstruction {
+
+ private final DexField[] fields;
+
+ public CfRecordFieldValues(DexField[] fields) {
+ this.fields = fields;
+ }
+
+ private static void specify(StructuralSpecification<CfRecordFieldValues, ?> spec) {
+ spec.withItemArray(f -> f.fields);
+ }
+
+ @Override
+ public void write(
+ AppView<?> appView,
+ ProgramMethod context,
+ DexItemFactory dexItemFactory,
+ GraphLens graphLens,
+ InitClassLens initClassLens,
+ NamingLens namingLens,
+ LensCodeRewriterUtils rewriter,
+ MethodVisitor visitor) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public CfRecordFieldValues asRecordFieldValues() {
+ return this;
+ }
+
+ @Override
+ public boolean isRecordFieldValues() {
+ return true;
+ }
+
+ public DexField[] getFields() {
+ return fields;
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ @Override
+ public int getCompareToId() {
+ return CfCompareHelper.RECORD_FIELD_VALUES_COMPARE_ID;
+ }
+
+ @Override
+ public int internalAcceptCompareTo(
+ CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+ return visitor.visit(this, other.asRecordFieldValues(), CfRecordFieldValues::specify);
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int parameterCount = fields.length;
+ int[] registers = new int[parameterCount];
+ for (int i = parameterCount - 1; i >= 0; i--) {
+ Slot slot = state.pop();
+ registers[i] = slot.register;
+ }
+ builder.addRecordFieldValues(
+ fields,
+ IntArrayList.wrap(registers),
+ state.push(builder.dexItemFactory().objectArrayType).register);
+ }
+
+ @Override
+ public ConstraintWithTarget inliningConstraint(
+ InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+ return inliningConstraints.forRecordFieldValues();
+ }
+
+ @Override
+ public void evaluate(
+ CfFrameVerificationHelper frameBuilder,
+ DexType context,
+ DexType returnType,
+ DexItemFactory factory,
+ InitClassLens initClassLens) {
+ for (DexField ignored : fields) {
+ frameBuilder.popInitialized(factory.objectType);
+ }
+ frameBuilder.push(factory.objectArrayType);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/code/DexCompareHelper.java b/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
index 0587c08..130e69b 100644
--- a/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
+++ b/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
@@ -9,6 +9,7 @@
// virtual instructions represented in our internal encoding.
static final int INIT_CLASS_COMPARE_ID;
static final int DEX_ITEM_CONST_STRING_COMPARE_ID;
+ static final int DEX_RECORD_FIELD_VALUES_COMPARE_ID;
private static int HIGHEST_DEX_OPCODE = 0xFF;
@@ -16,6 +17,7 @@
int lastId = HIGHEST_DEX_OPCODE;
INIT_CLASS_COMPARE_ID = ++lastId;
DEX_ITEM_CONST_STRING_COMPARE_ID = ++lastId;
+ DEX_RECORD_FIELD_VALUES_COMPARE_ID = ++lastId;
}
// Helper to signal that the concrete instruction is uniquely determined by its ID/opcode.
diff --git a/src/main/java/com/android/tools/r8/code/DexRecordFieldValues.java b/src/main/java/com/android/tools/r8/code/DexRecordFieldValues.java
new file mode 100644
index 0000000..13cde75
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/DexRecordFieldValues.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.code;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+
+public class DexRecordFieldValues extends Instruction {
+
+ public static final String NAME = "RecordFieldValues";
+ public static final String SMALI_NAME = "record-field-values*";
+
+ private final int outRegister;
+ private final int[] arguments;
+ private final DexField[] fields;
+
+ public DexRecordFieldValues(int outRegister, int[] arguments, DexField[] fields) {
+ this.outRegister = outRegister;
+ this.arguments = arguments;
+ this.fields = fields;
+ }
+
+ @Override
+ public void collectIndexedItems(
+ IndexedItemCollection indexedItems,
+ ProgramMethod context,
+ GraphLens graphLens,
+ LensCodeRewriterUtils rewriter) {
+ for (DexField field : fields) {
+ field.collectIndexedItems(indexedItems);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getSmaliName() {
+ return SMALI_NAME;
+ }
+
+ @Override
+ public int getOpcode() {
+ throw new Unreachable(
+ "DexRecordFieldValues instructions should always be rewritten into NewArray");
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ int getCompareToId() {
+ return DexCompareHelper.DEX_RECORD_FIELD_VALUES_COMPARE_ID;
+ }
+
+ @Override
+ int internalAcceptCompareTo(Instruction other, CompareToVisitor visitor) {
+ return visitor.visit(this, (DexRecordFieldValues) other, DexRecordFieldValues::specify);
+ }
+
+ private static void specify(StructuralSpecification<DexRecordFieldValues, ?> spec) {
+ spec.withItemArray(i -> i.fields);
+ }
+
+ private void appendArguments(StringBuilder builder) {
+ builder.append("{ ");
+ for (int i = 0; i < arguments.length; i++) {
+ if (i != 0) {
+ builder.append(",");
+ }
+ builder.append("v").append(arguments[i]);
+ }
+ builder.append(" }");
+ }
+
+ @Override
+ public String toString(ClassNameMapper naming) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("v").append(outRegister).append(" ");
+ appendArguments(sb);
+ return formatString(sb.toString());
+ }
+
+ @Override
+ public String toSmaliString(ClassNameMapper naming) {
+ return toString(naming);
+ }
+
+ @Override
+ public void write(
+ ShortBuffer dest,
+ ProgramMethod context,
+ GraphLens graphLens,
+ ObjectToOffsetMapping mapping,
+ LensCodeRewriterUtils rewriter) {
+ throw new Unreachable(
+ "DexRecordFieldValues instructions should always be rewritten into NewArray");
+ }
+
+ @Override
+ public boolean isRecordFieldValues() {
+ return true;
+ }
+
+ @Override
+ public void registerUse(UseRegistry registry) {
+ registry.registerRecordFieldValues(fields);
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder) {
+ IntList parameters = new IntArrayList();
+ for (int i = 0; i < arguments.length; i++) {
+ parameters.add(arguments[i]);
+ }
+ builder.addRecordFieldValues(fields, parameters, outRegister);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * getClass().hashCode() + Arrays.hashCode(fields);
+ }
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 5a10577..03d7a3d 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -183,6 +183,10 @@
return false;
}
+ public boolean isRecordFieldValues() {
+ return false;
+ }
+
public DexItemBasedConstString asDexItemBasedConstString() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
index d3c3351..063123a 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
@@ -30,6 +30,7 @@
public static final int INIT_CLASS_COMPARE_ID;
public static final int LABEL_COMPARE_ID;
public static final int POSITION_COMPARE_ID;
+ public static final int RECORD_FIELD_VALUES_COMPARE_ID;
static {
int lastId = Opcodes.IFNONNULL;
@@ -44,6 +45,7 @@
INIT_CLASS_COMPARE_ID = ++lastId;
LABEL_COMPARE_ID = ++lastId;
POSITION_COMPARE_ID = ++lastId;
+ RECORD_FIELD_VALUES_COMPARE_ID = ++lastId;
}
// Helper to signal that the concrete instruction is uniquely determined by its ID/opcode.
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 9319c67..4dd921c 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -51,6 +51,10 @@
return continuation;
}
+ public void registerRecordFieldValues(DexField[] fields) {
+ registerTypeReference(appView.dexItemFactory().objectArrayType);
+ }
+
public abstract void registerInitClass(DexType type);
public abstract void registerInvokeVirtual(DexMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
index f8793ab..64b59e4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -290,6 +290,11 @@
}
@Override
+ public T visit(RecordFieldValues instruction) {
+ return null;
+ }
+
+ @Override
public T visit(Rem instruction) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 9bccbfc..090fb45 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -700,6 +700,14 @@
return this;
}
+ public boolean isRecordFieldValues() {
+ return false;
+ }
+
+ public RecordFieldValues asRecordFieldValues() {
+ return null;
+ }
+
public boolean isArrayAccess() {
return false;
}
@@ -1400,7 +1408,8 @@
return isNewArrayEmpty()
|| isNewArrayFilledData()
|| isInvokeNewArray()
- || isInvokeMultiNewArray();
+ || isInvokeMultiNewArray()
+ || isRecordFieldValues();
}
public boolean isCreatingInstanceOrArray() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index 8aa631d..ddb611f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -118,6 +118,8 @@
T visit(Pop instruction);
+ T visit(RecordFieldValues instruction);
+
T visit(Rem instruction);
T visit(Return instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index d2c79d4..f1527a6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -75,4 +75,5 @@
int USHR = 66;
int XOR = 67;
int UNINITIALIZED_THIS_LOCAL_READ = 68;
+ int RECORD_FIELD_VALUES = 69;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
new file mode 100644
index 0000000..00df561
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.code.CfRecordFieldValues;
+import com.android.tools.r8.code.DexRecordFieldValues;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import java.util.Arrays;
+import java.util.List;
+
+public class RecordFieldValues extends Instruction {
+
+ private final DexField[] fields;
+
+ public RecordFieldValues(DexField[] fields, Value outValue, List<Value> fieldValues) {
+ super(outValue, fieldValues);
+ this.fields = fields;
+ }
+
+ public DexField[] getFields() {
+ return fields;
+ }
+
+ @Override
+ public int opcode() {
+ return Opcodes.RECORD_FIELD_VALUES;
+ }
+
+ @Override
+ public <T> T accept(InstructionVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+
+ @Override
+ public RecordFieldValues asRecordFieldValues() {
+ return this;
+ }
+
+ @Override
+ public boolean isRecordFieldValues() {
+ return true;
+ }
+
+ @Override
+ public void buildDex(DexBuilder builder) {
+ // There are no restrictions on the registers since this instruction eventually has to be
+ // removed through IR.
+ int[] arguments = new int[inValues().size()];
+ for (int i = 0; i < inValues().size(); i++) {
+ arguments[i] = builder.allocatedRegister(inValues().get(i), getNumber());
+ }
+ int dest = builder.allocatedRegister(outValue(), getNumber());
+ builder.add(this, new DexRecordFieldValues(dest, arguments, fields));
+ }
+
+ @Override
+ public TypeElement evaluate(AppView<?> appView) {
+ return TypeElement.fromDexType(
+ appView.dexItemFactory().objectArrayType, Nullability.definitelyNotNull(), appView);
+ }
+
+ @Override
+ public void buildCf(CfBuilder builder) {
+ builder.add(new CfRecordFieldValues(fields));
+ }
+
+ @Override
+ public boolean identicalNonValueNonPositionParts(Instruction other) {
+ if (!other.isRecordFieldValues()) {
+ return false;
+ }
+ RecordFieldValues o = other.asRecordFieldValues();
+ return Arrays.equals(o.fields, fields);
+ }
+
+ @Override
+ public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
+ return false;
+ }
+
+ @Override
+ public int maxInValueRegister() {
+ return Constants.U16BIT_MAX;
+ }
+
+ @Override
+ public int maxOutValueRegister() {
+ return Constants.U16BIT_MAX;
+ }
+
+ @Override
+ public ConstraintWithTarget inliningConstraint(
+ InliningConstraints inliningConstraints, ProgramMethod context) {
+ return inliningConstraints.forRecordFieldValues();
+ }
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ helper.loadInValues(this, it);
+ helper.storeOutValue(this, it);
+ }
+
+ @Override
+ public boolean hasInvariantOutType() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 8f271a1..40c7bb9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -104,6 +104,7 @@
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.code.Phi.StackMapPhi;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.RecordFieldValues;
import com.android.tools.r8.ir.code.Rem;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.SafeCheckCast;
@@ -1557,6 +1558,20 @@
add(instruction);
}
+ public void addRecordFieldValues(DexField[] fields, IntList registers, int outValue) {
+ List<Value> arguments = new ArrayList<>();
+ for (int register : registers) {
+ arguments.add(readRegister(register, ValueTypeConstraint.OBJECT));
+ }
+ Value out =
+ writeRegister(
+ outValue,
+ TypeElement.fromDexType(
+ appView.dexItemFactory().objectArrayType, definitelyNotNull(), appView),
+ ThrowingInfo.CAN_THROW);
+ add(new RecordFieldValues(fields, out, arguments));
+ }
+
public void addInvoke(
Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf) {
if (type == Type.POLYMORPHIC) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
new file mode 100644
index 0000000..4689b6d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.records;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.RecordFieldValues;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+/** Used to shrink record field arrays in dex compilations */
+public class RecordFieldValuesRewriter {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final IRConverter irConverter;
+
+ public static RecordFieldValuesRewriter create(AppView<AppInfoWithLiveness> appView) {
+ if (appView.enableWholeProgramOptimizations()
+ && appView.options().isGeneratingDex()
+ && appView.options().testing.enableRecordModeling) {
+ return new RecordFieldValuesRewriter(appView);
+ }
+ return null;
+ }
+
+ private RecordFieldValuesRewriter(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ irConverter = new IRConverter(appView, Timing.empty());
+ }
+
+ // Called after final tree shaking, prune and minify field names and field values.
+ // At least one instruction is a newRecordFieldArray.
+ public void rewriteRecordFieldValues() {
+ for (DexMethod recordFieldValuesReference : appView.appInfo().recordFieldValuesReferences) {
+ DexClass dexClass =
+ appView.contextIndependentDefinitionFor(recordFieldValuesReference.getHolderType());
+ assert dexClass.isProgramClass();
+ ProgramMethod programMethod =
+ dexClass.asProgramClass().lookupProgramMethod(recordFieldValuesReference);
+ assert programMethod != null;
+ rewriteRecordFieldValues(programMethod);
+ }
+ }
+
+ public void rewriteRecordFieldValues(ProgramMethod programMethod) {
+ IRCode irCode =
+ programMethod
+ .getDefinition()
+ .getCode()
+ .buildIR(programMethod, appView, programMethod.getOrigin());
+ boolean done = false;
+ ListIterator<BasicBlock> blockIterator = irCode.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ InstructionListIterator iterator = block.listIterator(irCode);
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (instruction.isRecordFieldValues()) {
+ rewriteRecordFieldArray(
+ instruction.asRecordFieldValues(), irCode, blockIterator, iterator);
+ done = true;
+ }
+ }
+ }
+ assert done;
+ irConverter.removeDeadCodeAndFinalizeIR(
+ irCode, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+ }
+
+ public void rewriteRecordFieldArray(
+ RecordFieldValues recordFieldArray,
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ InstructionListIterator iterator) {
+ List<Value> newInValues = computePresentFields(recordFieldArray, code.context());
+ ConstNumber arrayLengthIntConstant = code.createIntConstant(newInValues.size());
+ Position constantPosition =
+ appView.options().debug ? Position.none() : recordFieldArray.getPosition();
+ arrayLengthIntConstant.setPosition(constantPosition);
+ iterator.previous();
+ iterator.add(arrayLengthIntConstant);
+ iterator.next();
+ NewArrayEmpty newArrayEmpty =
+ new NewArrayEmpty(
+ recordFieldArray.outValue(),
+ arrayLengthIntConstant.outValue(),
+ appView.dexItemFactory().objectArrayType);
+ newArrayEmpty.setPosition(recordFieldArray.getPosition());
+ iterator.replaceCurrentInstruction(newArrayEmpty);
+ for (int i = 0; i < newInValues.size(); i++) {
+ ConstNumber intConstantI = code.createIntConstant(i);
+ intConstantI.setPosition(constantPosition);
+ iterator.add(intConstantI);
+ ArrayPut arrayPut =
+ new ArrayPut(
+ MemberType.OBJECT,
+ newArrayEmpty.outValue(),
+ intConstantI.outValue(),
+ newInValues.get(i));
+ iterator.add(arrayPut);
+ arrayPut.setPosition(recordFieldArray.getPosition());
+ }
+ if (newArrayEmpty.getBlock().hasCatchHandlers()) {
+ splitIfCatchHandlers(code, newArrayEmpty.getBlock(), blockIterator);
+ }
+ }
+
+ private void splitIfCatchHandlers(
+ IRCode code,
+ BasicBlock blockWithIncorrectThrowingInstructions,
+ ListIterator<BasicBlock> blockIterator) {
+ InstructionListIterator instructionsIterator =
+ blockWithIncorrectThrowingInstructions.listIterator(code);
+ BasicBlock currentBlock = blockWithIncorrectThrowingInstructions;
+ while (currentBlock != null && instructionsIterator.hasNext()) {
+ Instruction throwingInstruction =
+ instructionsIterator.nextUntil(Instruction::instructionTypeCanThrow);
+ BasicBlock nextBlock;
+ if (throwingInstruction != null) {
+ nextBlock = instructionsIterator.split(code, blockIterator);
+ // Back up to before the split before inserting catch handlers.
+ blockIterator.previous();
+ nextBlock.copyCatchHandlers(code, blockIterator, currentBlock, appView.options());
+ BasicBlock b = blockIterator.next();
+ assert b == nextBlock;
+ // Switch iteration to the split block.
+ instructionsIterator = nextBlock.listIterator(code);
+ currentBlock = nextBlock;
+ } else {
+ assert !instructionsIterator.hasNext();
+ instructionsIterator = null;
+ currentBlock = null;
+ }
+ }
+ }
+
+ private List<Value> computePresentFields(
+ RecordFieldValues recordFieldValues, ProgramMethod context) {
+ List<Value> inValues = recordFieldValues.inValues();
+ DexField[] fields = recordFieldValues.getFields();
+ assert inValues.size() == fields.length;
+ List<Value> newInValues = new ArrayList<>();
+ for (int index = 0; index < fields.length; index++) {
+ FieldResolutionResult resolution =
+ appView
+ .appInfo()
+ .resolveField(appView.graphLens().getRenamedFieldSignature(fields[index]), context);
+ if (resolution.isSuccessfulResolution()) {
+ newInValues.add(inValues.get(index));
+ }
+ }
+ return newInValues;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index dc88779..4cf1c8c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -287,6 +287,10 @@
return ConstraintWithTarget.classIsVisible(context, type, appView);
}
+ public ConstraintWithTarget forRecordFieldValues() {
+ return ConstraintWithTarget.ALWAYS;
+ }
+
public ConstraintWithTarget forNewArrayFilledData() {
return ConstraintWithTarget.ALWAYS;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 7092012..a7f5846 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -308,6 +308,7 @@
|| instruction.isSwitch()
|| instruction.isThrow()
|| instruction.isUnop()
+ || instruction.isRecordFieldValues()
: "Unexpected instruction of type " + instruction.getClass().getTypeName();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index e9fc30b..46124b9 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfRecordFieldValues;
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.graph.AppView;
@@ -75,6 +76,13 @@
// 0 : receiver (the record instance)
// 1 : the array to return
// 2+: spills
+ return appView.enableWholeProgramOptimizations()
+ && appView.options().testing.enableRecordModeling
+ ? generateCfCodeWithRecordModeling()
+ : generateCfCodeWithArray();
+ }
+
+ private CfCode generateCfCodeWithArray() {
DexItemFactory factory = appView.dexItemFactory();
List<CfInstruction> instructions = new ArrayList<>();
// Object[] fields = new Object[*length*];
@@ -86,23 +94,7 @@
DexField field = fields[i];
instructions.add(new CfLoad(ValueType.OBJECT, 1));
instructions.add(new CfConstNumber(i, ValueType.INT));
- instructions.add(new CfLoad(ValueType.OBJECT, 0));
- instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
- if (field.type.isPrimitiveType()) {
- factory.primitiveToBoxed.forEach(
- (primitiveType, boxedType) -> {
- if (primitiveType == field.type) {
- instructions.add(
- new CfInvoke(
- Opcodes.INVOKESTATIC,
- factory.createMethod(
- boxedType,
- factory.createProto(boxedType, primitiveType),
- factory.valueOfMethodName),
- false));
- }
- });
- }
+ loadFieldAsObject(instructions, field);
instructions.add(new CfArrayStore(MemberType.OBJECT));
}
// return fields;
@@ -110,6 +102,39 @@
instructions.add(new CfReturn(ValueType.OBJECT));
return standardCfCodeFromInstructions(instructions);
}
+
+ private CfCode generateCfCodeWithRecordModeling() {
+ List<CfInstruction> instructions = new ArrayList<>();
+ // fields[*i*] = this.*field* || *PrimitiveWrapper*.valueOf(this.*field*);
+ for (DexField field : fields) {
+ loadFieldAsObject(instructions, field);
+ }
+ // return recordFieldValues(fields);
+ instructions.add(new CfRecordFieldValues(fields));
+ instructions.add(new CfReturn(ValueType.OBJECT));
+ return standardCfCodeFromInstructions(instructions);
+ }
+
+ private void loadFieldAsObject(List<CfInstruction> instructions, DexField field) {
+ DexItemFactory factory = appView.dexItemFactory();
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+ if (field.type.isPrimitiveType()) {
+ factory.primitiveToBoxed.forEach(
+ (primitiveType, boxedType) -> {
+ if (primitiveType == field.type) {
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKESTATIC,
+ factory.createMethod(
+ boxedType,
+ factory.createProto(boxedType, primitiveType),
+ factory.valueOfMethodName),
+ false));
+ }
+ });
+ }
+ }
}
public static class RecordEqualsCfCodeProvider extends SyntheticCfCodeProvider {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 6f3e907..b0f3ea9 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -181,6 +181,10 @@
*/
public final Map<DexType, Visibility> initClassReferences;
/**
+ * Set of all methods including a RecordFieldValues instruction. Set only in final tree shaking.
+ */
+ public final Set<DexMethod> recordFieldValuesReferences;
+ /**
* All methods and fields whose value *must* never be propagated due to a configuration directive.
* (testing only).
*/
@@ -240,7 +244,8 @@
Set<DexType> prunedTypes,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
Set<DexType> lockCandidates,
- Map<DexType, Visibility> initClassReferences) {
+ Map<DexType, Visibility> initClassReferences,
+ Set<DexMethod> recordFieldValuesReferences) {
super(syntheticItems, classToFeatureSplitMap, mainDexInfo, missingClasses);
this.deadProtoTypes = deadProtoTypes;
this.liveTypes = liveTypes;
@@ -278,6 +283,7 @@
this.switchMaps = switchMaps;
this.lockCandidates = lockCandidates;
this.initClassReferences = initClassReferences;
+ this.recordFieldValuesReferences = recordFieldValuesReferences;
assert verify();
}
@@ -322,7 +328,8 @@
previous.prunedTypes,
previous.switchMaps,
previous.lockCandidates,
- previous.initClassReferences);
+ previous.initClassReferences,
+ previous.recordFieldValuesReferences);
}
private AppInfoWithLiveness(
@@ -374,7 +381,8 @@
: previous.prunedTypes,
previous.switchMaps,
pruneClasses(previous.lockCandidates, prunedItems, executorService, futures),
- pruneMapFromClasses(previous.initClassReferences, prunedItems, executorService, futures));
+ pruneMapFromClasses(previous.initClassReferences, prunedItems, executorService, futures),
+ previous.recordFieldValuesReferences);
}
private static Set<DexType> pruneClasses(
@@ -578,7 +586,8 @@
prunedTypes,
switchMaps,
lockCandidates,
- initClassReferences);
+ initClassReferences,
+ recordFieldValuesReferences);
}
private static KeepInfoCollection extendPinnedItems(
@@ -661,6 +670,7 @@
this.switchMaps = switchMaps;
this.lockCandidates = previous.lockCandidates;
this.initClassReferences = previous.initClassReferences;
+ this.recordFieldValuesReferences = previous.recordFieldValuesReferences;
previous.markObsolete();
assert verify();
}
@@ -1284,7 +1294,8 @@
prunedTypes,
lens.rewriteFieldKeys(switchMaps),
lens.rewriteReferences(lockCandidates),
- rewriteInitClassReferences(lens));
+ rewriteInitClassReferences(lens),
+ lens.rewriteReferences(recordFieldValuesReferences));
}
public Map<DexType, Visibility> rewriteInitClassReferences(GraphLens lens) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 3502922..d0bf27c 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -57,6 +57,12 @@
}
@Override
+ public void registerRecordFieldValues(DexField[] fields) {
+ super.registerRecordFieldValues(fields);
+ enqueuer.traceRecordFieldValues(fields, getContext());
+ }
+
+ @Override
public void registerInvokeVirtual(DexMethod invokedMethod) {
setMaxApiReferenceLevel(invokedMethod);
enqueuer.traceInvokeVirtual(invokedMethod, getContext());
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index fefa1d7..5b0ff74 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -406,6 +406,12 @@
private final Map<DexType, Visibility> initClassReferences = new IdentityHashMap<>();
/**
+ * A map from seen init-class references to the minimum required visibility of the corresponding
+ * static field.
+ */
+ private final Set<DexMethod> recordFieldValuesReferences = Sets.newIdentityHashSet();
+
+ /**
* A map from annotation classes to annotations that need to be processed should the classes ever
* become live.
*/
@@ -1121,6 +1127,14 @@
}
}
+ void traceRecordFieldValues(DexField[] fields, ProgramMethod currentMethod) {
+ // TODO(b/203377129): Consider adding an enqueuer extension instead of growing the
+ // number of fields in appInfoWithLiveness.
+ if (mode.isFinalTreeShaking()) {
+ recordFieldValuesReferences.add(currentMethod.getReference());
+ }
+ }
+
void traceInitClass(DexType type, ProgramMethod currentMethod) {
assert type.isClassType();
@@ -3745,7 +3759,8 @@
emptySet(),
Collections.emptyMap(),
lockCandidates,
- initClassReferences);
+ initClassReferences,
+ recordFieldValuesReferences);
appInfo.markObsolete();
if (options.testing.enqueuerInspector != null) {
options.testing.enqueuerInspector.accept(appInfoWithLiveness, mode);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
index 8f915a2..0e20d4b 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
@@ -29,11 +29,7 @@
private static final String EXPECTED_RESULT_D8 =
String.format(EXPECTED_RESULT, "Person", "Person");
private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT, "a", "a");
- // TODO(b/201277582): These results are temporary while we transition into pruned minified record
- // fields.
- private static final String EXPECTED_INVALID_RESULT_R8_ADVANCED_DEX =
- StringUtils.lines("a[a=-1]", "a[a=-1]");
- private static final String EXPECTED_RESULT_R8_ADVANCED_CF =
+ private static final String EXPECTED_RESULT_R8_RECORD_MODELING =
StringUtils.lines("a[a=Jane Doe]", "a[a=Bob]");
private final TestParameters parameters;
@@ -99,7 +95,7 @@
.compile()
.inspect(this::assertSingleField)
.run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_INVALID_RESULT_R8_ADVANCED_DEX);
+ .assertSuccessWithOutput(EXPECTED_RESULT_R8_RECORD_MODELING);
}
@Test
@@ -123,7 +119,7 @@
.compile()
.inspect(this::assertSingleField)
.run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_CF);
+ .assertSuccessWithOutput(EXPECTED_RESULT_R8_RECORD_MODELING);
}
@Test