Outline Type switch array creation

Bug: b/336510513
Change-Id: I3579a8c1907ccbe3d229f1e8022d6d6de594b8f8
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 35c644a..75e7c28 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -187,6 +187,15 @@
     }
 
     @Override
+    public void acceptTypeSwitchClass(DexProgramClass typeSwitchClass, ProgramMethod context) {
+      typeSwitchClass
+          .programMethods()
+          .forEach(
+              method ->
+                  methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer));
+    }
+
+    @Override
     public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
       backportedClass
           .programMethods()
@@ -656,6 +665,11 @@
     }
 
     @Override
+    public void acceptTypeSwitchClass(DexProgramClass typeSwitchClass, ProgramMethod context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
+
+    @Override
     public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
       synchronized (pendingInvokeSpecialBridges) {
         pendingInvokeSpecialBridges.add(info);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
new file mode 100644
index 0000000..95a5ea5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2024, 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.desugar.typeswitch;
+
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+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.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class SwitchHelperGenerator {
+
+  private final DexItemFactory factory;
+  private final DexType type;
+  private final DexField cacheField;
+  private final DexMethod getter;
+
+  SwitchHelperGenerator(
+      SyntheticProgramClassBuilder builder,
+      AppView<?> appView,
+      Consumer<List<CfInstruction>> generator) {
+    this.factory = appView.dexItemFactory();
+    this.type = builder.getType();
+    this.cacheField =
+        factory.createField(type, factory.objectArrayType, factory.createString("switchCases"));
+    this.getter =
+        factory.createMethod(
+            type,
+            factory.createProto(factory.objectArrayType),
+            factory.createString("getSwitchCases"));
+    synthesizeStaticField(builder);
+    synthesizeStaticMethod(builder, generator);
+  }
+
+  private void synthesizeStaticField(SyntheticProgramClassBuilder builder) {
+    builder.setStaticFields(
+        ImmutableList.of(
+            DexEncodedField.syntheticBuilder()
+                .setField(cacheField)
+                .setAccessFlags(FieldAccessFlags.createPublicStaticSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build()));
+  }
+
+  /**
+   * Generates the following code:
+   *
+   * <pre>
+   *   if (switchCases != null) {
+   *     return switchCases;
+   *   }
+   *   switchCases = <generate array from bootstrap method>;
+   *   return switchCases;
+   * </pre>
+   *
+   * We don't lock since the array generated is always the same and is never used in identity
+   * checks.
+   */
+  private void synthesizeStaticMethod(
+      SyntheticProgramClassBuilder builder, Consumer<List<CfInstruction>> generator) {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfStaticFieldRead(cacheField));
+    CfLabel target = new CfLabel();
+    instructions.add(new CfIf(IfType.EQ, ValueType.OBJECT, target));
+    instructions.add(new CfStaticFieldRead(cacheField));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    instructions.add(target);
+    instructions.add(new CfFrame());
+    generator.accept(instructions);
+    instructions.add(new CfStackInstruction(Opcode.Dup));
+    instructions.add(new CfStaticFieldWrite(cacheField));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+
+    builder.setDirectMethods(
+        ImmutableList.of(
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(getter)
+                .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                .setCode(new CfCode(type, 7, 3, instructions))
+                .disableAndroidApiLevelCheck()
+                .build()));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java
index da03ae1..7103887 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java
@@ -22,10 +22,12 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
+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.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -38,12 +40,14 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import org.objectweb.asm.Opcodes;
 
 public class TypeSwitchDesugaring implements CfInstructionDesugaring {
@@ -166,15 +170,14 @@
                   theContext,
                   methodProcessingContext,
                   desugaringCollection,
-                  dexItemFactory) -> {
-                // We add on stack (2) array, (3) dupped array, (4) index, (5) value.
-                localStackAllocator.allocateLocalStack(4);
-                List<CfInstruction> cfInstructions =
-                    generateTypeSwitchLoadArguments(callSite, context);
-                generateInvokeToDesugaredMethod(
-                    methodProcessingContext, cfInstructions, theContext, eventConsumer);
-                return cfInstructions;
-              })
+                  dexItemFactory) ->
+                  genSwitchMethod(
+                      localStackAllocator,
+                      eventConsumer,
+                      theContext,
+                      methodProcessingContext,
+                      cfInstructions ->
+                          generateTypeSwitchLoadArguments(cfInstructions, callSite, context)))
           .build();
     }
     if (callSite.methodName.isIdenticalTo(enumSwitchMethod.getName())
@@ -191,20 +194,45 @@
                   theContext,
                   methodProcessingContext,
                   desugaringCollection,
-                  dexItemFactory) -> {
-                // We add on stack (2) array, (3) dupped array, (4) index, (5) value.
-                localStackAllocator.allocateLocalStack(4);
-                List<CfInstruction> cfInstructions =
-                    generateEnumSwitchLoadArguments(callSite, context, enumType);
-                generateInvokeToDesugaredMethod(
-                    methodProcessingContext, cfInstructions, theContext, eventConsumer);
-                return cfInstructions;
-              })
+                  dexItemFactory) ->
+                  genSwitchMethod(
+                      localStackAllocator,
+                      eventConsumer,
+                      theContext,
+                      methodProcessingContext,
+                      cfInstructions ->
+                          generateEnumSwitchLoadArguments(
+                              cfInstructions, callSite, context, enumType)))
           .build();
     }
     return DesugarDescription.nothing();
   }
 
