Introduce NumberUnboxerMethodReprocessingEnqueuer

Change-Id: Ifb675a4f57b262256036f41a9a9de8dc5139353a
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
index d906b4c..63aa73a 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import java.util.Map;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 /**
  * GraphLens implementation with a parent lens where the mapping of types, methods and fields can be
@@ -291,24 +290,6 @@
 
   @Override
   public String toString() {
-    StringBuilder builder = new StringBuilder();
-    typeMap.forEach(
-        (from, to) ->
-            builder
-                .append(from.getTypeName())
-                .append(" -> ")
-                .append(to.getTypeName())
-                .append(System.lineSeparator()));
-    fieldMap.forEachManyToOneMapping(
-        (fromSet, to) -> {
-          builder.append(
-              fromSet.stream()
-                  .map(DexField::toSourceString)
-                  .collect(Collectors.joining("," + System.lineSeparator())));
-          builder.append(" -> ");
-          builder.append(to.toSourceString()).append(System.lineSeparator());
-        });
-    builder.append(getPrevious().toString());
-    return builder.toString();
+    return getClass().getTypeName();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
index c6b0d70..1e623e1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
@@ -40,18 +40,16 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
 
 public class NumberUnboxerImpl extends NumberUnboxer {
 
-  private AppView<AppInfoWithLiveness> appView;
-  private DexItemFactory factory;
-  private Set<DexType> boxedTypes;
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory factory;
+  private final Set<DexType> boxedTypes;
 
   // Temporarily keep the information here, and not in the MethodOptimizationInfo as the
   // optimization is developed and unfinished.
-  private Map<DexMethod, MethodBoxingStatus> methodBoxingStatus = new ConcurrentHashMap<>();
+  private final Map<DexMethod, MethodBoxingStatus> methodBoxingStatus = new ConcurrentHashMap<>();
   private Map<DexMethod, DexMethod> virtualMethodsRepresentative;
 
   public NumberUnboxerImpl(AppView<AppInfoWithLiveness> appView) {
@@ -314,13 +312,15 @@
     NumberUnboxerLens numberUnboxerLens =
         new NumberUnboxerTreeFixer(appView, unboxingResult).fixupTree(executorService, timing);
     appView.rewriteWithLens(numberUnboxerLens, executorService, timing);
+    new NumberUnboxerMethodReprocessingEnqueuer(appView, numberUnboxerLens)
+        .enqueueMethodsForReprocessing(postMethodProcessorBuilder, executorService, timing);
 
-    enqueueMethodsForReprocessing(postMethodProcessorBuilder);
+    if (appView.testing().printNumberUnboxed) {
+      printNumberUnboxed(unboxingResult);
+    }
+  }
 
-    // TODO(b/307872552): The result encodes for each method which return value and parameter of
-    //  each method should be unboxed. We need here to implement the treefixer using it, and set up
-    //  correctly the reprocessing with a code rewriter similar to the enum unboxing code rewriter.
-    //  We should implement the optimization, so far, we just print out the result.
+  private void printNumberUnboxed(Map<DexMethod, MethodBoxingStatusResult> unboxingResult) {
     StringBuilder stringBuilder = new StringBuilder();
     unboxingResult.forEach(
         (k, v) -> {
@@ -344,19 +344,6 @@
     appView.reporter().warning(stringBuilder.toString());
   }
 
-  private void enqueueMethodsForReprocessing(
-      PostMethodProcessor.Builder postMethodProcessorBuilder) {
-    postMethodProcessorBuilder.rewrittenWithLens(appView);
-
-    // TODO(b/307872552): Implement the reprocessing enqueuer so that only relevant methods are
-    //  reprocessed. For testing we temporarily reprocess all.
-    postMethodProcessorBuilder.addAll(
-        appView.appInfo().classes().stream()
-            .flatMap(c -> StreamSupport.stream(c.programMethods().spliterator(), false))
-            .collect(Collectors.toList()),
-        appView.graphLens());
-  }
-
   @Override
   public void onMethodPruned(ProgramMethod method) {
     // TODO(b/307872552): Should we do something about this? We might need to change the
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerMethodReprocessingEnqueuer.java
new file mode 100644
index 0000000..a48a77f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerMethodReprocessingEnqueuer.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2023, 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.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/** Finds the methods in the program that should be reprocessed due to number unboxing. */
+public class NumberUnboxerMethodReprocessingEnqueuer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final NumberUnboxerLens numberUnboxerLens;
+
+  public NumberUnboxerMethodReprocessingEnqueuer(
+      AppView<AppInfoWithLiveness> appView, NumberUnboxerLens numberUnboxerLens) {
+    this.appView = appView;
+    this.numberUnboxerLens = numberUnboxerLens;
+  }
+
+  public void enqueueMethodsForReprocessing(
+      PostMethodProcessor.Builder postMethodProcessorBuilder,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
+    timing.begin("Enqueue methods for reprocessing due to the number unboxer");
+    assert appView.graphLens() == numberUnboxerLens;
+    postMethodProcessorBuilder.rewrittenWithLens(appView);
+    enqueueAffectedCallees(postMethodProcessorBuilder);
+    enqueueAffectedCallers(postMethodProcessorBuilder, executorService);
+    timing.end();
+  }
+
+  boolean shouldReprocess(DexMethod reference) {
+    return reference.isNotIdenticalTo(numberUnboxerLens.getPreviousMethodSignature(reference));
+  }
+
+  private void enqueueAffectedCallees(PostMethodProcessor.Builder postMethodProcessorBuilder) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramMethodMatching(
+          DexEncodedMethod::hasCode,
+          method -> {
+            if (method.getDefinition().getCode().isSharedCodeObject()) {
+              return;
+            }
+            if (shouldReprocess(method.getReference())) {
+              assert !appView.appInfo().isNeverReprocessMethod(method);
+              postMethodProcessorBuilder.add(method, numberUnboxerLens);
+            }
+          });
+    }
+  }
+
+  // TODO(b/190154391): This could invalidate the @NeverReprocessMethod testing annotations (non
+  //  critical). If @NeverReprocessMethod is used, we would need to scan the application to mark
+  //  methods as unoptimizable prior to removing parameters from the application.
+  private void enqueueAffectedCallers(
+      PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService)
+      throws ExecutionException {
+    Collection<List<ProgramMethod>> methodsToReprocess =
+        ThreadUtils.processItemsWithResultsThatMatches(
+            appView.appInfo().classes(),
+            clazz -> {
+              List<ProgramMethod> methodsToReprocessInClass = new ArrayList<>();
+              clazz.forEachProgramMethodMatching(
+                  DexEncodedMethod::hasCode,
+                  method -> {
+                    AffectedMembersGraphLensUseRegistry registry =
+                        new AffectedMembersGraphLensUseRegistry(appView, method);
+                    if (method.registerCodeReferencesWithResult(registry)) {
+                      assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
+                      methodsToReprocessInClass.add(method);
+                    }
+                  });
+              return methodsToReprocessInClass;
+            },
+            res -> !res.isEmpty(),
+            appView.options().getThreadingModule(),
+            executorService);
+    methodsToReprocess.forEach(
+        methodsToReprocessInClass ->
+            postMethodProcessorBuilder.addAll(methodsToReprocessInClass, numberUnboxerLens));
+  }
+
+  public class AffectedMembersGraphLensUseRegistry
+      extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
+
+    public AffectedMembersGraphLensUseRegistry(
+        AppView<AppInfoWithLiveness> appViewWithLiveness, ProgramMethod context) {
+      super(appViewWithLiveness, context, false);
+    }
+
+    private void markAffected() {
+      setResult(Boolean.TRUE);
+    }
+
+    @Override
+    public void registerInvokeDirect(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.DIRECT);
+    }
+
+    @Override
+    public void registerInvokeInterface(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.INTERFACE);
+    }
+
+    @Override
+    public void registerInvokeStatic(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.STATIC);
+    }
+
+    @Override
+    public void registerInvokeSuper(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.SUPER);
+    }
+
+    @Override
+    public void registerInvokeVirtual(DexMethod method) {
+      registerInvokeMethod(method, InvokeType.VIRTUAL);
+    }
+
+    private void registerInvokeMethod(DexMethod method, InvokeType invokeType) {
+      // TODO(b/314117865): This is assuming that there are no non-rebound method references.
+      DexMethod reference =
+          numberUnboxerLens
+              .lookupMethod(
+                  method, getContext().getReference(), invokeType, numberUnboxerLens.getPrevious())
+              .getReference();
+      assert reference != null;
+      if (shouldReprocess(reference)) {
+        markAffected();
+      }
+    }
+
+    @Override
+    public void registerInstanceFieldRead(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerInstanceFieldWrite(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerStaticFieldRead(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerStaticFieldWrite(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    private void registerFieldAccess(DexField unused) {
+      // TODO(b/307872552): Deal with unboxed fields.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 7e3e4b5..b71ad68 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2174,8 +2174,8 @@
   public static class TestingOptions {
 
     public boolean enableNumberUnboxer = false;
+    public boolean printNumberUnboxed = false;
     public boolean roundtripThroughLir = false;
-
     public boolean canUseLir(AppView<?> appView) {
       return appView.enableWholeProgramOptimizations();
     }
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
index c14645d..ceaf904 100644
--- a/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
@@ -43,6 +43,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .addOptionsModification(opt -> opt.testing.enableNumberUnboxer = true)
+        .addOptionsModification(opt -> opt.testing.printNumberUnboxed = true)
         .setMinApi(parameters)
         .allowDiagnosticWarningMessages()
         .compile()