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());
+    }
+  }
+}