Support TypeSwitchDesugaring

Bug: b/336510513
Change-Id: I6f997f9adde61d981def8753eed47ba3b44c10f1
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 06fc75f..35c644a 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
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.typeswitch.TypeSwitchDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.profile.rewriting.ProfileRewritingCfInstructionDesugaringEventConsumer;
@@ -68,7 +69,8 @@
         ClasspathEmulatedInterfaceSynthesizerEventConsumer,
         ApiInvokeOutlinerDesugaringEventConsumer,
         VarHandleDesugaringEventConsumer,
-        DesugaredLibRewriterEventConsumer {
+        DesugaredLibRewriterEventConsumer,
+        TypeSwitchDesugaringEventConsumer {
 
   public static CfInstructionDesugaringEventConsumer createForD8(
       AppView<?> appView,
@@ -180,6 +182,11 @@
     }
 
     @Override
+    public void acceptTypeSwitchMethod(ProgramMethod typeSwitchMethod, ProgramMethod context) {
+      methodProcessor.scheduleMethodForProcessing(typeSwitchMethod, outermostEventConsumer);
+    }
+
+    @Override
     public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
       backportedClass
           .programMethods()
@@ -644,6 +651,11 @@
     }
 
     @Override
+    public void acceptTypeSwitchMethod(ProgramMethod typeSwitchMethod, 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/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 407daa8..70db152 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.twr.TwrInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.typeswitch.TypeSwitchDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.Box;
@@ -107,6 +108,9 @@
     if (appView.options().enableTryWithResourcesDesugaring()) {
       desugarings.add(new TwrInstructionDesugaring(appView));
     }
+    if (appView.options().enableTypeSwitchDesugaring) {
+      desugarings.add(new TypeSwitchDesugaring(appView));
+    }
     recordRewriter = RecordDesugaring.create(appView);
     if (recordRewriter != null) {
       desugarings.add(recordRewriter);
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
new file mode 100644
index 0000000..ce86ab6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaring.java
@@ -0,0 +1,167 @@
+// 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.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+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 java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class TypeSwitchDesugaring implements CfInstructionDesugaring {
+
+  private final AppView<?> appView;
+
+  private final DexString typeSwitch;
+  private final DexMethod typeSwitchMethod;
+  private final DexProto typeSwitchProto;
+  private final DexProto typeSwitchHelperProto;
+
+  public TypeSwitchDesugaring(AppView<?> appView) {
+    this.appView = appView;
+    DexItemFactory factory = appView.dexItemFactory();
+    typeSwitchProto = factory.createProto(factory.intType, factory.objectType, factory.intType);
+    DexType switchBootstrap = factory.createType("Ljava/lang/runtime/SwitchBootstraps;");
+    typeSwitch = factory.createString("typeSwitch");
+    typeSwitchMethod =
+        factory.createMethod(
+            switchBootstrap,
+            factory.createProto(
+                factory.callSiteType,
+                factory.methodHandlesLookupType,
+                factory.stringType,
+                factory.methodTypeType,
+                factory.objectArrayType),
+            typeSwitch);
+    typeSwitchHelperProto =
+        factory.createProto(
+            factory.intType, factory.objectType, factory.intType, factory.objectArrayType);
+  }
+
+  @Override
+  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+    if (!instruction.isInvokeDynamic()) {
+      return DesugarDescription.nothing();
+    }
+    DexCallSite callSite = instruction.asInvokeDynamic().getCallSite();
+    if (!(callSite.methodName.isIdenticalTo(typeSwitch)
+        && callSite.methodProto.isIdenticalTo(typeSwitchProto)
+        && callSite.bootstrapMethod.member.isDexMethod()
+        && callSite.bootstrapMethod.member.asDexMethod().isIdenticalTo(typeSwitchMethod))) {
+      return DesugarDescription.nothing();
+    }
+    // Call the desugared method.
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (position,
+                freshLocalProvider,
+                localStackAllocator,
+                desugaringInfo,
+                eventConsumer,
+                theContext,
+                methodProcessingContext,
+                desugaringCollection,
+                dexItemFactory) -> {
+              // We add on stack (2) array, (3) dupped array, (4) index, (5) value.
+              localStackAllocator.allocateLocalStack(4);
+              List<CfInstruction> cfInstructions = generateLoadArguments(callSite);
+              generateInvokeToDesugaredMethod(
+                  methodProcessingContext, cfInstructions, theContext, eventConsumer);
+              return cfInstructions;
+            })
+        .build();
+  }
+
+  private void generateInvokeToDesugaredMethod(
+      MethodProcessingContext methodProcessingContext,
+      List<CfInstruction> cfInstructions,
+      ProgramMethod context,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.TYPE_SWITCH_HELPER,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .disableAndroidApiLevelCheck()
+                        .setProto(typeSwitchHelperProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            methodSig -> {
+                              CfCode code =
+                                  TypeSwitchMethods.TypeSwitchMethods_typeSwitch(
+                                      appView.dexItemFactory(), methodSig);
+                              if (appView.options().hasMappingFileSupport()) {
+                                return code.getCodeAsInlining(
+                                    methodSig,
+                                    true,
+                                    context.getReference(),
+                                    false,
+                                    appView.dexItemFactory());
+                              }
+                              return code;
+                            }));
+    eventConsumer.acceptTypeSwitchMethod(method, context);
+    cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+  }
+
+  private List<CfInstruction> generateLoadArguments(DexCallSite callSite) {
+    // 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(appView.dexItemFactory().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));
+      if (bootstrapArg.isDexValueType()) {
+        cfInstructions.add(new CfConstClass(bootstrapArg.asDexValueType().getValue()));
+      } else if (bootstrapArg.isDexValueInt()) {
+        cfInstructions.add(
+            new CfConstNumber(bootstrapArg.asDexValueInt().getValue(), ValueType.INT));
+        cfInstructions.add(
+            new CfInvoke(
+                Opcodes.INVOKESTATIC, appView.dexItemFactory().integerMembers.valueOf, false));
+      } else if (bootstrapArg.isDexValueString()) {
+        cfInstructions.add(new CfConstString(bootstrapArg.asDexValueString().getValue()));
+      } else {
+        assert bootstrapArg.isDexValueConstDynamic();
+        throw new Unreachable("TODO(b/336510513): Enum descriptor should be implemented");
+      }
+      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
new file mode 100644
index 0000000..97115c6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchDesugaringEventConsumer.java
@@ -0,0 +1,12 @@
+// 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.graph.ProgramMethod;
+
+public interface TypeSwitchDesugaringEventConsumer {
+
+  void acceptTypeSwitchMethod(ProgramMethod typeSwitchMethod, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
new file mode 100644
index 0000000..ed9cbdf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
@@ -0,0 +1,172 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateTypeSwitchMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.desugar.typeswitch;
+
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.frame.FrameType;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
+
+public final class TypeSwitchMethods {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("[Ljava/lang/Object;");
+  }
+
+  public static CfCode TypeSwitchMethods_typeSwitch(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        5,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfIf(IfType.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfConstNumber(-1, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("[Ljava/lang/Object;"))
+                    })),
+            new CfLoad(ValueType.INT, 1),
+            new CfStore(ValueType.INT, 3),
+            label3,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("[Ljava/lang/Object;")),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.INT, 3),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfArrayLength(),
+            new CfIfCmp(IfType.GE, ValueType.INT, label11),
+            label4,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.INT, 3),
+            new CfArrayLoad(MemberType.OBJECT),
+            new CfStore(ValueType.OBJECT, 4),
+            label5,
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfInstanceOf(factory.classType),
+            new CfIf(IfType.EQ, ValueType.INT, label8),
+            label6,
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfCheckCast(factory.classType),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(factory.booleanType, factory.objectType),
+                    factory.createString("isInstance")),
+                false),
+            new CfIf(IfType.EQ, ValueType.INT, label10),
+            label7,
+            new CfLoad(ValueType.INT, 3),
+            new CfReturn(ValueType.INT),
+            label8,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("[Ljava/lang/Object;")),
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.objectType,
+                    factory.createProto(factory.booleanType, factory.objectType),
+                    factory.createString("equals")),
+                false),
+            new CfIf(IfType.EQ, ValueType.INT, label10),
+            label9,
+            new CfLoad(ValueType.INT, 3),
+            new CfReturn(ValueType.INT),
+            label10,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("[Ljava/lang/Object;")),
+                      FrameType.intType()
+                    })),
+            new CfIinc(3, 1),
+            new CfGoto(label3),
+            label11,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType(),
+                      FrameType.initializedNonNullReference(
+                          factory.createType("[Ljava/lang/Object;"))
+                    })),
+            new CfConstNumber(-2, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label12),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+}
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 40b7606..2faec2c 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,12 @@
   }
 
   @Override
