Enum unboxing: support simple interfaces
- Also fix FailingEnumUnboxingTest which was not testing
anything due to noTreeShaking().
Change-Id: I34e8792b2d24710a817fa677d501b57a9ffb750d
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 29a4256..3c55b1a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -437,6 +437,7 @@
DexMethod singleTarget = encodedSingleTarget.method;
DexClass dexClass = appView.definitionFor(singleTarget.holder);
if (dexClass == null) {
+ assert false;
return Reason.INVALID_INVOKE;
}
if (dexClass.isProgramClass()) {
@@ -448,14 +449,22 @@
return Reason.INVALID_INIT;
}
}
+ // Check that the enum-value only flows into parameters whose type exactly matches the
+ // enum's type.
int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
- if (invokeMethod.inValues().get(offset + i) == enumValue) {
+ if (invokeMethod.getArgument(offset + i) == enumValue) {
if (singleTarget.proto.parameters.values[i].toBaseType(factory) != enumClass.type) {
return Reason.GENERIC_INVOKE;
}
}
}
+ if (invokeMethod.isInvokeMethodWithReceiver()) {
+ Value receiver = invokeMethod.asInvokeMethodWithReceiver().getReceiver();
+ if (receiver == enumValue && dexClass.isInterface()) {
+ return Reason.DEFAULT_METHOD_INVOKE;
+ }
+ }
return Reason.ELIGIBLE;
}
if (dexClass.isClasspathClass()) {
@@ -666,6 +675,7 @@
INTERFACE,
INSTANCE_FIELD,
GENERIC_INVOKE,
+ DEFAULT_METHOD_INVOKE,
UNEXPECTED_STATIC_FIELD,
UNRESOLVABLE_FIELD,
CONST_CLASS,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 4e42286..08ba8b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -55,12 +55,6 @@
enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES);
return false;
}
- // TODO(b/147860220): interfaces without default methods should be acceptable if the build setup
- // is correct (all abstract methods are implemented).
- if (!clazz.interfaces.isEmpty()) {
- enumUnboxer.reportFailure(clazz.type, Reason.INTERFACE);
- return false;
- }
if (!clazz.instanceFields().isEmpty()) {
enumUnboxer.reportFailure(clazz.type, Reason.INSTANCE_FIELD);
return false;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index 2cfe861..f828413 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -4,21 +4,13 @@
package com.android.tools.r8.enumunboxing;
-import static junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.assertEquals;
-
import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInstanceFieldMain.EnumInstanceField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInterfaceMain.EnumInterface;
import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticFieldMain.EnumStaticField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticMethodMain.EnumStaticMethod;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumVirtualMethodMain.EnumVirtualMethod;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -29,11 +21,8 @@
public class FailingEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?>[] FAILURES = {
- EnumInterface.class,
EnumStaticField.class,
EnumInstanceField.class,
- EnumStaticMethod.class,
- EnumVirtualMethod.class
};
private final TestParameters parameters;
@@ -61,15 +50,12 @@
}
R8TestCompileResult compile =
r8FullTestBuilder
- .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
- .enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.addKeepRules(enumKeepRules.getKeepRule())
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
- .compile()
- .inspect(this::assertEnumsAsExpected);
+ .compile();
for (Class<?> failure : FAILURES) {
R8TestRunResult run =
compile
@@ -81,44 +67,6 @@
}
}
- private void assertEnumsAsExpected(CodeInspector inspector) {
- assertEquals(1, inspector.clazz(EnumInterface.class).getDexProgramClass().interfaces.size());
-
- assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent());
- assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent());
-
- assertEquals(
- 5,
- inspector
- .clazz(EnumStaticMethod.class)
- .getDexProgramClass()
- .getMethodCollection()
- .numberOfDirectMethods());
- assertEquals(1, inspector.clazz(EnumVirtualMethod.class).virtualMethods().size());
- }
-
- static class EnumInterfaceMain {
-
- public static void main(String[] args) {
- System.out.println(EnumInterface.A.ordinal());
- System.out.println(0);
- }
-
- @NeverClassInline
- enum EnumInterface implements Itf {
- A,
- B,
- C
- }
-
- interface Itf {
-
- default int ordinal() {
- return -1;
- }
- }
- }
-
static class EnumStaticFieldMain {
public static void main(String[] args) {
@@ -158,53 +106,4 @@
System.out.println(10);
}
}
-
- static class EnumStaticMethodMain {
-
- @NeverClassInline
- enum EnumStaticMethod {
- A,
- B,
- C;
-
- // Enum cannot be unboxed if it has a static method, we do not inline so the method is
- // present.
- @NeverInline
- static int foo() {
- return Math.addExact(-1, 0);
- }
- }
-
- public static void main(String[] args) {
- System.out.println(EnumStaticMethod.A.ordinal());
- System.out.println(0);
- System.out.println(EnumStaticMethod.foo());
- System.out.println(-1);
- }
- }
-
- static class EnumVirtualMethodMain {
-
- public static void main(String[] args) {
- EnumVirtualMethod e1 = EnumVirtualMethod.A;
- System.out.println(e1.ordinal());
- System.out.println(0);
- System.out.println(e1.valueOf());
- System.out.println(-1);
- }
-
- @NeverClassInline
- enum EnumVirtualMethod {
- A,
- B,
- C;
-
- // Enum cannot be unboxed if it has a virtual method, we do not inline so the method is
- // present.
- @NeverInline
- int valueOf() {
- return Math.addExact(-1, 0);
- }
- }
- }
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
new file mode 100644
index 0000000..5dcf75d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -0,0 +1,270 @@
+// Copyright (c) 2020, 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.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+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 InterfaceEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private static final Class<?>[] FAILURES = {
+ FailureDefaultMethodUsed.class, FailureUsedAsInterface.class,
+ };
+
+ private static final Class<?>[] SUCCESSES = {
+ SuccessAbstractMethod.class,
+ SuccessEmptyInterface.class,
+ SuccessUnusedDefaultMethod.class,
+ SuccessUnusedDefaultMethodOverride.class,
+ SuccessUnusedDefaultMethodOverrideEnum.class
+ };
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public InterfaceEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxingFailure() throws Exception {
+ R8TestCompileResult compile =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InterfaceEnumUnboxingTest.class)
+ .addKeepMainRules(SUCCESSES)
+ .addKeepMainRules(FAILURES)
+ .noMinification()
+ .enableMergeAnnotations()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ for (Class<?> failure : FAILURES) {
+ testClass(compile, failure, true);
+ }
+ for (Class<?> success : SUCCESSES) {
+ testClass(compile, success, false);
+ }
+ }
+
+ private void testClass(R8TestCompileResult compile, Class<?> testClass, boolean failure)
+ throws Exception {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m -> {
+ for (Class<?> declaredClass : testClass.getDeclaredClasses()) {
+ if (declaredClass.isEnum()) {
+ if (failure) {
+ assertEnumIsBoxed(declaredClass, testClass.getSimpleName(), m);
+ } else {
+ assertEnumIsUnboxed(declaredClass, testClass.getSimpleName(), m);
+ }
+ }
+ }
+ })
+ .run(parameters.getRuntime(), testClass)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ static class SuccessEmptyInterface {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.ordinal());
+ System.out.println(0);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {}
+ }
+
+ static class SuccessUnusedDefaultMethodOverrideEnum {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.ordinal());
+ System.out.println(0);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int ordinal() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class SuccessUnusedDefaultMethodOverride {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.method());
+ System.out.println(5);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C;
+
+ @Override
+ @NeverInline
+ public int method() {
+ return System.currentTimeMillis() > 0 ? 5 : -5;
+ }
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class SuccessUnusedDefaultMethod {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.ordinal());
+ System.out.println(0);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class SuccessAbstractMethod {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.method());
+ System.out.println(5);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C;
+
+ @Override
+ @NeverInline
+ public int method() {
+ return System.currentTimeMillis() > 0 ? 5 : -5;
+ }
+ }
+
+ @NeverMerge
+ interface Itf {
+ int method();
+ }
+ }
+
+ static class FailureDefaultMethodUsed {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.method());
+ System.out.println(3);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class FailureUsedAsInterface {
+
+ public static void main(String[] args) {
+ print(EnumInterface.A);
+ System.out.println(5);
+ }
+
+ @NeverInline
+ public static void print(Itf itf) {
+ System.out.println(itf.method());
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C;
+
+ @Override
+ @NeverInline
+ public int method() {
+ return System.currentTimeMillis() > 0 ? 5 : -5;
+ }
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+}