Move enum optimizations to their own package

- Extract EnumValueOptimizations from
  SwitchUtils and CodeRewriter
- Move EnumUnboxing and EnumValue
  optimizations to their own package
- Enable EnumValueInfoCollector if any
  of EnumUnboxing or Enumvalue optimizations
  are enabled

Bug: 147860220
Change-Id: I7001886ac5cd6c3f263df9b0828c7016ba21095a
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ecf6749..568e6ca 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -35,7 +35,6 @@
 import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
-import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
 import com.android.tools.r8.ir.optimize.SwitchMapCollector;
@@ -43,6 +42,7 @@
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
+import com.android.tools.r8.ir.optimize.enums.EnumInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.Kotlin;
@@ -520,6 +520,8 @@
       // Collect switch maps and ordinals maps.
       if (options.enableEnumValueOptimization) {
         appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness).run());
+      }
+      if (options.enableEnumValueOptimization || options.enableEnumUnboxing) {
         appViewWithLiveness.setAppInfo(new EnumInfoMapCollector(appViewWithLiveness).run());
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 37a8dc6..717df30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -21,9 +21,9 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
 import com.android.tools.r8.utils.PredicateSet;
@@ -91,7 +91,7 @@
   public void inlineCallsToDynamicMethod(
       DexEncodedMethod method,
       IRCode code,
-      CodeRewriter codeRewriter,
+      EnumValueOptimizer enumValueOptimizer,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       Inliner inliner) {
@@ -103,8 +103,8 @@
 
     // Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to
     // get rid of the enum switch in dynamicMethod().
-    if (appView.options().enableEnumValueOptimization) {
-      codeRewriter.rewriteConstantEnumMethodCalls(code);
+    if (enumValueOptimizer != null) {
+      enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
     }
   }
 
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 5c76d71..dce9801 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
@@ -59,7 +59,6 @@
 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;
@@ -72,6 +71,8 @@
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -158,6 +159,7 @@
   private final TypeChecker typeChecker;
   private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
   private final ServiceLoaderRewriter serviceLoaderRewriter;
+  private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
 
   // Assumers that will insert Assume instructions.
@@ -247,6 +249,7 @@
       this.stringSwitchRemover = null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
+      this.enumValueOptimizer = null;
       this.enumUnboxer = null;
       return;
     }
@@ -326,6 +329,8 @@
               ? new DesugaredLibraryAPIConverter(
                   appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED)
               : null;
+      this.enumValueOptimizer =
+          options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
       this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
     } else {
       this.classInliner = null;
@@ -350,6 +355,7 @@
               : null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
+      this.enumValueOptimizer = null;
       this.enumUnboxer = null;
     }
     this.stringSwitchRemover =
@@ -1174,10 +1180,10 @@
       timing.end();
     }
 
