Merge "Be more precise when relocating with shadowJar."
diff --git a/src/main/java/com/android/tools/r8/code/Base1Format.java b/src/main/java/com/android/tools/r8/code/Base1Format.java
index b62853d..9d88f53 100644
--- a/src/main/java/com/android/tools/r8/code/Base1Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base1Format.java
@@ -5,6 +5,8 @@
public abstract class Base1Format extends Instruction {
+ public static final int SIZE = 1;
+
public Base1Format(BytecodeStream stream) {
super(stream);
}
@@ -12,6 +14,6 @@
protected Base1Format() {}
public int getSize() {
- return 1;
+ return SIZE;
}
}
diff --git a/src/main/java/com/android/tools/r8/code/Base2Format.java b/src/main/java/com/android/tools/r8/code/Base2Format.java
index f8a48ac..1241e6a 100644
--- a/src/main/java/com/android/tools/r8/code/Base2Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base2Format.java
@@ -5,6 +5,8 @@
public abstract class Base2Format extends Instruction {
+ public static final int SIZE = 2;
+
protected Base2Format() {}
public Base2Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 2;
+ return SIZE;
}
}
diff --git a/src/main/java/com/android/tools/r8/code/Base3Format.java b/src/main/java/com/android/tools/r8/code/Base3Format.java
index 34bda57..c1618f5 100644
--- a/src/main/java/com/android/tools/r8/code/Base3Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base3Format.java
@@ -5,6 +5,8 @@
public abstract class Base3Format extends Instruction {
+ public static final int SIZE = 3;
+
protected Base3Format() {}
public Base3Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 3;
+ return SIZE;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/code/Base4Format.java b/src/main/java/com/android/tools/r8/code/Base4Format.java
index 7cdf1c5..f3448fa 100644
--- a/src/main/java/com/android/tools/r8/code/Base4Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base4Format.java
@@ -5,6 +5,8 @@
public abstract class Base4Format extends Instruction {
+ public static final int SIZE = 4;
+
protected Base4Format() {}
public Base4Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 4;
+ return SIZE;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/code/Base5Format.java b/src/main/java/com/android/tools/r8/code/Base5Format.java
index cc67572..10ddc5e 100644
--- a/src/main/java/com/android/tools/r8/code/Base5Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base5Format.java
@@ -5,6 +5,8 @@
public abstract class Base5Format extends Instruction {
+ public static final int SIZE = 5;
+
protected Base5Format() {}
public Base5Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
}
public int getSize() {
- return 5;
+ return SIZE;
}
}
\ No newline at end of file
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 5f1b33f..2863df6 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
@@ -543,6 +543,13 @@
return unlinkSingleSuccessor();
}
+ public void detachAllSuccessors() {
+ for (BasicBlock successor : successors) {
+ successor.predecessors.remove(this);
+ }
+ successors.clear();
+ }
+
public List<BasicBlock> unlink(BasicBlock successor, DominatorTree dominator) {
assert successors.contains(successor);
assert successor.predecessors.contains(this);
@@ -935,7 +942,8 @@
*
* <p>The constructed basic block has no predecessors and no successors.
*
- * @param blockNumber the block number of the goto block
+ * @param blockNumber the block number of the block
+ * @param theIf the if instruction
*/
public static BasicBlock createIfBlock(int blockNumber, If theIf) {
BasicBlock block = new BasicBlock();
@@ -945,6 +953,32 @@
return block;
}
+ /**
+ * Create a new basic block with an instruction followed by an if instruction.
+ *
+ * <p>The constructed basic block has no predecessors and no successors.
+ *
+ * @param blockNumber the block number of the block
+ * @param theIf the if instruction
+ * @param instruction the instruction to place before the if instruction
+ */
+ public static BasicBlock createIfBlock(int blockNumber, If theIf, Instruction instruction) {
+ BasicBlock block = new BasicBlock();
+ block.add(instruction);
+ block.add(theIf);
+ block.close(null);
+ block.setNumber(blockNumber);
+ return block;
+ }
+
+ public static BasicBlock createSwitchBlock(int blockNumber, Switch theSwitch) {
+ BasicBlock block = new BasicBlock();
+ block.add(theSwitch);
+ block.close(null);
+ block.setNumber(blockNumber);
+ return block;
+ }
+
public boolean isTrivialGoto() {
return instructions.size() == 1 && exit().isGoto();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 1880668..e2d41b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -121,6 +121,33 @@
}
}
+ // Estimated size of the resulting dex instruction in code units.
+ public static int estimatedDexSize(ConstType type, long value) {
+ if (MoveType.fromConstType(type) == MoveType.SINGLE) {
+ assert NumberUtils.is32Bit(value);
+ if (NumberUtils.is4Bit(value)) {
+ return Const4.SIZE;
+ } else if (NumberUtils.is16Bit(value)) {
+ return Const16.SIZE;
+ } else if ((value & 0x0000ffffL) == 0) {
+ return ConstHigh16.SIZE;
+ } else {
+ return Const.SIZE;
+ }
+ } else {
+ assert MoveType.fromConstType(type) == MoveType.WIDE;
+ if (NumberUtils.is16Bit(value)) {
+ return ConstWide16.SIZE;
+ } else if ((value & 0x0000ffffffffffffL) == 0) {
+ return ConstWideHigh16.SIZE;
+ } else if (NumberUtils.is32Bit(value)) {
+ return ConstWide32.SIZE;
+ } else {
+ return ConstWide.SIZE;
+ }
+ }
+ }
+
@Override
public int maxInValueRegister() {
assert false : "Const has no register arguments.";
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index b533452..3a068f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -115,6 +115,11 @@
builder.addIf(this);
}
+ // Estimated size of the resulting dex instruction in code units.
+ public static int estimatedDexSize() {
+ return 2;
+ }
+
@Override
public String toString() {
return super.toString() + " " + type + " block " + getTrueTarget().getNumber()
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 6b47ba2..6def63a 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
@@ -12,6 +12,8 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
+import com.google.common.primitives.Ints;
+import java.util.List;
public class Switch extends JumpInstruction {
@@ -48,29 +50,73 @@
}
// Number of targets if this switch is emitted as a packed switch.
- private long numberOfTargetsIfPacked() {
+ private static long numberOfTargetsIfPacked(int keys[]) {
return ((long) keys[keys.length - 1]) - ((long) keys[0]) + 1;
}
- private boolean canBePacked() {
+ public static boolean canBePacked(int keys[]) {
// The size of a switch payload is stored in an ushort in the Dex file.
- return numberOfTargetsIfPacked() <= Constants.U16BIT_MAX;
+ return numberOfTargetsIfPacked(keys) <= Constants.U16BIT_MAX;
+ }
+
+ // Number of targets if this switch is emitted as a packed switch.
+ public static int numberOfTargetsForPacked(int keys[]) {
+ assert canBePacked(keys);
+ return (int) numberOfTargetsIfPacked(keys);
+ }
+
+ // Size of the switch payload if emitted as packed (in code units).
+ static public long packedPayloadSize(int keys[]) {
+ return (numberOfTargetsForPacked(keys) * 2) + 4;
+ }
+
+ // Size of the switch payload if emitted as sparse (in code units).
+ public static long sparsePayloadSize(int keys[]) {
+ return (keys.length * 4) + 2;
+ }
+
+ /**
+ * Size of the switch payload instruction for the given keys. This will be the payload
+ * size for the smallest encoding of the provided keys.
+ *
+ * @param keys the switch keys
+ * @return Size of the switch payload instruction in code units
+ */
+ public static long payloadSize(List<Integer> keys) {
+ return payloadSize(Ints.toArray(keys));
+ }
+
+ /**
+ * Size of the switch payload instruction for the given keys.
+ *
+ * @see #payloadSize(List)
+ */
+ public static long payloadSize(int keys[]) {
+ long sparse = sparsePayloadSize(keys);
+ if (canBePacked(keys)) {
+ return Math.min(sparse, packedPayloadSize(keys));
+ } else {
+ return sparse;
+ }
+ }
+
+ private boolean canBePacked() {
+ return canBePacked(keys);
}
// Number of targets if this switch is emitted as a packed switch.
private int numberOfTargetsForPacked() {
- assert canBePacked();
- return (int) numberOfTargetsIfPacked();
+ return numberOfTargetsForPacked(keys);
}
// Size of the switch payload if emitted as packed (in code units).
private long packedPayloadSize() {
- return (numberOfTargetsForPacked() * 2) + 4;
+ return packedPayloadSize(keys);
}
// Size of the switch payload if emitted as sparse (in code units).
private long sparsePayloadSize() {
- return (keys.length * 4) + 2;
+ return sparsePayloadSize(keys);
}
private boolean emitPacked() {
@@ -121,6 +167,10 @@
return keys[index];
}
+ public int[] getKeys() {
+ return keys;
+ }
+
public int[] targetBlockIndices() {
return targetBlockIndices;
}
@@ -192,6 +242,7 @@
StringBuilder builder = new StringBuilder(super.toString()+ "\n");
for (int i = 0; i < numberOfKeys(); i++) {
builder.append(" ");
+ builder.append(getKey(i));
builder.append(" -> ");
builder.append(targetBlock(i).getNumber());
builder.append("\n");
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 815efe4..e30e1d0 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
@@ -37,6 +37,7 @@
import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.ConstType;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
@@ -63,6 +64,7 @@
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;
@@ -72,8 +74,13 @@
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.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -336,8 +343,130 @@
}
}
+ // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
+ public static class SwitchBuilder {
+ private Value value;
+ private Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
+ private BasicBlock fallthrough;
+ private int blockNumber;
+
+ public SwitchBuilder setValue(Value value) {
+ this.value = value;
+ return this;
+ }
+
+ public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) {
+ keyToTarget.put(key, target);
+ return this;
+ }
+
+ public SwitchBuilder setFallthrough(BasicBlock fallthrough) {
+ this.fallthrough = fallthrough;
+ 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<>();
+ targetToSuccessorIndex.defaultReturnValue(NOT_FOUND);
+
+ int[] keys = new int[keyToTarget.size()];
+ int[] targetBlockIndices = new int[keyToTarget.size()];
+ // Sort keys descending.
+ int count = 0;
+ IntIterator iter = keyToTarget.keySet().iterator();
+ while (iter.hasNext()) {
+ int key = iter.nextInt();
+ BasicBlock target = keyToTarget.get(key);
+ Integer targetIndex =
+ targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size());
+ keys[count] = key;
+ targetBlockIndices[count] = targetIndex;
+ count++;
+ }
+ Integer fallthroughIndex =
+ targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size());
+ Switch newSwitch = new Switch(value, keys, targetBlockIndices, fallthroughIndex);
+ BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch);
+ for (BasicBlock successor : targetToSuccessorIndex.keySet()) {
+ newSwitchBlock.link(successor);
+ }
+ return newSwitchBlock;
+ }
+ }
+
+ /**
+ * 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) {
+ // 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;
+ BasicBlock block = blocksIterator.previous();
+ assert block == originalSwitchBlock;
+ 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();
+ block = theSwitch.getBlock().unlinkSinglePredecessor();
+ assert theSwitch.getBlock().getPredecessors().size() == 0;
+ assert theSwitch.getBlock().getSuccessors().size() == 0;
+ assert block == originalBlock;
+
+ BasicBlock newSwitchBlock = switchBuilder.build();
+
+ // 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());
+ }
+ predecessor.link(newSwitchBlock);
+ blocksIterator.add(newSwitchBlock);
+
+ // The switch fallthrough block is still the same, and it is right after the new switch block.
+ IteratorUtils.peekNext(blocksIterator, newSwitchBlock.exit().fallthroughBlock());
+ }
+
public void rewriteSwitch(IRCode code) {
- for (BasicBlock block : code.blocks) {
+ ListIterator<BasicBlock> blocksIterator = code.listIterator();
+ while (blocksIterator.hasNext()) {
+ BasicBlock block = blocksIterator.next();
InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
@@ -361,6 +490,54 @@
If theIf = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.dest()));
iterator.replaceCurrentInstruction(theIf);
}
+ } else {
+ // Split keys into outliers and sequences.
+ List<List<Integer>> sequences = new ArrayList<>();
+ List<Integer> outliers = new ArrayList<>();
+
+ List<Integer> current = new ArrayList<>();
+ 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;
+ int key = keys[i];
+ if (((long) key - (long) previousKey) > 1) {
+ if (current.size() == 1) {
+ outliers.add(previousKey);
+ } else {
+ sequences.add(current);;
+ }
+ current = new ArrayList<>();
+ }
+ current.add(key);
+ previousKey = key;
+ }
+ if (current.size() == 1) {
+ outliers.add(previousKey);
+ } else {
+ 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));
+ for (Integer outlier : outliers) {
+ rewrittenSize += ConstNumber.estimatedDexSize(
+ ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+ rewrittenSize += If.estimatedDexSize();
+ }
+ // Rewrite if smaller.
+ if (rewrittenSize < currentSize) {
+ convertSwitchToSwitchAndIfs(
+ code, blocksIterator, block, iterator, theSwitch, outliers);
+ assert code.isConsistentSSA();
+ }
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
new file mode 100644
index 0000000..2bc52a3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -0,0 +1,23 @@
+// 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.utils;
+
+import java.util.ListIterator;
+
+public class IteratorUtils {
+ public static <T> T peekPrevious(ListIterator<T> iterator, T element) {
+ T previous = iterator.previous();
+ T next = iterator.next();
+ assert previous == next;
+ return previous;
+ }
+
+ public static <T> T peekNext(ListIterator<T> iterator, T element) {
+ T next = iterator.next();
+ T previous = iterator.previous();
+ assert previous == next;
+ return next;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 3ca9b1b..4d707a8 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -19,6 +19,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -30,7 +31,7 @@
public class TestBase {
@Rule
- public TemporaryFolder temp = new TemporaryFolder();
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
/**
* Write lines of text to a temporary file.
@@ -114,6 +115,15 @@
/**
* Compile an application with R8.
*/
+ protected AndroidApp compileWithR8(AndroidApp app)
+ throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+ R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
+ return ToolHelper.runR8(command);
+ }
+
+ /**
+ * Compile an application with R8.
+ */
protected AndroidApp compileWithR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
@@ -215,6 +225,13 @@
* Run application on Art with the specified main class and provided arguments.
*/
protected String runOnArt(AndroidApp app, Class mainClass, String... args) throws IOException {
+ return runOnArt(app, mainClass, Arrays.asList(args));
+ }
+
+ /**
+ * Run application on Art with the specified main class and provided arguments.
+ */
+ protected String runOnArt(AndroidApp app, Class mainClass, List<String> args) throws IOException {
Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
app.writeToZip(out, OutputMode.Indexed);
return ToolHelper.runArtNoVerificationErrors(
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 7f65c60..ba5e370 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
@@ -141,11 +140,17 @@
return out.toByteArray();
}
+ public List<byte[]> buildClasses() throws Exception {
+ List<byte[]> result = new ArrayList<>();
+ for (ClassBuilder clazz : classes) {
+ result.add(compile(clazz));
+ }
+ return result;
+ }
+
public AndroidApp build() throws Exception {
AndroidApp.Builder builder = AndroidApp.builder();
- for (ClassBuilder clazz : classes) {
- builder.addClassProgramData(compile(clazz));
- }
+ builder.addClassProgramData(buildClasses());
return builder.build();
}
@@ -154,7 +159,6 @@
}
public DexApplication read(InternalOptions options) throws Exception {
- DexItemFactory factory = new DexItemFactory();
Timing timing = new Timing("JasminTest");
return new ApplicationReader(build(), options, timing).read();
}
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
new file mode 100644
index 0000000..53ee6dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
@@ -0,0 +1,44 @@
+// 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.smali;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CheckSwitchInTestClass {
+ public static void main(String[] args) throws Exception {
+ // Load the generated Jasmin class, and get the test method.
+ Class<?> test = CheckSwitchInTestClass.class.getClassLoader().loadClass("Test");
+ Method method = test.getMethod("test", int.class);
+
+ // Get keys and default value from arguments.
+ List<Integer> keys = Arrays.stream(Arrays.copyOfRange(args, 0, args.length - 1))
+ .map(Integer::parseInt)
+ .sorted()
+ .collect(Collectors.toList());
+ int defaultValue = Integer.parseInt(args[args.length - 1]);
+
+ // Run over all keys and test a small interval around each.
+ long delta = 2;
+ for (Integer key : keys) {
+ for (long potential = key - delta; potential < key + delta; potential++) {
+ if (Integer.MIN_VALUE <= potential && potential <= Integer.MAX_VALUE) {
+ int testKey = (int) potential;
+ int result = ((Integer) method.invoke(null, testKey));
+ int expect = defaultValue;
+ if (keys.contains(testKey)) {
+ expect = testKey;
+ }
+ if (result != expect) {
+ System.out.println("Expected " + expect + " but got " + result);
+ System.exit(1);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 4253395..0322407 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
@@ -45,18 +46,13 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import org.antlr.runtime.RecognitionException;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
-public class SmaliTestBase {
+public class SmaliTestBase extends TestBase {
public static final String DEFAULT_CLASS_NAME = "Test";
public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME;
public static final String DEFAULT_METHOD_NAME = "method";
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
public static class MethodSignature {
public final String clazz;
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 7f89da7..e4ae46c 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -21,9 +21,14 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
public class SwitchRewritingTest extends SmaliTestBase {
@@ -456,4 +461,77 @@
// class file max.
runLargerSwitchJarTest(0, 1, 5503, null);
}
+
+ private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs)
+ throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ StringBuilder x = new StringBuilder();
+ StringBuilder y = new StringBuilder();
+ for (Integer key : keys) {
+ x.append(key).append(" : case_").append(key).append("\n");
+ y.append("case_").append(key).append(":\n");
+ y.append(" ldc ").append(key).append("\n");
+ y.append(" goto return_\n");
+ }
+
+ clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+ " .limit stack 1",
+ " .limit locals 1",
+ " iload_0",
+ " lookupswitch",
+ x.toString(),
+ " default : case_default",
+ y.toString(),
+ " case_default:",
+ " ldc " + defaultValue,
+ " return_:",
+ " ireturn");
+
+ // Add the Jasmin class and a class from Java source with the main method.
+ AndroidApp.Builder appBuilder = AndroidApp.builder();
+ appBuilder.addClassProgramData(builder.buildClasses());
+ appBuilder.addProgramFiles(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class));
+ AndroidApp app = compileWithR8(appBuilder.build());
+
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
+ DexCode code = method.getMethod().getCode().asDexCode();
+
+ int packedSwitches = 0;
+ int sparseSwitches = 0;
+ int ifs = 0;
+ for (Instruction instruction : code.instructions) {
+ if (instruction instanceof PackedSwitch) {
+ packedSwitches++;
+ }
+ if (instruction instanceof SparseSwitch) {
+ sparseSwitches++;
+ }
+ if (instruction instanceof IfEq || instruction instanceof IfEqz) {
+ ifs++;
+ }
+ }
+
+ assertEquals(1, packedSwitches);
+ assertEquals(0, sparseSwitches);
+ assertEquals(expectedIfs, ifs);
+
+ // Run the code
+ List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
+ args.add(Integer.toString(defaultValue));
+ runOnArt(app, CheckSwitchInTestClass.class, args);
+ }
+
+ @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);
+ runConvertCasesToIf(ImmutableList.of(
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2);
+ }
}