Support for Objects#hashCode in enum unboxer
Bug: b/326085657
Change-Id: I208618b5256edf71df8f0201c46108cbd7acbef5
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 4e2f94e..00f2da3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -438,6 +438,7 @@
public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
public final DexType enumType = createStaticallyKnownType(enumDescriptor);
public final DexType annotationType = createStaticallyKnownType(annotationDescriptor);
+ public final DexType arraysType = createStaticallyKnownType(arraysDescriptor);
public final DexType objectsType = createStaticallyKnownType(objectsDescriptor);
public final DexType collectionsType = createStaticallyKnownType(collectionsDescriptor);
public final DexType iterableType = createStaticallyKnownType(iterableDescriptor);
@@ -1407,6 +1408,8 @@
public class JavaUtilArraysMethods {
public final DexMethod asList;
+ public final DexMethod hashCode =
+ createMethod(arraysType, createProto(intType, objectArrayType), "hashCode");
public final DexMethod equalsObjectArray;
private JavaUtilArraysMethods() {
@@ -1794,6 +1797,8 @@
public final DexMethod equals =
createMethod(objectsType, createProto(booleanType, objectType, objectType), "equals");
+ public final DexMethod hash =
+ createMethod(objectsType, createProto(intType, objectArrayType), "hash");
public final DexMethod hashCode =
createMethod(objectsType, createProto(intType, objectType), "hashCode");
public final DexMethod isNull =
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 45a4545..9e1a36e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -66,8 +66,7 @@
public class PolicyScheduler {
public static List<Policy> getPolicies(
- AppView<?> appView,
- RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ AppView<?> appView, RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
if (appView.hasClassHierarchy()) {
return getPoliciesForR8(appView.withClassHierarchy(), runtimeTypeCheckInfo);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index dbb9d1a..1eb69e7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -179,7 +179,7 @@
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Collection<Instruction> instructionsToAdd,
+ Collection<? extends Instruction> instructionsToAdd,
InternalOptions options) {
// Assert that we are not inserting after the final jump, and also store peekNext() for later.
Instruction origNext = null;
@@ -196,14 +196,14 @@
BasicBlockInstructionListIterator dstIterator,
IRCode code,
BasicBlockIterator blockIterator,
- Collection<Instruction> instructionsToAdd,
+ Collection<? extends Instruction> instructionsToAdd,
InternalOptions options) {
if (!dstIterator.block.hasCatchHandlers() || instructionsToAdd.isEmpty()) {
dstIterator.addAll(instructionsToAdd);
return dstIterator;
}
- Iterator<Instruction> srcIterator = instructionsToAdd.iterator();
+ Iterator<? extends Instruction> srcIterator = instructionsToAdd.iterator();
// If the throwing instruction is before the cursor, then we must split the block first.
// If there is one afterwards, we can add instructions and when we split, the throwing one
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 0b2be34..6b33cca 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -238,7 +238,7 @@
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Collection<Instruction> instructionsToAdd,
+ Collection<? extends Instruction> instructionsToAdd,
InternalOptions options) {
return instructionIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
code, blockIterator, instructionsToAdd, options);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index e612141..19c675b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -36,13 +36,13 @@
}
}
- default void addAll(Collection<Instruction> instructions) {
+ default void addAll(Collection<? extends Instruction> instructions) {
for (Instruction instruction : instructions) {
add(instruction);
}
}
- default boolean addUntilThrowing(Iterator<Instruction> srcIterator) {
+ default boolean addUntilThrowing(Iterator<? extends Instruction> srcIterator) {
while (srcIterator.hasNext()) {
// Add all non-throwing instructions up until the first throwing instruction.
Instruction instruction = srcIterator.next();
@@ -57,7 +57,7 @@
InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Collection<Instruction> instructionsToAdd,
+ Collection<? extends Instruction> instructionsToAdd,
InternalOptions options);
default InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 0bb697e..e3ac085 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -199,7 +199,7 @@
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Collection<Instruction> instructionsToAdd,
+ Collection<? extends Instruction> instructionsToAdd,
InternalOptions options) {
return currentBlockIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
code, blockIterator, instructionsToAdd, options);
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
index 11dce59..c5d6c05 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
@@ -38,6 +38,10 @@
this.type = type;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public int opcode() {
return Opcodes.NEW_ARRAY_FILLED;
@@ -241,4 +245,30 @@
public void buildLir(LirBuilder<Value, ?> builder) {
builder.addNewArrayFilled(getArrayType(), arguments());
}
+
+ public static class Builder extends BuilderBase<Builder, NewArrayFilled> {
+
+ private List<Value> elements;
+ private DexType type;
+
+ public Builder setElements(List<Value> elements) {
+ this.elements = elements;
+ return this;
+ }
+
+ public Builder setType(DexType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public NewArrayFilled build() {
+ return amend(new NewArrayFilled(type, outValue, elements));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index 20998db..96be152 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -20,13 +19,8 @@
public final class ObjectsMethodRewrites {
public static MethodInvokeRewriter rewriteToArraysHashCode() {
- return (invoke, factory) -> {
- DexType arraysType = factory.createType(factory.arraysDescriptor);
- return new CfInvoke(
- Opcodes.INVOKESTATIC,
- factory.createMethod(arraysType, invoke.getMethod().proto, "hashCode"),
- false);
- };
+ return (invoke, factory) ->
+ new CfInvoke(Opcodes.INVOKESTATIC, factory.javaUtilArraysMethods.hashCode, false);
}
public static MethodInvokeRewriter rewriteRequireNonNull() {
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 3470908..4734657 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
@@ -1268,16 +1268,17 @@
assert false;
return Reason.INVALID_INVOKE_NEW_ARRAY;
}
- if (enumClass.type.isNotIdenticalTo(arrayBaseType.getClassType())) {
+ if (enumClass.getType().isIdenticalTo(arrayBaseType.getClassType())) {
+ if (Iterables.all(
+ newArrayFilled.arguments(), argument -> isAssignableToArray(argument, arrayBaseType))) {
+ return Reason.ELIGIBLE;
+ }
return Reason.INVALID_INVOKE_NEW_ARRAY;
}
-
- for (Value value : newArrayFilled.inValues()) {
- if (!isAssignableToArray(value, arrayBaseType)) {
- return Reason.INVALID_INVOKE_NEW_ARRAY;
- }
+ if (EnumUnboxerUtils.isArrayUsedOnlyForHashCode(newArrayFilled, factory)) {
+ return Reason.ELIGIBLE;
}
- return Reason.ELIGIBLE;
+ return Reason.INVALID_INVOKE_NEW_ARRAY;
}
private Reason analyzeCheckCastUser(CheckCast checkCast) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerUtils.java
new file mode 100644
index 0000000..77d819f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerUtils.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2024, 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.optimize.enums;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NewArrayFilled;
+import com.android.tools.r8.ir.code.Value;
+
+public class EnumUnboxerUtils {
+
+ public static boolean isArrayUsedOnlyForHashCode(
+ NewArrayFilled newArrayFilled, DexItemFactory factory) {
+ Value array = newArrayFilled.outValue();
+ if (!array.hasSingleUniqueUser() || array.hasPhiUsers()) {
+ return false;
+ }
+ InvokeStatic invoke = array.singleUniqueUser().asInvokeStatic();
+ if (invoke == null) {
+ return false;
+ }
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ return invokedMethod.isIdenticalTo(factory.javaUtilArraysMethods.hashCode)
+ || invokedMethod.isIdenticalTo(factory.objectsMethods.hash);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index 53e7b32..f5eeee2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.cf.code.CfArithmeticBinop;
import com.android.tools.r8.cf.code.CfArrayLength;
import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfGoto;
@@ -43,9 +44,52 @@
public final class EnumUnboxingCfMethods {
public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+ factory.createSynthesizedType("Ljava/lang/Integer;");
factory.createSynthesizedType("Ljava/lang/NullPointerException;");
}
+ public static CfCode EnumUnboxingMethods_boxedOrdinalOrNull(
+ DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.INT, 0),
+ new CfIf(IfType.NE, ValueType.INT, label1),
+ new CfConstNull(),
+ new CfGoto(label2),
+ label1,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(new int[] {0}, new FrameType[] {FrameType.intType()})),
+ new CfLoad(ValueType.INT, 0),
+ new CfConstNumber(1, ValueType.INT),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+ new CfInvoke(
+ 184,
+ factory.createMethod(
+ factory.createType("Ljava/lang/Integer;"),
+ factory.createProto(factory.createType("Ljava/lang/Integer;"), factory.intType),
+ factory.createString("valueOf")),
+ false),
+ label2,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(new int[] {0}, new FrameType[] {FrameType.intType()}),
+ new ArrayDeque<>(
+ Arrays.asList(
+ FrameType.initializedNonNullReference(
+ factory.createType("Ljava/lang/Integer;"))))),
+ new CfReturn(ValueType.OBJECT),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode EnumUnboxingMethods_compareTo(DexItemFactory factory, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index da575cd..aabff86 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.ir.optimize.enums.EnumUnboxerUtils.isArrayUsedOnlyForHashCode;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -40,6 +41,7 @@
import com.android.tools.r8.ir.code.NewArrayFilled;
import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -56,6 +58,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -232,11 +235,61 @@
}
}
}
+ if (code.metadata().mayHaveNewArrayFilled()) {
+ fixupPrimitiveElementsOfNonPrimitiveNewArrayFilled(code, convertedEnums, eventConsumer);
+ }
code.removeRedundantBlocks();
assert code.isConsistentSSABeforeTypesAreCorrect(appView);
return affectedPhis;
}
+ private void fixupPrimitiveElementsOfNonPrimitiveNewArrayFilled(
+ IRCode code,
+ Map<Instruction, DexType> convertedEnums,
+ EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+ BasicBlockIterator blocks = code.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ NewArrayFilled newArrayFilled;
+ while ((newArrayFilled = instructionIterator.nextUntil(Instruction::isNewArrayFilled))
+ != null) {
+ if (isArrayUsedOnlyForHashCode(newArrayFilled, factory)) {
+ // Insert a box operation.
+ Map<Value, InvokeStatic> argumentsToBox = new LinkedHashMap<>();
+ for (int argumentIndex = 0;
+ argumentIndex < newArrayFilled.arguments().size();
+ argumentIndex++) {
+ Value argument = newArrayFilled.getArgument(argumentIndex);
+ Position position = newArrayFilled.getPosition();
+ if (getEnumClassTypeOrNull(argument, convertedEnums) != null) {
+ InvokeStatic boxInstruction =
+ argumentsToBox.computeIfAbsent(
+ argument,
+ a ->
+ InvokeStatic.builder()
+ .setFreshOutValue(code, factory.boxedIntType.toTypeElement(appView))
+ .setSingleArgument(a)
+ .setMethod(
+ getSharedUtilityClass()
+ .ensureBoxedOrdinalOrNullMethod(
+ appView, code.context(), eventConsumer))
+ .setPosition(position)
+ .build());
+ newArrayFilled.replaceValue(argumentIndex, boxInstruction.outValue());
+ }
+ }
+ if (!argumentsToBox.isEmpty()) {
+ instructionIterator.previous();
+ instructionIterator =
+ instructionIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ code, blocks, argumentsToBox.values(), options);
+ }
+ }
+ }
+ }
+ }
+
private void rewriteArrayAccess(
IRCode code,
Set<Phi> affectedPhis,
@@ -498,6 +551,19 @@
Map<Instruction, DexType> convertedEnums,
InstructionListIterator instructionIterator) {
DexType arrayBaseType = newArrayFilled.getArrayType().toBaseType(factory);
+ if (isArrayUsedOnlyForHashCode(newArrayFilled, factory)) {
+ DexType rewrittenArrayType =
+ newArrayFilled.getArrayType().replaceBaseType(factory.objectType, factory);
+ NewArrayFilled rewrittenNewArrayFilled =
+ NewArrayFilled.builder()
+ .setElements(newArrayFilled.arguments())
+ .setFreshOutValue(
+ code, rewrittenArrayType.toTypeElement(appView, definitelyNotNull()))
+ .setType(rewrittenArrayType)
+ .build();
+ instructionIterator.replaceCurrentInstruction(rewrittenNewArrayFilled);
+ return;
+ }
if (!unboxedEnumsData.isUnboxedEnum(arrayBaseType)) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 37c814d..07dfc5e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -74,6 +74,7 @@
ensureEqualsMethod(appView);
ensureObjectsEqualsMethod(appView);
ensureOrdinalMethod(appView);
+ ensureBoxedOrdinalOrNullMethod(appView);
}
public ProgramMethod ensureCheckNotZeroMethod(
@@ -189,6 +190,25 @@
method -> EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(dexItemFactory, method));
}
+ public ProgramMethod ensureBoxedOrdinalOrNullMethod(
+ AppView<AppInfoWithLiveness> appView,
+ ProgramMethod context,
+ EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+ ProgramMethod method = ensureBoxedOrdinalOrNullMethod(appView);
+ eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+ return method;
+ }
+
+ private ProgramMethod ensureBoxedOrdinalOrNullMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.createString("boxedOrdinalOrNull"),
+ dexItemFactory.createProto(dexItemFactory.boxedIntType, dexItemFactory.intType),
+ method ->
+ EnumUnboxingCfMethods.EnumUnboxingMethods_boxedOrdinalOrNull(dexItemFactory, method));
+ }
+
private ProgramMethod internalEnsureMethod(
AppView<AppInfoWithLiveness> appView,
DexString methodName,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumObjectsHashCodeTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumObjectsHashCodeTest.java
new file mode 100644
index 0000000..ebca151
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumObjectsHashCodeTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2024, 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.enumunboxing;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.Objects;
+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 EnumObjectsHashCodeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .enableConstantArgumentAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("31", "70154", "31", "null", "31", "31", "31", "961");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testFromGetter();
+ testMixed();
+ testNull(MyEnum.getNull());
+ testNullUnboxed();
+ testParam(MyEnum.getA());
+ testPhi();
+ testRepeated();
+ }
+
+ @NeverInline
+ static void testFromGetter() {
+ System.out.println(Objects.hash(MyEnum.getA()));
+ }
+
+ @NeverInline
+ static void testMixed() {
+ System.out.println(Objects.hash(new A(), MyEnum.A, MyEnum.B));
+ }
+
+ @NeverInline
+ static void testNull(MyEnum e) {
+ System.out.println(Objects.hash(e));
+ }
+
+ @NeverInline
+ static void testNullUnboxed() {
+ MyEnum e = null;
+ MyEnum.print(e);
+ System.out.println(Objects.hash(e));
+ }
+
+ @NeverInline
+ static void testParam(MyEnum e) {
+ System.out.println(Objects.hash(e));
+ }
+
+ @NeverInline
+ static void testPhi() {
+ MyEnum e = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
+ System.out.println(Objects.hash(e));
+ }
+
+ @NeverInline
+ static void testRepeated() {
+ System.out.println(Objects.hash(MyEnum.A, MyEnum.A));
+ }
+ }
+
+ static class A {
+
+ @Override
+ public int hashCode() {
+ return 42;
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B;
+
+ @NeverInline
+ static MyEnum getA() {
+ return System.currentTimeMillis() > 0 ? A : B;
+ }
+
+ @NeverInline
+ static MyEnum getNull() {
+ return System.currentTimeMillis() > 0 ? null : A;
+ }
+
+ @KeepConstantArguments
+ @NeverInline
+ static void print(MyEnum e) {
+ System.out.println(e != null ? e.name() : "null");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
index 48818ec..c56421a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
@@ -9,6 +9,10 @@
// are different on each Enum implementation.
public class EnumUnboxingMethods {
+ public static Integer boxedOrdinalOrNull(int unboxedEnum) {
+ return unboxedEnum == 0 ? null : unboxedEnum - 1;
+ }
+
// An enum is unboxed to ordinal + 1.
// For example, enum E {A,B}, is unboxed to null -> 0, A -> 1, B-> 2.
// Computing the ordinal of an unboxed enum throws a null pointer exception on 0,
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 02dc773..9dc6503 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -205,7 +205,7 @@
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Collection<Instruction> instructionsToAdd,
+ Collection<? extends Instruction> instructionsToAdd,
InternalOptions options) {
throw new Unimplemented();
}