Redundant field elimination: large enum analysis

Bug: 169050248
Change-Id: I27610ca72f5c1195f1f83193013a2aa27e5c49ca
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 03455fb..281215f 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
@@ -316,9 +316,11 @@
       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.
+      // Enums with many instance rely on staticGets to set the $VALUES data instead of directly
+      // keeping the values in registers, due to the max capacity of the redundant field load
+      // elimination. The capacity has already been increased, so that this case is extremely
+      // uncommon (very large enums).
+      // TODO(b/169050248): We could consider analysing these to answer the object state here.
       return ObjectState.empty();
     }
     return ObjectState.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index cabff87..10b8b25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -54,11 +54,12 @@
 public class RedundantFieldLoadElimination {
 
   private static final int MAX_CAPACITY = 10000;
-  private static final int MAX_CAPACITY_PER_BLOCK = 50;
+  private static final int MIN_CAPACITY_PER_BLOCK = 50;
 
   private final AppView<?> appView;
   private final ProgramMethod method;
   private final IRCode code;
+  private final int maxCapacityPerBlock;
 
   // Values that may require type propagation.
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -74,6 +75,7 @@
     this.appView = appView;
     this.method = code.context();
     this.code = code;
+    this.maxCapacityPerBlock = Math.max(MIN_CAPACITY_PER_BLOCK, MAX_CAPACITY / code.blocks.size());
   }
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
@@ -185,7 +187,7 @@
         // Already visited.
         continue;
       }
-      activeState = activeStates.computeActiveStateOnBlockEntry(head);
+      activeState = activeStates.computeActiveStateOnBlockEntry(head, maxCapacityPerBlock);
       activeStates.removeDeadBlockExitStates(head, pendingNormalSuccessors);
       BasicBlock block = head;
       BasicBlock end = null;
@@ -444,19 +446,20 @@
 
     private int capacity = MAX_CAPACITY;
 