-    if (options.enableEnumValueOptimization) {
+    if (enumValueOptimizer != null) {
       assert appView.enableWholeProgramOptimizations();
       timing.begin("Remove switch maps");
-      codeRewriter.removeSwitchMaps(code);
+      enumValueOptimizer.removeSwitchMaps(code);
       timing.end();
     }
 
@@ -1251,10 +1257,10 @@
     codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
     timing.end();
 
-    if (options.enableEnumValueOptimization) {
+    if (enumValueOptimizer != null) {
       assert appView.enableWholeProgramOptimizations();
       timing.begin("Rewrite constant enum methods");
-      codeRewriter.rewriteConstantEnumMethodCalls(code);
+      enumValueOptimizer.rewriteConstantEnumMethodCalls(code);
       timing.end();
     }
 
@@ -1365,6 +1371,7 @@
           appView.withLiveness(),
           codeRewriter,
           stringOptimizer,
+          enumValueOptimizer,
           method,
           code,
           feedback,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5b695a7..7f2cee9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 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.DexItemFactory.ThrowableMethods;
 import com.android.tools.r8.graph.DexMethod;
@@ -57,11 +56,9 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -76,9 +73,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
@@ -93,7 +88,6 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
-import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -1043,69 +1037,6 @@
   }
 
   /**
-   * Inline the indirection of switch maps into the switch statement.
-   * <p>
-   * To ensure binary compatibility, javac generated code does not use ordinal values of enums
-   * directly in switch statements but instead generates a companion class that computes a mapping
-   * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can
-   * analyze these maps and inline the indirection into the switch map again.
-   * <p>
-   * In particular, we look for code of the form
-   *
-   * <blockquote><pre>
-   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
-   *   ...
-   * }
-   * </pre></blockquote>
-   */
-  public void removeSwitchMaps(IRCode code) {
-    for (BasicBlock block : code.blocks) {
-      JumpInstruction exit = block.exit();
-      // Pattern match a switch on a switch map as input.
-      if (exit.isIntSwitch()) {
-        IntSwitch switchInsn = exit.asIntSwitch();
-        EnumSwitchInfo info = SwitchUtils.analyzeSwitchOverEnum(switchInsn, appView.withLiveness());
-        if (info != null) {
-          Int2IntMap targetMap = new Int2IntArrayMap();
-          for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
-            assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
-            EnumValueInfo valueInfo =
-                info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
-            targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
-          }
-          int[] keys = targetMap.keySet().toIntArray();
-          Arrays.sort(keys);
-          int[] targets = new int[keys.length];
-          for (int i = 0; i < keys.length; i++) {
-            targets[i] = targetMap.get(keys[i]);
-          }
-
-          IntSwitch newSwitch =
-              new IntSwitch(
-                  info.ordinalInvoke.outValue(),
-                  keys,
-                  targets,
-                  switchInsn.getFallthroughBlockIndex());
-          // Replace the switch itself.
-          exit.replace(newSwitch, code);
-          // If the original input to the switch is now unused, remove it too. It is not dead
-          // as it might have side-effects but we ignore these here.
-          Instruction arrayGet = info.arrayGet;
-          if (!arrayGet.outValue().hasUsers()) {
-            arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
-            arrayGet.getBlock().removeInstruction(arrayGet);
-          }
-          Instruction staticGet = info.staticGet;
-          if (!staticGet.outValue().hasUsers()) {
-            assert staticGet.inValues().isEmpty();
-            staticGet.getBlock().removeInstruction(staticGet);
-          }
-        }
-      }
-    }
-  }
-
-  /**
    * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not
    * rewrite fallthrough targets as that would require block reordering and the transformation only
    * makes sense after SSA destruction where there are no phis.
@@ -2966,80 +2897,6 @@
     return true;
   }
 
-  public void rewriteConstantEnumMethodCalls(IRCode code) {
-    if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
-      return;
-    }
-
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-
-      if (!current.isInvokeMethodWithReceiver()) {
-        continue;
-      }
-      InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
-      DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
-      boolean isOrdinalInvoke = invokedMethod == dexItemFactory.enumMethods.ordinal;
-      boolean isNameInvoke = invokedMethod == dexItemFactory.enumMethods.name;
-      boolean isToStringInvoke = invokedMethod == dexItemFactory.enumMethods.toString;
-      if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
-        continue;
-      }
-
-      Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
-      if (receiver.isPhi()) {
-        continue;
-      }
-      Instruction definition = receiver.getDefinition();
-      if (!definition.isStaticGet()) {
-        continue;
-      }
-      DexField enumField = definition.asStaticGet().getField();
-
-      Map<DexField, EnumValueInfo> valueInfoMap =
-          appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
-      if (valueInfoMap == null) {
-        continue;
-      }
-
-      // The receiver value is identified as being from a constant enum field lookup by the fact
-      // that it is a static-get to a field whose type is the same as the enclosing class (which
-      // is known to be an enum type). An enum may still define a static field using the enum type
-      // so ensure the field is present in the ordinal map for final validation.
-      EnumValueInfo valueInfo = valueInfoMap.get(enumField);
-      if (valueInfo == null) {
-        continue;
-      }
-
-      Value outValue = methodWithReceiver.outValue();
-      if (isOrdinalInvoke) {
-        iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
-      } else if (isNameInvoke) {
-        iterator.replaceCurrentInstruction(
-            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
-      } else {
-        assert isToStringInvoke;
-        DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
-        if (!enumClazz.accessFlags.isFinal()) {
-          continue;
-        }
-        if (appView
-                .appInfo()
-                .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
-                .getSingleTarget()
-                .method
-            != dexItemFactory.enumMethods.toString) {
-          continue;
-        }
-        iterator.replaceCurrentInstruction(
-            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
-      }
-    }
-
-    assert code.isConsistentSSA();
-  }
-
   public void rewriteKnownArrayLengthCalls(IRCode code) {
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
deleted file mode 100644
index 0dd0bcf..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2017, 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.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.ArrayGet;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import java.util.Map;
-
-public class SwitchUtils {
-
-  public static final class EnumSwitchInfo {
-    public final DexType enumClass;
-    public final Instruction ordinalInvoke;
-    public final Instruction arrayGet;
-    public final Instruction staticGet;
-    public final Int2ReferenceMap<DexField> indexMap;
-    public final Map<DexField, EnumValueInfo> valueInfoMap;
-
-    private EnumSwitchInfo(DexType enumClass,
-        Instruction ordinalInvoke,
-        Instruction arrayGet, Instruction staticGet,
-        Int2ReferenceMap<DexField> indexMap,
-        Map<DexField, EnumValueInfo> valueInfoMap) {
-      this.enumClass = enumClass;
-      this.ordinalInvoke = ordinalInvoke;
-      this.arrayGet = arrayGet;
-      this.staticGet = staticGet;
-      this.indexMap = indexMap;
-      this.valueInfoMap = valueInfoMap;
-    }
-  }
-
-  /**
-   * Looks for a switch statement over the enum companion class of the form
-   *
-   * <blockquote>
-   *
-   * <pre>
-   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
-   *   ...
-   * }
-   * </pre>
-   *
-   * </blockquote>
-   *
-   * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
-   * and {@link SwitchMapCollector} for details.
-   */
-  public static EnumSwitchInfo analyzeSwitchOverEnum(
-      Instruction switchInsn, AppView<AppInfoWithLiveness> appView) {
-    AppInfoWithLiveness appInfo = appView.appInfo();
-    Instruction input = switchInsn.inValues().get(0).definition;
-    if (input == null || !input.isArrayGet()) {
-      return null;
-    }
-    ArrayGet arrayGet = input.asArrayGet();
-    Instruction index = arrayGet.index().definition;
-    if (index == null || !index.isInvokeVirtual()) {
-      return null;
-    }
-    InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
-    DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
-    DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
-    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
-    // After member rebinding, enumClass will be the actual java.lang.Enum class.
-    if (enumClass == null
-        || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
-        || ordinalMethod.name != dexItemFactory.ordinalMethodName
-        || ordinalMethod.proto.returnType != dexItemFactory.intType
-        || !ordinalMethod.proto.parameters.isEmpty()) {
-      return null;
-    }
-    Instruction array = arrayGet.array().definition;
-    if (array == null || !array.isStaticGet()) {
-      return null;
-    }
-    StaticGet staticGet = array.asStaticGet();
-    Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
-    if (indexMap == null || indexMap.isEmpty()) {
-      return null;
-    }
-    // Due to member rebinding, only the fields are certain to provide the actual enums
-    // class.
-    DexType enumType = indexMap.values().iterator().next().holder;
-    Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
-    if (valueInfoMap == null) {
-      return null;
-    }
-    return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap,
-        valueInfoMap);
-  }
-
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index c4a761b..b20eba1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
+import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -164,6 +165,7 @@
       AppView<AppInfoWithLiveness> appView,
       CodeRewriter codeRewriter,
       StringOptimizer stringOptimizer,
