Only run InstanceInitializerOutliner in primary conversion passes

This converts the InstanceInitializerOutliner to a CodeRewriterPass and
prevents it from running except when the method processor is the
D8MethodProcessor or R8's PrimaryMethodProcessor.

This allows IR processing using a custom MethodProcessor, which is
needed to apply the LensCodeRewriter after the final round of vertical
class merging.

Bug: b/315445393
Change-Id: I75a0368ac398f6a2023844884e5cc6c3465dfab7
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index 6b13747..4fc003f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -67,6 +67,11 @@
   }
 
   @Override
+  public boolean isD8MethodProcessor() {
+    return true;
+  }
+
+  @Override
   public boolean isProcessedConcurrently(ProgramMethod method) {
     // In D8 all methods are considered independently compiled.
     return true;
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 9a7b1e0..2dc1a3f 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
@@ -85,7 +85,6 @@
 import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
 import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -129,7 +128,6 @@
   protected ServiceLoaderRewriter serviceLoaderRewriter;
   protected final EnumUnboxer enumUnboxer;
   protected final NumberUnboxer numberUnboxer;
-  protected InstanceInitializerOutliner instanceInitializerOutliner;
   protected final RemoveVerificationErrorForUnknownReturnedValues
       removeVerificationErrorForUnknownReturnedValues;
 
@@ -218,7 +216,6 @@
       this.enumUnboxer = EnumUnboxer.empty();
       this.numberUnboxer = NumberUnboxer.empty();
       this.assumeInserter = null;
-      this.instanceInitializerOutliner = null;
       this.removeVerificationErrorForUnknownReturnedValues = null;
       return;
     }
@@ -230,13 +227,6 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(appView, this)
             : null;
-    if (appView.options().desugarState.isOn()
-        && appView.options().apiModelingOptions().enableOutliningOfMethods
-        && appView.options().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
-      this.instanceInitializerOutliner = new InstanceInitializerOutliner(appView);
-    } else {
-      this.instanceInitializerOutliner = null;
-    }
     removeVerificationErrorForUnknownReturnedValues =
         (appView.options().apiModelingOptions().enableLibraryApiModeling
                 && appView.options().canHaveVerifyErrorForUnknownUnusedReturnValue())
@@ -652,12 +642,9 @@
     timing.end();
     previous = printMethod(code, "IR after enum-switch optimization (SSA)", previous);
 
-    if (instanceInitializerOutliner != null) {
-      instanceInitializerOutliner.rewriteInstanceInitializers(
-          code, context, methodProcessor, methodProcessingContext);
-      assert code.verifyTypes(appView);
-      previous = printMethod(code, "IR after instance initializer outlining (SSA)", previous);
-    }
+    new InstanceInitializerOutliner(appView)
+        .run(code, methodProcessor, methodProcessingContext, timing);
+    previous = printMethod(code, "IR after instance initializer outlining (SSA)", previous);
 
     // Update the IR code if collected call site optimization info has something useful.
     // While aggregation of parameter information at call sites would be more precise than static
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index a80055a..829c985 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -17,6 +17,10 @@
     return null;
   }
 
+  public boolean isD8MethodProcessor() {
+    return false;
+  }
+
   public boolean isPrimaryMethodProcessor() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
index e1a4c6f..625a337 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -71,10 +71,6 @@
     reportNestDesugarDependencies();
     clearNestAttributes();
 
