Refactor service loader rewriter to code rewriter pass

Change-Id: Iad5989f2749fc3184a9a3c11fb13b4edfee38f64
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 60956f4..5e03a38 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -92,7 +92,7 @@
   }
 
   public boolean hasServiceImplementationsInFeature(
-      AppView<AppInfoWithLiveness> appView, DexType serviceType) {
+      AppView<? extends AppInfoWithLiveness> appView, DexType serviceType) {
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
     if (classToFeatureSplitMap.isEmpty()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index f384ef5..e111001 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -99,6 +99,10 @@
     return get(Opcodes.CHECK_CAST);
   }
 
+  public boolean mayHaveConstClass() {
+    return get(Opcodes.CONST_CLASS);
+  }
+
   public boolean mayHaveConstNumber() {
     return get(Opcodes.CONST_NUMBER);
   }
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 6a4995b..7bab38d 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
@@ -605,15 +605,8 @@
     CheckNotNullConverter.runIfNecessary(appView, code);
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
-    if (appView.hasLiveness()
-        && methodProcessor.isPrimaryMethodProcessor()
-        && options.enableServiceLoaderRewriting) {
-      timing.begin("Rewrite service loaders");
-      new ServiceLoaderRewriter(appView.withLiveness())
-          .rewrite(code, methodProcessor, methodProcessingContext);
-      timing.end();
-      previous = printMethod(code, "IR after service rewriting (SSA)", previous);
-    }
+    new ServiceLoaderRewriter(appView).run(code, methodProcessor, methodProcessingContext, timing);
+    previous = printMethod(code, "IR after service rewriting (SSA)", previous);
 
     if (identifierNameStringMarker != null) {
       timing.begin("Decouple identifier-name strings");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 455ef32..4d791b0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -24,11 +24,12 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -58,32 +59,44 @@
  * We therefore only conservatively rewrite if the invoke is on is on the form
  * ServiceLoader.load(X.class, X.class.getClassLoader()) or ServiceLoader.load(X.class, null).
  *
- * Android Nougat do not use ClassLoader.getSystemClassLoader() when passing null and will
- * almost certainly fail when trying to find the service. It seems unlikely that programs rely on
- * this behaviour.
+ * <p>Android Nougat do not use ClassLoader.getSystemClassLoader() when passing null and will almost
+ * certainly fail when trying to find the service. It seems unlikely that programs rely on this
+ * behaviour.
  */
-public class ServiceLoaderRewriter {
+public class ServiceLoaderRewriter extends CodeRewriterPass<AppInfoWithLiveness> {
 
-  private final AppView<AppInfoWithLiveness> appView;
   private final AndroidApiLevelCompute apiLevelCompute;
-  private final Reporter reporter;
   private final ServiceLoaderMethods serviceLoaderMethods;
 
-  public ServiceLoaderRewriter(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
+  public ServiceLoaderRewriter(AppView<?> appView) {
+    super(appView);
     this.apiLevelCompute = appView.apiLevelCompute();
-    serviceLoaderMethods = appView.dexItemFactory().serviceLoaderMethods;
-    reporter = shouldReportWhyAreYouNotInliningServiceLoaderLoad() ? appView.reporter() : null;
+    this.serviceLoaderMethods = appView.dexItemFactory().serviceLoaderMethods;
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "ServiceLoaderRewriter";
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return appView.hasLiveness()
+        && methodProcessor.isPrimaryMethodProcessor()
+        && options.enableServiceLoaderRewriting
+        && code.metadata().mayHaveConstClass()
+        && code.metadata().mayHaveInvokeStatic()
+        && code.metadata().mayHaveInvokeVirtual();
   }
 
   private boolean shouldReportWhyAreYouNotInliningServiceLoaderLoad() {
-    AppInfoWithLiveness appInfo = appView.appInfo();
+    AppInfoWithLiveness appInfo = appView().appInfo();
     return appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.load)
         || appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.loadWithClassLoader);
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  public void rewrite(
+  @Override
+  protected CodeRewriterResult rewriteCode(
       IRCode code,
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
@@ -106,15 +119,14 @@
       }
 
       // Check that the first argument is a const class.
-      Value argument = serviceLoaderLoad.inValues().get(0).getAliasedValue();
-      if (argument.isPhi() || !argument.definition.isConstClass()) {
+      Value argument = serviceLoaderLoad.getFirstArgument().getAliasedValue();
+      if (!argument.isDefinedByInstructionSatisfying(Instruction::isConstClass)) {
         report(code.context(), null, "The service loader type could not be determined");
         continue;
       }
 
-      ConstClass constClass = argument.getConstInstruction().asConstClass();
-
-      if (invokedMethod != serviceLoaderMethods.loadWithClassLoader) {
+      ConstClass constClass = argument.getDefinition().asConstClass();
+      if (invokedMethod.isNotIdenticalTo(serviceLoaderMethods.loadWithClassLoader)) {
         report(
             code.context(),
             constClass.getType(),
@@ -127,22 +139,22 @@
           "The returned ServiceLoader instance must only be used in a call to `java.util.Iterator"
               + " java.lang.ServiceLoader.iterator()`";
       Value serviceLoaderLoadOut = serviceLoaderLoad.outValue();
-      if (serviceLoaderLoadOut.numberOfAllUsers() != 1 || serviceLoaderLoadOut.hasPhiUsers()) {
+      if (!serviceLoaderLoadOut.hasSingleUniqueUser() || serviceLoaderLoadOut.hasPhiUsers()) {
         report(code.context(), constClass.getType(), invalidUserMessage);
         continue;
       }
 
       // Check that the only user is a call to iterator().
-      if (!serviceLoaderLoadOut.singleUniqueUser().isInvokeVirtual()
-          || serviceLoaderLoadOut.singleUniqueUser().asInvokeVirtual().getInvokedMethod()
-              != serviceLoaderMethods.iterator) {
+      InvokeVirtual singleUniqueUser = serviceLoaderLoadOut.singleUniqueUser().asInvokeVirtual();
+      if (singleUniqueUser == null
+          || singleUniqueUser.getInvokedMethod().isNotIdenticalTo(serviceLoaderMethods.iterator)) {
         report(
             code.context(), constClass.getType(), invalidUserMessage + ", but found other usages");
         continue;
       }
 
       // Check that the service is not kept.
-      if (appView.appInfo().isPinnedWithDefinitionLookup(constClass.getValue())) {
+      if (appView().appInfo().isPinnedWithDefinitionLookup(constClass.getValue())) {
         report(code.context(), constClass.getType(), "The service loader type is kept");
         continue;
       }
@@ -155,7 +167,7 @@
       }
 
       // Check that we are not service loading anything from a feature into base.
-      if (appServices.hasServiceImplementationsInFeature(appView, constClass.getValue())) {
+      if (appServices.hasServiceImplementationsInFeature(appView(), constClass.getValue())) {
         report(
             code.context(),
             constClass.getType(),
@@ -165,7 +177,7 @@
 
       // Check that ClassLoader used is the ClassLoader defined for the service configuration
       // that we are instantiating or NULL.
-      if (serviceLoaderLoad.inValues().get(1).isPhi()) {
+      if (serviceLoaderLoad.getLastArgument().isPhi()) {
         report(
             code.context(),
             constClass.getType(),
@@ -175,19 +187,19 @@
         continue;
       }
       InvokeVirtual classLoaderInvoke =
-          serviceLoaderLoad.inValues().get(1).definition.asInvokeVirtual();
+          serviceLoaderLoad.getLastArgument().getDefinition().asInvokeVirtual();
       boolean isGetClassLoaderOnConstClassOrNull =
-          serviceLoaderLoad.inValues().get(1).getType().isNullType()
+          serviceLoaderLoad.getLastArgument().getType().isNullType()
               || (classLoaderInvoke != null
-                  && classLoaderInvoke.inValues().size() == 1
+                  && classLoaderInvoke.arguments().size() == 1
                   && classLoaderInvoke.getReceiver().getAliasedValue().isConstClass()
                   && classLoaderInvoke
-                          .getReceiver()
-                          .getAliasedValue()
-                          .getConstInstruction()
-                          .asConstClass()
-                          .getValue()
-                      == constClass.getValue());
+                      .getReceiver()
+                      .getAliasedValue()
+                      .getDefinition()
+                      .asConstClass()
+                      .getValue()
+                      .isIdenticalTo(constClass.getValue()));
       if (!isGetClassLoaderOnConstClassOrNull) {
         report(
             code.context(),
@@ -212,7 +224,6 @@
         }
         classes.add(serviceImplementation);
       }
-
       if (seenNull) {
         continue;
       }
@@ -236,19 +247,24 @@
           .perform(classLoaderInvoke, synthesizedMethod.getReference());
     }
     assert code.isConsistentSSA(appView);
+    return synthesizedServiceLoaders.isEmpty()
+        ? CodeRewriterResult.NO_CHANGE
+        : CodeRewriterResult.HAS_CHANGED;
   }
 
   private void report(ProgramMethod method, DexType serviceLoaderType, String message) {
-    if (reporter != null) {
-      reporter.info(
-          new ServiceLoaderRewriterDiagnostic(
-              method.getOrigin(),
-              "Could not inline ServiceLoader.load"
-                  + (serviceLoaderType == null
-                      ? ""
-                      : (" of type " + serviceLoaderType.getTypeName()))
-                  + ": "
-                  + message));
+    if (shouldReportWhyAreYouNotInliningServiceLoaderLoad()) {
+      appView
+          .reporter()
+          .info(
+              new ServiceLoaderRewriterDiagnostic(
+                  method.getOrigin(),
+                  "Could not inline ServiceLoader.load"
+                      + (serviceLoaderType == null
+                          ? ""
+                          : (" of type " + serviceLoaderType.getTypeName()))
+                      + ": "
+                      + message));
     }
   }