Classify methods as checkNotNull() methods for enum unboxing

This adds a new EnumUnboxerMethodClassification to the MethodOptimizationInfo.

A given method is classified as a checkNotNull() method by the enum unboxer if it is a static method with a parameter of type java.lang.Object, which has a single if-zero user.

Follow up work will use this piece of optimization info to allow unboxing of enums with checkNotNull() users.

Bug: 192037990
Change-Id: I9024de37c46da742be63ac8d412fde458206e488
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 9dfe2e3..13b7140 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -62,6 +63,9 @@
   void setClassInlinerMethodConstraint(
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint);
 
+  void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification);
+
   void setInstanceInitializerInfoCollection(
       DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..239a123
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/CheckNotNullEnumUnboxerMethodClassification.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, 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.classification;
+
+public final class CheckNotNullEnumUnboxerMethodClassification
+    extends EnumUnboxerMethodClassification {
+
+  private int argumentIndex;
+
+  CheckNotNullEnumUnboxerMethodClassification(int argumentIndex) {
+    this.argumentIndex = argumentIndex;
+  }
+
+  public int getArgumentIndex() {
+    return argumentIndex;
+  }
+
+  @Override
+  public boolean isCheckNotNullClassification() {
+    return true;
+  }
+
+  @Override
+  public CheckNotNullEnumUnboxerMethodClassification asCheckNotNullClassification() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..2b34291
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassification.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, 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.classification;
+
+public abstract class EnumUnboxerMethodClassification {
+
+  public static UnknownEnumUnboxerMethodClassification unknown() {
+    return UnknownEnumUnboxerMethodClassification.getInstance();
+  }
+
+  public EnumUnboxerMethodClassification fixupAfterRemovingThisParameter() {
+    // Only static methods are currently classified by the enum unboxer.
+    assert isUnknownClassification();
+    return unknown();
+  }
+
+  public boolean isCheckNotNullClassification() {
+    return false;
+  }
+
+  public CheckNotNullEnumUnboxerMethodClassification asCheckNotNullClassification() {
+    return null;
+  }
+
+  public boolean isUnknownClassification() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
new file mode 100644
index 0000000..a608d51
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/EnumUnboxerMethodClassificationAnalysis.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, 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.classification;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Set;
+
+public class EnumUnboxerMethodClassificationAnalysis {
+
+  /**
+   * Simple analysis that classifies the given method using {@link
+   * CheckNotNullEnumUnboxerMethodClassification} if the method is static and has a parameter of
+   * type Object, which has a single if-zero user.
+   */
+  public static EnumUnboxerMethodClassification analyze(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) {
+    if (!appView.options().enableEnumUnboxing) {
+      // The classification is unused when enum unboxing is disabled.
+      return EnumUnboxerMethodClassification.unknown();
+    }
+
+    if (!method.getAccessFlags().isStatic() || method.getParameters().isEmpty()) {
+      // Not classified for enum unboxing.
+      return EnumUnboxerMethodClassification.unknown();
+    }
+
+    // Look for an argument with a single if-zero user.
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    InstructionIterator entryIterator = code.entryBlock().iterator();
+    for (int index = 0; index < method.getParameters().size(); index++) {
+      DexType parameter = method.getParameter(index);
+      if (parameter != dexItemFactory.objectType) {
+        continue;
+      }
+
+      Argument argument = entryIterator.next().asArgument();
+      if (hasSingleIfZeroUser(argument)) {
+        return new CheckNotNullEnumUnboxerMethodClassification(index);
+      }
+    }
+
+    return EnumUnboxerMethodClassification.unknown();
+  }
+
+  private static boolean hasSingleIfZeroUser(Argument argument) {
+    Value value = argument.outValue();
+    if (value.hasDebugUsers() || value.hasPhiUsers()) {
+      return false;
+    }
+    Set<Instruction> users = value.uniqueUsers();
+    if (users.size() != 1) {
+      return false;
+    }
+    Instruction user = users.iterator().next();
+    if (!user.isIf()) {
+      return false;
+    }
+    If ifUser = user.asIf();
+    return ifUser.isZeroTest() && (ifUser.getType() == Type.EQ || ifUser.getType() == Type.NE);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
new file mode 100644
index 0000000..9f51d0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/classification/UnknownEnumUnboxerMethodClassification.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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.classification;
+
+public final class UnknownEnumUnboxerMethodClassification extends EnumUnboxerMethodClassification {
+
+  private static final UnknownEnumUnboxerMethodClassification INSTANCE =
+      new UnknownEnumUnboxerMethodClassification();
+
+  private UnknownEnumUnboxerMethodClassification() {}
+
+  static UnknownEnumUnboxerMethodClassification getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isUnknownClassification() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index a98bc38..2d8ff77 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -65,6 +66,11 @@
   }
 
   @Override
+  public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
+    return EnumUnboxerMethodClassification.unknown();
+  }
+
+  @Override
   public TypeElement getDynamicUpperBoundType() {
     return UNKNOWN_TYPE;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index a25eabb..4de895c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -36,6 +37,8 @@
 
   public abstract ClassInlinerMethodConstraint getClassInlinerMethodConstraint();
 
+  public abstract EnumUnboxerMethodClassification getEnumUnboxerMethodClassification();
+
   public abstract TypeElement getDynamicUpperBoundType();
 
   public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 15afa8c..8755235 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -86,6 +86,8 @@
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ClassInlinerMethodConstraintAnalysis;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassificationAnalysis;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -137,6 +139,7 @@
       identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
     computeClassInlinerMethodConstraint(method, code, feedback, timing);
+    computeEnumUnboxerMethodClassification(method, code, feedback, timing);
     computeSimpleInliningConstraint(method, code, feedback, timing);
     computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
     computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
@@ -783,6 +786,20 @@
     feedback.setClassInlinerMethodConstraint(method, classInlinerMethodConstraint);
   }
 
+  private void computeEnumUnboxerMethodClassification(
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    timing.begin("Compute enum unboxer method classification");
+    computeEnumUnboxerMethodClassification(method, code, feedback);
+    timing.end();
+  }
+
+  private void computeEnumUnboxerMethodClassification(
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
+    EnumUnboxerMethodClassification enumUnboxerMethodClassification =
+        EnumUnboxerMethodClassificationAnalysis.analyze(appView, method, code);
+    feedback.setEnumUnboxerMethodClassification(method, enumUnboxerMethodClassification);
+  }
+
   private void computeSimpleInliningConstraint(
       ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     if (appView.options().enableSimpleInliningConstraints) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 7a0dae1..f50c71a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -41,6 +42,8 @@
       DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
   private ClassInlinerMethodConstraint classInlinerConstraint =
       ClassInlinerMethodConstraint.alwaysFalse();
+  private EnumUnboxerMethodClassification enumUnboxerMethodClassification =
+      EnumUnboxerMethodClassification.unknown();
   private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
   private ClassTypeElement returnsObjectWithLowerBoundType =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
@@ -154,6 +157,7 @@
     nonNullParamOrThrow = template.nonNullParamOrThrow;
     nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
     classInlinerConstraint = template.classInlinerConstraint;
+    enumUnboxerMethodClassification = template.enumUnboxerMethodClassification;
     apiReferenceLevel = template.apiReferenceLevel;
   }
 
@@ -246,6 +250,16 @@
   }
 
   @Override
+  public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
+    return enumUnboxerMethodClassification;
+  }
+
+  void setEnumUnboxerMethodClassification(
+      EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+    this.enumUnboxerMethodClassification = enumUnboxerMethodClassification;
+  }
+
+  @Override
   public TypeElement getDynamicUpperBoundType() {
     return returnsObjectWithUpperBoundType;
   }
@@ -555,6 +569,8 @@
   public void adjustOptimizationInfoAfterRemovingThisParameter(
       AppView<AppInfoWithLiveness> appView) {
     classInlinerConstraint = classInlinerConstraint.fixupAfterRemovingThisParameter();
+    enumUnboxerMethodClassification =
+        enumUnboxerMethodClassification.fixupAfterRemovingThisParameter();
     simpleInliningConstraint =
         simpleInliningConstraint.fixupAfterRemovingThisParameter(
             appView.simpleInliningConstraintFactory());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index dd781e8..c9390d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -264,6 +265,13 @@
   }
 
   @Override
+  public synchronized void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+    getMethodOptimizationInfoForUpdating(method)
+        .setEnumUnboxerMethodClassification(enumUnboxerMethodClassification);
+  }
+
+  @Override
   public synchronized void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index e862824..623d8d2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -117,6 +118,10 @@
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {}
 
   @Override
+  public void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {}
+
+  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 5c39d40..b248a6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -169,6 +170,12 @@
   }
 
   @Override
+  public void setEnumUnboxerMethodClassification(
+      ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
+    // Ignored.
+  }
+
+  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {