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 =