Replace the use of switchmaps with direct use of ordinal.
Bug:
Change-Id: Iee5cd16ba848617a7eaac3d55283621c54b36ff1
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e98503f..f70a295 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -43,49 +43,65 @@
return this;
}
- /** Enable/disable tree shaking. This overrides any settings in proguard configuration files. */
+ /**
+ * Enable/disable tree shaking. This overrides any settings in proguard configuration files.
+ */
public Builder setTreeShaking(boolean useTreeShaking) {
treeShaking = Optional.of(useTreeShaking);
return this;
}
- /** Enable/disable minification. This overrides any settings in proguard configuration files. */
+ /**
+ * Enable/disable minification. This overrides any settings in proguard configuration files.
+ */
public Builder setMinification(boolean useMinification) {
minification = Optional.of(useMinification);
return this;
}
- /** Add proguard configuration file resources for automatic main dex list calculation. */
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
public Builder addMainDexRules(Path... paths) {
Collections.addAll(mainDexRules, paths);
return this;
}
- /** Add proguard configuration file resources for automatic main dex list calculation. */
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
public Builder addMainDexRules(List<Path> paths) {
mainDexRules.addAll(paths);
return this;
}
- /** Add proguard configuration file resources. */
+ /**
+ * Add proguard configuration file resources.
+ */
public Builder addProguardConfigurationFiles(Path... paths) {
Collections.addAll(proguardConfigFiles, paths);
return this;
}
- /** Add proguard configuration file resources. */
+ /**
+ * Add proguard configuration file resources.
+ */
public Builder addProguardConfigurationFiles(List<Path> paths) {
proguardConfigFiles.addAll(paths);
return this;
}
- /** Set a proguard mapping file resource. */
+ /**
+ * Set a proguard mapping file resource.
+ */
public Builder setProguardMapFile(Path path) {
getAppBuilder().setProguardMapFile(path);
return this;
}
- /** Set a package distribution file resource. */
+ /**
+ * Set a package distribution file resource.
+ */
public Builder setPackageDistributionFile(Path path) {
getAppBuilder().setPackageDistributionFile(path);
return this;
@@ -154,6 +170,7 @@
// Internal state to verify parsing properties not enforced by the builder.
private static class ParseState {
+
CompilationMode mode = null;
}
@@ -313,6 +330,7 @@
return useMinification;
}
+ @Override
InternalOptions getInternalOptions() {
InternalOptions internal = new InternalOptions(proguardConfiguration.getDexItemFactory());
assert !internal.debug;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 40b0ead..cb9fb20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -82,6 +82,7 @@
public DexString valueOfMethodName = createString("valueOf");
public DexString getClassMethodName = createString("getClass");
+ public DexString ordinalMethodName = createString("ordinal");
public DexString stringDescriptor = createString("Ljava/lang/String;");
public DexString objectDescriptor = createString("Ljava/lang/Object;");
@@ -139,6 +140,7 @@
}
public class LongMethods {
+
public DexMethod compare;
private LongMethods() {
@@ -148,18 +150,20 @@
}
public class ThrowableMethods {
+
public final DexMethod addSuppressed;
public final DexMethod getSuppressed;
private ThrowableMethods() {
addSuppressed = createMethod(throwableDescriptor,
- createString("addSuppressed"), voidDescriptor, new DexString[] { throwableDescriptor });
+ createString("addSuppressed"), voidDescriptor, new DexString[]{throwableDescriptor});
getSuppressed = createMethod(throwableDescriptor,
createString("getSuppressed"), throwableArrayDescriptor, DexString.EMPTY_ARRAY);
}
}
public class ObjectMethods {
+
public DexMethod getClass;
private ObjectMethods() {
@@ -169,6 +173,7 @@
}
public class ObjectsMethods {
+
public DexMethod requireNonNull;
private ObjectsMethods() {
@@ -178,6 +183,7 @@
}
public class StringBuildingMethods {
+
public DexMethod appendBoolean;
public DexMethod appendChar;
public DexMethod appendCharArray;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index db44f9a..35cf808 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -840,6 +841,15 @@
}
/**
+ * Remove an instruction.
+ */
+ public void removeInstruction(Instruction toRemove) {
+ int index = instructions.indexOf(toRemove);
+ assert index >= 0;
+ removeInstructions(Collections.singletonList(index));
+ }
+
+ /**
* Create a new basic block with a single goto instruction.
*
* <p>The constructed basic block has no predecessors and has one
@@ -1101,7 +1111,8 @@
}
}
- /** Append catch handlers from another block <code>fromBlock</code> (which must have catch
+ /**
+ * Append catch handlers from another block <code>fromBlock</code> (which must have catch
* handlers) to the catch handlers of this block.
*
* Note that after appending catch handlers their targets are referenced by both
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 96e00f2..a30444e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -73,10 +73,18 @@
}
}
- private int numberOfKeys() {
+ public int numberOfKeys() {
return targetBlockIndices.length;
}
+ public int getKey(int index) {
+ if (type == Type.PACKED) {
+ return keys[0] + index;
+ } else {
+ return keys[index];
+ }
+ }
+
public int[] targetBlockIndices() {
return targetBlockIndices;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 12f0aa2..b92ff71 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -412,7 +412,8 @@
if (!options.debug) {
return;
}
- Value value = writeRegister(register, MoveType.fromConstType(type), ThrowingInfo.NO_THROW, null);
+ Value value = writeRegister(register, MoveType.fromConstType(type), ThrowingInfo.NO_THROW,
+ null);
assert value.getLocalInfo() == null;
addInstruction(new DebugLocalUninitialized(type, value));
}
@@ -637,7 +638,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(left, type);
Value in2 = readNumericRegister(right, type);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Div instruction = new Div(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
add(instruction);
@@ -648,7 +650,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(value, type);
Value in2 = readLiteral(type, constant);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Div instruction = new Div(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
add(instruction);
@@ -697,7 +700,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(left, type);
Value in2 = readNumericRegister(right, type);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Rem instruction = new Rem(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
addInstruction(instruction);
@@ -708,7 +712,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(value, type);
Value in2 = readLiteral(type, constant);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Rem instruction = new Rem(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
addInstruction(instruction);
@@ -1119,6 +1124,7 @@
Value switchValue = readRegister(value, MoveType.SINGLE);
// Change a switch with just one case to an if.
+ // TODO(62247472): Move these into their own pass.
if (numberOfTargets == 1) {
addSwitchIf(keys[0], value, labelOffsets[0], fallthroughOffset);
return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7aef78c..091714a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -426,6 +426,7 @@
if (memberValuePropagation != null) {
memberValuePropagation.rewriteWithConstantValues(code);
}
+ codeRewriter.removeSwitchMaps(code);
if (options.inlineAccessors && inliner != null) {
inliner.performInlining(method, code, callGraph);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 81a9e55..9034c37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -9,9 +9,11 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexClass;
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.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Binop;
@@ -28,6 +30,7 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.JumpInstruction;
@@ -37,15 +40,26 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.code.Switch.Type;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -303,6 +317,244 @@
}
/**
+ * Inline the indirection of switch maps into the switch statement.
+ * <p>
+ * To ensure binary compatibility, javac generated code does not use ordinal values of enums
+ * directly in switch statements but instead generates a companion class that computes a mapping
+ * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can
+ * analyze these maps and inline the indirection into the switch map again.
+ * <p>
+ * In particular, we look for code of the form
+ *
+ * <blockquote><pre>
+ * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+ * ...
+ * }
+ * </pre></blockquote>
+ * See {@link #extractIndexMapFrom} and {@link #extractOrdinalsMapFor} for
+ * details of the companion class and ordinals computation.
+ */
+ public void removeSwitchMaps(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction insn = it.next();
+ // Pattern match a switch on a switch map as input.
+ if (insn.isSwitch()) {
+ Switch switchInsn = insn.asSwitch();
+ Instruction input = switchInsn.inValues().get(0).definition;
+ if (input == null || !input.isArrayGet()) {
+ continue;
+ }
+ ArrayGet arrayGet = input.asArrayGet();
+ Instruction index = arrayGet.index().definition;
+ if (index == null || !index.isInvokeVirtual()) {
+ continue;
+ }
+ InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
+ DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
+ DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
+ if (enumClass == null
+ || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
+ || ordinalMethod.name != dexItemFactory.ordinalMethodName
+ || ordinalMethod.proto.returnType != dexItemFactory.intType
+ || !ordinalMethod.proto.parameters.isEmpty()) {
+ continue;
+ }
+ Instruction array = arrayGet.array().definition;
+ if (array == null || !array.isStaticGet()) {
+ continue;
+ }
+ StaticGet staticGet = array.asStaticGet();
+ if (staticGet.getField().name.toSourceString().startsWith("$SwitchMap$")) {
+ Int2ReferenceMap<DexField> indexMap = extractIndexMapFrom(staticGet.getField());
+ if (indexMap == null || indexMap.isEmpty()) {
+ continue;
+ }
+ // Due to member rebinding, only the fields are certain to provide the actual enums
+ // class.
+ DexType switchMapHolder = indexMap.values().iterator().next().getHolder();
+ Reference2IntMap ordinalsMap = extractOrdinalsMapFor(switchMapHolder);
+ if (ordinalsMap != null) {
+ Int2IntMap targetMap = new Int2IntArrayMap();
+ int keys[] = new int[switchInsn.numberOfKeys()];
+ for (int i = 0; i < keys.length; i++) {
+ keys[i] = ordinalsMap.getInt(indexMap.get(switchInsn.getKey(i)));
+ targetMap.put(keys[i], switchInsn.targetBlockIndices()[i]);
+ }
+ Arrays.sort(keys);
+ int[] targets = new int[keys.length];
+ for (int i = 0; i < keys.length; i++) {
+ targets[i] = targetMap.get(keys[i]);
+ }
+
+ Switch newSwitch = new Switch(Type.SPARSE, ordinalInvoke.outValue(), keys,
+ targets, switchInsn.getFallthroughBlockIndex());
+ // Replace the switch itself.
+ it.replaceCurrentInstruction(newSwitch);
+ // If the original input to the switch is now unused, remove it too. It is not dead
+ // as it might have side-effects but we ignore these here.
+ if (arrayGet.outValue().numberOfUsers() == 0) {
+ arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
+ arrayGet.getBlock().removeInstruction(arrayGet);
+ }
+ if (staticGet.outValue().numberOfUsers() == 0) {
+ assert staticGet.inValues().isEmpty();
+ staticGet.getBlock().removeInstruction(staticGet);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Extracts the mapping from ordinal values to switch case constants.
+ * <p>
+ * This is done by pattern-matching on the class initializer of the synthetic switch map class.
+ * For a switch
+ *
+ * <blockquote><pre>
+ * switch (day) {
+ * case WEDNESDAY:
+ * case FRIDAY:
+ * System.out.println("3 or 5");
+ * break;
+ * case SUNDAY:
+ * System.out.println("7");
+ * break;
+ * default:
+ * System.out.println("other");
+ * }
+ * </pre></blockquote>
+ *
+ * the generated companing class initializer will have the form
+ *
+ * <blockquote><pre>
+ * class Switches$1 {
+ * static {
+ * $SwitchMap$switchmaps$Days[Days.WEDNESDAY.ordinal()] = 1;
+ * $SwitchMap$switchmaps$Days[Days.FRIDAY.ordinal()] = 2;
+ * $SwitchMap$switchmaps$Days[Days.SUNDAY.ordinal()] = 3;
+ * }
+ * </pre></blockquote>
+ *
+ * Note that one map per class is generated, so the map might contain additional entries as used
+ * by other switches in the class.
+ */
+ private Int2ReferenceMap<DexField> extractIndexMapFrom(DexField field) {
+ DexClass clazz = appInfo.definitionFor(field.getHolder());
+ if (!clazz.accessFlags.isSynthetic()) {
+ return null;
+ }
+ DexEncodedMethod initializer = clazz.getClassInitializer();
+ if (initializer == null || initializer.getCode() == null) {
+ return null;
+ }
+ IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions());
+ Int2ReferenceMap<DexField> switchMap = new Int2ReferenceArrayMap<>();
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator it = block.listIterator();
+ Instruction insn = it.nextUntil(i -> i.isStaticGet() && i.asStaticGet().getField() == field);
+ if (insn == null) {
+ continue;
+ }
+ for (Instruction use : insn.outValue().uniqueUsers()) {
+ if (use.isArrayPut()) {
+ Instruction index = use.asArrayPut().source().definition;
+ if (index == null || !index.isConstNumber()) {
+ return null;
+ }
+ int integerIndex = index.asConstNumber().getIntValue();
+ Instruction value = use.asArrayPut().index().definition;
+ if (value == null || !value.isInvokeVirtual()) {
+ return null;
+ }
+ InvokeVirtual invoke = value.asInvokeVirtual();
+ DexClass holder = appInfo.definitionFor(invoke.getInvokedMethod().holder);
+ if (holder == null ||
+ (!holder.accessFlags.isEnum() && holder.type != dexItemFactory.enumType)) {
+ return null;
+ }
+ Instruction enumGet = invoke.arguments().get(0).definition;
+ if (enumGet == null || !enumGet.isStaticGet()) {
+ return null;
+ }
+ DexField enumField = enumGet.asStaticGet().getField();
+ if (!appInfo.definitionFor(enumField.getHolder()).accessFlags.isEnum()) {
+ return null;
+ }
+ if (switchMap.put(integerIndex, enumField) != null) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+ return switchMap;
+ }
+
+ /**
+ * Extracts the ordinal values for an Enum class from the classes static initializer.
+ * <p>
+ * An Enum class has a field for each value. In the class initializer, each field is initialized
+ * to a singleton object that represents the value. This code matches on the corresponding call
+ * to the constructor (instance initializer) and extracts the value of the second argument, which
+ * is the ordinal.
+ */
+ private Reference2IntMap<DexField> extractOrdinalsMapFor(DexType enumClass) {
+ DexClass clazz = appInfo.definitionFor(enumClass);
+ if (clazz == null || clazz.isLibraryClass()) {
+ // We have to keep binary compatibility in tact for libraries.
+ return null;
+ }
+ DexEncodedMethod initializer = clazz.getClassInitializer();
+ if (!clazz.accessFlags.isEnum() || initializer == null || initializer.getCode() == null) {
+ return null;
+ }
+ IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions());
+ Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
+ ordinalsMap.defaultReturnValue(-1);
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ Instruction insn = it.next();
+ if (!insn.isStaticPut()) {
+ continue;
+ }
+ StaticPut staticPut = insn.asStaticPut();
+ if (staticPut.getField().type != enumClass) {
+ continue;
+ }
+ Instruction newInstance = staticPut.inValue().definition;
+ if (newInstance == null || !newInstance.isNewInstance()) {
+ continue;
+ }
+ Instruction ordinal = null;
+ for (Instruction ctorCall : newInstance.outValue().uniqueUsers()) {
+ if (!ctorCall.isInvokeDirect()) {
+ continue;
+ }
+ InvokeDirect invoke = ctorCall.asInvokeDirect();
+ if (!dexItemFactory.isConstructor(invoke.getInvokedMethod())
+ || invoke.arguments().size() < 3) {
+ continue;
+ }
+ ordinal = invoke.arguments().get(2).definition;
+ break;
+ }
+ if (ordinal == null || !ordinal.isConstNumber()) {
+ return null;
+ }
+ if (ordinalsMap.put(staticPut.getField(), ordinal.asConstNumber().getIntValue()) != -1) {
+ return null;
+ }
+ }
+ return ordinalsMap;
+ }
+
+ /**
* Rewrite all branch targets to the destination of trivial goto chains when possible.
* Does not rewrite fallthrough targets as that would require block reordering and the
* transformation only makes sense after SSA destruction where there are no phis.
@@ -920,11 +1172,11 @@
if (current.isInvokeMethod()) {
DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod();
if (invokedMethod == dexItemFactory.longMethods.compare) {
- List<Value> inValues = current.inValues();
- assert inValues.size() == 2;
- iterator.replaceCurrentInstruction(
- new Cmp(NumericType.LONG, Bias.NONE, current.outValue(), inValues.get(0),
- inValues.get(1)));
+ List<Value> inValues = current.inValues();
+ assert inValues.size() == 2;
+ iterator.replaceCurrentInstruction(
+ new Cmp(NumericType.LONG, Bias.NONE, current.outValue(), inValues.get(0),
+ inValues.get(1)));
} else if (!canUseObjectsNonNull
&& invokedMethod == dexItemFactory.objectsMethods.requireNonNull) {
// Rewrite calls to Objects.requireNonNull(Object) because Javac 9 start to use it for
diff --git a/src/test/examples/switchmaps/Colors.java b/src/test/examples/switchmaps/Colors.java
new file mode 100644
index 0000000..b2c8dc1
--- /dev/null
+++ b/src/test/examples/switchmaps/Colors.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, 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 switchmaps;
+
+public enum Colors {
+ RED("rar"), BLUE("blew"), GREEN("soylent"), GRAY("fifty");
+
+ private String aField;
+
+ Colors(String string) {
+ aField = string;
+ }
+
+ @Override
+ public String toString() {
+ return aField;
+ }
+}
diff --git a/src/test/examples/switchmaps/Days.java b/src/test/examples/switchmaps/Days.java
new file mode 100644
index 0000000..b484bb5
--- /dev/null
+++ b/src/test/examples/switchmaps/Days.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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 switchmaps;
+
+public enum Days {
+ MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
+}
diff --git a/src/test/examples/switchmaps/Switches.java b/src/test/examples/switchmaps/Switches.java
new file mode 100644
index 0000000..bf16856
--- /dev/null
+++ b/src/test/examples/switchmaps/Switches.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2017, 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 switchmaps;
+
+public class Switches {
+
+ public static void main(String... args) {
+ for (Days value : Days.values()) {
+ switchWithDefault(value);
+ switchFull(value);
+ }
+ for (Colors color : Colors.values()) {
+ switchOnColors(color);
+ }
+ }
+
+ private static void switchOnColors(Colors color) {
+ System.out.println(color.toString());
+ switch (color) {
+ case GRAY:
+ System.out.println("not really");
+ break;
+ case GREEN:
+ System.out.println("sooo green");
+ break;
+ default:
+ System.out.println("colorful");
+ }
+ }
+
+ private static void switchWithDefault(Days day) {
+ switch (day) {
+ case WEDNESDAY:
+ case FRIDAY:
+ System.out.println("3 or 5");
+ break;
+ case SUNDAY:
+ System.out.println("7");
+ break;
+ default:
+ System.out.println("other");
+ }
+ }
+
+ private static void switchFull(Days day) {
+ switch (day) {
+ case MONDAY:
+ case WEDNESDAY:
+ case THURSDAY:
+ System.out.println("1, 3 or 4");
+ case TUESDAY:
+ case FRIDAY:
+ System.out.println("2 or 5");
+ break;
+ case SUNDAY:
+ System.out.println("7");
+ break;
+ case SATURDAY:
+ System.out.println("6");
+ break;
+ default:
+ System.out.println("other");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 9e7588d..4a06085 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -100,6 +100,7 @@
{"minification.Minification", null},
{"enclosingmethod.Main", null},
{"interfaceinlining.Main", null},
+ {"switchmaps.Switches", null},
};
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a93fb12..bcb9f7d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -34,7 +34,7 @@
/**
* Write lines of text to a temporary file.
*/
- protected Path writeTextToTempFile(String... lines) throws IOException{
+ protected Path writeTextToTempFile(String... lines) throws IOException {
Path file = temp.newFile().toPath();
FileUtils.writeTextFile(file, lines);
return file;
@@ -86,7 +86,7 @@
/**
* Compile an application with R8 using the supplied proguard configuration.
*/
- protected AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
+ protected AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
return compileWithR8(readClasses(classes), proguardConfig);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
new file mode 100644
index 0000000..4e8881a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2017, 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.rewrite.switchmaps;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RewriteSwitchMapsTest extends TestBase {
+
+ private static final String JAR_FILE = "switchmaps.jar";
+ private static final String SWITCHMAP_CLASS_NAME = "switchmaps.Switches$1";
+ private static final String PG_CONFIG =
+ "-keep class switchmaps.Switches { public static void main(...); } " +
+ "-dontobfuscate";
+
+ @Test
+ public void checkSwitchMapsRemoved()
+ throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ builder.addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()));
+ builder.addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR).resolve(JAR_FILE));
+ AndroidApp result = compileWithR8(builder.build(), writeTextToTempFile(PG_CONFIG));
+ DexInspector inspector = new DexInspector(result);
+ Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
+ }
+}