+  private List<CfInstruction> genSwitchMethod(
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod theContext,
+      MethodProcessingContext methodProcessingContext,
+      Consumer<List<CfInstruction>> generator) {
+    localStackAllocator.allocateLocalStack(3);
+    DexProgramClass clazz =
+        appView
+            .getSyntheticItems()
+            .createClass(
+                kinds -> kinds.TYPE_SWITCH_CLASS,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder -> new SwitchHelperGenerator(builder, appView, generator));
+    eventConsumer.acceptTypeSwitchClass(clazz, theContext);
+    List<CfInstruction> cfInstructions = new ArrayList<>();
+    Iterator<DexEncodedMethod> iter = clazz.methods().iterator();
+    cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, iter.next().getReference(), false));
+    assert !iter.hasNext();
+    generateInvokeToDesugaredMethod(
+        cfInstructions, methodProcessingContext, theContext, eventConsumer);
+    return cfInstructions;
+  }
+
   private boolean isEnumSwitchProto(DexProto methodProto) {
     return methodProto.getReturnType().isIdenticalTo(factory.intType)
         && methodProto.getArity() == 2
@@ -212,8 +240,8 @@
   }
 
   private void generateInvokeToDesugaredMethod(
-      MethodProcessingContext methodProcessingContext,
       List<CfInstruction> cfInstructions,
+      MethodProcessingContext methodProcessingContext,
       ProgramMethod context,
       CfInstructionDesugaringEventConsumer eventConsumer) {
     ProgramMethod method =
@@ -244,10 +272,14 @@
   }
 
   private List<CfInstruction> generateEnumSwitchLoadArguments(
-      DexCallSite callSite, ProgramMethod context, DexType enumType) {
+      List<CfInstruction> cfInstructions,
+      DexCallSite callSite,
+      ProgramMethod context,
+      DexType enumType) {
     return generateSwitchLoadArguments(
+        cfInstructions,
         callSite,
-        (bootstrapArg, cfInstructions) -> {
+        bootstrapArg -> {
           if (bootstrapArg.isDexValueType()) {
             cfInstructions.add(new CfConstClass(bootstrapArg.asDexValueType().getValue()));
           } else if (bootstrapArg.isDexValueString()) {
@@ -262,10 +294,11 @@
   }
 
   private List<CfInstruction> generateTypeSwitchLoadArguments(
-      DexCallSite callSite, ProgramMethod context) {
+      List<CfInstruction> cfInstructions, DexCallSite callSite, ProgramMethod context) {
     return generateSwitchLoadArguments(
+        cfInstructions,
         callSite,
-        (bootstrapArg, cfInstructions) -> {
+        bootstrapArg -> {
           if (bootstrapArg.isDexValueType()) {
             cfInstructions.add(new CfConstClass(bootstrapArg.asDexValueType().getValue()));
           } else if (bootstrapArg.isDexValueInt()) {
@@ -286,18 +319,17 @@
   }
 
   private List<CfInstruction> generateSwitchLoadArguments(
-      DexCallSite callSite, BiConsumer<DexValue, List<CfInstruction>> adder) {
+      List<CfInstruction> cfInstructions, DexCallSite callSite, Consumer<DexValue> adder) {
     // We need to call the method with the bootstrap args as parameters.
     // We need to convert the bootstrap args into a list of cf instructions.
     // The object and the int are already pushed on stack, we simply need to push the extra array.
-    List<CfInstruction> cfInstructions = new ArrayList<>();
     cfInstructions.add(new CfConstNumber(callSite.bootstrapArgs.size(), ValueType.INT));
     cfInstructions.add(new CfNewArray(factory.objectArrayType));
     for (int i = 0; i < callSite.bootstrapArgs.size(); i++) {
       DexValue bootstrapArg = callSite.bootstrapArgs.get(i);
       cfInstructions.add(new CfStackInstruction(Opcode.Dup));
       cfInstructions.add(new CfConstNumber(i, ValueType.INT));
-      adder.accept(bootstrapArg, cfInstructions);
+      adder.accept(bootstrapArg);
       cfInstructions.add(new CfArrayStore(MemberType.OBJECT));
     }
     return cfInstructions;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringEventConsumer.java
index 97115c6..271e459 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringEventConsumer.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.ir.desugar.typeswitch;
 
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 
 public interface TypeSwitchDesugaringEventConsumer {
 
   void acceptTypeSwitchMethod(ProgramMethod typeSwitchMethod, ProgramMethod context);
+
+  void acceptTypeSwitchClass(DexProgramClass typeSwitchClass, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
index 2faec2c..c9f2364 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -74,6 +74,17 @@
   }
 
   @Override
+  public void acceptTypeSwitchClass(DexProgramClass typeSwitchClass, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context,
+        additionsBuilder -> {
+          additionsBuilder.addRule(typeSwitchClass);
+          typeSwitchClass.forEachProgramMethod(additionsBuilder::addRule);
+        });
+    parent.acceptTypeSwitchClass(typeSwitchClass, context);
+  }
+
+  @Override
   public void acceptTypeSwitchMethod(ProgramMethod typeSwitchMethod, ProgramMethod context) {
     additionsCollection.addMethodAndHolderIfContextIsInProfile(typeSwitchMethod, context);
     parent.acceptBackportedMethod(typeSwitchMethod, context);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index e88703b..177bf18 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -54,6 +54,7 @@
   // Locally generated synthetic classes.
   public final SyntheticKind LAMBDA = generator.forInstanceClass("Lambda");
   public final SyntheticKind THREAD_LOCAL = generator.forInstanceClass("ThreadLocal");
+  public final SyntheticKind TYPE_SWITCH_CLASS = generator.forInstanceClass("TypeSwitch");
 
   // Merging not permitted since this could defeat the purpose of the synthetic class.
   public final SyntheticKind SHARED_SUPER_CLASS =