Enum unboxing: postpone field analysis

Bug: 155368026
Change-Id: Ifb91956225fcfb7293476fa0fb7323b685c3633b
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 b838429..4c10624 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
@@ -739,7 +739,6 @@
       postMethodProcessorBuilder.put(inliner);
     }
     if (enumUnboxer != null) {
-      enumUnboxer.finishAnalysis();
       enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
     }
     if (!options.debug) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldDataMap.java
index 0c9c30c..f02e488 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldDataMap.java
@@ -8,10 +8,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
 
 public class EnumInstanceFieldDataMap {
   private final ImmutableMap<DexType, ImmutableMap<DexField, EnumInstanceFieldKnownData>>
@@ -28,42 +24,4 @@
     assert instanceFieldMap.get(enumType).containsKey(enumInstanceField);
     return instanceFieldMap.get(enumType).get(enumInstanceField);
   }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private final Map<DexType, Map<DexField, EnumInstanceFieldData>> instanceFieldMap =
-        new ConcurrentHashMap<>();
-
-    public EnumInstanceFieldData computeInstanceFieldDataIfAbsent(
-        DexType enumType,
-        DexField enumInstanceField,
-        Function<DexField, EnumInstanceFieldData> enumFieldDataSupplier) {
-      Map<DexField, EnumInstanceFieldData> typeMap =
-          instanceFieldMap.computeIfAbsent(enumType, ignored -> new ConcurrentHashMap<>());
-      return typeMap.computeIfAbsent(enumInstanceField, enumFieldDataSupplier);
-    }
-
-    public EnumInstanceFieldDataMap build(ImmutableSet<DexType> enumsToUnbox) {
-      ImmutableMap.Builder<DexType, ImmutableMap<DexField, EnumInstanceFieldKnownData>> builder =
-          ImmutableMap.builder();
-      instanceFieldMap.forEach(
-          (enumType, typeMap) -> {
-            ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> typeBuilder =
-                ImmutableMap.builder();
-            typeMap.forEach(
-                (field, data) -> {
-                  if (enumsToUnbox.contains(enumType)) {
-                    assert data.isKnown();
-                    typeBuilder.put(field, data.asEnumFieldKnownData());
-                  }
-                });
-            builder.put(enumType, typeBuilder.build());
-          });
-      instanceFieldMap.clear();
-      return new EnumInstanceFieldDataMap(builder.build());
-    }
-  }
 }
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 62c1087..dac0f6d 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
@@ -66,6 +66,7 @@
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
@@ -104,8 +105,8 @@
   // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
   // enum if the optimization eventually decides to unbox it.
   private final Map<DexType, ProgramMethodSet> enumsUnboxingCandidates;
-  private final EnumInstanceFieldDataMap.Builder enumInstanceFieldData =
-      EnumInstanceFieldDataMap.builder();
+  private final Map<DexType, Set<DexField>> requiredEnumInstanceFieldData =
+      new ConcurrentHashMap<>();
   private final Set<DexType> enumsToUnboxWithPackageRequirement = Sets.newIdentityHashSet();
 
   private EnumUnboxingRewriter enumUnboxerRewriter;
@@ -127,6 +128,12 @@
     enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
   }
 
+  private void requiredEnumInstanceFieldData(DexType enumType, DexField field) {
+    requiredEnumInstanceFieldData
+        .computeIfAbsent(enumType, ignored -> Sets.newConcurrentHashSet())
+        .add(field);
+  }
+
   private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
     assert enumClass.isEnum();
     reportFailure(enumClass.type, reason);
@@ -299,15 +306,7 @@
     }
     // The name data is required for the correct mapping from the enum name to the ordinal in the
     // valueOf utility method.
-    EnumInstanceFieldData nameData =
-        enumInstanceFieldData.computeInstanceFieldDataIfAbsent(
-            enumType,
-            factory.enumMembers.nameField,
-            field -> computeEnumFieldData(field, enumClass));
-    if (nameData.isUnknown()) {
-      markEnumAsUnboxable(Reason.MISSING_NAME_DATA, enumClass);
-      return;
-    }
+    requiredEnumInstanceFieldData(enumType, factory.enumMembers.nameField);
     eligibleEnums.add(enumType);
   }
 
