Introduce EnumUnboxing analysis
Bug:147860220
Change-Id: I7f732157156065cab37e7c9ee2f73ec31b2a630a
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 1837037..435b746 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -158,6 +158,7 @@
public final DexString trimName = createString("trim");
public final DexString valueOfMethodName = createString("valueOf");
+ public final DexString valuesMethodName = createString("values");
public final DexString toStringMethodName = createString("toString");
public final DexString internMethodName = createString("intern");
@@ -778,6 +779,7 @@
public final DexMethod ordinal;
public final DexMethod name;
public final DexMethod toString;
+ public final DexMethod compareTo;
public final DexMethod constructor =
createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName);
@@ -809,6 +811,29 @@
toStringMethodName,
stringDescriptor,
DexString.EMPTY_ARRAY);
+ compareTo =
+ createMethod(
+ enumDescriptor,
+ compareToMethodName,
+ stringDescriptor,
+ new DexString[] {enumDescriptor});
+ }
+
+ public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
+ assert enumClass.isEnum();
+ return method.holder == enumClass.type
+ && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
+ && method.proto.parameters.size() == 0
+ && method.name == valuesMethodName;
+ }
+
+ public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
+ assert enumClass.isEnum();
+ return method.holder == enumClass.type
+ && method.proto.returnType == enumClass.type
+ && method.proto.parameters.size() == 1
+ && method.proto.parameters.values[0] == stringType
+ && method.name == valueOfMethodName;
}
}
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 51e7743..b3a62b7 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
@@ -56,6 +56,7 @@
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.Devirtualizer;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.EnumUnboxer;
import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -156,6 +157,7 @@
private final TypeChecker typeChecker;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
private final ServiceLoaderRewriter serviceLoaderRewriter;
+ private final EnumUnboxer enumUnboxer;
// Assumers that will insert Assume instructions.
public final Collection<Assumer> assumers = new ArrayList<>();
@@ -244,6 +246,7 @@
this.stringSwitchRemover = null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumUnboxer = null;
return;
}
this.lambdaRewriter =
@@ -316,12 +319,13 @@
options.enableUninstantiatedTypeOptimization
? new UninstantiatedTypeOptimization(appViewWithLiveness)
: null;
- this.typeChecker = new TypeChecker(appView.withLiveness());
+ this.typeChecker = new TypeChecker(appViewWithLiveness);
this.d8NestBasedAccessDesugaring = null;
this.serviceLoaderRewriter =
options.enableServiceLoaderRewriting
- ? new ServiceLoaderRewriter(appView.withLiveness())
+ ? new ServiceLoaderRewriter(appViewWithLiveness)
: null;
+ this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
} else {
this.classInliner = null;
this.classStaticizer = null;
@@ -342,6 +346,7 @@
options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
+ this.enumUnboxer = null;
}
this.stringSwitchRemover =
options.isStringSwitchConversionEnabled()
@@ -661,6 +666,10 @@
libraryMethodOverrideAnalysis.finish();
}
+ if (enumUnboxer != null) {
+ enumUnboxer.finishEnumAnalysis();
+ }
+
// Post processing:
// 1) Second pass for methods whose collected call site information become more precise.
// 2) Second inlining pass for dealing with double inline callers.
@@ -1388,6 +1397,10 @@
previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
+ if (enumUnboxer != null && methodProcessor.isPost()) {
+ enumUnboxer.unboxEnums(code);
+ }
+
// This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
if (desugaredLibraryAPIConverter != null
&& (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
@@ -1464,6 +1477,10 @@
timing.end();
}
+ if (enumUnboxer != null && methodProcessor.isPrimary()) {
+ enumUnboxer.analyzeEnums(code);
+ }
+
assert code.verifyTypes(appView);
if (appView.enableWholeProgramOptimizations()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index b2bf648..34609c9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -19,6 +19,10 @@
return getPhase() == Phase.PRIMARY;
}
+ default boolean isPost() {
+ return getPhase() == Phase.POST;
+ }
+
default CallSiteInformation getCallSiteInformation() {
return CallSiteInformation.empty();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
new file mode 100644
index 0000000..8e46dd3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
@@ -0,0 +1,296 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class EnumUnboxer {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final Set<DexType> enumsToUnbox;
+
+ private final boolean debugLogEnabled;
+ private final Map<DexType, Reason> debugLogs;
+ private final DexItemFactory factory;
+
+ public EnumUnboxer(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ if (appView.options().testing.enableEnumUnboxingDebugLogs) {
+ debugLogEnabled = true;
+ debugLogs = new ConcurrentHashMap<>();
+ } else {
+ debugLogEnabled = false;
+ debugLogs = null;
+ }
+ enumsToUnbox = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
+ }
+
+ public void unboxEnums(IRCode code) {
+ // TODO(b/147860220): To implement.
+ // Do not forget static get, which is implicitly valid (no inValue).
+ }
+
+ public void analyzeEnums(IRCode code) {
+ // Enum <clinit> and <init> are analyzed in between the two processing phases using optimization
+ // feedback.
+ DexClass dexClass = appView.definitionFor(code.method.method.holder);
+ if (dexClass.isEnum() && code.method.isInitializer()) {
+ return;
+ }
+ analyzeEnumsInMethod(code);
+ }
+
+ private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
+ assert enumClass.isEnum();
+ reportFailure(enumClass.type, reason);
+ enumsToUnbox.remove(enumClass.type);
+ }
+
+ private DexProgramClass getEnumUnboxingCandidateOrNull(TypeLatticeElement lattice) {
+ if (lattice.isClassType()) {
+ DexType classType = lattice.asClassTypeLatticeElement().getClassType();
+ return getEnumUnboxingCandidateOrNull(classType);
+ }
+ if (lattice.isArrayType()) {
+ ArrayTypeLatticeElement arrayLattice = lattice.asArrayTypeLatticeElement();
+ if (arrayLattice.getArrayBaseTypeLattice().isClassType()) {
+ DexType classType =
+ arrayLattice.getArrayBaseTypeLattice().asClassTypeLatticeElement().getClassType();
+ return getEnumUnboxingCandidateOrNull(classType);
+ }
+ }
+ return null;
+ }
+
+ private DexProgramClass getEnumUnboxingCandidateOrNull(DexType anyType) {
+ if (!enumsToUnbox.contains(anyType)) {
+ return null;
+ }
+ return appView.definitionForProgramType(anyType);
+ }
+
+ private void analyzeEnumsInMethod(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ for (Instruction instruction : block.getInstructions()) {
+ Value outValue = instruction.outValue();
+ DexProgramClass enumClass =
+ outValue == null ? null : getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
+ if (enumClass != null) {
+ validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+ }
+ }
+ for (Phi phi : block.getPhis()) {
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getTypeLattice());
+ if (enumClass != null) {
+ validateEnumUsages(code, phi.uniqueUsers(), phi.uniquePhiUsers(), enumClass);
+ }
+ }
+ }
+ }
+
+ private Reason validateEnumUsages(
+ IRCode code, Set<Instruction> uses, Set<Phi> phiUses, DexProgramClass enumClass) {
+ for (Instruction user : uses) {
+ Reason reason = instructionAllowEnumUnboxing(user, code, enumClass);
+ if (reason != Reason.ELIGIBLE) {
+ markEnumAsUnboxable(reason, enumClass);
+ return reason;
+ }
+ }
+ for (Phi phi : phiUses) {
+ for (Value operand : phi.getOperands()) {
+ if (getEnumUnboxingCandidateOrNull(operand.getTypeLattice()) != enumClass) {
+ markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
+ return Reason.INVALID_PHI;
+ }
+ }
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ public void finishEnumAnalysis() {
+ for (DexType toUnbox : enumsToUnbox) {
+ DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
+ assert enumClass != null;
+
+ DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
+ assert initializer != null;
+ if (initializer.getOptimizationInfo().mayHaveSideEffects()) {
+ markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
+ continue;
+ }
+
+ if (enumClass.classInitializationMayHaveSideEffects(appView)) {
+ markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
+ continue;
+ }
+
+ Map<DexField, EnumValueInfo> enumValueInfoMapFor =
+ appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumClass.type);
+ if (enumValueInfoMapFor == null) {
+ markEnumAsUnboxable(Reason.MISSING_INFO_MAP, enumClass);
+ continue;
+ }
+ if (enumValueInfoMapFor.size() != enumClass.staticFields().size() - 1) {
+ markEnumAsUnboxable(Reason.UNEXPECTED_STATIC_FIELD, enumClass);
+ }
+ }
+ if (debugLogEnabled) {
+ reportEnumsAnalysis();
+ }
+ }
+
+ private Reason instructionAllowEnumUnboxing(
+ Instruction instruction, IRCode code, DexProgramClass enumClass) {
+
+ // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
+ if (instruction.isInvokeMethod()) {
+ InvokeMethod invokeMethod = instruction.asInvokeMethod();
+ if (invokeMethod.getInvokedMethod().holder.isArrayType()) {
+ // The only valid methods is clone for values() to be correct.
+ if (invokeMethod.getInvokedMethod().name == factory.cloneMethodName) {
+ return Reason.ELIGIBLE;
+ }
+ return Reason.INVALID_INVOKE_ON_ARRAY;
+ }
+ DexEncodedMethod invokedEncodedMethod =
+ invokeMethod.lookupSingleTarget(appView, code.method.method.holder);
+ if (invokedEncodedMethod == null) {
+ return Reason.INVALID_INVOKE;
+ }
+ DexMethod invokedMethod = invokedEncodedMethod.method;
+ DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+ if (dexClass == null) {
+ return Reason.INVALID_INVOKE;
+ }
+ if (dexClass.isProgramClass()) {
+ // All invokes in the program are generally valid, but specific care is required
+ // for values() and valueOf().
+ if (dexClass.isEnum() && factory.enumMethods.isValuesMethod(invokedMethod, dexClass)) {
+ return Reason.VALUES_INVOKE;
+ }
+ if (dexClass.isEnum() && factory.enumMethods.isValueOfMethod(invokedMethod, dexClass)) {
+ return Reason.VALUE_OF_INVOKE;
+ }
+ return Reason.ELIGIBLE;
+ }
+ if (dexClass.isClasspathClass()) {
+ return Reason.INVALID_INVOKE;
+ }
+ assert dexClass.isLibraryClass();
+ if (dexClass.type != factory.enumType) {
+ return Reason.UNSUPPORTED_LIBRARY_CALL;
+ }
+ // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may be
+ // interesting to model. A the moment rewrite only Enum#ordinal().
+ if (debugLogEnabled) {
+ if (invokedMethod == factory.enumMethods.compareTo) {
+ return Reason.COMPARE_TO_INVOKE;
+ }
+ if (invokedMethod == factory.enumMethods.name) {
+ return Reason.NAME_INVOKE;
+ }
+ if (invokedMethod == factory.enumMethods.toString) {
+ return Reason.TO_STRING_INVOKE;
+ }
+ }
+ if (invokedMethod != factory.enumMethods.ordinal) {
+ return Reason.UNSUPPORTED_LIBRARY_CALL;
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ if (instruction.isAssume()) {
+ Value outValue = instruction.outValue();
+ return validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+ }
+
+ // Return is used for valueOf methods.
+ if (instruction.isReturn()) {
+ DexType returnType = code.method.method.proto.returnType;
+ if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
+ return Reason.IMPLICIT_UP_CAST_IN_RETURN;
+ }
+ return Reason.ELIGIBLE;
+ }
+
+ return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
+ }
+
+ private void reportEnumsAnalysis() {
+ assert debugLogEnabled;
+ Reporter reporter = appView.options().reporter;
+ reporter.info(
+ new StringDiagnostic(
+ "Unboxed enums (Unboxing succeeded "
+ + enumsToUnbox.size()
+ + "): "
+ + Arrays.toString(enumsToUnbox.toArray())));
+ StringBuilder sb = new StringBuilder();
+ sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n");
+ for (DexType enumType : debugLogs.keySet()) {
+ sb.append("- ")
+ .append(enumType)
+ .append(": ")
+ .append(debugLogs.get(enumType).toString())
+ .append('\n');
+ }
+ reporter.info(new StringDiagnostic(sb.toString()));
+ }
+
+ void reportFailure(DexType enumType, Reason reason) {
+ if (debugLogEnabled) {
+ debugLogs.put(enumType, reason);
+ }
+ }
+
+ public enum Reason {
+ ELIGIBLE,
+ SUBTYPES,
+ INTERFACE,
+ INSTANCE_FIELD,
+ UNEXPECTED_STATIC_FIELD,
+ VIRTUAL_METHOD,
+ UNEXPECTED_DIRECT_METHOD,
+ INVALID_PHI,
+ INVALID_INIT,
+ INVALID_CLINIT,
+ INVALID_INVOKE,
+ INVALID_INVOKE_ON_ARRAY,
+ IMPLICIT_UP_CAST_IN_RETURN,
+ VALUE_OF_INVOKE,
+ VALUES_INVOKE,
+ COMPARE_TO_INVOKE,
+ TO_STRING_INVOKE,
+ NAME_INVOKE,
+ UNSUPPORTED_LIBRARY_CALL,
+ MISSING_INFO_MAP,
+ OTHER_UNSUPPORTED_INSTRUCTION;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
new file mode 100644
index 0000000..3fb84e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
@@ -0,0 +1,114 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.EnumUnboxer.Reason;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+class EnumUnboxingCandidateAnalysis {
+
+ private final AppView<?> appView;
+ private final EnumUnboxer enumUnboxer;
+ private final DexItemFactory factory;
+
+ EnumUnboxingCandidateAnalysis(AppView<?> appView, EnumUnboxer enumUnboxer) {
+ this.appView = appView;
+ this.enumUnboxer = enumUnboxer;
+ factory = appView.dexItemFactory();
+ }
+
+ Set<DexType> findCandidates() {
+ Set<DexType> enums = Sets.newConcurrentHashSet();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (isEnumUnboxingCandidate(clazz)) {
+ enums.add(clazz.type);
+ }
+ }
+ return enums;
+ }
+
+ private boolean isEnumUnboxingCandidate(DexProgramClass clazz) {
+ if (!clazz.isEnum()) {
+ return false;
+ }
+ if (!clazz.isEffectivelyFinal(appView)) {
+ 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;
+ }
+ if (!enumHasBasicStaticFields(clazz)) {
+ enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_STATIC_FIELD);
+ return false;
+ }
+ if (!clazz.virtualMethods().isEmpty()) {
+ enumUnboxer.reportFailure(clazz.type, Reason.VIRTUAL_METHOD);
+ 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;
+ }
+ for (DexEncodedMethod directMethod : clazz.directMethods()) {
+ if (!(factory.enumMethods.isValuesMethod(directMethod.method, clazz)
+ || factory.enumMethods.isValueOfMethod(directMethod.method, clazz)
+ || isStandardEnumInitializer(directMethod)
+ || directMethod.isClassInitializer())) {
+ enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isStandardEnumInitializer(DexEncodedMethod method) {
+ return method.isInstanceInitializer()
+ && method.method.proto == factory.enumMethods.constructor.proto;
+ }
+
+ // The enum should have the $VALUES static field and only fields directly referencing the enum
+ // instances.
+ private boolean enumHasBasicStaticFields(DexProgramClass clazz) {
+ for (DexEncodedField staticField : clazz.staticFields()) {
+ if (staticField.field.type == clazz.type
+ && staticField.accessFlags.isEnum()
+ && staticField.accessFlags.isFinal()) {
+ // Enum field, valid, do nothing.
+ } else if (staticField.field.type.isArrayType()
+ && staticField.field.type.toArrayElementType(factory) == clazz.type
+ && staticField.accessFlags.isSynthetic()
+ && staticField.accessFlags.isFinal()
+ && staticField.field.name == factory.enumValuesFieldName) {
+ // Field $VALUES, valid, do nothing.
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+}
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 22df0a3..9eb8b6c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -204,6 +204,7 @@
public boolean enableInlining =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableinlining") == null;
+ public boolean enableEnumUnboxing = false;
// TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
public boolean applyInliningToInlinee =
System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
@@ -1010,6 +1011,7 @@
public boolean enableCheckCastAndInstanceOfRemoval = true;
public boolean enableDeadSwitchCaseElimination = true;
public boolean enableSwitchToIfRewriting = true;
+ public boolean enableEnumUnboxingDebugLogs = false;
public boolean forceRedundantConstNumberRemoval = false;
public boolean forceAssumeNoneInsertion = false;
public boolean invertConditionals = false;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
new file mode 100644
index 0000000..332ad2e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -0,0 +1,66 @@
+// 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 static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+
+public class EnumUnboxingTestBase extends TestBase {
+
+ static final String KEEP_ENUM =
+ "-keepclassmembers enum * { public static **[] values(); public static **"
+ + " valueOf(java.lang.String); }";
+
+ public void assertLines2By2Correct(String string) {
+ List<String> lines = StringUtils.splitLines(string);
+ assert lines.size() % 2 == 0;
+ for (int i = 0; i < lines.size(); i += 2) {
+ assertEquals(
+ "Different lines: " + lines.get(i) + " || " + lines.get(i + 1) + "\n" + string,
+ lines.get(i),
+ lines.get(i + 1));
+ }
+ }
+
+ void enableEnumOptions(InternalOptions options) {
+ options.enableEnumUnboxing = true;
+ options.testing.enableEnumUnboxingDebugLogs = true;
+ }
+
+ void assertEnumIsUnboxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+ Diagnostic diagnostic = m.getInfos().get(0);
+ assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums"));
+ assertTrue(
+ "Expected enum to be removed (" + testName + "): ",
+ diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
+ }
+
+ void assertEnumIsBoxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+ Diagnostic diagnostic = m.getInfos().get(1);
+ assertTrue(diagnostic.getDiagnosticMessage().startsWith("Boxed enums"));
+ assertTrue(
+ "Expected enum NOT to be removed (" + testName + "): ",
+ diagnostic.getDiagnosticMessage().contains(enumClass.getSimpleName()));
+ }
+
+ static TestParametersCollection enumUnboxingTestParameters() {
+ return getTestParameters()
+ .withCfRuntime(CfVm.JDK9)
+ .withDexRuntime(DexVm.Version.first())
+ .withDexRuntime(DexVm.Version.last())
+ .withAllApiLevels()
+ .build();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..247e8a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
@@ -0,0 +1,191 @@
+// 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 static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+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.TestParametersCollection;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInstanceFieldMain.EnumInstanceField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumInterfaceMain.EnumInterface;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticFieldMain.EnumStaticField;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumStaticMethodMain.EnumStaticMethod;
+import com.android.tools.r8.enumunboxing.FailingEnumUnboxingAnalysisTest.EnumVirtualMethodMain.EnumVirtualMethod;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 FailingEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private static final Class<?>[] FAILURES = {
+ EnumInterface.class,
+ EnumStaticField.class,
+ EnumInstanceField.class,
+ EnumStaticMethod.class,
+ EnumVirtualMethod.class
+ };
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public FailingEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testEnumUnboxingFailure() throws Exception {
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend()).addInnerClasses(FailingEnumUnboxingAnalysisTest.class);
+ for (Class<?> failure : FAILURES) {
+ r8FullTestBuilder.addKeepMainRule(failure.getEnclosingClass());
+ }
+ R8TestCompileResult compile =
+ r8FullTestBuilder
+ .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
+ .enableInliningAnnotations()
+ .addKeepRules(KEEP_ENUM)
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertEnumsAsExpected);
+ for (Class<?> failure : FAILURES) {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsBoxed(failure, failure.getSimpleName(), m))
+ .run(parameters.getRuntime(), failure.getEnclosingClass())
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+ }
+
+ private void assertEnumsAsExpected(CodeInspector inspector) {
+ assertEquals(1, inspector.clazz(EnumInterface.class).getDexClass().interfaces.size());
+
+ assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent());
+ assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent());
+
+ assertEquals(5, inspector.clazz(EnumStaticMethod.class).getDexClass().directMethods().size());
+ 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);
+ }
+
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ interface Itf {
+
+ default int ordinal() {
+ return -1;
+ }
+ }
+ }
+
+ static class EnumStaticFieldMain {
+
+ public static void main(String[] args) {
+ System.out.println(EnumStaticField.A.ordinal());
+ System.out.println(0);
+ System.out.println(EnumStaticField.X.ordinal());
+ System.out.println(0);
+ }
+
+ enum EnumStaticField {
+ A,
+ B,
+ C;
+ static EnumStaticField X = A;
+ }
+ }
+
+ static class EnumInstanceFieldMain {
+
+ enum EnumInstanceField {
+ A(10),
+ B(20),
+ C(30);
+ private int a;
+
+ EnumInstanceField(int i) {
+ this.a = i;
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(EnumInstanceField.A.ordinal());
+ System.out.println(0);
+ System.out.println(EnumInstanceField.A.a);
+ System.out.println(10);
+ }
+ }
+
+ static class EnumStaticMethodMain {
+
+ 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);
+ }
+
+ 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/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..57bbc5c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -0,0 +1,238 @@
+// 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 static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+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.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.EnumSet;
+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 FailingMethodEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private static final Class<?>[] FAILURES = {
+ NullCheck.class,
+ Check.class,
+ FieldPut.class,
+ FieldPutObject.class,
+ ToString.class,
+ EnumSetTest.class,
+ FailingPhi.class
+ };
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public FailingMethodEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testEnumUnboxingFailure() throws Exception {
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class);
+ for (Class<?> failure : FAILURES) {
+ r8FullTestBuilder.addKeepMainRule(failure);
+ }
+ R8TestCompileResult compile =
+ r8FullTestBuilder
+ .addKeepRules(KEEP_ENUM)
+ .addOptionsModification(this::enableEnumOptions)
+ .enableInliningAnnotations()
+ .addOptionsModification(
+ // Disabled to avoid toString() being removed.
+ opt -> opt.enableEnumValueOptimization = false)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertEnumsAsExpected);
+ for (Class<?> failure : FAILURES) {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m ->
+ assertEnumIsBoxed(
+ failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
+ .run(parameters.getRuntime(), failure)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+ }
+
+ private void assertEnumsAsExpected(CodeInspector inspector) {
+ // Check all as expected (else we test nothing)
+ assertTrue(inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck").isPresent());
+ assertTrue(inspector.clazz(Check.class).uniqueMethodWithName("check").isPresent());
+
+ assertEquals(1, inspector.clazz(FieldPut.class).allInstanceFields().size());
+ assertEquals(1, inspector.clazz(FieldPutObject.class).allInstanceFields().size());
+
+ assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ static class NullCheck {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ System.out.println(nullCheck(MyEnum.A));
+ System.out.println(false);
+ System.out.println(nullCheck(null));
+ System.out.println(true);
+ }
+
+ // Do not resolve the == with constants after inlining.
+ @NeverInline
+ static boolean nullCheck(MyEnum e) {
+ return e == null;
+ }
+ }
+
+ static class Check {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ MyEnum e1 = MyEnum.A;
+ System.out.println(check(e1));
+ System.out.println(false);
+ }
+
+ // Do not resolve the == with constants after inlining.
+ @NeverInline
+ static boolean check(MyEnum e) {
+ return e == MyEnum.B;
+ }
+ }
+
+ static class FieldPut {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ MyEnum e;
+
+ public static void main(String[] args) {
+ FieldPut fieldPut = new FieldPut();
+ fieldPut.setA();
+ System.out.println(fieldPut.e.ordinal());
+ System.out.println(0);
+ }
+
+ void setA() {
+ e = MyEnum.A;
+ }
+ }
+
+ static class FieldPutObject {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ Object e;
+
+ public static void main(String[] args) {
+ FieldPutObject fieldPut = new FieldPutObject();
+ fieldPut.setA();
+ Object obj = new Object();
+ fieldPut.e = obj;
+ System.out.println(fieldPut.e);
+ System.out.println(obj);
+ }
+
+ void setA() {
+ e = MyEnum.A;
+ }
+ }
+
+ static class ToString {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ MyEnum e1 = MyEnum.A;
+ System.out.println(e1.toString());
+ System.out.println("A");
+ }
+ }
+
+ static class EnumSetTest {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ EnumSet<MyEnum> es = EnumSet.allOf(MyEnum.class);
+ System.out.println(es.size());
+ System.out.println("3");
+ }
+ }
+
+ static class FailingPhi {
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ public static void main(String[] args) {
+ System.out.println(switchOn(1));
+ System.out.println("B");
+ System.out.println(switchOn(2));
+ System.out.println("class java.lang.Object");
+ }
+
+ // Avoid removing the switch entirely.
+ @NeverInline
+ static Object switchOn(int i) {
+ switch (i) {
+ case 0:
+ return MyEnum.A;
+ case 1:
+ return MyEnum.B;
+ default:
+ return Object.class;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..079c9f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
@@ -0,0 +1,62 @@
+// 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.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 OrdinalEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public OrdinalEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<Ordinal> classToTest = Ordinal.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classToTest, ENUM_CLASS)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(KEEP_ENUM)
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Ordinal {
+
+ public static void main(String[] args) {
+ System.out.println(MyEnum.A.ordinal());
+ System.out.println(0);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..87cacd5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
@@ -0,0 +1,79 @@
+// 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.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 PhiEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public PhiEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = Phi.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classToTest, ENUM_CLASS)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(KEEP_ENUM)
+ .enableInliningAnnotations()
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Phi {
+
+ public static void main(String[] args) {
+ System.out.println(switchOn(1).ordinal());
+ System.out.println(1);
+ System.out.println(switchOn(2).ordinal());
+ System.out.println(2);
+ }
+
+ // Avoid removing the switch entirely.
+ @NeverInline
+ static MyEnum switchOn(int i) {
+ switch (i) {
+ case 0:
+ return MyEnum.A;
+ case 1:
+ return MyEnum.B;
+ default:
+ return MyEnum.C;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..699c72d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
@@ -0,0 +1,79 @@
+// 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.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 SwitchEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public SwitchEnumUnboxingAnalysisTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final Class<?> ENUM_CLASS = MyEnum.class;
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<Switch> classToTest = Switch.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SwitchEnumUnboxingAnalysisTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(KEEP_ENUM)
+ .enableInliningAnnotations()
+ .addOptionsModification(this::enableEnumOptions)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m))
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ enum MyEnum {
+ A,
+ B,
+ C
+ }
+
+ static class Switch {
+
+ public static void main(String[] args) {
+ System.out.println(switchOnEnum(MyEnum.A));
+ System.out.println(0xC0FFEE);
+ System.out.println(switchOnEnum(MyEnum.B));
+ System.out.println(0xBABE);
+ }
+
+ // Avoid removing the switch entirely.
+ @NeverInline
+ static int switchOnEnum(MyEnum e) {
+ switch (e) {
+ case A:
+ return 0xC0FFEE;
+ case B:
+ return 0xBABE;
+ default:
+ return 0xDEADBEEF;
+ }
+ }
+ }
+}