Improve the switch rewriting
- Split switch into ifs and/or switches (not just ifs and one switch)
- Ensure debug information is kept during rewriting
Saves ~7 KB on framework and ~64 KB on GMS Core V10.
Change-Id: I7d4f94ead500ee69ee3cd3d1a667804e9791f670
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 6def63a..08ab002 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
@@ -13,6 +13,8 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import com.google.common.primitives.Ints;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.List;
public class Switch extends JumpInstruction {
@@ -159,6 +161,11 @@
}
}
+ // Estimated size of the resulting dex instruction in code units (excluding the payload).
+ public static int estimatedDexSize() {
+ return 3;
+ }
+
public int numberOfKeys() {
return keys.length;
}
@@ -175,6 +182,14 @@
return targetBlockIndices;
}
+ public Int2ReferenceSortedMap<BasicBlock> getKeyToTargetMap() {
+ Int2ReferenceSortedMap<BasicBlock> result = new Int2ReferenceAVLTreeMap<>();
+ for (int i = 0; i < keys.length; i++) {
+ result.put(getKey(i), targetBlock(i));
+ }
+ return result;
+ }
+
@Override
public BasicBlock fallthroughBlock() {
return getBlock().getSuccessors().get(fallthroughBlockIndex);
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 a758223..7741fda 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
@@ -64,21 +64,23 @@
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.IteratorUtils;
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.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayList;
@@ -344,11 +346,25 @@
}
// TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
- public static class SwitchBuilder {
+ public abstract static class InstructionBuilder<T> {
+ protected int blockNumber;
+
+ public abstract T self();
+
+ public T setBlockNumber(int blockNumber) {
+ this.blockNumber = blockNumber;
+ return self();
+ }
+ }
+
+ public static class SwitchBuilder extends InstructionBuilder<SwitchBuilder> {
private Value value;
private Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
private BasicBlock fallthrough;
- private int blockNumber;
+
+ public SwitchBuilder self() {
+ return this;
+ }
public SwitchBuilder setValue(Value value) {
this.value = value;
@@ -365,11 +381,6 @@
return this;
}
- public SwitchBuilder setBlockNumber(int blockNumber) {
- this.blockNumber = blockNumber;
- return this;
- }
-
public BasicBlock build() {
final int NOT_FOUND = -1;
Object2IntMap<BasicBlock> targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>();
@@ -400,65 +411,134 @@
}
}
+ public static class IfBuilder extends InstructionBuilder<IfBuilder> {
+ private final IRCode code;
+ private Value left;
+ private int right;
+ private BasicBlock target;
+ private BasicBlock fallthrough;
+ private int blockNumber;
+
+ public IfBuilder(IRCode code) {
+ this.code = code;
+ }
+
+ public IfBuilder self() {
+ return this;
+ }
+
+ public IfBuilder setLeft(Value left) {
+ this.left = left;
+ return this;
+ }
+
+ public IfBuilder setRight(int right) {
+ this.right = right;
+ return this;
+ }
+
+ public IfBuilder setTarget(BasicBlock target) {
+ this.target = target;
+ return this;
+ }
+
+ public IfBuilder setFallthrough(BasicBlock fallthrough) {
+ this.fallthrough = fallthrough;
+ return this;
+ }
+
+ public BasicBlock build() {
+ assert target != null;
+ assert fallthrough != null;
+ If newIf;
+ BasicBlock ifBlock;
+ if (right != 0) {
+ ConstNumber rightConst = code.createIntConstant(right);
+ newIf = new If(Type.EQ, ImmutableList.of(left, rightConst.dest()));
+ ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, rightConst);
+ } else {
+ newIf = new If(Type.EQ, left);
+ ifBlock = BasicBlock.createIfBlock(blockNumber, newIf);
+ }
+ ifBlock.link(target);
+ ifBlock.link(fallthrough);
+ return ifBlock;
+ }
+ }
+
/**
* Covert the switch instruction to a sequence of if instructions checking for a specified
* set of keys, followed by a new switch with the remaining keys.
*/
private void convertSwitchToSwitchAndIfs(
IRCode code, ListIterator<BasicBlock> blocksIterator, BasicBlock originalBlock,
- InstructionListIterator iterator, Switch theSwitch, List<Integer> keysToRemove) {
+ InstructionListIterator iterator, Switch theSwitch,
+ List<IntList> switches, IntList keysToRemove) {
+
+ // Extract the information from the switch before removing it.
+ Int2ReferenceSortedMap<BasicBlock> keyToTarget = theSwitch.getKeyToTargetMap();
+ Set<Value> originalDebugValues = ImmutableSet.copyOf(theSwitch.getDebugValues());
+
// Split the switch instruction into its own block and remove it.
iterator.previous();
BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator);
assert !originalSwitchBlock.hasCatchHandlers();
assert originalSwitchBlock.getInstructions().size() == 1;
blocksIterator.remove();
-
- int nextBlockNumber = code.getHighestBlockNumber() + 1;
-
- // Collect targets for the keys to peel off, and create a new switch instruction without
- // these keys.
- SwitchBuilder switchBuilder = new SwitchBuilder();
- List<BasicBlock> peeledOffTargets = new ArrayList<>();
- for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
- BasicBlock target = theSwitch.targetBlock(i);
- if (!keysToRemove.contains(theSwitch.getKey(i))) {
- switchBuilder.addKeyAndTarget(theSwitch.getKey(i), theSwitch.targetBlock(i));
- } else {
- peeledOffTargets.add(target);
- }
- }
- assert peeledOffTargets.size() == keysToRemove.size();
- switchBuilder.setValue(theSwitch.value());
- switchBuilder.setFallthrough(theSwitch.fallthroughBlock());
- switchBuilder.setBlockNumber(nextBlockNumber++);
theSwitch.getBlock().detachAllSuccessors();
BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor();
assert theSwitch.getBlock().getPredecessors().size() == 0;
assert theSwitch.getBlock().getSuccessors().size() == 0;
assert block == originalBlock;
- BasicBlock newSwitchBlock = switchBuilder.build();
+ // Collect the new blocks for adding to the block list.
+ int nextBlockNumber = code.getHighestBlockNumber() + 1;
+ LinkedList<BasicBlock> newBlocks = new LinkedList<>();
- // Create if blocks for each of the peeled off keys, and link them into the graph.
- BasicBlock predecessor = originalBlock;
- for (int i = 0; i < keysToRemove.size(); i++) {
- int key = keysToRemove.get(i);
- BasicBlock peeledOffTarget = peeledOffTargets.get(i);
- ConstNumber keyConst = code.createIntConstant(key);
- If theIf = new If(Type.EQ, ImmutableList.of(keyConst.dest(), theSwitch.value()));
- BasicBlock ifBlock = BasicBlock.createIfBlock(nextBlockNumber++, theIf, keyConst);
- predecessor.link(ifBlock);
- ifBlock.link(peeledOffTarget);
- predecessor = ifBlock;
- blocksIterator.add(ifBlock);
- assert !peeledOffTarget.getPredecessors().contains(theSwitch.getBlock());
+ // Keep track of the current fallthrough, starting with the original.
+ BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
+
+ // Build the switch-blocks backwards, to always have the fallthrough block in hand.
+ for (int i = switches.size() - 1; i >= 0; i--) {
+ SwitchBuilder switchBuilder = new SwitchBuilder();
+ switchBuilder.setValue(theSwitch.value());
+ IntList keys = switches.get(i);
+ for (int j = 0; j < keys.size(); j++) {
+ int key = keys.getInt(j);
+ switchBuilder.addKeyAndTarget(key, keyToTarget.get(key));
+ }
+ switchBuilder
+ .setFallthrough(fallthroughBlock)
+ .setBlockNumber(nextBlockNumber++);
+ BasicBlock newSwitchBlock = switchBuilder.build();
+ newBlocks.addFirst(newSwitchBlock);
+ fallthroughBlock = newSwitchBlock;
}
- predecessor.link(newSwitchBlock);
- blocksIterator.add(newSwitchBlock);
- // The switch fallthrough block is still the same, and it is right after the new switch block.
- assert newSwitchBlock.exit().fallthroughBlock() == IteratorUtils.peekNext(blocksIterator);
+ // Build the if-blocks backwards, to always have the fallthrough block in hand.
+ for (int i = keysToRemove.size() - 1; i >= 0; i--) {
+ int key = keysToRemove.getInt(i);
+ BasicBlock peeledOffTarget = keyToTarget.get(key);
+ IfBuilder ifBuilder = new IfBuilder(code);
+ ifBuilder
+ .setLeft(theSwitch.value())
+ .setRight(key)
+ .setTarget(peeledOffTarget)
+ .setFallthrough(fallthroughBlock)
+ .setBlockNumber(nextBlockNumber++);
+ BasicBlock ifBlock = ifBuilder.build();
+ newBlocks.addFirst(ifBlock);
+ fallthroughBlock = ifBlock;
+ }
+
+ // Attach the debug values from the original switch to the first of the new instructions,
+ originalDebugValues.forEach(fallthroughBlock.exit()::addDebugValue);
+
+ // Finally link the block before the original switch to the new block sequence.
+ originalBlock.link(fallthroughBlock);
+
+ // Finally add the blocks.
+ newBlocks.forEach(blocksIterator::add);
}
public void rewriteSwitch(IRCode code) {
@@ -490,16 +570,16 @@
}
} else {
// Split keys into outliers and sequences.
- List<List<Integer>> sequences = new ArrayList<>();
- List<Integer> outliers = new ArrayList<>();
+ List<IntList> sequences = new ArrayList<>();
+ IntList outliers = new IntArrayList();
- List<Integer> current = new ArrayList<>();
+ IntList current = new IntArrayList();
int[] keys = theSwitch.getKeys();
int previousKey = keys[0];
current.add(previousKey);
for (int i = 1; i < keys.length; i++) {
assert current.size() > 0;
- assert current.get(current.size() - 1) == previousKey;
+ assert current.getInt(current.size() - 1) == previousKey;
int key = keys[i];
if (((long) key - (long) previousKey) > 1) {
if (current.size() == 1) {
@@ -507,7 +587,7 @@
} else {
sequences.add(current);;
}
- current = new ArrayList<>();
+ current = new IntArrayList();
}
current.add(key);
previousKey = key;
@@ -518,22 +598,45 @@
sequences.add(current);
}
- // Only check for rewrite if there is one sequence and one or two outliers.
- if (sequences.size() == 1 && outliers.size() <= 2) {
- // Get the existing dex size for the payload (excluding the switch itself).
- long currentSize = Switch.payloadSize(keys);
- // Estimate the dex size of the rewritten payload and the additional if instructions.
- long rewrittenSize = Switch.payloadSize(sequences.get(0));
+ // Get the existing dex size for the payload and switch.
+ long currentSize = Switch.payloadSize(keys) + Switch.estimatedDexSize();
+
+ // Never replace with more than 10 if/switch instructions.
+ if (outliers.size() + sequences.size() <= 10) {
+ // Calculate estimated size for splitting into ifs and switches.
+ long rewrittenSize = 0;
for (Integer outlier : outliers) {
- rewrittenSize += ConstNumber.estimatedDexSize(
- ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+ if (outlier != 0) {
+ rewrittenSize += ConstNumber.estimatedDexSize(
+ ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+ }
rewrittenSize += If.estimatedDexSize();
}
- // Rewrite if smaller.
+ for (List<Integer> sequence : sequences) {
+ rewrittenSize += Switch.payloadSize(sequence);
+ }
+ rewrittenSize += Switch.estimatedDexSize() * sequences.size();
+
if (rewrittenSize < currentSize) {
convertSwitchToSwitchAndIfs(
- code, blocksIterator, block, iterator, theSwitch, outliers);
- assert code.isConsistentSSA();
+ code, blocksIterator, block, iterator, theSwitch, sequences, outliers);
+ }
+ } else if (outliers.size() > 1) {
+ // Calculate estimated size for splitting into switches (packed for the sequences
+ // and sparse for the outliers).
+ long rewrittenSize = 0;
+ for (List<Integer> sequence : sequences) {
+ rewrittenSize += Switch.payloadSize(sequence);
+ }
+ rewrittenSize += Switch.payloadSize(outliers);
+ rewrittenSize += Switch.estimatedDexSize() * (sequences.size() + 1);
+
+ if (rewrittenSize < currentSize) {
+ // Create a copy to not modify sequences.
+ List<IntList> seqs = new ArrayList<>(sequences);
+ seqs.add(outliers);
+ convertSwitchToSwitchAndIfs(
+ code, blocksIterator, block, iterator, theSwitch, seqs, IntLists.EMPTY_LIST);
}
}
}
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 890cb85..109c43e 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -262,6 +262,44 @@
return -1;
}
+ public static int switchRewriteToIfs(int x) {
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 0:
+ return 0;
+ case 100:
+ return 1;
+ }
+ return -1;
+ }
+
+ public static int switchRewriteToSwitches(int x) {
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ return 0;
+ case 2:
+ return 0;
+ case 100:
+ return 1;
+ case 101:
+ return 1;
+ case 102:
+ return 1;
+ }
+ return -1;
+ }
+
public static void main(String[] args) {
noLocals();
unusedLocals();
@@ -278,5 +316,7 @@
tempInCase(42);
localSwap(1, 2);
argumentLiveAtReturn(-1);
+ switchRewriteToIfs(1);
+ switchRewriteToSwitches(1);
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 6f73f02c..60db262 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -484,4 +484,48 @@
checkNoLocal("t"),
run());
}
+
+ @Test
+ public void switchRewriteToIfs() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "switchRewriteToIfs"),
+ run(),
+ checkLine(SOURCE_FILE, 267),
+ stepOver(),
+ checkLine(SOURCE_FILE, 268),
+ checkLocal("x", Value.createInt(1)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 269),
+ checkLocal("x", Value.createInt(2)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 271),
+ checkLocal("x", Value.createInt(4)),
+ checkNoLocal("t"),
+ run());
+ }
+
+ @Test
+ public void switchRewriteToSwitches() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "switchRewriteToSwitches"),
+ run(),
+ checkLine(SOURCE_FILE, 282),
+ stepOver(),
+ checkLine(SOURCE_FILE, 283),
+ checkLocal("x", Value.createInt(1)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 284),
+ checkLocal("x", Value.createInt(2)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 286),
+ checkLocal("x", Value.createInt(4)),
+ checkNoLocal("t"),
+ run());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 5ffd815..531831b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -437,4 +437,228 @@
info.checkLineHasExactLocals(14, "x", "int");
info.checkLineHasExactLocals(16, "x", "int");
}
+
+ @Test
+ public void testLocalSwitchRewriteToIfs() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ /*
+ This is the original Java source code. The code generated by javac have been
+ slightly modified below to end local t on the lookupswitch instruction.
+
+ public static int switchRewrite(int x) { // Line 1
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 1:
+ return 7;
+ case 100:
+ return 8;
+ }
+ return -1;
+ }
+ */
+ MethodSignature foo = clazz.addStaticMethod("switchRewrite", ImmutableList.of("I"), "I",
+ ".limit stack 2",
+ ".limit locals 2",
+ ".var 0 is x I from L0 to L7",
+ ".var 1 is t I from L1 to L3",
+
+ "L0:",
+ ".line 3",
+ " iload 0",
+ " iconst_1",
+ " iadd",
+ " istore 1",
+ "L1:",
+ ".line 4",
+ " iload 1",
+ " istore 0",
+ "L2:",
+ ".line 5",
+ " iload 0",
+ " iload 0",
+ " iadd",
+ " istore 0",
+ "L3_ORIGINAL:", // This is where javac normally ends t.
+ ".line 7",
+ " iload 0",
+ "L3:", // Moved L3 here to end t on the switch instruction.
+ "lookupswitch",
+ " 0: L4",
+ " 100: L5",
+ " default: L6",
+ "L4:",
+ ".line 9",
+ " iconst_0",
+ " ireturn",
+ "L5:",
+ ".line 11",
+ " iconst_1",
+ " ireturn",
+ "L6:",
+ ".line 13",
+ " iconst_m1",
+ " ireturn",
+ "L7:"
+ );
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc 1",
+ " invokestatic Test/switchRewrite(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ String expected = "-1";
+ String javaResult = runOnJava(builder, clazz.name);
+ assertEquals(expected, javaResult);
+
+ AndroidApp jasminApp = builder.build();
+ AndroidApp d8App = ToolHelper.runD8(jasminApp);
+ String artResult = runOnArt(d8App, clazz.name);
+ assertEquals(expected, artResult);
+
+ DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+ info.checkStartLine(3);
+ info.checkLineHasExactLocals(3, "x", "int");
+ info.checkLineHasExactLocals(4, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(5, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(7, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(9, "x", "int");
+ info.checkLineHasExactLocals(11, "x", "int");
+ info.checkLineHasExactLocals(13, "x", "int");
+ }
+
+ @Test
+ public void testLocalSwitchRewriteToSwitches() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ /*
+ This is the original Java source code. The code generated by javac have been
+ slightly modified below to end local t on the lookupswitch instruction.
+
+ public static int switchRewrite(int x) { // Line 1
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ return 0;
+ case 2:
+ return 0;
+ case 100:
+ return 1;
+ case 101:
+ return 1;
+ case 102:
+ return 1;
+ }
+ return -1;
+ }
+ */
+ MethodSignature foo = clazz.addStaticMethod("switchRewrite", ImmutableList.of("I"), "I",
+ ".limit stack 2",
+ ".limit locals 2",
+ ".var 0 is x I from L0 to L11",
+ ".var 1 is t I from L1 to L3",
+
+ "L0:",
+ ".line 3",
+ " iload 0",
+ " iconst_1",
+ " iadd",
+ " istore 1",
+ "L1:",
+ ".line 4",
+ " iload 1",
+ " istore 0",
+ "L2:",
+ ".line 5",
+ " iload 0",
+ " iload 0",
+ " iadd",
+ " istore 0",
+ "L3_ORIGINAL:", // This is where javac normally ends t.
+ ".line 7",
+ " iload 0",
+ "L3:", // Moved L3 here to end t on the switch instruction.
+ "lookupswitch",
+ " 0: L4",
+ " 1: L5",
+ " 2: L6",
+ " 100: L7",
+ " 101: L8",
+ " 102: L9",
+ " default: L10",
+ "L4:",
+ ".line 9",
+ " iconst_0",
+ " ireturn",
+ "L5:",
+ ".line 11",
+ " iconst_0",
+ " ireturn",
+ "L6:",
+ ".line 13",
+ " iconst_0",
+ " ireturn",
+ "L7:",
+ ".line 15",
+ " iconst_1",
+ " ireturn",
+ "L8:",
+ ".line 17",
+ " iconst_1",
+ " ireturn",
+ "L9:",
+ ".line 19",
+ " iconst_1",
+ " ireturn",
+ "L10:",
+ ".line 21",
+ " iconst_m1",
+ " ireturn",
+ "L11:"
+ );
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc 1",
+ " invokestatic Test/switchRewrite(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ String expected = "-1";
+ String javaResult = runOnJava(builder, clazz.name);
+ assertEquals(expected, javaResult);
+
+ AndroidApp jasminApp = builder.build();
+ AndroidApp d8App = ToolHelper.runD8(jasminApp);
+ String artResult = runOnArt(d8App, clazz.name);
+ assertEquals(expected, artResult);
+
+ DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+ info.checkStartLine(3);
+ info.checkLineHasExactLocals(3, "x", "int");
+ info.checkLineHasExactLocals(4, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(5, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(7, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(9, "x", "int");
+ info.checkLineHasExactLocals(11, "x", "int");
+ info.checkLineHasExactLocals(13, "x", "int");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index c256920..3750a86 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -34,7 +34,8 @@
public class SwitchRewritingTest extends SmaliTestBase {
private boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
- return Math.abs((long) key1 - (long) key2) <= 2;
+ assert key1 != key2;
+ return Math.abs((long) key1 - (long) key2) == 1;
}
private boolean some16BitConst(Instruction instruction) {
@@ -114,7 +115,7 @@
}
}
- private void runTwoCaseSparseToPackedDexTest(int key1, int key2) {
+ private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
@@ -156,24 +157,29 @@
if (twoCaseWillUsePackedSwitch(key1, key2)) {
assertTrue(code.instructions[0] instanceof PackedSwitch);
} else {
- assertTrue(code.instructions[0] instanceof SparseSwitch);
+ if (key1 == 0) {
+ assertTrue(code.instructions[0] instanceof IfEqz);
+ } else {
+ // Const instruction before if.
+ assertTrue(code.instructions[1] instanceof IfEq);
+ }
}
}
@Test
- public void twoCaseSparseToPackedDex() {
+ public void twoCaseSparseToPackedOrIfsDex() {
for (int delta = 1; delta <= 3; delta++) {
- runTwoCaseSparseToPackedDexTest(0, delta);
- runTwoCaseSparseToPackedDexTest(-delta, 0);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
- runTwoCaseSparseToPackedDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
+ runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
}
- runTwoCaseSparseToPackedDexTest(-1, 1);
- runTwoCaseSparseToPackedDexTest(-2, 1);
- runTwoCaseSparseToPackedDexTest(-1, 2);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
}
private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
@@ -356,7 +362,12 @@
if (twoCaseWillUsePackedSwitch(key1, key2)) {
assertTrue(code.instructions[3] instanceof PackedSwitch);
} else {
- assertTrue(code.instructions[3] instanceof SparseSwitch);
+ if (key1 == 0) {
+ assertTrue(code.instructions[3] instanceof IfEqz);
+ } else {
+ // Const instruction before if.
+ assertTrue(code.instructions[4] instanceof IfEq);
+ }
}
}
@@ -462,8 +473,8 @@
runLargerSwitchJarTest(0, 1, 5503, null);
}
- private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs)
- throws Exception {
+ private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
+ int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
@@ -514,8 +525,8 @@
}
}
- assertEquals(1, packedSwitches);
- assertEquals(0, sparseSwitches);
+ assertEquals(expectedPackedSwitches, packedSwitches);
+ assertEquals(expectedSparceSwitches, sparseSwitches);
assertEquals(expectedIfs, ifs);
// Run the code
@@ -526,12 +537,34 @@
@Test
public void convertCasesToIf() throws Exception {
- runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1);
- runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1);
- runConvertCasesToIf(ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1);
- runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1);
- runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2);
+ // Switches that are completely converted to ifs.
+ runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
+
+ // Switches that are completely converted to ifs and one switch.
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
runConvertCasesToIf(ImmutableList.of(
- Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2);
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
+
+ // Switches that are completely converted to ifs and two switches.
+ runConvertCasesToIf(ImmutableList.of(
+ 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
+
+ // Switches that are completely converted two switches (one sparse and one packed).
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, -900, -800, -700, -600, -500, -400, -300,
+ 1000, 1001, 1002, 1003, 1004,
+ 2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
}
}
\ No newline at end of file