Enum unboxing: Support enums with static methods
- Include support for Javac generated methods valueOf and values.
- Enable enum unboxing for enum with switches in the general case.
Bug: 150172351
Bug: 150370354
Change-Id: I3cb16e2ebda59d75c2e0d07cf2293d8a5c5cddc3
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 200b7f0..f2bf9dd 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
@@ -481,7 +481,7 @@
private void synthesizeEnumUnboxingUtilityClass(
Builder<?> builder, ExecutorService executorService) throws ExecutionException {
if (enumUnboxer != null) {
- enumUnboxer.synthesizeUtilityClass(builder, this, executorService);
+ enumUnboxer.synthesizeUtilityMethods(builder, this, executorService);
}
}
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 d878322..56e1df5 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
@@ -7,7 +7,7 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClass.FieldSetter;
import com.android.tools.r8.graph.DexEncodedField;
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexMethod;
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.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueNull;
@@ -57,6 +58,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
@@ -152,20 +154,10 @@
} else if (instruction.isCheckCast()) {
analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
} else if (instruction.isInvokeStatic()) {
- // TODO(b/150370354): Since we temporary allow enum unboxing on enums with values and
- // valueOf static methods only if such methods are unused, such methods cannot be
- // called. the long term solution is to simply move called methods to a companion class,
- // as any static helper method, and remove these checks.
DexMethod invokedMethod = instruction.asInvokeStatic().getInvokedMethod();
DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
if (enumClass != null) {
- if (factory.enumMethods.isValueOfMethod(invokedMethod, enumClass)) {
- markEnumAsUnboxable(Reason.VALUE_OF_INVOKE, enumClass);
- } else if (factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
- markEnumAsUnboxable(Reason.VALUES_INVOKE, enumClass);
- } else {
- assert false; // We do not allow any other static call in unboxing candidates.
- }
+ eligibleEnums.add(enumClass.type);
}
}
}
@@ -304,8 +296,8 @@
return;
}
ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
- NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
+ NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
if (enumUnboxingLens != null) {
appView.setGraphLense(enumUnboxingLens);
@@ -599,12 +591,11 @@
return Sets.newIdentityHashSet();
}
- public void synthesizeUtilityClass(
- DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+ public void synthesizeUtilityMethods(
+ Builder<?> builder, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
if (enumUnboxerRewriter != null) {
- enumUnboxerRewriter.synthesizeEnumUnboxingUtilityClass(
- appBuilder, converter, executorService);
+ enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(builder, converter, executorService);
}
}
@@ -662,6 +653,7 @@
private class TreeFixer {
+ private final List<DexEncodedMethod> unboxedEnumsMethods = new ArrayList<>();
private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
private final Set<DexType> enumsToUnbox;
@@ -670,27 +662,26 @@
}
private NestedGraphLense fixupTypeReferences() {
+ assert enumUnboxerRewriter != null;
// Fix all methods and fields using enums to unbox.
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (enumsToUnbox.contains(clazz.type)) {
assert clazz.instanceFields().size() == 0;
- // TODO(b/150370354): Remove when static methods are supported.
- if (appView.options().testing.enumUnboxingRewriteJavaCGeneratedMethod) {
- // Clear only the initializers.
- clazz
- .methods()
- .forEach(
- m -> {
- if (m.isInitializer()) {
- clearEnumToUnboxMethod(m);
- }
- });
- clazz.getMethodCollection().replaceMethods(this::fixupMethod);
- } else {
- clazz.methods().forEach(this::clearEnumToUnboxMethod);
- }
+ // Clear the initializers and move the static methods to the utility class.
+ clazz
+ .methods()
+ .forEach(
+ m -> {
+ if (m.isInitializer()) {
+ clearEnumToUnboxMethod(m);
+ } else {
+ assert m.isStatic();
+ unboxedEnumsMethods.add(
+ fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType));
+ }
+ });
} else {
- clazz.getMethodCollection().replaceMethods(this::fixupMethod);
+ clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
fixupFields(clazz.staticFields(), clazz::setStaticField);
fixupFields(clazz.instanceFields(), clazz::setInstanceField);
}
@@ -698,6 +689,10 @@
for (DexType toUnbox : enumsToUnbox) {
lensBuilder.map(toUnbox, factory.intType);
}
+ DexProgramClass utilityClass =
+ appView.definitionForProgramType(factory.enumUnboxingUtilityType);
+ assert utilityClass != null : "Should have been synthesized in the enqueuer";
+ utilityClass.addDirectMethods(unboxedEnumsMethods);
return lensBuilder.build(factory, appView.graphLense());
}
@@ -714,7 +709,19 @@
appView);
}
- private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
+ private DexEncodedMethod fixupEncodedMethodToUtility(
+ DexEncodedMethod encodedMethod, DexType newHolder) {
+ DexMethod method = encodedMethod.method;
+ DexString newMethodName =
+ factory.createString(
+ enumUnboxerRewriter.compatibleName(method.holder) + "$$" + method.name.toString());
+ DexMethod newMethod = fixupMethod(method, newHolder, newMethodName);
+ lensBuilder.move(method, newMethod, encodedMethod.isStatic());
+ encodedMethod.accessFlags.promoteToPublic();
+ return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ }
+
+ private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
DexMethod newMethod = fixupMethod(encodedMethod.method);
if (newMethod != encodedMethod.method) {
lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic());
@@ -746,7 +753,11 @@
}
private DexMethod fixupMethod(DexMethod method) {
- return factory.createMethod(method.holder, fixupProto(method.proto), method.name);
+ return fixupMethod(method, method.holder, method.name);
+ }
+
+ private DexMethod fixupMethod(DexMethod method, DexType newHolder, DexString newMethodName) {
+ return factory.createMethod(newHolder, fixupProto(method.proto), newMethodName);
}
private DexProto fixupProto(DexProto proto) {
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 5412037..a6763fc 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
@@ -82,23 +82,10 @@
enumUnboxer.reportFailure(clazz.type, Reason.MISSING_INFO_MAP);
return false;
}
- // Methods values, valueOf, init, clinit are present on each enum.
- // Methods init and clinit are required if the enum is used.
- // Methods valueOf and values are normally kept by the commonly used/recommended enum keep rule
- // -keepclassmembers,allowoptimization enum * {
- // public static **[] values();
- // public static ** valueOf(java.lang.String);
- // }
- // In general there will be 4 methods, unless the enum keep rule is not present.
- if (clazz.directMethods().size() > 4) {
- enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
- return false;
- }
+ // TODO(b/155036467): Fail lazily when an unsupported method is not only present but also used.
+ // Only Enums with default initializers and static methods can be unboxed at the moment.
for (DexEncodedMethod directMethod : clazz.directMethods()) {
- if (!(factory.enumMethods.isValuesMethod(directMethod.method, clazz)
- || factory.enumMethods.isValueOfMethod(directMethod.method, clazz)
- || isStandardEnumInitializer(directMethod)
- || directMethod.isClassInitializer())) {
+ if (!(directMethod.isStatic() || isStandardEnumInitializer(directMethod))) {
enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
return false;
}
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 383b03b..2133252 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
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -45,7 +45,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -67,7 +66,6 @@
private final Map<DexMethod, DexEncodedMethod> extraUtilityMethods = new ConcurrentHashMap<>();
private final Map<DexField, DexEncodedField> extraUtilityFields = new ConcurrentHashMap<>();
- private final DexType utilityClassType;
private final DexMethod ordinalUtilityMethod;
private final DexMethod valuesUtilityMethod;
@@ -83,15 +81,14 @@
}
this.enumsToUnbox = builder.build();
- this.utilityClassType = factory.enumUnboxingUtilityType;
this.ordinalUtilityMethod =
factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_ORDINAL);
this.valuesUtilityMethod =
factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intArrayType, factory.intType),
ENUM_UNBOXING_UTILITY_VALUES);
}
@@ -222,13 +219,13 @@
return enumsToUnbox.containsEnum(type.asClassType().getClassType());
}
- private String compatibleName(DexType type) {
+ public String compatibleName(DexType type) {
return type.toSourceString().replace('.', '$');
}
private DexField createValuesField(DexType type) {
return factory.createField(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.intArrayType,
factory.enumValuesFieldName + "$field$" + compatibleName(type));
}
@@ -244,7 +241,7 @@
private DexMethod createValuesMethod(DexType type) {
return factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intArrayType),
factory.enumValuesFieldName + "$method$" + compatibleName(type));
}
@@ -253,7 +250,11 @@
DexMethod method, DexField fieldValues, int numEnumInstances) {
CfCode cfCode =
new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
- appView, utilityClassType, fieldValues, numEnumInstances, valuesUtilityMethod)
+ appView,
+ factory.enumUnboxingUtilityType,
+ fieldValues,
+ numEnumInstances,
+ valuesUtilityMethod)
.generateCfCode();
return synthesizeUtilityMethod(cfCode, method, true);
}
@@ -262,7 +263,7 @@
assert enumsToUnbox.containsEnum(type);
DexMethod valueOf =
factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intType, factory.stringType),
"valueOf" + compatibleName(type));
extraUtilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, type));
@@ -283,9 +284,8 @@
&& enumsToUnbox.containsEnum(baseType.asClassType().getClassType());
}
- // TODO(b/150172351): Synthesize the utility class upfront in the enqueuer.
- void synthesizeEnumUnboxingUtilityClass(
- DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+ void synthesizeEnumUnboxingUtilityMethods(
+ Builder<?> builder, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
// Synthesize a class which holds various utility methods that may be called from the IR
// rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
@@ -302,38 +302,50 @@
if (requiredMethods.isEmpty()) {
return;
}
- DexEncodedField[] fields = extraUtilityFields.values().toArray(DexEncodedField.EMPTY_ARRAY);
- Arrays.sort(fields, (f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
+ List<DexEncodedField> fields = new ArrayList<>(extraUtilityFields.values());
+ fields.sort((f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
DexProgramClass utilityClass =
- new DexProgramClass(
- utilityClassType,
- null,
- new SynthesizedOrigin("EnumUnboxing ", getClass()),
- ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
- factory.objectType,
- DexTypeList.empty(),
- factory.createString("enumunboxing"),
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- DexAnnotationSet.empty(),
- fields,
- DexEncodedField.EMPTY_ARRAY,
- // All synthesized methods are static in this case.
- requiredMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
- DexEncodedMethod.EMPTY_ARRAY,
- factory.getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
- appBuilder.addSynthesizedClass(utilityClass, utilityClassInMainDexList());
- appView.appInfo().addSynthesizedClass(utilityClass);
- converter.optimizeSynthesizedClass(utilityClass, executorService);
+ appView.definitionForProgramType(factory.enumUnboxingUtilityType);
+ assert utilityClass != null : "Should have been synthesized in the enqueuer.";
+ utilityClass.appendStaticFields(fields);
+ utilityClass.addDirectMethods(requiredMethods);
+ assert requiredMethods.stream().allMatch(DexEncodedMethod::isPublic);
+ if (utilityClassInMainDexList()) {
+ builder.addToMainDexList(Collections.singletonList(utilityClass.type));
+ }
+ converter.processMethodsConcurrently(requiredMethods, executorService);
+ }
+
+ public static DexProgramClass synthesizeEmptyEnumUnboxingUtilityClass(AppView<?> appView) {
+ DexItemFactory factory = appView.dexItemFactory();
+ return new DexProgramClass(
+ factory.enumUnboxingUtilityType,
+ null,
+ new SynthesizedOrigin("EnumUnboxing ", EnumUnboxingRewriter.class),
+ ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+ factory.objectType,
+ DexTypeList.empty(),
+ factory.createString("enumunboxing"),
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ factory.getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
}
private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
CfCode cfCode =
new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
- appView, utilityClassType, enumType, enumsToUnbox.getEnumValueInfoMap(enumType))
+ appView,
+ factory.enumUnboxingUtilityType,
+ enumType,
+ enumsToUnbox.getEnumValueInfoMap(enumType))
.generateCfCode();
return new DexEncodedMethod(
method,
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 5540e34..f1fcd4b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -84,6 +84,7 @@
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
@@ -2632,6 +2633,7 @@
new IdentityHashMap<>();
Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
+ Set<DexProgramClass> liveTypes = Sets.newIdentityHashSet();
Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
@@ -2642,7 +2644,8 @@
Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
boolean isEmpty() {
- boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
+ boolean empty =
+ syntheticInstantiations.isEmpty() && liveMethods.isEmpty() && liveTypes.isEmpty();
assert !empty || (pinnedMethods.isEmpty() && mainDexTypes.isEmpty());
return empty;
}
@@ -2656,6 +2659,14 @@
}
}
+ void addLiveType(DexProgramClass clazz, boolean isMainDexClass) {
+ assert !liveTypes.contains(clazz);
+ liveTypes.add(clazz);
+ if (isMainDexClass) {
+ mainDexTypes.add(clazz.type);
+ }
+ }
+
void addClasspathClass(DexClasspathClass clazz) {
DexClasspathClass old = syntheticClasspathClasses.put(clazz.type, clazz);
assert old == null;
@@ -2678,6 +2689,9 @@
syntheticInstantiations.values()) {
appBuilder.addProgramClass(clazzAndContext.getFirst());
}
+ for (DexProgramClass liveClass : liveTypes) {
+ appBuilder.addProgramClass(liveClass);
+ }
appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
appBuilder.addToMainDexList(mainDexTypes);
}
@@ -2697,6 +2711,12 @@
InstantiationReason.SYNTHESIZED_CLASS,
fakeReason);
}
+ for (DexProgramClass liveType : liveTypes) {
+ // TODO(b/155373435): Replace with
+ // enqueuer.workList.enqueueMarkTypeLiveAction(liveType, fakeReason);
+ // Introduce a new class MarkTypeLiveAction as well as the enqueueMarkTypeLiveAction().
+ enqueuer.markTypeAsLive(liveType, fakeReason);
+ }
for (ProgramMethod liveMethod : liveMethods.values()) {
assert !enqueuer.targetedMethods.contains(liveMethod.getMethod());
DexProgramClass holder = liveMethod.getHolder();
@@ -2718,6 +2738,7 @@
synthesizeInterfaceMethodBridges(additions);
synthesizeLambdas(additions);
synthesizeLibraryConversionWrappers(additions);
+ synthesizeEnumUnboxingUtilityClass(additions);
if (additions.isEmpty()) {
return;
}
@@ -2735,6 +2756,16 @@
additions.enqueueWorkItems(this);
}
+ private void synthesizeEnumUnboxingUtilityClass(SyntheticAdditions additions) {
+ if (appView.options().enableEnumUnboxing
+ && appView.definitionFor(appView.dexItemFactory().enumUnboxingUtilityType) == null) {
+ DexProgramClass utilityClass =
+ EnumUnboxingRewriter.synthesizeEmptyEnumUnboxingUtilityClass(appView);
+ // The flag isMainDexClass may be set later, the compiler does not know at this point.
+ additions.addLiveType(utilityClass, false);
+ }
+ }
+
private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
DexProgramClass holder = bridge.getHolder();
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
new file mode 100644
index 0000000..ca34533
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
@@ -0,0 +1,151 @@
+// 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.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 StaticMethodsEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ 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 StaticMethodsEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = StaticMethods.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StaticMethodsEnumUnboxingTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> {
+ assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
+ assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
+ })
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public static void print(Object o) {
+ System.out.println(o);
+ }
+
+ @NeverInline
+ public static void printEnum(MyEnum e) {
+ System.out.println(e.ordinal());
+ }
+
+ @NeverInline
+ protected static void printProtected() {
+ System.out.println("protected");
+ }
+
+ @NeverInline
+ static void printPackagePrivate() {
+ System.out.println("package-private");
+ }
+
+ @NeverInline
+ private static void printPrivate() {
+ System.out.println("private");
+ }
+
+ @NeverInline
+ public static void callPrivate() {
+ System.out.print("call: ");
+ printPrivate();
+ }
+ }
+
+ // Use two enums to test collision between values and valueOf.
+ enum MyEnum2 {
+ A,
+ B,
+ C;
+ }
+
+ static class StaticMethods {
+
+ public static void main(String[] args) {
+ testCustomMethods();
+ testNonPublicMethods();
+ testGeneratedMethods();
+ testGeneratedMethods2();
+ }
+
+ @NeverInline
+ private static void testNonPublicMethods() {
+ MyEnum.printPrivate();
+ System.out.println("private");
+ MyEnum.printPackagePrivate();
+ System.out.println("package-private");
+ MyEnum.printProtected();
+ System.out.println("protected");
+ MyEnum.callPrivate();
+ System.out.println("call: private");
+ }
+
+ @NeverInline
+ private static void testCustomMethods() {
+ MyEnum.print("print");
+ System.out.println("print");
+ MyEnum.printEnum(MyEnum.A);
+ System.out.println(0);
+ }
+
+ @NeverInline
+ private static void testGeneratedMethods() {
+ System.out.println(MyEnum.valueOf("C").ordinal());
+ System.out.println(2);
+ System.out.println(MyEnum.values()[0].ordinal());
+ System.out.println(0);
+ }
+
+ @NeverInline
+ private static void testGeneratedMethods2() {
+ System.out.println(MyEnum2.valueOf("C").ordinal());
+ System.out.println(2);
+ System.out.println(MyEnum2.values()[0].ordinal());
+ System.out.println(0);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index 3ccdd37..dc2e217 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -50,15 +50,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
.inspectDiagnosticMessages(
- m -> {
- // TODO(b/150370354): We need to allow static helper method to re-enable unboxing
- // with switches.
- if (enumValueOptimization && enumKeepRules.getKeepRule().equals("")) {
- assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
- } else {
- assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
- }
- })
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
.run(parameters.getRuntime(), classToTest)
.assertSuccess();
assertLines2By2Correct(run.getStdOut());