@@ -365,6 +364,7 @@
       ExecutorService executorService,
       OptimizationFeedbackDelayed feedback)
       throws ExecutionException {
+    EnumInstanceFieldDataMap enumInstanceFieldDataMap = finishAnalysis();
     // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
     if (enumsUnboxingCandidates.isEmpty()) {
       return;
@@ -372,8 +372,7 @@
     ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
     // Update keep info on any of the enum methods of the removed classes.
     updatePinnedItems(enumsToUnbox);
-    enumUnboxerRewriter =
-        new EnumUnboxingRewriter(appView, enumsToUnbox, enumInstanceFieldData.build(enumsToUnbox));
+    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox, enumInstanceFieldDataMap);
     DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
     Map<DexType, DexType> newMethodLocation = synthesizeUnboxedEnumsMethodsLocations(appBuilder);
     NestedGraphLens enumUnboxingLens =
@@ -496,12 +495,38 @@
             });
   }
 
-  public void finishAnalysis() {
+  public EnumInstanceFieldDataMap finishAnalysis() {
     analyzeInitializers();
     analyzeAccessibility();
+    EnumInstanceFieldDataMap enumInstanceFieldDataMap = analyzeFields();
     if (debugLogEnabled) {
       reportEnumsAnalysis();
     }
+    return enumInstanceFieldDataMap;
+  }
+
+  private EnumInstanceFieldDataMap analyzeFields() {
+    ImmutableMap.Builder<DexType, ImmutableMap<DexField, EnumInstanceFieldKnownData>> builder =
+        ImmutableMap.builder();
+    requiredEnumInstanceFieldData.forEach(
+        (enumType, fields) -> {
+          ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> typeBuilder =
+              ImmutableMap.builder();
+          if (enumsUnboxingCandidates.containsKey(enumType)) {
+            DexProgramClass enumClass = appView.definitionFor(enumType).asProgramClass();
+            assert enumClass != null;
+            for (DexField field : fields) {
+              EnumInstanceFieldData enumInstanceFieldData = computeEnumFieldData(field, enumClass);
+              if (enumInstanceFieldData.isUnknown()) {
+                markEnumAsUnboxable(Reason.MISSING_INSTANCE_FIELD_DATA, enumClass);
+                return;
+              }
+              typeBuilder.put(field, enumInstanceFieldData.asEnumFieldKnownData());
+            }
+            builder.put(enumType, typeBuilder.build());
+          }
+        });
+    return new EnumInstanceFieldDataMap(builder.build());
   }
 
   private void analyzeAccessibility() {
@@ -839,15 +864,7 @@
       } else if (singleTarget == factory.enumMembers.nameMethod
           || singleTarget == factory.enumMembers.toString) {
         assert invokeMethod.asInvokeMethodWithReceiver().getReceiver() == enumValue;
-        EnumInstanceFieldData nameData =
-            enumInstanceFieldData.computeInstanceFieldDataIfAbsent(
-                enumClass.type,
-                factory.enumMembers.nameField,
-                field -> computeEnumFieldData(field, enumClass));
-        if (nameData.isUnknown()) {
-          computeEnumFieldData(factory.enumMembers.nameField, enumClass);
-          return Reason.MISSING_NAME_DATA;
-        }
+        requiredEnumInstanceFieldData(enumClass.type, factory.enumMembers.nameField);
         return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMembers.ordinalMethod) {
         return Reason.ELIGIBLE;
@@ -892,12 +909,7 @@
       InstanceGet instanceGet = instruction.asInstanceGet();
       assert instanceGet.getField().holder == enumClass.type;
       DexField field = instanceGet.getField();
-      EnumInstanceFieldData enumFieldData =
-          enumInstanceFieldData.computeInstanceFieldDataIfAbsent(
-              enumClass.type, field, theField -> computeEnumFieldData(theField, enumClass));
-      if (enumFieldData.isUnknown()) {
-        return Reason.INVALID_FIELD_READ;
-      }
+      requiredEnumInstanceFieldData(enumClass.type, field);
       return Reason.ELIGIBLE;
     }
 
@@ -1113,7 +1125,7 @@
     COMPARE_TO_INVOKE,
     UNSUPPORTED_LIBRARY_CALL,
     MISSING_INFO_MAP,
-    MISSING_NAME_DATA,
+    MISSING_INSTANCE_FIELD_DATA,
     INVALID_FIELD_READ,
     INVALID_FIELD_PUT,
     INVALID_ARRAY_PUT,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index c2d139b..54d9aaf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -86,6 +86,7 @@
     this.factory = appView.dexItemFactory();
     EnumValueInfoMapCollection.Builder builder = EnumValueInfoMapCollection.builder();
     for (DexType toUnbox : enumsToUnbox) {
+      assert appView.appInfo().withLiveness().getEnumValueInfoMap(toUnbox) != null;
       builder.put(toUnbox, appView.appInfo().withLiveness().getEnumValueInfoMap(toUnbox));
     }
     this.enumsToUnbox = builder.build();
@@ -460,7 +461,7 @@
                 appView,
                 factory.enumUnboxingUtilityType,
                 field.type,
-                enumsToUnbox.getEnumValueInfoMap(field.holder),
+                enumsToUnbox.getEnumValueInfoMap(enumType),
                 unboxedEnumsInstanceFieldData
                     .getInstanceFieldData(enumType, field)
                     .asEnumFieldMappingData())
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 5ccd903..0c51e9d 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -21,16 +21,21 @@
 
   private final TestParameters parameters;
   private final boolean missingStaticMethods;
