Enum unboxing: improve enum instance state tracking
Bug: 155368026
Change-Id: Iac3057cad74510f20a4a955a9dc538e7996a8691
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 3d8df1a..ebcdfff 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -25,6 +26,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
@@ -141,9 +143,132 @@
* array as long as the array is identified as being the {@code $VALUES} array.
*/
private SingleFieldValue computeSingleEnumFieldValue(Value value) {
+ if (!context.getHolder().isEnum()) {
+ return null;
+ }
assert !value.hasAliasedValue();
- if (!context.getHolder().isEnum()
- || !value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+ if (isEnumValuesArray(value)) {
+ return computeSingleEnumFieldValueForValuesArray(value);
+ }
+ return computeSingleEnumFieldValueForInstance(value);
+ }
+
+ private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
+ if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
+ return null;
+ }
+
+ NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
+ if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+ return null;
+ }
+ if (value.hasDebugUsers() || value.hasPhiUsers()) {
+ return null;
+ }
+ if (!newArrayEmpty.size().isConstNumber()) {
+ return null;
+ }
+
+ int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+ if (valuesSize == 0) {
+ // No need to compute the state of an empty array.
+ return null;
+ }
+
+ ObjectState[] valuesState = new ObjectState[valuesSize];
+ DexEncodedField valuesField = null;
+ for (Instruction user : value.uniqueUsers()) {
+ switch (user.opcode()) {
+ case ARRAY_PUT:
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut.array() != value) {
+ return null;
+ }
+ if (!arrayPut.index().isConstNumber()) {
+ return null;
+ }
+ int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+ if (index < 0 || index >= valuesSize) {
+ return null;
+ }
+ ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
+ if (objectState == null || objectState.isEmpty()) {
+ // We need the state of all fields for the analysis to be valuable.
+ return null;
+ }
+ assert verifyValuesArrayIndexMatchesOrdinal(index, objectState);
+ if (valuesState[index] != null) {
+ return null;
+ }
+ valuesState[index] = objectState;
+ break;
+
+ case STATIC_PUT:
+ DexEncodedField field =
+ context.getHolder().lookupStaticField(user.asStaticPut().getField());
+ if (field == null) {
+ return null;
+ }
+ if (valuesField != null) {
+ return null;
+ }
+ valuesField = field;
+ break;
+
+ default:
+ return null;
+ }
+ }
+
+ if (valuesField == null) {
+ return null;
+ }
+
+ for (ObjectState objectState : valuesState) {
+ if (objectState == null) {
+ return null;
+ }
+ }
+
+ return appView
+ .abstractValueFactory()
+ .createSingleFieldValue(valuesField.field, new EnumValuesObjectState(valuesState));
+ }
+
+ private ObjectState computeEnumInstanceObjectState(Value value) {
+ Value root = value.getAliasedValue();
+ if (root.isPhi()) {
+ return ObjectState.empty();
+ }
+ Instruction definition = root.getDefinition();
+ if (definition.isNewInstance()) {
+ return computeObjectState(definition.outValue());
+ }
+ if (definition.isStaticGet()) {
+ // TODO(b/166532388) : Enums with many instance rely on staticGets to set the $VALUES data
+ // instead of directly keeping the values in registers. We could consider analysing these
+ // and answer the analysed object state here.
+ return ObjectState.empty();
+ }
+ return ObjectState.empty();
+ }
+
+ private boolean verifyValuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
+ DexEncodedField ordinalField =
+ appView
+ .appInfo()
+ .resolveField(appView.dexItemFactory().enumMembers.ordinalField, context)
+ .getResolvedField();
+ assert ordinalField != null;
+ AbstractValue ordinalState = objectState.getAbstractFieldValue(ordinalField);
+ assert ordinalState != null;
+ assert ordinalState.isSingleNumberValue();
+ assert ordinalState.asSingleNumberValue().getIntValue() == ordinal;
+ return true;
+ }
+
+ private SingleFieldValue computeSingleEnumFieldValueForInstance(Value value) {
+ if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
new file mode 100644
index 0000000..f1ec89c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
@@ -0,0 +1,82 @@
+// 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.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class EnumValuesObjectState extends ObjectState {
+
+ private final ObjectState[] state;
+
+ public EnumValuesObjectState(ObjectState[] state) {
+ assert state.length > 0;
+ assert Arrays.stream(state).noneMatch(Objects::isNull);
+ this.state = state;
+ }
+
+ @Override
+ public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ return UnknownValue.getInstance();
+ }
+
+ public ObjectState getObjectStateForOrdinal(int ordinal) {
+ if (ordinal < 0 || ordinal >= state.length) {
+ return ObjectState.empty();
+ }
+ return state[ordinal];
+ }
+
+ @Override
+ public boolean isEnumValuesObjectState() {
+ return true;
+ }
+
+ @Override
+ public EnumValuesObjectState asEnumValuesObjectState() {
+ return this;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ // Non-empty by construction.
+ return false;
+ }
+
+ @Override
+ public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+ ObjectState[] newState = new ObjectState[state.length];
+ for (int i = 0; i < state.length; i++) {
+ newState[i] = state[i].rewrittenWithLens(appView, lens);
+ }
+ return new EnumValuesObjectState(newState);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (getClass() != o.getClass()) {
+ return false;
+ }
+ EnumValuesObjectState other = (EnumValuesObjectState) o;
+ if (state.length != other.state.length) {
+ return false;
+ }
+ for (int i = 0; i < state.length; i++) {
+ if (!state[i].equals(other.state[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(state);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
index b724858..4e6d970 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -35,6 +35,14 @@
@Override
public abstract int hashCode();
+ public boolean isEnumValuesObjectState() {
+ return false;
+ }
+
+ public EnumValuesObjectState asEnumValuesObjectState() {
+ return null;
+ }
+
public static class Builder {
private final Map<DexField, AbstractValue> state = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 0cb75c3..98a7fa4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -50,6 +50,10 @@
return value;
}
+ public int getIntValue() {
+ return (int) value;
+ }
+
@Override
public boolean equals(Object o) {
return this == o;
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 dac0f6d..8fafcb7 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
@@ -994,33 +994,32 @@
private EnumInstanceFieldData computeEnumFieldData(
DexField instanceField, DexProgramClass enumClass) {
- EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap =
- appView.appInfo().getEnumValueInfoMap(enumClass.type);
DexEncodedField encodedInstanceField =
appView.appInfo().resolveFieldOn(enumClass, instanceField).getResolvedField();
assert encodedInstanceField != null;
boolean canBeOrdinal = instanceField.type.isIntType();
Map<DexField, AbstractValue> data = new IdentityHashMap<>();
+ EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap =
+ appView.appInfo().getEnumValueInfoMap(enumClass.type);
for (DexField staticField : enumValueInfoMap.enumValues()) {
- DexEncodedField encodedStaticField = enumClass.lookupStaticField(staticField);
- AbstractValue enumInstanceValue = encodedStaticField.getOptimizationInfo().getAbstractValue();
- // TODO(b/155368026): Support other constants than Number and String, so far we support only
- // these two because they're the most common and we don't have to deal with accessibility.
- if (!enumInstanceValue.isSingleFieldValue()) {
- return EnumInstanceFieldUnknownData.getInstance();
- }
- ObjectState enumInstanceState = enumInstanceValue.asSingleFieldValue().getState();
- AbstractValue fieldValue = enumInstanceState.getAbstractFieldValue(encodedInstanceField);
- if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
- return EnumInstanceFieldUnknownData.getInstance();
- }
- data.put(staticField, fieldValue);
- if (canBeOrdinal) {
- int ordinalValue = enumValueInfoMap.getEnumValueInfo(staticField).ordinal;
- assert fieldValue.isSingleNumberValue();
- int computedValue = (int) fieldValue.asSingleNumberValue().getValue();
- if (computedValue != ordinalValue) {
- canBeOrdinal = false;
+ ObjectState enumInstanceState =
+ computeEnumInstanceObjectState(enumClass, staticField, enumValueInfoMap);
+ if (enumInstanceState == null) {
+ // The enum instance is effectively unused. No need to generate anything for it, the path
+ // will never be taken.
+ } else {
+ AbstractValue fieldValue = enumInstanceState.getAbstractFieldValue(encodedInstanceField);
+ if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
+ return EnumInstanceFieldUnknownData.getInstance();
+ }
+ data.put(staticField, fieldValue);
+ if (canBeOrdinal) {
+ int ordinalValue = enumValueInfoMap.getEnumValueInfo(staticField).ordinal;
+ assert fieldValue.isSingleNumberValue();
+ int computedValue = fieldValue.asSingleNumberValue().getIntValue();
+ if (computedValue != ordinalValue) {
+ canBeOrdinal = false;
+ }
}
}
}
@@ -1030,6 +1029,52 @@
return new EnumInstanceFieldMappingData(data);
}
+ // We need to access the enum instance object state to figure out if it contains known constant
+ // field values. The enum instance may be accessed in two ways, directly through the enum
+ // static field, or through the enum $VALUES field. If none of them are kept, the instance is
+ // effectively unused. The object state may be stored in the enum static field optimization
+ // info, if kept, or in the $VALUES optimization info, if kept.
+ // If the enum instance is unused, this method answers null.
+ private ObjectState computeEnumInstanceObjectState(
+ DexProgramClass enumClass,
+ DexField staticField,
+ EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap) {
+ // Attempt 1: Get object state from the instance field's optimization info.
+ DexEncodedField encodedStaticField = enumClass.lookupStaticField(staticField);
+ AbstractValue enumInstanceValue = encodedStaticField.getOptimizationInfo().getAbstractValue();
+ if (enumInstanceValue.isSingleFieldValue()) {
+ return enumInstanceValue.asSingleFieldValue().getState();
+ }
+ if (enumInstanceValue.isUnknown()) {
+ return ObjectState.empty();
+ }
+ assert enumInstanceValue.isZero();
+
+ // Attempt 2: Get object state from the values field's optimization info.
+ DexEncodedField valuesField =
+ enumClass.lookupStaticField(
+ factory.createField(
+ enumClass.type,
+ factory.createArrayType(1, enumClass.type),
+ factory.enumValuesFieldName));
+ AbstractValue valuesValue = valuesField.getOptimizationInfo().getAbstractValue();
+ if (valuesValue.isZero()) {
+ // Unused enum instance.
+ return null;
+ }
+ if (valuesValue.isUnknown()) {
+ return ObjectState.empty();
+ }
+ assert valuesValue.isSingleFieldValue();
+ ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
+ if (valuesState.isEnumValuesObjectState()) {
+ return valuesState
+ .asEnumValuesObjectState()
+ .getObjectStateForOrdinal(enumValueInfoMap.getEnumValueInfo(staticField).ordinal);
+ }
+ return ObjectState.empty();
+ }
+
private boolean isFirstInstructionAfterArguments(InvokeMethod invokeMethod, IRCode code) {
BasicBlock basicBlock = code.entryBlock();
for (Instruction instruction : basicBlock.getInstructions()) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index f2f0a06..8b35793 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -46,6 +46,7 @@
// TODO(b/155368026): Support fields and const class fields.
// Move this to something similar than SingleValue#createMaterializingInstruction
if (value.isSingleStringValue()) {
+ assert returnType == appView.dexItemFactory().stringType;
instructions.add(new CfConstString(value.asSingleStringValue().getDexString()));
} else if (value.isSingleNumberValue()) {
instructions.add(
@@ -88,14 +89,16 @@
// if (i == 2) { return 20;}
enumValueInfoMap.forEach(
(field, enumValueInfo) -> {
- CfLabel dest = new CfLabel();
- instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
- instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
- instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest));
AbstractValue value = fieldDataMap.getData(field);
- addCfInstructionsForAbstractValue(instructions, value, returnType);
- instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
- instructions.add(dest);
+ if (value != null) {
+ CfLabel dest = new CfLabel();
+ instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
+ instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
+ instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest));
+ addCfInstructionsForAbstractValue(instructions, value, returnType);
+ instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
+ instructions.add(dest);
+ }
});
// throw null;
@@ -155,7 +158,7 @@
CfLabel dest = new CfLabel();
instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
AbstractValue value = fieldDataMap.getData(field);
- addCfInstructionsForAbstractValue(instructions, value, factory.intType);
+ addCfInstructionsForAbstractValue(instructions, value, factory.stringType);
instructions.add(
new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.equals, false));
instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
new file mode 100644
index 0000000..ddc0806
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EmptyEnumUnboxingTest.java
@@ -0,0 +1,75 @@
+// 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 EmptyEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EmptyEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EmptyEnumUnboxingTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m ->
+ // TODO(b/166532373): Unbox enum with no cases.
+ assertEnumIsBoxed(MyEnum.class, Main.class.getSimpleName(), m))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ ;
+
+ @NeverInline
+ static void print() {
+ System.out.println("PRINT");
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ MyEnum.print();
+ System.out.println("PRINT");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
index 0c51e9d..590cc7c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -52,7 +52,7 @@
.addKeepRules(enumKeepRules.getKeepRules())
.addOptionsModification(
options -> {
- options.enableEnumUnboxing = true;
+ assert options.enableEnumUnboxing;
options.testing.enableEnumUnboxingDebugLogs = true;
})
.allowDiagnosticMessages()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java
new file mode 100644
index 0000000..a6b7027
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/UnusedCaseEnumUnboxingTest.java
@@ -0,0 +1,98 @@
+// 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 com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedCaseEnumUnboxingTest extends EnumUnboxingTestBase {
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public UnusedCaseEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(UnusedCaseEnumUnboxingTest.class)
+ .addKeepMainRule(Main.class)
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertFieldsRemoved)
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsUnboxed(MyEnum.class, Main.class.getSimpleName(), m))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ private void assertFieldsRemoved(CodeInspector codeInspector) {
+ codeInspector.clazz(Main.class);
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ USED1("used1"),
+ UNUSED1("unused1"),
+ USED2("used2"),
+ UNUSED2("unused2");
+
+ final String myField;
+
+ MyEnum(String data) {
+ this.myField = data;
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ printEnumField(MyEnum.USED1);
+ System.out.println("used1");
+ printEnumField(MyEnum.USED2);
+ System.out.println("used2");
+
+ printOrdinal(MyEnum.USED1);
+ System.out.println("0");
+ printOrdinal(MyEnum.USED2);
+ System.out.println("2");
+ }
+
+ @NeverInline
+ private static void printEnumField(MyEnum e) {
+ System.out.println(e.myField);
+ }
+
+ @NeverInline
+ private static void printOrdinal(MyEnum e) {
+ System.out.println(e.ordinal());
+ }
+ }
+}