-    if (instanceInitializerOutliner != null) {
-      processSimpleSynthesizeMethods(
-          instanceInitializerOutliner.getSynthesizedMethods(), executorService);
-    }
     if (assertionErrorTwoArgsConstructorRewriter != null) {
       processSimpleSynthesizeMethods(
           assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index e87faba..16d82e6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -430,10 +430,6 @@
     if (inliner != null) {
       inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
     }
-    if (instanceInitializerOutliner != null) {
-      instanceInitializerOutliner.onLastWaveDone(postMethodProcessorBuilder);
-      instanceInitializerOutliner = null;
-    }
     if (serviceLoaderRewriter != null) {
       serviceLoaderRewriter.onLastWaveDone(postMethodProcessorBuilder);
       serviceLoaderRewriter = null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
index 2a90c2e..a9dfc03 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -16,7 +17,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -29,13 +29,14 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.conversion.PostMethodProcessor;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+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.optimize.info.DefaultMethodOptimizationInfo;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.ir.synthetic.NewInstanceSourceCode;
 import com.android.tools.r8.shaking.ComputeApiLevelUseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -46,32 +47,21 @@
  * Unlike the ApiInvokeOutlinerDesugaring that works on CF, this works on IR to properly replace the
  * users of the NewInstance call.
  */
-public class InstanceInitializerOutliner {
+public class InstanceInitializerOutliner extends CodeRewriterPass<AppInfo> {
 
-  private final AppView<?> appView;
   private final DexItemFactory factory;
 
-  private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
-
   public InstanceInitializerOutliner(AppView<?> appView) {
-    this.appView = appView;
+    super(appView);
     this.factory = appView.dexItemFactory();
   }
 
-  public List<ProgramMethod> getSynthesizedMethods() {
-    return synthesizedMethods;
-  }
-
-  public void rewriteInstanceInitializers(
+  @Override
+  protected CodeRewriterResult rewriteCode(
       IRCode code,
-      ProgramMethod context,
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     assert !methodProcessor.isPostMethodProcessor();
-    // Do not outline from already synthesized methods.
-    if (context.getDefinition().isD8R8Synthesized()) {
-      return;
-    }
     Map<NewInstance, Value> rewrittenNewInstances = new IdentityHashMap<>();
     ComputedApiLevel minApiLevel = appView.computedMinApiLevel();
     InstructionListIterator iterator = code.instructionListIterator();
@@ -104,7 +94,7 @@
         continue;
       }
       // Check if this is already outlined.
-      if (isOutlinedAtSameOrLowerLevel(context.getHolder(), apiReferenceLevel)) {
+      if (isOutlinedAtSameOrLowerLevel(code.context().getHolder(), apiReferenceLevel)) {
         continue;
       }
       DexEncodedMethod synthesizedInstanceInitializer =
@@ -125,7 +115,7 @@
       rewrittenNewInstances.put(newInstance, outlinedMethodInvoke.outValue());
     }
     if (rewrittenNewInstances.isEmpty()) {
-      return;
+      return CodeRewriterResult.NO_CHANGE;
     }
     // Scan over NewInstance calls that needs to be outlined. We insert a call to a synthetic method
     // with a NewInstance to preserve class-init semantics.
@@ -173,14 +163,10 @@
     // the outline again in R8 - but allow inlining of other calls to min api level methods, we have
     // to recompute the api level.
     if (appView.enableWholeProgramOptimizations()) {
-      recomputeApiLevel(context, code);
+      recomputeApiLevel(code.context(), code);
     }
 
-    assert code.isConsistentSSA(appView);
-  }
-
-  public void onLastWaveDone(PostMethodProcessor.Builder postMethodProcessorBuilder) {
-    postMethodProcessorBuilder.addAll(synthesizedMethods, appView.graphLens());
+    return CodeRewriterResult.HAS_CHANGED;
   }
 
   private boolean canSkipClInit(
@@ -249,9 +235,7 @@
     methodProcessor
         .getEventConsumer()
         .acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext());
-    synchronized (synthesizedMethods) {
-      synthesizedMethods.add(method);
-    }
+    methodProcessor.scheduleDesugaredMethodForProcessing(method);
     return method.getDefinition();
   }
 
@@ -271,31 +255,56 @@
                 kinds -> kinds.API_MODEL_OUTLINE,
                 methodProcessingContext.createUniqueContext(),
                 appView,
-                builder ->
-                    builder
-                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                        .setProto(proto)
-                        .setApiLevelForDefinition(appView.computedMinApiLevel())
-                        .setApiLevelForCode(computedApiLevel)
-                        .setCode(
-                            m ->
-                                ForwardMethodBuilder.builder(appView.dexItemFactory())
-                                    .setConstructorTargetWithNewInstance(targetMethod)
-                                    .setStaticSource(m)
-                                    .build()));
+                builder -> {
+                  DynamicType exactDynamicReturnType =
+                      DynamicType.createExact(
+                          targetMethod
+                              .getHolderType()
+                              .toTypeElement(appView, Nullability.definitelyNotNull())
+                              .asClassType());
+                  builder
+                      .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                      .setProto(proto)
+                      .setApiLevelForDefinition(appView.computedMinApiLevel())
+                      .setApiLevelForCode(computedApiLevel)
+                      .setCode(
+                          m ->
+                              ForwardMethodBuilder.builder(appView.dexItemFactory())
+                                  .setConstructorTargetWithNewInstance(targetMethod)
+                                  .setStaticSource(m)
+                                  .build())
+                      .setOptimizationInfo(
+                          DefaultMethodOptimizationInfo.getInstance()
+                              .toMutableOptimizationInfo()
+                              .setDynamicType(exactDynamicReturnType));
+                });
     methodProcessor
         .getEventConsumer()
         .acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext());
-    synchronized (synthesizedMethods) {
-      synthesizedMethods.add(method);
-      ClassTypeElement exactType =
-          targetMethod
-              .getHolderType()
-              .toTypeElement(appView, Nullability.definitelyNotNull())
-              .asClassType();
-      OptimizationFeedback.getSimpleFeedback()
-          .setDynamicReturnType(method, appView, DynamicType.createExact(exactType));
-    }
+    methodProcessor.scheduleDesugaredMethodForProcessing(method);
     return method.getDefinition();
   }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    if (!appView.options().desugarState.isOn()
+        || !appView.options().apiModelingOptions().enableOutliningOfMethods
+        || !appView.options().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+      return false;
+    }
+    // Only outline in primary optimization pass.
+    if (!methodProcessor.isD8MethodProcessor() && !methodProcessor.isPrimaryMethodProcessor()) {
+      return false;
+    }
+    // Do not outline from already synthesized methods.
+    if (code.context().getDefinition().isD8R8Synthesized()) {
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "InstanceInitializerOutliner";
+  }
 }
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 602b555..33f1160 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
@@ -710,7 +710,7 @@
     setDynamicType(newDynamicType);
   }
 
-  private MutableMethodOptimizationInfo setDynamicType(DynamicType dynamicType) {
+  public MutableMethodOptimizationInfo setDynamicType(DynamicType dynamicType) {
     assert !dynamicType.hasDynamicUpperBoundType()
         || !dynamicType.asDynamicTypeWithUpperBound().getDynamicUpperBoundType().isPrimitiveType();
     this.dynamicType = dynamicType;