Merge "Revert "Revised workaround for Android 6.0 dex2oat divergence issue.""
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 3798c3f..bdbf44f 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.10-dev";
+ public static final String LABEL = "1.4.11-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index 64395de..16a03e2 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -10,17 +10,21 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.Value;
import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -221,6 +225,7 @@
public Map<Value, TypeInfo> computeVerificationTypes() {
computingVerificationTypes = true;
types = new HashMap<>();
+ List<ConstNumber> nullsUsedInPhis = new ArrayList<>();
Set<Value> worklist = new HashSet<>();
{
InstructionIterator it = code.instructionIterator();
@@ -261,6 +266,12 @@
} else if (instruction.outType().isObject()) {
Value outValue = instruction.outValue();
if (instruction.hasInvariantOutType()) {
+ if (instruction.isConstNumber()) {
+ assert instruction.asConstNumber().isZero();
+ if (outValue.numberOfAllUsers() == outValue.numberOfPhiUsers()) {
+ nullsUsedInPhis.add(instruction.asConstNumber());
+ }
+ }
DexType type = instruction.computeVerificationType(this);
types.put(outValue, createInitializedType(type));
addUsers(outValue, worklist);
@@ -284,6 +295,20 @@
}
}
computingVerificationTypes = false;
+ for (ConstNumber instruction : nullsUsedInPhis) {
+ TypeInfo refinedType = null;
+ for (Phi phi : instruction.outValue().uniquePhiUsers()) {
+ if (refinedType == null) {
+ refinedType = types.get(phi);
+ } else if (refinedType.getDexType() != types.get(phi).getDexType()) {
+ refinedType = null;
+ break;
+ }
+ }
+ if (refinedType != null) {
+ types.put(instruction.outValue(), refinedType);
+ }
+ }
return types;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 3bd4a00..f71588b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -109,11 +109,11 @@
}
}
- private static boolean isNegativeZeroDouble(double value) {
+ public static boolean isNegativeZeroDouble(double value) {
return Double.doubleToLongBits(value) == Double.doubleToLongBits(-0.0);
}
- private static boolean isNegativeZeroFloat(float value) {
+ public static boolean isNegativeZeroFloat(float value) {
return Float.floatToIntBits(value) == Float.floatToIntBits(-0.0f);
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 0773237..a25bae0 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -167,6 +167,7 @@
private final List<DexEncodedMethod> directMethods = new ArrayList<>();
private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
+ private boolean hasReachabilitySensitiveMethod = false;
public CreateDexClassVisitor(
Origin origin,
@@ -301,6 +302,7 @@
addAnnotation(DexAnnotation.createAnnotationDefaultAnnotation(
type, defaultAnnotations, application.getFactory()));
}
+ checkReachabilitySensitivity();
DexClass clazz =
classKind.create(
type,
@@ -325,6 +327,45 @@
classConsumer.accept(clazz);
}
+ // If anything is marked reachability sensitive, all methods need to be parsed including
+ // locals information. This propagates the reachability sensitivity bit so that if any field
+ // or method is annotated, all methods get parsed with locals information.
+ private void checkReachabilitySensitivity() {
+ if (hasReachabilitySensitiveMethod || hasReachabilitySensitiveField()) {
+ for (DexEncodedMethod method : directMethods) {
+ Code code = method.getCode();
+ if (code != null && code.isJarCode()) {
+ code.asJarCode().markReachabilitySensitive();
+ }
+ }
+ for (DexEncodedMethod method : virtualMethods) {
+ Code code = method.getCode();
+ if (code != null && code.isJarCode()) {
+ code.asJarCode().markReachabilitySensitive();
+ }
+ }
+ }
+ }
+
+ private boolean hasReachabilitySensitiveField() {
+ DexType reachabilitySensitive = application.getFactory().annotationReachabilitySensitive;
+ for (DexEncodedField field : instanceFields) {
+ for (DexAnnotation annotation : field.annotations.annotations) {
+ if (annotation.annotation.type == reachabilitySensitive) {
+ return true;
+ }
+ }
+ }
+ for (DexEncodedField field : staticFields) {
+ for (DexAnnotation annotation : field.annotations.annotations) {
+ if (annotation.annotation.type == reachabilitySensitive) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private void addDefaultAnnotation(String name, DexValue value) {
if (defaultAnnotations == null) {
defaultAnnotations = new ArrayList<>();
@@ -626,6 +667,7 @@
parent.version);
Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
if (parent.methodSignatures.add(signature)) {
+ parent.hasReachabilitySensitiveMethod |= isReachabilitySensitive();
if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
parent.directMethods.add(dexMethod);
} else {
@@ -644,6 +686,17 @@
}
}
+ private boolean isReachabilitySensitive() {
+ DexType reachabilitySensitive =
+ parent.application.getFactory().annotationReachabilitySensitive;
+ for (DexAnnotation annotation : getAnnotations()) {
+ if (annotation.annotation.type == reachabilitySensitive) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private List<DexAnnotation> getAnnotations() {
if (annotations == null) {
annotations = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index c2f4943..57a4c78 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -52,8 +52,8 @@
private final Origin origin;
private MethodNode node;
protected ReparseContext context;
-
protected final JarApplicationReader application;
+ private boolean reachabilitySensitive = false;
public JarCode(
DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
@@ -64,6 +64,13 @@
context.codeList.add(this);
}
+ public void markReachabilitySensitive() {
+ // We need to mark before we have reparsed so that the method code is reparsed
+ // including debug information.
+ assert context != null;
+ this.reachabilitySensitive = true;
+ }
+
public MethodNode getNode() {
triggerDelayedParsingIfNeccessary();
return node;
@@ -271,9 +278,16 @@
private void parseCode(ReparseContext context, boolean useJsrInliner) {
// If the keep attributes do not specify keeping LocalVariableTable, LocalVariableTypeTable or
// LineNumberTable, then we can skip parsing all the debug related attributes during code read.
+ // If the method is reachability sensitive we have to include debug information in order
+ // to get locals information which we need to extend the live ranges of locals for their
+ // entire scope.
int parsingOptions = ClassReader.SKIP_FRAMES;
ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
- if (!keep.localVariableTable && !keep.localVariableTypeTable && !keep.lineNumberTable) {
+
+ if (!keep.localVariableTable
+ && !keep.localVariableTypeTable
+ && !keep.lineNumberTable
+ && !reachabilitySensitive) {
parsingOptions |= ClassReader.SKIP_DEBUG;
}
SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
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 33b04cb..7a6a9a4 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
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.NumberUtils;
import java.util.function.Function;
@@ -153,8 +154,46 @@
}
}
- // Estimated size of the resulting dex instruction in code units.
- public static int estimatedDexSize(ValueType type, long value) {
+ // Estimated size of the resulting instructions in code units (bytes in CF, 16-bit in Dex).
+ public static int estimatedSize(InternalOutputMode mode, ValueType type, long value) {
+ return mode.isGeneratingDex() ? estimatedDexSize(type, value) : estimatedCfSize(type, value);
+ }
+
+ private static int estimatedCfSize(ValueType type, long value) {
+ switch (type) {
+ case INT:
+ if (-1 <= value && value <= 5) {
+ return 1;
+ } else if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
+ return 2;
+ } else {
+ return 3;
+ }
+ case LONG:
+ if (value == 0 || value == 1) {
+ return 1;
+ } else {
+ return 3;
+ }
+ case FLOAT:
+ if (value == 0 || value == 1 || value == 2) {
+ return CfConstNumber.isNegativeZeroFloat((float) value) ? 2 : 1;
+ } else {
+ return 3;
+ }
+ case DOUBLE:
+ if (value == 0 || value == 1) {
+ return CfConstNumber.isNegativeZeroDouble((double) value) ? 2 : 1;
+ } else {
+ return 3;
+ }
+ case OBJECT:
+ return 1;
+ }
+ throw new UnsupportedOperationException("Not a constant number");
+ }
+
+ private static int estimatedDexSize(ValueType type, long value) {
if (type.isSingle()) {
assert NumberUtils.is32Bit(value);
if (NumberUtils.is4Bit(value)) {
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 ca2fb4a..421872f 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
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.InternalOutputMode;
import java.util.List;
public class If extends JumpInstruction {
@@ -126,9 +127,14 @@
builder.addIf(this);
}
- // Estimated size of the resulting dex instruction in code units.
- public static int estimatedDexSize() {
- return 2;
+ // Estimated size of the resulting instructions in code units (bytes in CF, 16-bit in Dex).
+ public static int estimatedSize(InternalOutputMode mode) {
+ if (mode.isGeneratingClassFiles()) {
+ // op + branch1 + branch2
+ return 3;
+ } else {
+ return 2;
+ }
}
@Override
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 5b0bf73..9493ff5 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
@@ -17,7 +17,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
-import com.google.common.primitives.Ints;
+import com.android.tools.r8.utils.InternalOutputMode;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.ArrayList;
@@ -62,75 +62,109 @@
return ((long) keys[keys.length - 1]) - ((long) keys[0]) + 1;
}
- public static boolean canBePacked(int keys[]) {
+ public static boolean canBePacked(InternalOutputMode mode, int keys[]) {
// The size of a switch payload is stored in an ushort in the Dex file.
- return numberOfTargetsIfPacked(keys) <= Constants.U16BIT_MAX;
+ return canBePacked(mode, numberOfTargetsIfPacked(keys));
}
- // 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);
+ public static boolean canBePacked(InternalOutputMode mode, long numberOfTargets) {
+ // The size of a switch payload is stored in an ushort in the Dex file.
+ return numberOfTargets
+ <= (mode.isGeneratingClassFiles() ? Constants.U32BIT_MAX : Constants.U16BIT_MAX);
}
- // Size of the switch payload if emitted as packed (in code units).
- // This size can not exceed Constants.U16BIT_MAX * 2 + 4 and can be contained in an integer.
- private static int packedPayloadSize(int keys[]) {
- return (numberOfTargetsForPacked(keys) * 2) + 4;
+ // Estimated size of the resulting dex instruction in code units (bytes in CF, 16-bit in Dex).
+ public static long estimatedSize(InternalOutputMode mode, int[] keys) {
+ long sparseSize = sparsePayloadSize(mode, keys) + baseSparseSize(mode);
+ long packedSize = Long.MAX_VALUE;
+ if (canBePacked(mode, keys)) {
+ long packedPayloadSize = packedPayloadSize(mode, keys);
+ packedSize = packedPayloadSize + basePackedSize(mode);
+ if (packedSize < packedPayloadSize) {
+ packedSize = Integer.MAX_VALUE;
+ }
+ }
+ return Math.min(sparseSize, packedSize);
}
- // Size of the switch payload if emitted as sparse (in code units).
- // This size can not exceed Constants.U16BIT_MAX * 4 + 2 and can be contained in an integer.
- private static int sparsePayloadSize(int keys[]) {
- return (keys.length * 4) + 2;
+ public static long estimatedSparseSize(InternalOutputMode mode, long keys) {
+ return sparsePayloadSize(mode, keys) + baseSparseSize(mode);
}
- /**
- * 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 int payloadSize(List<Integer> keys) {
- return payloadSize(Ints.toArray(keys));
- }
-
- /**
- * Size of the switch payload instruction for the given keys.
- *
- * @see #payloadSize(List)
- */
- public static int payloadSize(int keys[]) {
- int sparse = sparsePayloadSize(keys);
- if (canBePacked(keys)) {
- return Math.min(sparse, packedPayloadSize(keys));
+ // Estimated size of the packed/table instructions in code units excluding the payload (bytes in
+ // CF, 16-bit in Dex).
+ public static int basePackedSize(InternalOutputMode mode) {
+ if (mode.isGeneratingClassFiles()) {
+ // Table switch: opcode + padding + default + high + low.
+ return 1 + 3 + 4 + 4 + 4;
} else {
- return sparse;
+ return 3;
}
}
- private boolean canBePacked() {
- return canBePacked(keys);
+ // Estimated size of the sparse/lookup instructions in code units excluding the payload (bytes in
+ // CF, 16-bit in Dex).
+ public static int baseSparseSize(InternalOutputMode mode) {
+ if (mode.isGeneratingClassFiles()) {
+ // Lookup switch: opcode + padding + default + number of keys.
+ return 1 + 3 + 4 + 4;
+ } else {
+ return 3;
+ }
}
- // Number of targets if this switch is emitted as a packed switch.
- private int numberOfTargetsForPacked() {
- return numberOfTargetsForPacked(keys);
+ // Size of the switch payload if emitted as packed in code units (bytes in CF, 16-bit in Dex).
+ public static long packedPayloadSize(InternalOutputMode mode, long numberOfTargets) {
+ if (mode.isGeneratingClassFiles()) {
+ assert numberOfTargets <= Constants.U32BIT_MAX;
+ return numberOfTargets * 4;
+ } else {
+ // This size can not exceed Constants.U16BIT_MAX * 2 + 4 and can be contained in an 32-bit
+ // integer.
+ return (numberOfTargets * 2) + 4;
+ }
}
- // Size of the switch payload if emitted as packed (in code units).
- private int packedPayloadSize() {
- return packedPayloadSize(keys);
+ // Size of the switch payload if emitted as packed in code units (bytes in CF, 16-bit in Dex).
+ public static long packedPayloadSize(InternalOutputMode mode, int keys[]) {
+ assert canBePacked(mode, keys);
+ long numberOfTargets = numberOfTargetsIfPacked(keys);
+ return packedPayloadSize(mode, numberOfTargets);
}
- // Size of the switch payload if emitted as sparse (in code units).
- private int sparsePayloadSize() {
- return sparsePayloadSize(keys);
+ // Size of the switch payload if emitted as sparse in code units (bytes in CF, 16-bit in Dex).
+ public static long sparsePayloadSize(InternalOutputMode mode, int[] keys) {
+ return sparsePayloadSize(mode, keys.length);
}
- private boolean emitPacked() {
- return canBePacked() && packedPayloadSize() <= sparsePayloadSize();
+ // Size of the switch payload if emitted as sparse in code units (bytes in CF, 16-bit in Dex).
+ public static long sparsePayloadSize(InternalOutputMode mode, long keys) {
+ if (mode.isGeneratingClassFiles()) {
+ // Lookup-switch has a 32-bit int key and 32-bit int value for each offset, 8 bytes per entry.
+ return keys * 8;
+ } else {
+ // This size can not exceed Constants.U16BIT_MAX * 4 and can be contained in a
+ // 32-bit integer.
+ return keys * 4 + 2;
+ }
+ }
+
+ private boolean canBePacked(InternalOutputMode mode) {
+ return canBePacked(mode, keys);
+ }
+
+ // Size of the switch payload if emitted as packed in code units (bytes in CF, 16-bit in Dex).
+ private long packedPayloadSize(InternalOutputMode mode) {
+ return packedPayloadSize(mode, keys);
+ }
+
+ // Size of the switch payload if emitted as sparse in code units (bytes in CF, 16-bit in Dex).
+ private long sparsePayloadSize(InternalOutputMode mode) {
+ return sparsePayloadSize(mode, keys);
+ }
+
+ private boolean emitPacked(InternalOutputMode mode) {
+ return canBePacked(mode) && packedPayloadSize(mode) <= sparsePayloadSize(mode);
}
public int getFirstKey() {
@@ -155,17 +189,13 @@
@Override
public void buildDex(DexBuilder builder) {
int value = builder.allocatedRegister(value(), getNumber());
- if (emitPacked()) {
+ if (emitPacked(InternalOutputMode.DexIndexed)) {
builder.addSwitch(this, new PackedSwitch(value));
} else {
builder.addSwitch(this, new SparseSwitch(value));
}
}
- // 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;
@@ -213,10 +243,11 @@
getBlock().getSuccessors().set(fallthroughBlockIndex, block);
}
- public Nop buildPayload(int[] targets, int fallthroughTarget) {
+ public Nop buildPayload(int[] targets, int fallthroughTarget, InternalOutputMode mode) {
assert keys.length == targets.length;
- if (emitPacked()) {
- int targetsCount = numberOfTargetsForPacked();
+ assert mode.isGeneratingDex();
+ if (emitPacked(mode)) {
+ int targetsCount = (int) numberOfTargetsIfPacked(keys);
if (targets.length == targetsCount) {
// All targets are already present.
return new PackedSwitchPayload(getFirstKey(), targets);
@@ -287,7 +318,7 @@
CfLabel fallthroughLabel = builder.getLabel(fallthroughBlock());
List<CfLabel> labels = new ArrayList<>(numberOfKeys());
List<BasicBlock> successors = getBlock().getSuccessors();
- if (emitPacked()) {
+ if (emitPacked(InternalOutputMode.ClassFile)) {
int min = keys[0];
int max = keys[keys.length - 1];
int index = 0;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 7089293..fcd28b2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -61,6 +61,7 @@
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
@@ -626,7 +627,7 @@
int fallthroughTarget =
getInfo(fallthroughTargetInstruction).getOffset() - getInfo(ir).getOffset();
- return ir.buildPayload(targets, fallthroughTarget);
+ return ir.buildPayload(targets, fallthroughTarget, InternalOutputMode.DexIndexed);
}
// Helpers for computing the try items and handlers.
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 05fd603..f0d5d7b 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
@@ -1161,9 +1161,11 @@
}
private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- // After all the optimizations have take place, we compute whether method should be inlinedex.
+ // After all the optimizations have take place, we compute whether method should be inlined.
ConstraintWithTarget state;
- if (!options.enableInlining || inliner == null) {
+ if (!options.enableInlining
+ || inliner == null
+ || method.getOptimizationInfo().isReachabilitySensitive()) {
state = ConstraintWithTarget.NEVER;
} else {
state = inliner.computeInliningConstraint(code, method);
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 d976c6f..553eaa2 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
@@ -81,6 +81,7 @@
import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
@@ -88,6 +89,7 @@
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LongInterval;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Equivalence;
@@ -108,13 +110,13 @@
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 it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
@@ -124,6 +126,7 @@
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.PriorityQueue;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -526,6 +529,198 @@
newBlocks.forEach(blocksIterator::add);
}
+ private static class Interval {
+
+ private final IntList keys = new IntArrayList();
+
+ public Interval(IntList... allKeys) {
+ assert allKeys.length > 0;
+ for (IntList keys : allKeys) {
+ assert keys.size() > 0;
+ this.keys.addAll(keys);
+ }
+ }
+
+ public int getMin() {
+ return keys.getInt(0);
+ }
+
+ public int getMax() {
+ return keys.getInt(keys.size() - 1);
+ }
+
+ public void addInterval(Interval other) {
+ assert getMax() < other.getMin();
+ keys.addAll(other.keys);
+ }
+
+ public long packedSavings(InternalOutputMode mode) {
+ long packedTargets = (long) getMax() - (long) getMin() + 1;
+ if (!Switch.canBePacked(mode, packedTargets)) {
+ return Long.MIN_VALUE + 1;
+ }
+ long sparseCost = Switch.baseSparseSize(mode) + Switch.sparsePayloadSize(mode, keys.size());
+ long packedCost = Switch.basePackedSize(mode) + Switch.packedPayloadSize(mode, packedTargets);
+ return sparseCost - packedCost;
+ }
+
+ public long estimatedSize(InternalOutputMode mode) {
+ return Switch.estimatedSize(mode, keys.toIntArray());
+ }
+ }
+
+ private Interval combineOrAddInterval(
+ List<Interval> intervals, Interval previous, Interval current) {
+ // As a first iteration, we only combine intervals if their packed size is less than their
+ // sparse counterpart. In CF we will have to add a load and a jump which add to the
+ // stack map table (1 is the size of a same entry).
+ InternalOutputMode mode = options.getInternalOutputMode();
+ int penalty = mode.isGeneratingClassFiles() ? 3 + 1 : 0;
+ if (previous == null) {
+ intervals.add(current);
+ return current;
+ }
+ Interval combined = new Interval(previous.keys, current.keys);
+ long packedSavings = combined.packedSavings(mode);
+ if (packedSavings <= 0
+ || packedSavings < previous.estimatedSize(mode) + current.estimatedSize(mode) - penalty) {
+ intervals.add(current);
+ return current;
+ } else {
+ intervals.set(intervals.size() - 1, combined);
+ return combined;
+ }
+ }
+
+ private void tryAddToBiggestSavings(
+ Set<Interval> biggestPackedSet,
+ PriorityQueue<Interval> intervals,
+ Interval toAdd,
+ int maximumNumberOfSwitches) {
+ assert !biggestPackedSet.contains(toAdd);
+ long savings = toAdd.packedSavings(options.getInternalOutputMode());
+ if (savings <= 0) {
+ return;
+ }
+ if (intervals.size() < maximumNumberOfSwitches) {
+ intervals.add(toAdd);
+ biggestPackedSet.add(toAdd);
+ } else if (savings > intervals.peek().packedSavings(options.getInternalOutputMode())) {
+ intervals.add(toAdd);
+ biggestPackedSet.add(toAdd);
+ biggestPackedSet.remove(intervals.poll());
+ }
+ }
+
+ private int sizeForKeysWrittenAsIfs(ValueType type, Collection<Integer> keys) {
+ int ifsSize = If.estimatedSize(options.getInternalOutputMode()) * keys.size();
+ // In Cf we also require a load as well (and a stack map entry)
+ if (options.getInternalOutputMode().isGeneratingClassFiles()) {
+ ifsSize += keys.size() * (3 + 1);
+ }
+ for (int k : keys) {
+ if (k != 0) {
+ ifsSize += ConstNumber.estimatedSize(options.getInternalOutputMode(), type, k);
+ }
+ }
+ return ifsSize;
+ }
+
+ private int codeUnitMargin() {
+ return options.getInternalOutputMode().isGeneratingClassFiles() ? 3 : 1;
+ }
+
+ private int findIfsForCandidates(List<Interval> newSwitches, Switch theSwitch, IntList outliers) {
+ Set<Interval> switchesToRemove = new HashSet<>();
+ InternalOutputMode mode = options.getInternalOutputMode();
+ int outliersAsIfSize = 0;
+ // The candidateForIfs is either an index to a switch that can be eliminated totally or a sparse
+ // where removing a key may produce a greater saving. It is only if keys are small in the packed
+ // switch that removing the keys makes sense (size wise).
+ for (Interval candidate : newSwitches) {
+ int maxIfBudget = 10;
+ long switchSize = candidate.estimatedSize(mode);
+ int sizeOfAllKeysAsIf = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), candidate.keys);
+ if (candidate.keys.size() <= maxIfBudget
+ && sizeOfAllKeysAsIf < switchSize - codeUnitMargin()) {
+ outliersAsIfSize += sizeOfAllKeysAsIf;
+ switchesToRemove.add(candidate);
+ outliers.addAll(candidate.keys);
+ continue;
+ }
+ // One could do something clever here, but we use a simple algorithm that use the fact that
+ // all keys are sorted in ascending order and that the smallest absolute value will give the
+ // best saving.
+ IntList candidateKeys = candidate.keys;
+ int smallestPosition = -1;
+ long smallest = Long.MAX_VALUE;
+ for (int i = 0; i < candidateKeys.size(); i++) {
+ long current = Math.abs((long) candidateKeys.getInt(i));
+ if (current < smallest) {
+ smallestPosition = i;
+ smallest = current;
+ }
+ }
+ // Add as many keys forward and backward as we have budget and we decrease in size.
+ IntList ifKeys = new IntArrayList();
+ ifKeys.add(candidateKeys.getInt(smallestPosition));
+ long previousSavings = 0;
+ long currentSavings =
+ switchSize
+ - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
+ - Switch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+ int minIndex = smallestPosition - 1;
+ int maxIndex = smallestPosition + 1;
+ while (ifKeys.size() < maxIfBudget && currentSavings > previousSavings) {
+ if (minIndex >= 0 && maxIndex < candidateKeys.size()) {
+ long valMin = Math.abs((long) candidateKeys.getInt(minIndex));
+ int valMax = Math.abs(candidateKeys.getInt(maxIndex));
+ if (valMax <= valMin) {
+ ifKeys.add(candidateKeys.getInt(maxIndex++));
+ } else {
+ ifKeys.add(candidateKeys.getInt(minIndex--));
+ }
+ } else if (minIndex >= 0) {
+ ifKeys.add(candidateKeys.getInt(minIndex--));
+ } else if (maxIndex < candidateKeys.size()) {
+ ifKeys.add(candidateKeys.getInt(maxIndex++));
+ } else {
+ // No more elements to add as if's.
+ break;
+ }
+ previousSavings = currentSavings;
+ currentSavings =
+ switchSize
+ - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
+ - Switch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+ }
+ if (previousSavings >= currentSavings) {
+ // Remove the last added key since it did not contribute to savings.
+ int lastKey = ifKeys.getInt(ifKeys.size() - 1);
+ ifKeys.removeInt(ifKeys.size() - 1);
+ if (lastKey == candidateKeys.getInt(minIndex + 1)) {
+ minIndex++;
+ } else {
+ maxIndex--;
+ }
+ }
+ // Adjust pointers into the candidate keys.
+ minIndex++;
+ maxIndex--;
+ if (ifKeys.size() > 0) {
+ int ifsSize = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys);
+ long newSwitchSize = Switch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+ if (newSwitchSize + ifsSize + codeUnitMargin() < switchSize) {
+ candidateKeys.removeElements(minIndex, maxIndex);
+ outliers.addAll(ifKeys);
+ outliersAsIfSize += ifsSize;
+ }
+ }
+ }
+ newSwitches.removeAll(switchesToRemove);
+ return outliersAsIfSize;
+ }
+
public void rewriteSwitch(IRCode code) {
ListIterator<BasicBlock> blocksIterator = code.listIterator();
while (blocksIterator.hasNext()) {
@@ -555,75 +750,91 @@
iterator.replaceCurrentInstruction(theIf);
}
} else {
- // Split keys into outliers and sequences.
- List<IntList> sequences = new ArrayList<>();
- IntList outliers = new IntArrayList();
+ // If there are more than 1 key, we use the following algorithm to find keys to combine.
+ // First, scan through the keys forward and combine each packed interval with the
+ // previous interval if it gives a net saving.
+ // Secondly, go through all created intervals and combine the ones without a saving into
+ // a single interval and keep a max number of packed switches.
+ // Finally, go through all intervals and check if the switch or part of the switch
+ // should be transformed to ifs.
- IntList current = new IntArrayList();
+ // Phase 1: Combine packed intervals.
+ InternalOutputMode mode = options.getInternalOutputMode();
int[] keys = theSwitch.getKeys();
+ int maxNumberOfIfsOrSwitches = 10;
+ PriorityQueue<Interval> biggestPackedSavings =
+ new PriorityQueue<>(
+ (x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
+ Set<Interval> biggestPackedSet = new HashSet<>();
+ List<Interval> intervals = new ArrayList<>();
int previousKey = keys[0];
- current.add(previousKey);
+ IntList currentKeys = new IntArrayList();
+ currentKeys.add(previousKey);
+ Interval previousInterval = null;
for (int i = 1; i < keys.length; i++) {
- assert current.size() > 0;
- assert current.getInt(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);
+ Interval current = new Interval(currentKeys);
+ Interval added = combineOrAddInterval(intervals, previousInterval, current);
+ if (added != current && biggestPackedSet.contains(previousInterval)) {
+ biggestPackedSet.remove(previousInterval);
+ biggestPackedSavings.remove(previousInterval);
}
- current = new IntArrayList();
+ tryAddToBiggestSavings(
+ biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+ previousInterval = added;
+ currentKeys = new IntArrayList();
}
- current.add(key);
+ currentKeys.add(key);
previousKey = key;
}
- if (current.size() == 1) {
- outliers.add(previousKey);
- } else {
- sequences.add(current);
+ Interval current = new Interval(currentKeys);
+ Interval added = combineOrAddInterval(intervals, previousInterval, current);
+ if (added != current && biggestPackedSet.contains(previousInterval)) {
+ biggestPackedSet.remove(previousInterval);
+ biggestPackedSavings.remove(previousInterval);
+ }
+ tryAddToBiggestSavings(
+ biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+
+ // Phase 2: combine sparse intervals into a single bin.
+ // Check if we should save a space for a sparse switch, if so, remove the switch with
+ // the smallest savings.
+ if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
+ && maxNumberOfIfsOrSwitches < intervals.size()) {
+ biggestPackedSet.remove(biggestPackedSavings.poll());
+ }
+ Interval sparse = null;
+ List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
+ for (int i = 0; i < intervals.size(); i++) {
+ Interval interval = intervals.get(i);
+ if (biggestPackedSet.contains(interval)) {
+ newSwitches.add(interval);
+ } else if (sparse == null) {
+ sparse = interval;
+ newSwitches.add(sparse);
+ } else {
+ sparse.addInterval(interval);
+ }
}
- // Get the existing dex size for the payload and switch.
- int currentSize = Switch.payloadSize(keys) + Switch.estimatedDexSize();
+ // Phase 3: at this point we are guaranteed to have the biggest saving switches
+ // in newIntervals, potentially with a switch combining the remaining intervals.
+ // Now we check to see if we can create any if's to reduce size.
+ IntList outliers = new IntArrayList();
+ int outliersAsIfSize = findIfsForCandidates(newSwitches, theSwitch, outliers);
- // 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) {
- if (outlier != 0) {
- rewrittenSize += ConstNumber.estimatedDexSize(
- theSwitch.value().outType(), outlier);
- }
- rewrittenSize += If.estimatedDexSize();
- }
- for (List<Integer> sequence : sequences) {
- rewrittenSize += Switch.payloadSize(sequence);
- }
- rewrittenSize += Switch.estimatedDexSize() * sequences.size();
+ long newSwitchesSize = 0;
+ List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
+ for (Interval interval : newSwitches) {
+ newSwitchesSize += interval.estimatedSize(mode);
+ newSwitchSequences.add(interval.keys);
+ }
- if (rewrittenSize < currentSize) {
- convertSwitchToSwitchAndIfs(
- 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);
- }
+ long currentSize = Switch.estimatedSize(mode, theSwitch.getKeys());
+ if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
+ convertSwitchToSwitchAndIfs(
+ code, blocksIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a3c805f..7c27264 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -156,6 +156,18 @@
return programConsumer != null;
}
+ public InternalOutputMode getInternalOutputMode() {
+ assert hasConsumer();
+ if (isGeneratingDexIndexed()) {
+ return InternalOutputMode.DexIndexed;
+ } else if (isGeneratingDexFilePerClassFile()) {
+ return InternalOutputMode.DexFilePerClassFile;
+ } else if (isGeneratingClassFiles()) {
+ return InternalOutputMode.ClassFile;
+ }
+ throw new UnsupportedOperationException("Cannot find internal output mode.");
+ }
+
public boolean isGeneratingDex() {
return isGeneratingDexIndexed() || isGeneratingDexFilePerClassFile();
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOutputMode.java b/src/main/java/com/android/tools/r8/utils/InternalOutputMode.java
new file mode 100644
index 0000000..d5fc357
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/InternalOutputMode.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, 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;
+
+public enum InternalOutputMode {
+ DexIndexed,
+ DexFilePerClassFile,
+ ClassFile;
+
+ public boolean isGeneratingClassFiles() {
+ return this == ClassFile;
+ }
+
+ public boolean isGeneratingDex() {
+ return this != ClassFile;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index edd6fd6..4c2fedc 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.code.AddIntLit8;
import com.android.tools.r8.code.Const4;
@@ -29,7 +28,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
class TestClass {
@@ -124,7 +122,8 @@
private void checkNoLocals(DexCode code) {
// Even if we preserve live range of locals, we do not output locals information
// as this is a release build.
- assertTrue(Arrays.stream(code.getDebugInfo().events)
+ assertTrue((code.getDebugInfo() == null) ||
+ Arrays.stream(code.getDebugInfo().events)
.allMatch(event -> !(event instanceof StartLocal)));
}
@@ -171,10 +170,10 @@
.addKeepRules(keepRules)
// Keep the annotation class.
.addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
- // Keep the annotation and debug information which is needed for debug mode to actually
- // keep things alive.
- .addKeepRules("-keepattributes *Annotations*,LineNumberTable," +
- "LocalVariableTable,LocalVariableTypeTable")
+ // Keep the annotation so R8 can find it and honor it. It also needs to be available
+ // at runtime so that the Art runtime can honor it as well, so if it is not kept we
+ // do not have to honor it as the runtime will not know to do so in any case.
+ .addKeepRules("-keepattributes RuntimeVisibleAnnotations")
.compile()
.inspector();
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index 65eb6f0..4d00bba 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.List;
@@ -152,72 +151,6 @@
}
}
- private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
- JasminBuilder builder = new JasminBuilder();
- JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
- clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
- " .limit stack 1",
- " .limit locals 1",
- " iload 0",
- " lookupswitch",
- " " + key1 + " : case_1",
- " " + key2 + " : case_2",
- " default : case_default",
- " case_1:",
- " iconst_3",
- " goto return_",
- " case_2:",
- " iconst_4",
- " goto return_",
- " case_default:",
- " iconst_5",
- " return_:",
- " ireturn");
-
- clazz.addMainMethod(
- " .limit stack 2",
- " .limit locals 1",
- " getstatic java/lang/System/out Ljava/io/PrintStream;",
- " ldc 2",
- " invokestatic Test/test(I)I",
- " invokevirtual java/io/PrintStream/print(I)V",
- " return");
-
- AndroidApp app =
- ToolHelper.runR8(
- ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
- .addLibraryFiles(runtimeJar(backend))
- .build());
-
- MethodSubject method =
- getMethodSubject(app, "Test", new MethodSignature("test", "int", ImmutableList.of("int")));
- Statistics stat = countInstructions(method.iterateInstructions());
- if (SwitchRewritingTest.twoCaseWillUsePackedSwitch(key1, key2)) {
- int expectedPackedSwitchCount = 1;
- int expectedSparseSwitchCount = 0;
- assertEquals(new Statistics(0, expectedPackedSwitchCount, expectedSparseSwitchCount), stat);
- } else {
- assertEquals(new Statistics(2, 0, 0), stat);
- }
- }
-
- @Test
- public void twoCaseSparseToPackedJar() throws Exception {
- for (int delta = 1; delta <= 3; delta++) {
- runTwoCaseSparseToPackedJarTest(0, delta);
- runTwoCaseSparseToPackedJarTest(-delta, 0);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
- runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
- }
- runTwoCaseSparseToPackedJarTest(-1, 1);
- runTwoCaseSparseToPackedJarTest(-2, 1);
- runTwoCaseSparseToPackedJarTest(-1, 2);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
- }
-
private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
Integer additionalLastKey) throws Exception {
JasminBuilder builder = new JasminBuilder();
@@ -372,30 +305,139 @@
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);
+ runConvertCasesToIf(ImmutableList.of(0, 1, 2), -100, 3, 0, 0);
+ if (backend == Backend.DEX) {
+ runConvertCasesToIf(ImmutableList.of(1000, 2000, 3000, 4000, 5000), -100, 5, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3), -100, 4, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4), -100, 5, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4, 5), -100, 6, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4, 5, 6), -100, 0, 1, 0);
+ } else {
+ runConvertCasesToIf(ImmutableList.of(1000, 2000, 3000, 4000, 5000), -100, 0, 0, 1);
+ runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3), -100, 0, 1, 0);
+ }
+ runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003), -100, 0, 1, 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), -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);
+ runConvertCasesToIf(ImmutableList.of(0, 3, 1000, 1001, 1002, 1003, 1004, 1005), -100, 2, 1, 0);
+ runConvertCasesToIf(
+ ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, 1005), -100, 1, 1, 0);
+ runConvertCasesToIf(
+ ImmutableList.of(1000, 1001, 1002, 1003, 1004, 1005, Integer.MAX_VALUE), -100, 1, 1, 0);
+ runConvertCasesToIf(
+ ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 1005, 2000), -100, 2, 1, 0);
+ runConvertCasesToIf(
+ ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, 1005, 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 to a combination of ifs and switches.
+ if (backend == Backend.DEX) {
+ runConvertCasesToIf(
+ ImmutableList.of(100, 200, 300, 400, 500, 600, 700, 800, 900, 1000), -100, 10, 0, 0);
+ runConvertCasesToIf(
+ ImmutableList.of(0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 5, 1, 0);
+ runConvertCasesToIf(
+ ImmutableList.of(-1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 1005),
+ -100,6,1,0);
+ runConvertCasesToIf(
+ ImmutableList.of(-1000, 0, 1, 2, 3, 4, 5, 6, 1000, 1001, 1002, 1003, 1004, 1005, 1006),
+ -100,1,2,0);
+ } else {
+ // runConvertCasesToIf(ImmutableList.of(1000, 2000, 3000, 4000, 5000, 6000), -100, 0, 0, 1);
+ runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4), -100, 0, 1, 0);
+ 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);
+ }
+ 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);
+ // For small keys and 0 having If's is marginally better.
+ runConvertCasesToIf(
+ ImmutableList.of(
+ -1000, -900, -800, -700, -600, -500, -400, -300, -200, -100, -1, 0, 1,
+ 1000, 1001, 1002, 1003, 1004,
+ 2000, 2100, 2200, 2300, 2400, 2500),
+ -100,3,1,1);
- // 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);
+ // Switches that hit maximum number of switchs and ifs.
+ runConvertCasesToIf(
+ ImmutableList.of(100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100), -100, 0, 0, 1);
+ if (backend == Backend.DEX) {
+ runConvertCasesToIf(
+ ImmutableList.of(
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 301, 302, 303, 304, 305, 306, 307, 308, 309,
+ 401, 402, 403, 404, 405, 406, 407, 408, 409,
+ 501, 502, 503, 504, 505, 506, 507, 508, 509,
+ 601, 602, 603, 604, 605, 606, 607, 608, 609,
+ 701, 702, 703, 704, 705, 706, 707, 708, 709,
+ 801, 802, 803, 804, 805, 806, 807, 808, 809,
+ 901, 902, 903, 904, 905, 906, 907, 908, 909,
+ 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+ 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109),
+ -100,8,9,1);
+ runConvertCasesToIf(
+ ImmutableList.of(
+ -2000, -1000,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 301, 302, 303, 304, 305, 306, 307, 308, 309,
+ 401, 402, 403, 404, 405, 406, 407, 408, 409,
+ 501, 502, 503, 504, 505, 506, 507, 508, 509,
+ 601, 602, 603, 604, 605, 606, 607, 608, 609,
+ 701, 702, 703, 704, 705, 706, 707, 708, 709,
+ 801, 802, 803, 804, 805, 806, 807, 808, 809,
+ 901, 902, 903, 904, 905, 906, 907, 908, 909,
+ 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+ 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109,
+ 10000, 11000),
+ -100,8,9,1);
+ } else {
+ runConvertCasesToIf(
+ ImmutableList.of(
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 301, 302, 303, 304, 305, 306, 307, 308, 309,
+ 401, 402, 403, 404, 405, 406, 407, 408, 409,
+ 501, 502, 503, 504, 505, 506, 507, 508, 509,
+ 601, 602, 603, 604, 605, 606, 607, 608, 609,
+ 701, 702, 703, 704, 705, 706, 707, 708, 709,
+ 801, 802, 803, 804, 805, 806, 807, 808, 809,
+ 901, 902, 903, 904, 905, 906, 907, 908, 909,
+ 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+ 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109),
+ -100,0,9,1);
+ runConvertCasesToIf(
+ ImmutableList.of(
+ -2000, -1000,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 301, 302, 303, 304, 305, 306, 307, 308, 309,
+ 401, 402, 403, 404, 405, 406, 407, 408, 409,
+ 501, 502, 503, 504, 505, 506, 507, 508, 509,
+ 601, 602, 603, 604, 605, 606, 607, 608, 609,
+ 701, 702, 703, 704, 705, 706, 707, 708, 709,
+ 801, 802, 803, 804, 805, 806, 807, 808, 809,
+ 901, 902, 903, 904, 905, 906, 907, 908, 909,
+ 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+ 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109,
+ 10000, 11000),
+ -100,0,9,1);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
index 354f3c2..12562ac 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -30,11 +30,6 @@
public class SwitchRewritingTest extends SmaliTestBase {
- static boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
- assert key1 != key2;
- return Math.abs((long) key1 - (long) key2) == 1;
- }
-
private boolean some16BitConst(Instruction instruction) {
return instruction instanceof Const4
|| instruction instanceof ConstHigh16
@@ -112,74 +107,6 @@
}
}
- private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2)
- throws CompilationFailedException {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
- MethodSignature signature = builder.addStaticMethod(
- "int",
- DEFAULT_METHOD_NAME,
- ImmutableList.of("int"),
- 0,
- " sparse-switch p0, :sparse_switch_data",
- " const/4 v0, 0x5",
- " goto :return",
- " :case_1",
- " const/4 v0, 0x3",
- " goto :return",
- " :case_2",
- " const/4 v0, 0x4",
- " :return",
- " return v0",
- " :sparse_switch_data",
- " .sparse-switch",
- " " + key1 + " -> :case_1",
- " " + key2 + " -> :case_2",
- " .end sparse-switch");
-
- builder.addMainMethod(
- 2,
- " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- " const/4 v1, 0",
- " invoke-static { v1 }, LTest;->method(I)I",
- " move-result v1",
- " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
- " return-void"
- );
-
- AndroidApp originalApplication = buildApplication(builder);
- AndroidApp processedApplication = processApplication(originalApplication);
- DexEncodedMethod method = getMethod(processedApplication, signature);
- DexCode code = method.getCode().asDexCode();
- if (twoCaseWillUsePackedSwitch(key1, key2)) {
- assertTrue(code.instructions[0] instanceof PackedSwitch);
- } else {
- if (key1 == 0) {
- // Const instruction may be before if.
- assertTrue(code.instructions[0] instanceof IfEqz || code.instructions[1] instanceof IfEqz);
- } else {
- // Const instruction before if.
- assertTrue(code.instructions[1] instanceof IfEq);
- }
- }
- }
-
- @Test
- public void twoCaseSparseToPackedOrIfsDex() throws CompilationFailedException {
- for (int delta = 1; delta <= 3; delta++) {
- runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
- runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
- runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
- runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
- }
- 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,
Integer additionalLastKey) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);