Merge commit '501efac45091692d75c0ceef53e8a9e2368ca0d5' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index c867e02..58753de 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -181,6 +181,15 @@
private static void run(AndroidApp inputApp, InternalOptions options, ExecutorService executor)
throws IOException {
+ if (options.printMemory) {
+ // Run GC twice to remove objects with finalizers.
+ System.gc();
+ System.gc();
+ Runtime runtime = Runtime.getRuntime();
+ System.out.println("D8 is running with total memory:" + runtime.totalMemory());
+ System.out.println("D8 is running with free memory:" + runtime.freeMemory());
+ System.out.println("D8 is running with max memory:" + runtime.maxMemory());
+ }
Timing timing = Timing.create("D8", options);
try {
// Disable global optimizations.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ec0f361..d68ff6b 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;
@@ -282,6 +283,15 @@
}
// Synthetic assertion to check that testing assertions works and can be enabled.
assert forTesting(options, () -> !options.testing.testEnableTestAssertions);
+ if (options.printMemory) {
+ // Run GC twice to remove objects with finalizers.
+ System.gc();
+ System.gc();
+ Runtime runtime = Runtime.getRuntime();
+ System.out.println("R8 is running with total memory:" + runtime.totalMemory());
+ System.out.println("R8 is running with free memory:" + runtime.freeMemory());
+ System.out.println("R8 is running with max memory:" + runtime.maxMemory());
+ }
try {
AppView<AppInfoWithClassHierarchy> appView;
{
@@ -688,6 +698,14 @@
performFinalMainDexTracing(appView, executorService);
+ if (appView.appInfo().hasLiveness()) {
+ 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/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 22eaec8..c1c3fb7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1329,7 +1329,7 @@
}
public class RecordMembers {
- public final DexMethod init = createMethod(recordType, createProto(voidType), "<init>");
+ public final DexMethod constructor = createMethod(recordType, createProto(voidType), "<init>");
public final DexMethod equals =
createMethod(recordType, createProto(booleanType, objectType), "equals");
public final DexMethod hashCode = createMethod(recordType, createProto(intType), "hashCode");
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index bea5b47..2cd2be0 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -42,6 +42,8 @@
boolean isReadFromAnnotation();
+ boolean isReadFromRecordInvokeDynamic();
+
boolean isReadFromMethodHandle();
boolean isWritten();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 4edec8a..0a7d218 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -28,6 +28,7 @@
public static int FLAG_IS_READ_FROM_METHOD_HANDLE = 1 << 1;
public static int FLAG_IS_WRITTEN_FROM_METHOD_HANDLE = 1 << 2;
public static int FLAG_HAS_REFLECTIVE_ACCESS = 1 << 3;
+ public static int FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC = 1 << 4;
// A direct reference to the definition of the field.
private DexField field;
@@ -189,7 +190,10 @@
/** Returns true if this field is read by the program. */
@Override
public boolean isRead() {
- return !readsWithContexts.isEmpty() || isReadFromAnnotation() || isReadFromMethodHandle();
+ return !readsWithContexts.isEmpty()
+ || isReadFromAnnotation()
+ || isReadFromMethodHandle()
+ || isReadFromRecordInvokeDynamic();
}
@Override
@@ -206,10 +210,23 @@
return (flags & FLAG_IS_READ_FROM_METHOD_HANDLE) != 0;
}
+ @Override
+ public boolean isReadFromRecordInvokeDynamic() {
+ return (flags & FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC) != 0;
+ }
+
public void setReadFromMethodHandle() {
flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
}
+ public void setReadFromRecordInvokeDynamic() {
+ flags |= FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
+ }
+
+ public void clearReadFromRecordInvokeDynamic() {
+ flags &= ~FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
+ }
+
/** Returns true if this field is written by the program. */
@Override
public boolean isWritten() {
@@ -279,7 +296,11 @@
}
public void clearReads() {
+ assert !hasReflectiveAccess();
+ assert !isReadFromAnnotation();
+ assert !isReadFromMethodHandle();
readsWithContexts = AbstractAccessContexts.empty();
+ clearReadFromRecordInvokeDynamic();
}
public void clearWrites() {
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 c3912ca..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);
@@ -178,7 +182,7 @@
}
}
- public void registerCallSite(DexCallSite callSite) {
+ protected void registerCallSiteExceptBootstrapArgs(DexCallSite callSite) {
boolean isLambdaMetaFactory =
dexItemFactory().isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
@@ -190,10 +194,17 @@
// Lambda metafactory will use this type as the main SAM
// interface for the dynamically created lambda class.
registerTypeReference(callSite.methodProto.returnType);
+ }
+ protected void registerCallSiteBootstrapArgs(DexCallSite callSite, int start, int end) {
+ boolean isLambdaMetaFactory =
+ appView.dexItemFactory().isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
// Register bootstrap method arguments.
// Only Type, MethodHandle, and MethodType need to be registered.
- for (DexValue arg : callSite.bootstrapArgs) {
+ assert start >= 0;
+ assert end <= callSite.bootstrapArgs.size();
+ for (int i = start; i < end; i++) {
+ DexValue arg = callSite.bootstrapArgs.get(i);
switch (arg.getValueKind()) {
case METHOD_HANDLE:
DexMethodHandle handle = arg.asDexValueMethodHandle().value;
@@ -219,6 +230,11 @@
}
}
+ public void registerCallSite(DexCallSite callSite) {
+ registerCallSiteExceptBootstrapArgs(callSite);
+ registerCallSiteBootstrapArgs(callSite, 0, callSite.bootstrapArgs.size());
+ }
+
public void registerProto(DexProto proto) {
registerTypeReference(proto.returnType);
for (DexType type : proto.parameters.values) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 2e237a2..7e6d5d3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -258,6 +258,7 @@
if (fieldAccessInfo.hasReflectiveAccess()
|| fieldAccessInfo.isAccessedFromMethodHandle()
+ || fieldAccessInfo.isReadFromRecordInvokeDynamic()
|| fieldAccessInfo.isReadFromAnnotation()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index dd29130..30060d9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -20,11 +20,11 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
import com.android.tools.r8.ir.analysis.value.NullOrAbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.EnumValuesObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index ec51f91..0b9a6cd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.google.common.collect.ImmutableMap;
public abstract class StaticFieldValues {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
index f9a2e25..9a61590 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
@@ -32,12 +31,6 @@
// Already handled above.
assert !appView.dexItemFactory().classMethods.isReflectiveNameLookup(invokedMethod);
- // Modeling of other library methods.
- DexType holder = invokedMethod.holder;
- if (holder == appView.dexItemFactory().objectType
- && invokedMethod == appView.dexItemFactory().objectMembers.constructor) {
- return EmptyFieldSet.getInstance();
- }
return UnknownFieldSet.getInstance();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
index f8c2bf0..5cbf665 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
@@ -9,8 +9,8 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 09c661c..e1cdb6c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index a5d76c3..71a6dbf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.StaticGet;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
index e9b7fde..d14f5a9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.analysis.value;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import java.util.Objects;
public class SingleStatefulFieldValue extends SingleFieldValue {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
index 80de8a3..809b365 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.analysis.value;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
public class SingleStatelessFieldValue extends SingleFieldValue {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
similarity index 83%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
index f8056dc..1b94542 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
@@ -1,13 +1,15 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.function.BiConsumer;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
index 13686eb..397e79f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
@@ -1,13 +1,15 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Arrays;
import java.util.Objects;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
index 567449e..559fa77 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
@@ -1,13 +1,15 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.IdentityHashMap;
import java.util.Map;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
index 012a204..f709c59 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
@@ -1,14 +1,16 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.analysis.value;
+package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.IdentityHashMap;
import java.util.Map;
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/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 293635b..07ba0ce 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -189,7 +189,7 @@
this.appView = appView;
this.options = appView.options();
this.printer = printer;
- this.codeRewriter = new CodeRewriter(appView, this);
+ this.codeRewriter = new CodeRewriter(appView);
this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
this.classInitializerDefaultsOptimization =
new ClassInitializerDefaultsOptimization(appView, this);
@@ -1141,7 +1141,7 @@
String previous = printMethod(code, "Initial IR (SSA)", null);
if (options.testing.irModifier != null) {
- options.testing.irModifier.accept(code);
+ options.testing.irModifier.accept(code, appView);
}
if (options.canHaveArtStringNewInitBug()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
index 76c468a6..b5e08d5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfToCfRewriter.java
@@ -55,10 +55,7 @@
recordInvokeDynamic
.computeRecordFieldNamesComputationInfo()
.internalComputeNameFor(
- recordInvokeDynamic.getRecordClass().type,
- appView,
- appView.graphLens(),
- namingLens);
+ recordInvokeDynamic.getRecordType(), appView, appView.graphLens(), namingLens);
DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
return writeRecordInvokeDynamic(
recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
@@ -89,7 +86,7 @@
new DexMethodHandle(
MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
- bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordClass().type));
+ bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordType()));
bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
for (DexField field : recordInvokeDynamic.getFields()) {
bootstrapArgs.add(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 3829aa0..9a7c53c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -301,8 +301,7 @@
RecordInstructionDesugaringEventConsumer eventConsumer,
MethodProcessingContext methodProcessingContext) {
localStackAllocator.allocateLocalStack(1);
- DexMethod getFieldsAsObjects =
- getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordClass().type);
+ DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null;
ArrayList<CfInstruction> instructions = new ArrayList<>();
instructions.add(new CfStackInstruction(Dup));
@@ -320,7 +319,7 @@
}
private List<CfInstruction> desugarInvokeRecordEquals(RecordInvokeDynamic recordInvokeDynamic) {
- DexMethod equalsRecord = equalsRecordMethod(recordInvokeDynamic.getRecordClass().type);
+ DexMethod equalsRecord = equalsRecordMethod(recordInvokeDynamic.getRecordType());
assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(equalsRecord) != null;
return Collections.singletonList(new CfInvoke(Opcodes.INVOKESPECIAL, equalsRecord, false));
}
@@ -331,17 +330,17 @@
RecordInstructionDesugaringEventConsumer eventConsumer,
MethodProcessingContext methodProcessingContext) {
localStackAllocator.allocateLocalStack(2);
- DexMethod getFieldsAsObjects =
- getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordClass().type);
- assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null;
+ DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
+ assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects)
+ != null;
ArrayList<CfInstruction> instructions = new ArrayList<>();
instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
- instructions.add(new CfConstClass(recordInvokeDynamic.getRecordClass().type, true));
+ instructions.add(new CfConstClass(recordInvokeDynamic.getRecordType(), true));
if (appView.options().testing.enableRecordModeling
&& appView.enableWholeProgramOptimizations()) {
instructions.add(
new CfDexItemBasedConstString(
- recordInvokeDynamic.getRecordClass().type,
+ recordInvokeDynamic.getRecordType(),
recordInvokeDynamic.computeRecordFieldNamesComputationInfo()));
} else {
instructions.add(new CfConstString(recordInvokeDynamic.getFieldNames()));
@@ -465,7 +464,7 @@
Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
DexEncodedMethod init =
DexEncodedMethod.syntheticBuilder()
- .setMethod(factory.recordMembers.init)
+ .setMethod(factory.recordMembers.constructor)
.setAccessFlags(methodAccessFlags)
.setCode(null)
// Will be traced by the enqueuer.
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/desugar/records/RecordRewriterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
index dc589e2..3eb87cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
@@ -14,6 +14,7 @@
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;
@@ -24,8 +25,12 @@
public static boolean isInvokeDynamicOnRecord(
CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
+ return isInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView, context);
+ }
+
+ public static boolean isInvokeDynamicOnRecord(
+ DexCallSite callSite, AppView<?> appView, ProgramMethod context) {
DexItemFactory factory = appView.dexItemFactory();
- DexCallSite callSite = invokeDynamic.getCallSite();
// 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
if (!bootstrapMethod.type.isInvokeStatic()) {
@@ -68,7 +73,7 @@
DexString fieldNames = valueString.getValue();
assert fieldNames.toString().isEmpty()
|| (fieldNames.toString().split(";").length == callSite.bootstrapArgs.size() - 2);
- assert recordClass.instanceFields().size() == 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
@@ -143,6 +148,10 @@
return fields;
}
+ DexType getRecordType() {
+ return recordClass.getType();
+ }
+
DexProgramClass getRecordClass() {
return recordClass;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 71a8249..cdc2c6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -85,7 +85,6 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.code.Xor;
-import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
@@ -156,15 +155,12 @@
private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
private static final int SELF_RECURSION_LIMIT = 4;
- public final IRConverter converter;
-
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
private final InternalOptions options;
- public CodeRewriter(AppView<?> appView, IRConverter converter) {
+ public CodeRewriter(AppView<?> appView) {
this.appView = appView;
- this.converter = converter;
this.options = appView.options();
this.dexItemFactory = appView.dexItemFactory();
}
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..e42266b 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
@@ -16,9 +16,9 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
@@ -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/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 228c259..487d677 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,8 +28,8 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleConstValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.BasicBlock;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
index 0ceb39e..645a4bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
index 4b8a513..d629d45 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index ebe34b8..8b44af5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
index 09a482d..5403b86 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -13,8 +13,8 @@
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleConstValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext;
import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsages;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 40e53fb..40627da 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -48,8 +48,8 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.EnumValuesObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.ArrayLength;
import com.android.tools.r8.ir.code.ArrayPut;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index ab74fb6..bc6f506 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -81,6 +81,7 @@
.add(dexItemFactory.longMembers.toString)
.add(dexItemFactory.npeMethods.init)
.add(dexItemFactory.npeMethods.initWithMessage)
+ .add(dexItemFactory.recordMembers.constructor)
.add(dexItemFactory.objectMembers.constructor)
.add(dexItemFactory.objectMembers.getClass)
.add(dexItemFactory.shortMembers.toString)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index b986800..ca99313 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
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/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 696bce1..13d7fc6 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -49,8 +49,7 @@
if (renaming != null) {
return rewriteTo(renaming);
}
- if (appView.options().isMinifying()) {
- // TODO(b/202367773): This should also apply if optimizing.
+ if (appView.options().isMinifying() || appView.options().isOptimizing()) {
return rewriteToDefaultSourceFile();
}
return null;
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
index 17ac83a..5ad5e27 100644
--- a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/RecordFieldNamesComputationInfo.java
@@ -128,10 +128,6 @@
recordClass.lookupInstanceField(graphLens.getRenamedFieldSignature(fields[i]));
if (recordField != null) {
names.add(nameSupplier.apply(i));
- } else {
- // TODO(b/201277582): Temporarily work-around as long as the field values are not also
- // shrunk in dex.
- names.add("<pruned>");
}
}
return dexStringFromFieldNames(names, definitions.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
index cd1e030..ed2596a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/VirtualRootMethodsAnalysis.java
@@ -208,7 +208,8 @@
}
private void promoteToFinalIfPossible(DexProgramClass clazz) {
- if (!clazz.isAbstract()
+ if (!appView.testing().disableMarkingClassesFinal
+ && !clazz.isAbstract()
&& !clazz.isInterface()
&& appView.getKeepInfo(clazz).isOptimizationAllowed(appView.options())) {
clazz.getAccessFlags().promoteToFinal();
@@ -216,7 +217,8 @@
}
private void promoteToFinalIfPossible(ProgramMethod method, VirtualRootMethod virtualRootMethod) {
- if (!method.getHolder().isInterface()
+ if (!appView.testing().disableMarkingMethodsFinal
+ && !method.getHolder().isInterface()
&& !method.getAccessFlags().isAbstract()
&& !virtualRootMethod.hasOverrides()
&& appView.getKeepInfo(method).isOptimizationAllowed(appView.options())) {
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 33288f7..d0bf27c 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -4,11 +4,14 @@
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
+
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.code.CfOrDexInstruction;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
@@ -54,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());
@@ -95,6 +104,11 @@
enqueuer.traceInstanceFieldReadFromMethodHandle(field, getContext());
}
+ private void registerInstanceFieldReadFromRecordMethodHandle(DexField field) {
+ setMaxApiReferenceLevel(field);
+ enqueuer.traceInstanceFieldReadFromRecordMethodHandle(field, getContext());
+ }
+
@Override
public void registerInstanceFieldWrite(DexField field) {
setMaxApiReferenceLevel(field);
@@ -178,10 +192,33 @@
@Override
public void registerCallSite(DexCallSite callSite) {
- super.registerCallSite(callSite);
+ super.registerCallSiteExceptBootstrapArgs(callSite);
+ if (isInvokeDynamicOnRecord(callSite, appView, getContext())
+ && appView.options().testing.enableRecordModeling) {
+ registerRecordCallSiteBootstrapArgs(callSite);
+ } else {
+ super.registerCallSiteBootstrapArgs(callSite, 0, callSite.bootstrapArgs.size());
+ }
enqueuer.traceCallSite(callSite, getContext());
}
+ private void registerRecordCallSiteBootstrapArgs(DexCallSite callSite) {
+ // The Instance Get method handle in invokeDynamicOnRecord are considered:
+ // - a record use if not a constant value,
+ // - unused if a constant value.
+ registerCallSiteBootstrapArgs(callSite, 0, 2);
+ for (int i = 2; i < callSite.getBootstrapArgs().size(); i++) {
+ DexField field = callSite.getBootstrapArgs().get(i).asDexValueMethodHandle().value.asField();
+ DexEncodedField encodedField =
+ appView.appInfo().resolveField(field, getContext()).getResolvedField();
+ // Member value propagation does not rewrite method handles, special handling for this
+ // method handle access is done after the final tree shaking.
+ if (!encodedField.getOptimizationInfo().isDead()) {
+ registerInstanceFieldReadFromRecordMethodHandle(field);
+ }
+ }
+ }
+
private void setMaxApiReferenceLevel(DexReference reference) {
if (reference.isDexMember()) {
maxApiReferenceLevel =
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 a7b0a76..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();
@@ -1406,15 +1420,25 @@
}
void traceInstanceFieldRead(DexField field, ProgramMethod currentMethod) {
- traceInstanceFieldRead(field, currentMethod, false);
+ traceInstanceFieldRead(field, currentMethod, FieldReadType.READ);
}
void traceInstanceFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
- traceInstanceFieldRead(field, currentMethod, true);
+ traceInstanceFieldRead(field, currentMethod, FieldReadType.READ_FROM_METHOD_HANDLE);
+ }
+
+ void traceInstanceFieldReadFromRecordMethodHandle(DexField field, ProgramMethod currentMethod) {
+ traceInstanceFieldRead(field, currentMethod, FieldReadType.READ_FROM_RECORD_METHOD_HANDLE);
+ }
+
+ private enum FieldReadType {
+ READ,
+ READ_FROM_METHOD_HANDLE,
+ READ_FROM_RECORD_METHOD_HANDLE
}
private void traceInstanceFieldRead(
- DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
+ DexField fieldReference, ProgramMethod currentMethod, FieldReadType readType) {
if (!registerFieldRead(fieldReference, currentMethod)) {
return;
}
@@ -1440,8 +1464,10 @@
+ "` to field marked dead: "
+ field.getReference().toSourceString();
- if (fromMethodHandle) {
+ if (readType == FieldReadType.READ_FROM_METHOD_HANDLE) {
fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
+ } else if (readType == FieldReadType.READ_FROM_RECORD_METHOD_HANDLE) {
+ fieldAccessInfoCollection.get(field.getReference()).setReadFromRecordInvokeDynamic();
}
if (Log.ENABLED) {
@@ -3733,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/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 88c96ef..bc0344a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1647,7 +1647,7 @@
public boolean noLocalsTableOnInput = false;
public boolean forceNameReflectionOptimization = false;
public boolean enableNarrowAndWideningingChecksInD8 = false;
- public Consumer<IRCode> irModifier = null;
+ public BiConsumer<IRCode, AppView<?>> irModifier = null;
public Consumer<IRCode> inlineeIrModifier = null;
public int basicBlockMuncherIterationLimit = NO_LIMIT;
public boolean dontReportFailingCheckDiscarded = false;
@@ -1671,12 +1671,16 @@
public Set<String> allowedUnusedDontWarnPatterns = new HashSet<>();
public boolean enableTestAssertions =
System.getProperty("com.android.tools.r8.enableTestAssertions") != null;
+ public boolean disableMarkingMethodsFinal =
+ System.getProperty("com.android.tools.r8.disableMarkingMethodsFinal") != null;
+ public boolean disableMarkingClassesFinal =
+ System.getProperty("com.android.tools.r8.disableMarkingClassesFinal") != null;
public boolean testEnableTestAssertions = false;
public boolean keepMetadataInR8IfNotRewritten = true;
// If set, pruned record fields are not used in hashCode/equals/toString and toString prints
// minified field names instead of original field names.
- public boolean enableRecordModeling = false;
+ public boolean enableRecordModeling = true;
public boolean allowConflictingSyntheticTypes = false;
diff --git a/src/test/examplesJava17/records/RecordShrinkField.java b/src/test/examplesJava17/records/RecordShrinkField.java
index b3cd366..2a21904 100644
--- a/src/test/examplesJava17/records/RecordShrinkField.java
+++ b/src/test/examplesJava17/records/RecordShrinkField.java
@@ -6,9 +6,9 @@
public class RecordShrinkField {
- record Person(String name, int age, int unused) {
+ record Person(int unused, String name, int age) {
Person(String name, int age) {
- this(name, age, -1);
+ this(-1, name, age);
}
}
diff --git a/src/test/examplesJava17/records/RecordWithConstClass.java b/src/test/examplesJava17/records/RecordWithConstClass.java
new file mode 100644
index 0000000..db06f51
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordWithConstClass.java
@@ -0,0 +1,18 @@
+// 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 records;
+
+import records.differentpackage.PrivateConstClass;
+
+public class RecordWithConstClass {
+
+ record MyRecordWithConstClass(Class<?> theClass) { }
+
+ public static void main(String[] args) {
+ MyRecordWithConstClass inst =
+ new MyRecordWithConstClass(PrivateConstClass.getPrivateConstClass());
+ System.out.println(inst);
+ }
+}
diff --git a/src/test/examplesJava17/records/differentpackage/PrivateConstClass.java b/src/test/examplesJava17/records/differentpackage/PrivateConstClass.java
new file mode 100644
index 0000000..fbf93c6
--- /dev/null
+++ b/src/test/examplesJava17/records/differentpackage/PrivateConstClass.java
@@ -0,0 +1,14 @@
+// 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 records.differentpackage;
+
+public class PrivateConstClass {
+
+ private static class PrivateClass {}
+
+ public static Class<?> getPrivateConstClass() {
+ return PrivateClass.class;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 08bb64b..b3bcdb4 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -146,7 +146,8 @@
.setMode(mode)
.setDisableTreeShaking(true)
.setDisableMinification(true)
- .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes *", "-dontoptimize"), Origin.unknown())
.build();
ToolHelper.runR8(command, this::configure);
break;
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index aec1329..8f5dc82 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
@@ -84,7 +85,7 @@
assert instructions.get(index + 4) instanceof CfStackInstruction;
}
- private void processIR(IRCode code) {
+ private void processIR(IRCode code, AppView<?> appView) {
if (!code.method().qualifiedName().equals(TryRangeTestLimitRange.class.getName() + ".main")) {
return;
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterClassMergingTest.java
new file mode 100644
index 0000000..8093d0c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterClassMergingTest.java
@@ -0,0 +1,117 @@
+// 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtending;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonPublicOverrideOfPublicMethodAfterClassMergingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+ assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject, isExtending(iClassSubject));
+ assertThat(
+ aClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.m()", "B.m()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ (System.currentTimeMillis() > 0 ? new B() : new C()).m();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ abstract static class I {}
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ abstract static class J {
+
+ public abstract void m();
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class A extends I {
+
+ @NeverInline
+ void m() {
+ System.out.println("A.m()");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class B extends J {
+
+ @Override
+ public void m() {
+ System.out.println("B.m()");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class C extends J {
+
+ @Override
+ public void m() {
+ System.out.println("C.m()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
new file mode 100644
index 0000000..de26733
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
@@ -0,0 +1,132 @@
+// 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.classmerging.horizontal.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalOverrideAfterInterfaceMergingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+ assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject, isImplementing(iClassSubject));
+ // TODO(b/203446070): Package private A.m() should not override public I.m().
+ assertThat(
+ aClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/203446070): Should always succeed.
+ .applyIf(
+ parameters.isCfRuntime(),
+ runResult -> runResult.assertSuccessWithOutputLines("A.m()", "B.m()"),
+ runResult ->
+ runResult.applyIf(
+ parameters.getDexRuntimeVersion().isDalvik(),
+ ignore ->
+ runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ ignore ->
+ runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class)));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ (System.currentTimeMillis() > 0 ? new B() : new C()).m();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {}
+
+ // Should not be merged into I, since I has a subclass with a package private method signature
+ // `void m()`.
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+
+ void m();
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class A implements I {
+
+ // Intentionally package private. If J is merged into I then this is an illegal override of
+ // I.m().
+ @NeverInline
+ void m() {
+ System.out.println("A.m()");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class B implements J {
+
+ @Override
+ public void m() {
+ System.out.println("B.m()");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class C implements J {
+
+ @Override
+ public void m() {
+ System.out.println("C.m()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
new file mode 100644
index 0000000..a75fd7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
@@ -0,0 +1,140 @@
+// 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.classmerging.horizontal.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtending;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalSiblingAfterInterfaceMergingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+ assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+
+ ClassSubject a0ClassSubject = inspector.clazz(A0.class);
+ assertThat(a0ClassSubject, isPresent());
+ assertThat(
+ a0ClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject, isExtending(a0ClassSubject));
+ assertThat(aClassSubject, isImplementing(iClassSubject));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/203446070): Should always succeed.
+ .applyIf(
+ parameters.isCfRuntime(),
+ runResult -> runResult.assertSuccessWithOutputLines("A.m()", "B.m()"),
+ runResult ->
+ runResult.applyIf(
+ parameters.getDexRuntimeVersion().isDalvik(),
+ ignore ->
+ runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ ignore ->
+ runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class)));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ (System.currentTimeMillis() > 0 ? new B() : new C()).m();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {}
+
+ // Should not be merged into I, since I has a subclass with a package private method signature
+ // `void m()`.
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+
+ void m();
+ }
+
+ @NoHorizontalClassMerging
+ @NoVerticalClassMerging
+ static class A0 {
+
+ // Intentionally package private. If J is merged into I then this is an illegal override of
+ // I.m().
+ @NeverInline
+ void m() {
+ System.out.println("A.m()");
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class A extends A0 implements I {}
+
+ @NoHorizontalClassMerging
+ static class B implements J {
+
+ @Override
+ public void m() {
+ System.out.println("B.m()");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class C implements J {
+
+ @Override
+ public void m() {
+ System.out.println("C.m()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index 05bbb73..ef9112c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -25,7 +25,7 @@
public class DebugInfoWhenInliningTest extends DebugTestBase {
private static final String CLASS_NAME = "Inlining1";
- private static final String SOURCE_FILE = "Inlining1.java";
+ private static final String SOURCE_FILE = "SourceFile";
private DebugTestConfig makeConfig(
LineNumberOptimization lineNumberOptimization, boolean writeProguardMap) throws Exception {
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 228275b..7207f6d 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
@@ -34,10 +34,11 @@
"true",
"false",
"false",
- "%s[name=Jane Doe, age=42]");
+ "%s[%s=Jane Doe, %s=42]");
private static final String EXPECTED_RESULT_D8 =
- String.format(EXPECTED_RESULT, "Empty", "Person");
- private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT, "a", "b");
+ String.format(EXPECTED_RESULT, "Empty", "Person", "name", "age");
+ private static final String EXPECTED_RESULT_R8 =
+ String.format(EXPECTED_RESULT, "a", "b", "a", "b");
private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
new file mode 100644
index 0000000..49df3e2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
@@ -0,0 +1,113 @@
+// 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 com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordKeepRulesTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordShrinkField";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+
+ private static final String KEEP_RULE_CLASS_NAME =
+ "-keep,allowshrinking,allowoptimization class records.RecordShrinkField$Person";
+ private static final String KEEP_RULE_FIELD_NAMES =
+ "-keepclassmembers,allowshrinking,allowoptimization class records.RecordShrinkField$Person {"
+ + " <fields>; }";
+ private static final String KEEP_RULE_FIELDS_NO_NAMES =
+ "-keepclassmembers,allowobfuscation class records.RecordShrinkField$Person { <fields>; }";
+ private static final String KEEP_RULE_ALL =
+ "-keep class records.RecordShrinkField$Person { <fields>; }";
+
+ private static final String EXPECTED_RESULT_R8_WITH_CLASS_NAME =
+ StringUtils.lines("RecordShrinkField$Person[a=Jane Doe]", "RecordShrinkField$Person[a=Bob]");
+ private static final String EXPECTED_RESULT_R8_WITH_FIELD_NAMES =
+ StringUtils.lines("a[name=Jane Doe]", "a[name=Bob]");
+ private static final String EXPECTED_RESULT_R8_WITH_FIELD_NO_NAMES =
+ StringUtils.lines("a[a=-1, b=Jane Doe, c=42]", "a[a=-1, b=Bob, c=42]");
+ private static final String EXPECTED_RESULT_R8_WITH_ALL =
+ StringUtils.lines(
+ "RecordShrinkField$Person[unused=-1, name=Jane Doe, age=42]",
+ "RecordShrinkField$Person[unused=-1, name=Bob, age=42]");
+
+ private final TestParameters parameters;
+ private final boolean proguardCompatibility;
+
+ public RecordKeepRulesTest(TestParameters parameters, boolean proguardCompatibility) {
+ this.parameters = parameters;
+ this.proguardCompatibility = proguardCompatibility;
+ }
+
+ @Parameterized.Parameters(name = "{0}; proguardCompat: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ @Test
+ public void testR8KeepRuleClassName() throws Exception {
+ testR8FieldNames(KEEP_RULE_CLASS_NAME, EXPECTED_RESULT_R8_WITH_CLASS_NAME);
+ testR8CfThenDexFieldNames(KEEP_RULE_CLASS_NAME, EXPECTED_RESULT_R8_WITH_CLASS_NAME);
+ }
+
+ @Test
+ public void testR8KeepRuleFieldNames() throws Exception {
+ testR8FieldNames(KEEP_RULE_FIELD_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NAMES);
+ testR8CfThenDexFieldNames(KEEP_RULE_FIELD_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NAMES);
+ }
+
+ @Test
+ public void testR8KeepRuleFieldsNoNames() throws Exception {
+ testR8FieldNames(KEEP_RULE_FIELDS_NO_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NO_NAMES);
+ testR8CfThenDexFieldNames(KEEP_RULE_FIELDS_NO_NAMES, EXPECTED_RESULT_R8_WITH_FIELD_NO_NAMES);
+ }
+
+ @Test
+ public void testR8KeepRuleAll() throws Exception {
+ testR8FieldNames(KEEP_RULE_ALL, EXPECTED_RESULT_R8_WITH_ALL);
+ testR8CfThenDexFieldNames(KEEP_RULE_ALL, EXPECTED_RESULT_R8_WITH_ALL);
+ }
+
+ private void testR8FieldNames(String keepRules, String expectedOutput) throws Exception {
+ testForR8Compat(parameters.getBackend(), proguardCompatibility)
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addKeepRules(keepRules)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ private void testR8CfThenDexFieldNames(String keepRules, String expectedOutput) throws Exception {
+ Path desugared =
+ testForR8Compat(Backend.CF, proguardCompatibility)
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addKeepRules(keepRules)
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .writeToZip();
+ testForD8(parameters.getBackend())
+ .addProgramFiles(desugared)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+}
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 df6be44..8dc4a82 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
@@ -25,17 +25,10 @@
private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT =
- StringUtils.lines("%s[name=Jane Doe, age=42, unused=-1]", "%s[name=Bob, age=42, unused=-1]");
+ StringUtils.lines("%s[unused=-1, name=Jane Doe, age=42]", "%s[unused=-1, name=Bob, age=42]");
private static final String EXPECTED_RESULT_D8 =
String.format(EXPECTED_RESULT, "Person", "Person");
- private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT, "a", "a");
- // TODO(b/201277582): These results are temporary while we transition into pruned minified record
- // fields.
- private static final String EXPECTED_RESULT_R8_ADVANCED_DEX =
- StringUtils.lines(
- "a[a=Jane Doe, <pruned>=42, <pruned>=-1]", "a[a=Bob, <pruned>=42, <pruned>=-1]");
- private static final String EXPECTED_RESULT_R8_ADVANCED_CF =
- StringUtils.lines("a[a=Jane Doe, b=42, c=-1]", "a[a=Bob, b=42, c=-1]");
+ private static final String EXPECTED_RESULT_R8 = StringUtils.lines("a[a=Jane Doe]", "a[a=Bob]");
private final TestParameters parameters;
@@ -74,59 +67,6 @@
}
@Test
- public void testR8Compat() throws Exception {
- testForR8Compat(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(MAIN_TYPE)
- .addKeepRules(
- "-keepclassmembers,allowshrinking,allowoptimization class"
- + " records.RecordShrinkField$Person { <fields>; }")
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .inspect(this::assertSingleField)
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT_R8);
- }
-
- @Test
- public void testR8AdvancedShrinking() throws Exception {
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .inspect(this::assertSingleField)
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_DEX);
- }
-
- @Test
- public void testR8CfThenDexAdvancedShrinking() throws Exception {
- Path desugared =
- testForR8(Backend.CF)
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(MAIN_TYPE)
- .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addOptionsModification(opt -> opt.testing.enableRecordModeling = true)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .writeToZip();
- testForR8(parameters.getBackend())
- .addProgramFiles(desugared)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(MAIN_TYPE)
- .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
- .compile()
- .inspect(this::assertSingleField)
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT_R8_ADVANCED_CF);
- }
-
- @Test
public void testR8CfThenDex() throws Exception {
Path desugared =
testForR8(Backend.CF)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
new file mode 100644
index 0000000..f76b137
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
@@ -0,0 +1,102 @@
+// 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 com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordWithNonMaterializableConstClassTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordWithConstClass";
+ private static final String PRIVATE_CLASS_NAME =
+ "records.differentpackage.PrivateConstClass$PrivateClass";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final byte[][] EXTRA_DATA =
+ RecordTestUtils.getProgramData("differentpackage/PrivateConstClass");
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT_FORMAT =
+ StringUtils.lines("%s[%s=class " + PRIVATE_CLASS_NAME + "]");
+ private static final String EXPECTED_RESULT_D8 =
+ String.format(EXPECTED_RESULT_FORMAT, "MyRecordWithConstClass", "theClass");
+ private static final String EXPECTED_RESULT_R8 = String.format(EXPECTED_RESULT_FORMAT, "a", "a");
+
+ private final TestParameters parameters;
+
+ public RecordWithNonMaterializableConstClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addProgramClassFileData(EXTRA_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_D8);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addProgramClassFileData(EXTRA_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_D8);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addProgramClassFileData(EXTRA_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_R8);
+ }
+
+ @Test
+ public void testR8CfThenDex() throws Exception {
+ Path desugared =
+ testForR8(Backend.CF)
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addProgramClassFileData(EXTRA_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .writeToZip();
+ testForR8(parameters.getBackend())
+ .addProgramFiles(desugared)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_TYPE)
+ .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT_R8);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
index 6001549..5cea9fa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
@@ -86,6 +87,10 @@
});
}
+ private void modifyIr(IRCode irCode, AppView<?> appView) {
+ modifyIr(irCode);
+ }
+
private void modifyIr(IRCode irCode) {
if (irCode.context().getReference().qualifiedName().equals(A.class.getTypeName() + ".foo")) {
assertEquals(7, irCode.blocks.size());
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
index 87ae7f4..5a7be26 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayLengthRewriteTest.java
@@ -3,6 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.rewrite.arrays;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -17,15 +21,12 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
@RunWith(Parameterized.class)
public class ArrayLengthRewriteTest extends TestBase {
@Parameters(name = "{0}, debug = {1}")
public static Iterable<?> data() {
- return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values());
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
private final TestParameters parameters;
@@ -62,7 +63,7 @@
assumeTrue(parameters.isDexRuntime());
testForD8()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.setMode(debugMode ? CompilationMode.DEBUG : CompilationMode.RELEASE)
.addProgramClasses(Main.class)
.run(parameters.getRuntime(), Main.class)
@@ -72,7 +73,7 @@
@Test public void r8() throws Exception {
testForR8(parameters.getBackend())
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.setMode(debugMode ? CompilationMode.DEBUG : CompilationMode.RELEASE)
.addProgramClasses(Main.class)
.addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
new file mode 100644
index 0000000..4a76a6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -0,0 +1,85 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 ArrayWithDataLengthRewriteTest extends TestBase {
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ private final TestParameters parameters;
+
+ public ArrayWithDataLengthRewriteTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final String[] expectedOutput = {"3"};
+
+ @Test
+ public void d8() throws Exception {
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .setMode(CompilationMode.RELEASE)
+ .addProgramClasses(Main.class)
+ .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(expectedOutput)
+ .inspect(this::assertNoArrayLength);
+ }
+
+ @Test
+ public void r8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getRuntime())
+ .addProgramClasses(Main.class)
+ .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(expectedOutput)
+ .inspect(this::assertNoArrayLength);
+ }
+
+ private void transformArray(IRCode irCode, AppView<?> appView) {
+ if (irCode.context().getReference().getName().toString().contains("main")) {
+ new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+ assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
+ }
+ }
+
+ private void assertNoArrayLength(CodeInspector inspector) {
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertTrue(mainClass.isPresent());
+ assertTrue(
+ mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+ }
+
+ public static final class Main {
+ public static void main(String[] args) {
+ int[] ints = new int[3];
+ ints[0] = 5;
+ ints[1] = 6;
+ ints[2] = 1;
+ System.out.println(ints.length);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 2d74926..7f35d90 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -90,6 +90,11 @@
}
@Override
+ public boolean isExtending(ClassSubject subject) {
+ throw new Unreachable("Cannot determine if an absent class is extending a given class");
+ }
+
+ @Override
public boolean isImplementing(ClassSubject subject) {
throw new Unreachable("Cannot determine if an absent class is implementing a given interface");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 9c057e6..04f0d52 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -180,6 +180,8 @@
public abstract boolean isAnnotation();
+ public abstract boolean isExtending(ClassSubject subject);
+
public abstract boolean isImplementing(ClassSubject subject);
public abstract boolean isImplementing(Class<?> clazz);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index a0839d8..69ebe80 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -294,6 +294,11 @@
}
@Override
+ public boolean isExtending(ClassSubject subject) {
+ return getSuperClass().getDexProgramClass().getType() == subject.getDexProgramClass().getType();
+ }
+
+ @Override
public boolean isInterface() {
return dexClass.isInterface();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 6e83620..db174e0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -505,6 +505,22 @@
};
}
+ public static Matcher<ClassSubject> isExtending(ClassSubject superSubject) {
+ assertThat(superSubject, isPresent());
+ assertThat(superSubject, not(isInterface()));
+ return new TypeSafeMatcher<ClassSubject>() {
+ @Override
+ public boolean matchesSafely(ClassSubject subject) {
+ return subject.isPresent() && subject.isExtending(superSubject);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("extends ").appendText(superSubject.getOriginalName());
+ }
+ };
+ }
+
public static Matcher<FieldSubject> isFieldOfType(DexType type) {
return new TypeSafeMatcher<FieldSubject>() {
@Override
diff --git a/tools/retrace.py b/tools/retrace.py
index 0aa5af2..14593ab 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -151,7 +151,7 @@
retrace_args += [
'-cp',
- utils.R8_JAR if no_r8lib else utils.R8LIB_JAR,
+ utils.R8_JAR if no_r8lib else utils.R8RETRACE_JAR,
'com.android.tools.r8.retrace.Retrace',
map_path
]