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);