+  public void acceptTypeSwitchMethod(ProgramMethod typeSwitchMethod, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(typeSwitchMethod, context);
+    parent.acceptBackportedMethod(typeSwitchMethod, context);
+  }
+
+  @Override
   public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
     additionsCollection.addMethodAndHolderIfContextIsInProfile(backportedMethod, context);
     parent.acceptBackportedMethod(backportedMethod, 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 fad4880..e88703b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -65,6 +65,8 @@
   public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy");
 
   // Method synthetics.
+  public final SyntheticKind TYPE_SWITCH_HELPER =
+      generator.forSingleMethodWithGlobalMerging("TypeSwitch");
   public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
       generator.forSingleMethodWithGlobalMerging("CheckNotZero");
   public final SyntheticKind RECORD_HELPER = generator.forSingleMethodWithGlobalMerging("Record");
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 18de8df..80ab442 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -718,6 +718,8 @@
   public DesugarState desugarState = DesugarState.ON;
   // Flag to turn on/off partial VarHandle desugaring.
   public boolean enableVarHandleDesugaring = false;
+  // Flag to turn on/off partial Type switch desugaring.
+  public boolean enableTypeSwitchDesugaring = true;
   // Flag to turn off backport methods (and report errors if triggered).
   public boolean disableBackportsAndReportIfTriggered = false;
   // Flag to turn on/off reduction of nest to improve class merging optimizations.
diff --git a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
index 144af9d..0b5bfa4 100644
--- a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
@@ -4,10 +4,8 @@
 package switchpatternmatching;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -16,11 +14,13 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
+import switchpatternmatching.TypeSwitchTest.Main;
 
 @RunWith(Parameterized.class)
 public class StringSwitchTest extends TestBase {
@@ -81,21 +81,22 @@
   @Test
   public void testD8() throws Exception {
     parameters.assumeDexRuntime();
-    assertThrows(
-        CompilationFailedException.class,
-        () -> testForD8().addInnerClasses(getClass()).setMinApi(parameters).compile());
+    testForD8()
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void testR8() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addInnerClasses(getClass())
-                .setMinApi(parameters)
-                .addKeepMainRule(Main.class)
-                .compile());
+    Assume.assumeTrue("For Cf we should compile with Jdk 21 library", parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   static class Main {
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
index f272cde..12e5fad 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
@@ -4,10 +4,8 @@
 package switchpatternmatching;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -16,6 +14,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,12 +33,7 @@
 
   public static String EXPECTED_OUTPUT =
       StringUtils.lines(
-          "null",
-          "String",
-          "Color: RED",
-          "Record class: Point[i=0, j=0]",
-          "Array of int, length = 0",
-          "Other");
+          "null", "String", "Color: RED", "Point: [0;0]", "Array of int, length = 0", "Other");
 
   @Test
   public void testJvm() throws Exception {
@@ -47,12 +41,12 @@
     CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
     // javac generated an invokedynamic using bootstrap method argument of an arrya type (sort 9
     // is org.objectweb.asm.Type.ARRAY).
-      inspector
-          .clazz(Main.class)
-          .uniqueMethodWithOriginalName("typeSwitch")
-          .streamInstructions()
-          .filter(InstructionSubject::isInvokeDynamic)
-          .count();
+    inspector
+        .clazz(Main.class)
+        .uniqueMethodWithOriginalName("typeSwitch")
+        .streamInstructions()
+        .filter(InstructionSubject::isInvokeDynamic)
+        .count();
     // javac generated an invokedynamic using bootstrap method
     // java.lang.runtime.SwitchBootstraps.typeSwitch.
     assertEquals(
@@ -94,21 +88,22 @@
   @Test
   public void testD8() throws Exception {
     parameters.assumeDexRuntime();
-    assertThrows(
-        CompilationFailedException.class,
-        () -> testForD8().addInnerClasses(getClass()).setMinApi(parameters).compile());
+    testForD8()
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void testR8() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addInnerClasses(getClass())
-                .setMinApi(parameters)
-                .addKeepMainRule(Main.class)
-                .compile());
+    Assume.assumeTrue("For Cf we should compile with Jdk 21 library", parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   record Point(int i, int j) {}
@@ -126,7 +121,7 @@
         case null -> System.out.println("null");
         case String string -> System.out.println("String");
         case Color color -> System.out.println("Color: " + color);
-        case Point point -> System.out.println("Record class: " + point);
+        case Point point -> System.out.println("Point: [" + point.i + ";" + point.j + "]");
         case int[] intArray -> System.out.println("Array of int, length = " + intArray.length);
         default -> System.out.println("Other");
       }
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
index 5d1443b..1a0b31a 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
@@ -47,11 +47,17 @@
     System.out.println(commandString);
     Process process = builder.start();
     ProcessResult result = ToolHelper.drainProcessOutputStreams(process, commandString);
+    String content;
     if (result.exitCode != 0) {
-      throw new IllegalStateException(result.toString());
+      // TODO(b/338309049): Fix the formatting and throw again here.
+      System.out.println("Google formatting failed");
+      System.out.println(result.stderr);
+      System.out.println("Falling back to unformatted code generation");
+      content = String.join("\n", Files.readAllLines(tempFile));
+    } else {
+      content = result.stdout;
     }
     // Fix line separators.
-    String content = result.stdout;
     if (!StringUtils.LINE_SEPARATOR.equals("\n")) {
       return content.replace(StringUtils.LINE_SEPARATOR, "\n");
     }
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/GenerateTypeSwitchMethods.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/GenerateTypeSwitchMethods.java
new file mode 100644
index 0000000..5c5ec95
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/GenerateTypeSwitchMethods.java
@@ -0,0 +1,71 @@
+// 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.cfmethodgeneration;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.TestDataSourceSet;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateTypeSwitchMethods extends MethodGenerationBase {
+  private final DexType GENERATED_TYPE =
+      factory.createType("Lcom/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods;");
+  private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of(TypeSwitchMethods.class);
+
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+  }
+
+  public GenerateTypeSwitchMethods(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected DexType getGeneratedType() {
+    return GENERATED_TYPE;
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return METHOD_TEMPLATE_CLASSES;
+  }
+
+  @Override
+  protected int getYear() {
+    return 2024;
+  }
+
+  @Test
+  public void testRecordMethodsGenerated() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+    sorted.sort(Comparator.comparing(Class::getTypeName));
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+    assertEquals(
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+  }
+
+  public static void main(String[] args) throws Exception {
+    setUpSystemPropertiesForMain(
+        TestDataSourceSet.TESTS_JAVA_8, TestDataSourceSet.TESTBASE_DATA_LOCATION);
+    new GenerateTypeSwitchMethods(null).generateMethodsAndWriteThemToFile();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 8a24f74..e10ab64 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -371,7 +371,9 @@
     }
   }
 
-  protected static void setUpSystemPropertiesForMain(TestDataSourceSet sourceSet) {
+  protected static void setUpSystemPropertiesForMain(
+      TestDataSourceSet sourceSet, TestDataSourceSet sourceSet2) {
     System.setProperty("TEST_DATA_LOCATION", sourceSet.getBuildDir().toString());
+    System.setProperty("TESTBASE_DATA_LOCATION", sourceSet2.getBuildDir().toString());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
new file mode 100644
index 0000000..58bd939
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
@@ -0,0 +1,29 @@
+// 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.cfmethodgeneration;
+
+public class TypeSwitchMethods {
+
+  public static int typeSwitch(Object obj, int restart, Object[] tests) {
+    if (obj == null) {
+      return -1;
+    }
+    for (int i = restart; i < tests.length; i++) {
+      Object test = tests[i];
+      if (test instanceof Class<?>) {
+        if (((Class<?>) test).isInstance(obj)) {
+          return i;
+        }
+      } else {
+        // This is an integer, a string or an enum instance.
+        if (obj.equals(test)) {
+          return i;
+        }
+      }
+    }
+    // Default case.
+    return -2;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/generation/GenerateDesugaredLibraryBridge.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/generation/GenerateDesugaredLibraryBridge.java
index 71541b8..6c83010 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/generation/GenerateDesugaredLibraryBridge.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/generation/GenerateDesugaredLibraryBridge.java
@@ -93,7 +93,8 @@
   }
 
   public static void main(String[] args) throws Exception {
-    setUpSystemPropertiesForMain(TestDataSourceSet.TESTS_JAVA_8);
+    setUpSystemPropertiesForMain(
+        TestDataSourceSet.TESTS_JAVA_8, TestDataSourceSet.TESTBASE_DATA_LOCATION);
     new GenerateDesugaredLibraryBridge(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
index ba1153a..9e491b3 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
@@ -64,7 +64,8 @@
   }
 
   public static void main(String[] args) throws Exception {
-    setUpSystemPropertiesForMain(TestDataSourceSet.TESTS_JAVA_8);
+    setUpSystemPropertiesForMain(
+        TestDataSourceSet.TESTS_JAVA_8, TestDataSourceSet.TESTBASE_DATA_LOCATION);
     new GenerateRecordMethods(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
index d6ea6e2..11a7512 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
@@ -66,7 +66,8 @@
   }
 
   public static void main(String[] args) throws Exception {
-    setUpSystemPropertiesForMain(TestDataSourceSet.TESTS_JAVA_8);
+    setUpSystemPropertiesForMain(
+        TestDataSourceSet.TESTS_JAVA_8, TestDataSourceSet.TESTBASE_DATA_LOCATION);
     new GenerateEnumUnboxingMethods(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 7cd9e28..9e964ea 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -170,7 +170,8 @@
   }
 
   public static void main(String[] args) throws Exception {
-    setUpSystemPropertiesForMain(TestDataSourceSet.TESTS_JAVA_8);
+    setUpSystemPropertiesForMain(
+        TestDataSourceSet.TESTS_JAVA_8, TestDataSourceSet.TESTBASE_DATA_LOCATION);
     new GenerateBackportMethods(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
index 330d280..08281ff 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
@@ -185,7 +185,8 @@
   }
 
   public static void main(String[] args) throws Exception {
-    setUpSystemPropertiesForMain(TestDataSourceSet.TESTS_JAVA_8);
+    setUpSystemPropertiesForMain(
+        TestDataSourceSet.TESTS_JAVA_8, TestDataSourceSet.TESTBASE_DATA_LOCATION);
     new GenerateVarHandleMethods(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
index 7136058..aa73357 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
@@ -59,7 +59,8 @@
   }
 
   public static void main(String[] args) throws Exception {
-    setUpSystemPropertiesForMain(TestDataSourceSet.TESTS_JAVA_8);
+    setUpSystemPropertiesForMain(
+        TestDataSourceSet.TESTS_JAVA_8, TestDataSourceSet.TESTBASE_DATA_LOCATION);
     new GenerateCfUtilityMethodsForCodeOptimizations(
             getTestParameters().withNoneRuntime().build().iterator().next())
         .generateMethodsAndWriteThemToFile();
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index 745b3dd..ded4662 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -137,6 +137,7 @@
   public enum TestDataSourceSet {
     LEGACY(null),
     TESTS_JAVA_8("tests_java_8/build/classes/java/test"),
+    TESTBASE_DATA_LOCATION("testbase/build/classes/java/main"),
     TESTS_BOOTSTRAP("tests_bootstrap/build/classes/java/test"),
     SPECIFIED_BY_GRADLE_PROPERTY(null);