+      EnumValueOptimizer enumValueOptimizer,
       DexEncodedMethod method,
       IRCode code,
       OptimizationFeedback feedback,
@@ -282,7 +284,7 @@
       appView.withGeneratedMessageLiteBuilderShrinker(
           shrinker ->
               shrinker.inlineCallsToDynamicMethod(
-                  method, code, codeRewriter, feedback, methodProcessor, inliner));
+                  method, code, enumValueOptimizer, feedback, methodProcessor, inliner));
     }
 
     if (anyInlinedMethods) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
index 2472589..e0991e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInfoMapCollector.java
@@ -1,7 +1,7 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// 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;
+package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
similarity index 99%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c38b7b0..5d737b5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
similarity index 97%
rename from src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
rename to src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 3fb84e1..7b207c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -10,7 +10,7 @@
 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.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
new file mode 100644
index 0000000..c8122470
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -0,0 +1,268 @@
+// 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.enums;
+
+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.DexType;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.SwitchMapCollector;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import java.util.Arrays;
+import java.util.Map;
+
+public class EnumValueOptimizer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory factory;
+
+  public EnumValueOptimizer(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  public void rewriteConstantEnumMethodCalls(IRCode code) {
+    if (!code.metadata().mayHaveInvokeMethodWithReceiver()) {
+      return;
+    }
+
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+
+      if (!current.isInvokeMethodWithReceiver()) {
+        continue;
+      }
+      InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
+      DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
+      boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
+      boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
+      boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
+      if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
+        continue;
+      }
+
+      Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
+      if (receiver.isPhi()) {
+        continue;
+      }
+      Instruction definition = receiver.getDefinition();
+      if (!definition.isStaticGet()) {
+        continue;
+      }
+      DexField enumField = definition.asStaticGet().getField();
+
+      Map<DexField, EnumValueInfo> valueInfoMap =
+          appView.appInfo().withLiveness().getEnumValueInfoMapFor(enumField.type);
+      if (valueInfoMap == null) {
+        continue;
+      }
+
+      // The receiver value is identified as being from a constant enum field lookup by the fact
+      // that it is a static-get to a field whose type is the same as the enclosing class (which
+      // is known to be an enum type). An enum may still define a static field using the enum type
+      // so ensure the field is present in the ordinal map for final validation.
+      EnumValueInfo valueInfo = valueInfoMap.get(enumField);
+      if (valueInfo == null) {
+        continue;
+      }
+
+      Value outValue = methodWithReceiver.outValue();
+      if (isOrdinalInvoke) {
+        iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
+      } else if (isNameInvoke) {
+        iterator.replaceCurrentInstruction(
+            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+      } else {
+        assert isToStringInvoke;
+        DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
+        if (!enumClazz.accessFlags.isFinal()) {
+          continue;
+        }
+        DexEncodedMethod singleTarget =
+            appView
+                .appInfo()
+                .resolveMethodOnClass(valueInfo.type, factory.objectMethods.toString)
+                .getSingleTarget();
+        if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
+          continue;
+        }
+        iterator.replaceCurrentInstruction(
+            new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+      }
+    }
+    assert code.isConsistentSSA();
+  }
+
+  /**
+   * Inline the indirection of switch maps into the switch statement.
+   *
+   * <p>To ensure binary compatibility, javac generated code does not use ordinal values of enums
+   * directly in switch statements but instead generates a companion class that computes a mapping
+   * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can analyze
+   * these maps and inline the indirection into the switch map again.
+   *
+   * <p>In particular, we look for code of the form
+   *
+   * <blockquote>
+   *
+   * <pre>
+   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+   *   ...
+   * }
+   * </pre>
+   *
+   * </blockquote>
+   */
+  public void removeSwitchMaps(IRCode code) {
+    for (BasicBlock block : code.blocks) {
+      JumpInstruction exit = block.exit();
+      // Pattern match a switch on a switch map as input.
+      if (!exit.isIntSwitch()) {
+        continue;
+      }
+      IntSwitch switchInsn = exit.asIntSwitch();
+      EnumSwitchInfo info = analyzeSwitchOverEnum(switchInsn);
+      if (info == null) {
+        continue;
+      }
+      Int2IntMap targetMap = new Int2IntArrayMap();
+      for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
+        assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
+        EnumValueInfo valueInfo = info.valueInfoMap.get(info.indexMap.get(switchInsn.getKey(i)));
+        targetMap.put(valueInfo.ordinal, switchInsn.targetBlockIndices()[i]);
+      }
+      int[] keys = targetMap.keySet().toIntArray();
+      Arrays.sort(keys);
+      int[] targets = new int[keys.length];
+      for (int i = 0; i < keys.length; i++) {
+        targets[i] = targetMap.get(keys[i]);
+      }
+
+      IntSwitch newSwitch =
+          new IntSwitch(
+              info.ordinalInvoke.outValue(), keys, targets, switchInsn.getFallthroughBlockIndex());
+      // Replace the switch itself.
+      exit.replace(newSwitch, code);
+      // If the original input to the switch is now unused, remove it too. It is not dead
+      // as it might have side-effects but we ignore these here.
+      Instruction arrayGet = info.arrayGet;
+      if (!arrayGet.outValue().hasUsers()) {
+        arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
+        arrayGet.getBlock().removeInstruction(arrayGet);
+      }
+      Instruction staticGet = info.staticGet;
+      if (!staticGet.outValue().hasUsers()) {
+        assert staticGet.inValues().isEmpty();
+        staticGet.getBlock().removeInstruction(staticGet);
+      }
+    }
+  }
+
+  private static final class EnumSwitchInfo {
+
+    final DexType enumClass;
+    final Instruction ordinalInvoke;
+    final Instruction arrayGet;
+    public final Instruction staticGet;
+    final Int2ReferenceMap<DexField> indexMap;
+    final Map<DexField, EnumValueInfo> valueInfoMap;
+
+    private EnumSwitchInfo(
+        DexType enumClass,
+        Instruction ordinalInvoke,
+        Instruction arrayGet,
+        Instruction staticGet,
+        Int2ReferenceMap<DexField> indexMap,
+        Map<DexField, EnumValueInfo> valueInfoMap) {
+      this.enumClass = enumClass;
+      this.ordinalInvoke = ordinalInvoke;
+      this.arrayGet = arrayGet;
+      this.staticGet = staticGet;
+      this.indexMap = indexMap;
+      this.valueInfoMap = valueInfoMap;
+    }
+  }
+
+  /**
+   * Looks for a switch statement over the enum companion class of the form
+   *
+   * <blockquote>
+   *
+   * <pre>
+   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+   *   ...
+   * }
+   * </pre>
+   *
+   * </blockquote>
+   *
+   * and extracts the components and the index and ordinal maps. See {@link EnumInfoMapCollector}
+   * and {@link SwitchMapCollector} for details.
+   */
+  private EnumSwitchInfo analyzeSwitchOverEnum(Instruction switchInsn) {
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    Instruction input = switchInsn.inValues().get(0).definition;
+    if (input == null || !input.isArrayGet()) {
+      return null;
+    }
+    ArrayGet arrayGet = input.asArrayGet();
+    Instruction index = arrayGet.index().definition;
+    if (index == null || !index.isInvokeVirtual()) {
+      return null;
+    }
+    InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
+    DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
+    DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    // After member rebinding, enumClass will be the actual java.lang.Enum class.
+    if (enumClass == null
+        || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
+        || ordinalMethod.name != dexItemFactory.ordinalMethodName
+        || ordinalMethod.proto.returnType != dexItemFactory.intType
+        || !ordinalMethod.proto.parameters.isEmpty()) {
+      return null;
+    }
+    Instruction array = arrayGet.array().definition;
+    if (array == null || !array.isStaticGet()) {
+      return null;
+    }
+    StaticGet staticGet = array.asStaticGet();
+    Int2ReferenceMap<DexField> indexMap = appInfo.getSwitchMapFor(staticGet.getField());
+    if (indexMap == null || indexMap.isEmpty()) {
+      return null;
+    }
+    // Due to member rebinding, only the fields are certain to provide the actual enums
+    // class.
+    DexType enumType = indexMap.values().iterator().next().holder;
+    Map<DexField, EnumValueInfo> valueInfoMap = appInfo.getEnumValueInfoMapFor(enumType);
+    if (valueInfoMap == null) {
+      return null;
+    }
+    return new EnumSwitchInfo(enumType, ordinalInvoke, arrayGet, staticGet, indexMap, valueInfoMap);
+  }
+}