+  private final EnumKeepRules enumKeepRules;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameterized.Parameters(name = "{0} missing: {1} keep: {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        BooleanUtils.values(),
+        getAllEnumKeepRules());
   }
 
-  public EnumUnboxingB160535628Test(TestParameters parameters, boolean missingStaticMethods) {
+  public EnumUnboxingB160535628Test(
+      TestParameters parameters, boolean missingStaticMethods, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.missingStaticMethods = missingStaticMethods;
+    this.enumKeepRules = enumKeepRules;
   }
 
   @Test
@@ -44,6 +49,7 @@
             .addProgramClasses(ProgramValueOf.class, ProgramStaticMethod.class)
             .addProgramFiles(javaLibShrunk)
             .addKeepMainRules(ProgramValueOf.class, ProgramStaticMethod.class)
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(
                 options -> {
                   options.enableEnumUnboxing = true;
@@ -58,18 +64,23 @@
                 this::assertEnumUnboxedIfStaticMethodsPresent);
     if (missingStaticMethods) {
       compile
-          .run(parameters.getRuntime(), ProgramValueOf.class)
-          .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
-          .assertFailureWithErrorThatMatches(containsString("valueOf"));
-      compile
           .run(parameters.getRuntime(), ProgramStaticMethod.class)
           .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
           .assertFailureWithErrorThatMatches(containsString("staticMethod"));
     } else {
-      compile.run(parameters.getRuntime(), ProgramValueOf.class).assertSuccessWithOutputLines("0");
       compile
           .run(parameters.getRuntime(), ProgramStaticMethod.class)
-          .assertSuccessWithOutputLines("42");
+          .assertSuccessWithOutputLines("0", "42");
+    }
+    if (missingStaticMethods && enumKeepRules == EnumKeepRules.NONE) {
+      compile
+          .run(parameters.getRuntime(), ProgramValueOf.class)
+          .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
+          .assertFailureWithErrorThatMatches(containsString("valueOf"));
+    } else {
+      compile
+          .run(parameters.getRuntime(), ProgramValueOf.class)
+          .assertSuccessWithOutputLines("0", "0");
     }
   }
 
@@ -77,6 +88,7 @@
     return testForR8(Backend.CF)
         .addProgramClasses(Lib.class, Lib.LibEnumStaticMethod.class, Lib.LibEnum.class)
         .addKeepRules("-keep enum * { <fields>; }")
+        .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepRules(missingStaticMethods ? "" : "-keep enum * { static <methods>; }")
         .addOptionsModification(
             options -> {
@@ -130,13 +142,15 @@
   public static class ProgramValueOf {
 
     public static void main(String[] args) {
-      System.out.println(Lib.LibEnumStaticMethod.valueOf(Lib.LibEnum.A.name()).ordinal());
+      System.out.println(Lib.LibEnum.A.ordinal());
+      System.out.println(Lib.LibEnum.valueOf(Lib.LibEnum.A.name()).ordinal());
     }
   }
 
   public static class ProgramStaticMethod {
 
     public static void main(String[] args) {
+      System.out.println(Lib.LibEnumStaticMethod.A.ordinal());
       System.out.println(Lib.LibEnumStaticMethod.staticMethod());
     }
   }