-    BlockState computeActiveStateOnBlockEntry(BasicBlock block) {
+    BlockState computeActiveStateOnBlockEntry(BasicBlock block, int maxCapacityPerBlock) {
       if (block.isEntry()) {
-        return new BlockState();
+        return new BlockState(maxCapacityPerBlock);
       }
       List<BasicBlock> predecessors = block.getPredecessors();
       Iterator<BasicBlock> predecessorIterator = predecessors.iterator();
-      BlockState state = new BlockState(activeStateAtExit.get(predecessorIterator.next()));
+      BlockState state =
+          new BlockState(maxCapacityPerBlock, activeStateAtExit.get(predecessorIterator.next()));
       while (predecessorIterator.hasNext()) {
         BasicBlock predecessor = predecessorIterator.next();
         BlockState predecessorExitState = activeStateAtExit.get(predecessor);
         if (predecessorExitState == null) {
           // Not processed yet.
-          return new BlockState();
+          return new BlockState(maxCapacityPerBlock);
         }
         state.intersect(predecessorExitState);
       }
@@ -479,7 +482,7 @@
 
     private void ensureCapacity(BlockState state) {
       int stateSize = state.size();
-      assert stateSize <= MAX_CAPACITY_PER_BLOCK;
+      assert stateSize <= state.maxCapacity;
       int numberOfItemsToRemove = stateSize - capacity;
       if (numberOfItemsToRemove <= 0) {
         return;
@@ -568,9 +571,14 @@
 
     private LinkedHashMap<DexField, FieldValue> nonFinalStaticFieldValues;
 
-    public BlockState() {}
+    private final int maxCapacity;
 
-    public BlockState(BlockState state) {
+    public BlockState(int maxCapacity) {
+      this.maxCapacity = maxCapacity;
+    }
+
+    public BlockState(int maxCapacity, BlockState state) {
+      this(maxCapacity);
       if (state != null) {
         if (state.finalInstanceFieldValues != null && !state.finalInstanceFieldValues.isEmpty()) {
           finalInstanceFieldValues = new LinkedHashMap<>();
@@ -606,8 +614,8 @@
 
     public void ensureCapacityForNewElement() {
       int size = size();
-      assert size <= MAX_CAPACITY_PER_BLOCK;
-      if (size == MAX_CAPACITY_PER_BLOCK) {
+      assert size <= maxCapacity;
+      if (size == maxCapacity) {
         reduceSize(1);
       }
     }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java
new file mode 100644
index 0000000..66c861a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java
@@ -0,0 +1,246 @@
+// 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.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+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 LargeEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "B1falsetruefalse1",
+          "B2falsetruefalse2",
+          "B3falsetruefalse3",
+          "B4falsetruefalse4",
+          "B5falsetruefalse5",
+          "B6falsetruefalse6",
+          "B7falsetruefalse7",
+          "B8falsetruefalse8",
+          "B9falsetruefalse9",
+          "B10falsetruefalse10",
+          "B11falsetruefalse11",
+          "B12falsetruefalse12",
+          "B13falsetruefalse13",
+          "E1falsefalsetrue14",
+          "E2falsefalsetrue15",
+          "E3falsefalsetrue16",
+          "E4falsefalsetrue17",
+          "E5falsefalsetrue18",
+          "E6falsefalsetrue19",
+          "E7falsefalsetrue20",
+          "E8falsefalsetrue21",
+          "E9falsefalsetrue22",
+          "E10falsefalsetrue23",
+          "E11falsefalsetrue24",
+          "E12falsefalsetrue25",
+          "E13falsefalsetrue26",
+          "E14falsefalsetrue27",
+          "E15falsefalsetrue28",
+          "G1falsefalsefalse29",
+          "G2falsefalsefalse30",
+          "G3falsefalsefalse31",
+          "G4falsefalsefalse32",
+          "G5falsefalsefalse33",
+          "G6falsefalsefalse34",
+          "G7falsefalsefalse35",
+          "G8falsefalsefalse36",
+          "G9falsefalsefalse37",
+          "I1truefalsefalse38",
+          "I2truefalsefalse39",
+          "I3truefalsefalse40",
+          "I4truefalsefalse41",
+          "I5truefalsefalse42",
+          "I6truefalsefalse43",
+          "I7truefalsefalse44",
+          "I8truefalsefalse45",
+          "I9truefalsefalse46",
+          "I10truefalsefalse47",
+          "I11truefalsefalse48",
+          "I12truefalsefalse49",
+          "I13truefalsefalse50",
+          "I14truefalsefalse51",
+          "I15truefalsefalse52",
+          "I16truefalsefalse53",
+          "J1falsefalsefalse54",
+          "J2falsefalsefalse55");
+
+  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 LargeEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> mainClass = Main.class;
+    testForR8(parameters.getBackend())
+        .addProgramClasses(mainClass, LargeEnum.class)
+        .addKeepMainRule(mainClass)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .allowDiagnosticInfoMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            m -> assertEnumIsUnboxed(LargeEnum.class, mainClass.getSimpleName(), m))
+        .run(parameters.getRuntime(), mainClass)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @NeverClassInline
+  enum LargeEnum {
+    B1("1"),
+    B2("2"),
+    B3("3"),
+    B4("4"),
+    B5("5"),
+    B6("6"),
+    B7("7"),
+    B8("8"),
+    B9("9"),
+    B10("10"),
+    B11("11"),
+    B12("12"),
+    B13("13"),
+
+    E1("14"),
+    E2("15"),
+    E3("16"),
+    E4("17"),
+    E5("18"),
+    E6("19"),
+    E7("20"),
+    E8("21"),
+    E9("22"),
+    E10("23"),
+    E11("24"),
+    E12("25"),
+    E13("26"),
+    E14("27"),
+    E15("28"),
+
+    G1("29"),
+    G2("30"),
+    G3("31"),
+    G4("32"),
+    G5("33"),
+    G6("34"),
+    G7("35"),
+    G8("36"),
+    G9("37"),
+
+    I1("38"),
+    I2("39"),
+    I3("40"),
+    I4("41"),
+    I5("42"),
+    I6("43"),
+    I7("44"),
+    I8("45"),
+    I9("46"),
+    I10("47"),
+    I11("48"),
+    I12("49"),
+    I13("50"),
+    I14("51"),
+    I15("52"),
+    I16("53"),
+
+    J1("54"),
+    J2("55");
+
+    private final String num;
+
+    LargeEnum(String num) {
+      this.num = num;
+    }
+
+    public String getNum() {
+      return num;
+    }
+
+    public boolean isI() {
+      return this == I1
+          || this == I2
+          || this == I3
+          || this == I4
+          || this == I5
+          || this == I6
+          || this == I7
+          || this == I8
+          || this == I9
+          || this == I10
+          || this == I11
+          || this == I12
+          || this == I13
+          || this == I14
+          || this == I15
+          || this == I16;
+    }
+
+    public boolean isB() {
+      return this == B1
+          || this == B2
+          || this == B3
+          || this == B4
+          || this == B5
+          || this == B6
+          || this == B7
+          || this == B8
+          || this == B9
+          || this == B10
+          || this == B11
+          || this == B12
+          || this == B13;
+    }
+
+    public boolean isE() {
+      return this == E1
+          || this == E2
+          || this == E3
+          || this == E4
+          || this == E5
+          || this == E6
+          || this == E7
+          || this == E8
+          || this == E9
+          || this == E10
+          || this == E11
+          || this == E12
+          || this == E13
+          || this == E14
+          || this == E15;
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      for (LargeEnum value : LargeEnum.values()) {
+        System.out.println(
+            value.toString() + value.isI() + value.isB() + value.isE() + value.getNum());
+      }
+    }
+  }
+}