Use switches for the enum unboxer generated methods
Bug: b/167942775
Change-Id: I418b2b729080f15daf27798082814a6694dac966
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldData.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldData.java
index 69a5179..742750d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldData.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumInstanceFieldData.java
@@ -108,6 +108,10 @@
return mapping.get(unboxedEnumValue);
}
+ public ImmutableInt2ReferenceSortedMap<AbstractValue> getMapping() {
+ return mapping;
+ }
+
public void forEach(BiConsumer<? super Integer, ? super AbstractValue> consumer) {
mapping.forEach(consumer);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 183243b..6b8b450 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -69,8 +69,8 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@@ -739,7 +739,7 @@
fixupProto(factory.prependHolderToProto(representative.getReference())),
localUtilityClass.getType(),
newMethodSignature -> !localUtilityMethods.containsKey(newMethodSignature));
- Int2ObjectSortedMap<DexMethod> methodMap = new Int2ObjectLinkedOpenHashMap<>();
+ Int2ReferenceSortedMap<DexMethod> methodMap = new Int2ReferenceLinkedOpenHashMap<>();
IdentityHashMap<DexType, DexMethod> typeToMethod = new IdentityHashMap<>();
map.forEach(
(methodReference, newMethodReference) ->
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 755f207..abff1ff 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfIf;
-import com.android.tools.r8.cf.code.CfIfCmp;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfLabel;
@@ -21,6 +20,8 @@
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.cf.code.CfSwitch;
+import com.android.tools.r8.cf.code.CfSwitch.Kind;
import com.android.tools.r8.cf.code.CfThrow;
import com.android.tools.r8.cf.code.frame.FrameType;
import com.android.tools.r8.errors.Unreachable;
@@ -36,9 +37,12 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
-import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.BooleanUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.BiConsumer;
import org.objectweb.asm.Opcodes;
public abstract class EnumUnboxingCfCodeProvider extends SyntheticCfCodeProvider {
@@ -68,17 +72,66 @@
}
}
+ <T> void addCfSwitch(
+ List<CfInstruction> instructions,
+ BiConsumer<List<CfInstruction>, T> generateCase,
+ Int2ReferenceSortedMap<T> cases,
+ T defaultCase,
+ CfFrame.Builder frameBuilder,
+ boolean defaultThrows) {
+ // The switch is *always* going to be converted to IR then either to dex or back to cf. The IR
+ // representation does not differentiate table and look-up switches, and generates the most
+ // appropriate one in the back-end.
+ // The keys should however be sorted in natural order for packing to table switch to be
+ // generated, which should be implicitly the case with the Int2ObjectSortedMap.
+ assert defaultCase == null || !defaultThrows;
+ boolean hasDefaultCase = defaultCase != null || defaultThrows;
+ assert cases.size() + BooleanUtils.intValue(hasDefaultCase) >= 2;
+ int[] keys = new int[cases.size() - BooleanUtils.intValue(!hasDefaultCase)];
+ List<CfLabel> targets = new ArrayList<>(keys.length);
+ int index = 0;
+ for (int key : cases.keySet()) {
+ if (index < keys.length) {
+ keys[index++] = key;
+ targets.add(new CfLabel());
+ }
+ }
+ CfLabel defaultLabel = new CfLabel();
+ T actualDefaultCase = hasDefaultCase ? defaultCase : cases.get(cases.lastIntKey());
+ assert ArrayUtils.isSorted(keys);
+ assert keys.length == targets.size();
+ // We expect the value to switch on to be in local slot 0.
+ instructions.add(new CfLoad(ValueType.fromDexType(appView.dexItemFactory().intType), 0));
+ instructions.add(new CfSwitch(Kind.LOOKUP, defaultLabel, keys, targets));
+ for (int i = 0; i < keys.length; i++) {
+ instructions.add(targets.get(i));
+ instructions.add(frameBuilder.build());
+ generateCase.accept(instructions, cases.get(keys[i]));
+ assert instructions.get(instructions.size() - 1).isReturn();
+ }
+ instructions.add(defaultLabel);
+ instructions.add(frameBuilder.build());
+ if (defaultThrows) {
+ // default: throw null;
+ instructions.add(new CfConstNull());
+ instructions.add(new CfThrow());
+ } else {
+ generateCase.accept(instructions, actualDefaultCase);
+ assert instructions.get(instructions.size() - 1).isReturn();
+ }
+ }
+
public static class EnumUnboxingMethodDispatchCfCodeProvider extends EnumUnboxingCfCodeProvider {
private final GraphLens codeLens;
private final DexMethod superEnumMethod;
- private final Int2ObjectSortedMap<DexMethod> methodMap;
+ private final Int2ReferenceSortedMap<DexMethod> methodMap;
public EnumUnboxingMethodDispatchCfCodeProvider(
AppView<?> appView,
DexType holder,
DexMethod superEnumMethod,
- Int2ObjectSortedMap<DexMethod> methodMap) {
+ Int2ReferenceSortedMap<DexMethod> methodMap) {
super(appView, holder);
this.codeLens = appView.codeLens();
this.superEnumMethod = superEnumMethod;
@@ -87,45 +140,15 @@
@Override
public CfCodeWithLens generateCfCode() {
- // TODO(b/167942775): Should use a table-switch for large enums (maybe same threshold in the
- // rewriter of switchmaps).
-
assert !methodMap.isEmpty();
- DexItemFactory factory = appView.dexItemFactory();
- boolean hasDefaultCase = superEnumMethod != null;
+ List<CfInstruction> instructions = new ArrayList<>();
DexMethod representative = methodMap.values().iterator().next();
-
- int invokeSize = representative.getParameters().size() + 2;
- int branchSize = 5;
- int instructionsSize =
- methodMap.size() * (invokeSize + branchSize)
- + (hasDefaultCase ? invokeSize : -branchSize);
- List<CfInstruction> instructions = new ArrayList<>(instructionsSize);
-
CfFrame.Builder frameBuilder = CfFrame.builder();
for (DexType parameter : representative.getParameters()) {
frameBuilder.appendLocal(FrameType.initialized(parameter));
}
- methodMap.forEach(
- (unboxedEnumValue, method) -> {
- boolean lastCase = methodMap.lastIntKey() == unboxedEnumValue && !hasDefaultCase;
- if (!lastCase) {
- CfLabel dest = new CfLabel();
- instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
- instructions.add(new CfConstNumber(unboxedEnumValue, ValueType.INT));
- instructions.add(new CfIfCmp(IfType.NE, ValueType.INT, dest));
- addReturnInvoke(instructions, method);
- instructions.add(dest);
- instructions.add(frameBuilder.build());
- } else {
- addReturnInvoke(instructions, method);
- }
- });
-
- if (hasDefaultCase) {
- addReturnInvoke(instructions, superEnumMethod);
- }
- assert instructions.size() == instructionsSize;
+ addCfSwitch(
+ instructions, this::addReturnInvoke, methodMap, superEnumMethod, frameBuilder, false);
return new CfCodeWithLens(getHolder(), defaultMaxStack(), defaultMaxLocals(), instructions);
}
@@ -187,44 +210,32 @@
@Override
public CfCode generateCfCode() {
- // TODO(b/167942775): Should use a table-switch for large enums (maybe same threshold in the
- // rewriter of switchmaps).
// Generated static method, for class com.x.MyEnum {A(10),B(20);} would look like:
// String UtilityClass#com.x.MyEnum_toString(int i) {
- // if (i == 1) { return 10;}
- // if (i == 2) { return 20;}
- // throw null;
+ // switch (i) {
+ // case 1: return 10;
+ // case 2: return 20;
+ // default: throw null; // or throw default value.
+ // }
+ // }
DexItemFactory factory = appView.dexItemFactory();
List<CfInstruction> instructions = new ArrayList<>();
-
- // if (i == 1) { return 10;}
- // if (i == 2) { return 20;}
CfFrame.Builder frameBuilder =
CfFrame.builder().appendLocal(FrameType.initialized(factory.intType));
- fieldDataMap.forEach(
- (unboxedEnumValue, value) -> {
- CfLabel dest = new CfLabel();
- instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
- instructions.add(new CfConstNumber(unboxedEnumValue, ValueType.INT));
- instructions.add(new CfIfCmp(IfType.NE, ValueType.INT, dest));
- addCfInstructionsForAbstractValue(instructions, value, returnType);
- instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
- instructions.add(dest);
- instructions.add(frameBuilder.build());
- });
-
- if (nullValue != null) {
- // return "null"
- addCfInstructionsForAbstractValue(instructions, nullValue, returnType);
- instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
- } else {
- // throw null;
- instructions.add(new CfConstNull());
- instructions.add(new CfThrow());
- }
-
+ addCfSwitch(
+ instructions,
+ this::addReturnValue,
+ fieldDataMap.getMapping(),
+ nullValue,
+ frameBuilder,
+ nullValue == null);
return standardCfCodeFromInstructions(instructions);
}
+
+ private void addReturnValue(List<CfInstruction> instructions, AbstractValue value) {
+ addCfInstructionsForAbstractValue(instructions, value, returnType);
+ instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
+ }
}
public static class EnumUnboxingValueOfCfCodeProvider extends EnumUnboxingCfCodeProvider {