Merge "Test access to locals on instruction level when debugging"
diff --git a/src/main/java/com/android/tools/r8/code/PackedSwitch.java b/src/main/java/com/android/tools/r8/code/PackedSwitch.java
index 051da72..4ced6f9 100644
--- a/src/main/java/com/android/tools/r8/code/PackedSwitch.java
+++ b/src/main/java/com/android/tools/r8/code/PackedSwitch.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.code;
-import com.android.tools.r8.ir.code.Switch.Type;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -44,7 +43,7 @@
int offset = getOffset();
int payloadOffset = offset + getPayloadOffset();
int fallthroughOffset = offset + getSize();
- builder.resolveAndBuildSwitch(Type.PACKED, AA, fallthroughOffset, payloadOffset);
+ builder.resolveAndBuildSwitch(AA, fallthroughOffset, payloadOffset);
}
public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
index 25a5dcb..a3e2d15 100644
--- a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
@@ -25,9 +25,9 @@
}
}
- public PackedSwitchPayload(int size, int first_key, int[] targets) {
- assert size > 0; // Empty switches should be eliminated.
- this.size = size;
+ public PackedSwitchPayload(int first_key, int[] targets) {
+ assert targets.length > 0; // Empty switches should be eliminated.
+ this.size = targets.length;
this.first_key = first_key;
this.targets = targets;
}
@@ -67,10 +67,6 @@
return 4 + (2 * targets.length);
}
- public static int getSizeForTargets(int targets) {
- return 4 + (2 * targets);
- }
-
@Override
public int numberOfKeys() {
return size;
diff --git a/src/main/java/com/android/tools/r8/code/SparseSwitch.java b/src/main/java/com/android/tools/r8/code/SparseSwitch.java
index 8e6862a..f431454 100644
--- a/src/main/java/com/android/tools/r8/code/SparseSwitch.java
+++ b/src/main/java/com/android/tools/r8/code/SparseSwitch.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
-import com.android.tools.r8.ir.code.Switch.Type;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -43,7 +42,7 @@
int offset = getOffset();
int payloadOffset = offset + getPayloadOffset();
int fallthroughOffset = offset + getSize();
- builder.resolveAndBuildSwitch(Type.SPARSE, AA, fallthroughOffset, payloadOffset);
+ builder.resolveAndBuildSwitch(AA, fallthroughOffset, payloadOffset);
}
public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
index 027c253b..328e6d2 100644
--- a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
@@ -29,9 +29,9 @@
}
}
- public SparseSwitchPayload(int size, int[] keys, int[] targets) {
- assert size > 0; // Empty switches should be eliminated.
- this.size = size;
+ public SparseSwitchPayload(int[] keys, int[] targets) {
+ assert targets.length > 0; // Empty switches should be eliminated.
+ this.size = targets.length;
this.keys = keys;
this.targets = targets;
}
@@ -74,10 +74,6 @@
return 2 + (2 * keys.length) + (2 * targets.length);
}
- public static int getSizeForTargets(int targets) {
- return 2 + (4 * targets);
- }
-
@Override
public int numberOfKeys() {
return size;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 1636c56..39f741f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
@@ -94,6 +95,12 @@
public boolean add(DexAnnotationSetRefList annotationSetRefList) {
return true;
}
+
+ @Override
+ public boolean setAnnotationsDirectoryForClass(DexProgramClass clazz,
+ DexAnnotationDirectory annotationDirectory) {
+ return true;
+ }
}
public ApplicationWriter(
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index 3ea63bb..c216cec 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -113,6 +113,7 @@
public static final int U4BIT_MAX = (1 << 4) - 1;
public static final int U8BIT_MAX = (1 << 8) - 1;
public static final int U16BIT_MAX = (1 << 16) - 1;
+ public static final long U32BIT_MAX = (1L << 32) - 1;
public static final int ACC_PUBLIC = 0x1;
public static final int ACC_PRIVATE = 0x2;
public static final int ACC_PROTECTED = 0x4;
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 33cea6d..8afbc8b 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexAnnotationSetRefList;
@@ -48,7 +49,6 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LebUtils;
-import com.android.tools.r8.utils.OrderedMergingIterator;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.security.MessageDigest;
@@ -76,7 +76,6 @@
private final NamingLens namingLens;
private final DexOutputBuffer dest = new DexOutputBuffer();
private final MixedSectionOffsets mixedSectionOffsets;
- private int numberOfAnnotationDirectories;
public FileWriter(
ObjectToOffsetMapping mapping,
@@ -124,7 +123,8 @@
public FileWriter collect() {
// Use the class array from the mapping, as it has a deterministic iteration order.
- new ProgramClassDependencyCollector(application, mapping.getClasses()).run(mapping.getClasses());
+ new ProgramClassDependencyCollector(application, mapping.getClasses())
+ .run(mapping.getClasses());
// Sort the class members.
// Needed before adding static-value arrays and writing annotation directories and classes.
@@ -145,6 +145,8 @@
DexItem.collectAll(mixedSectionOffsets, mapping.getCallSites());
+ DexItem.collectAll(mixedSectionOffsets, mapping.getClasses());
+
return this;
}
@@ -218,8 +220,8 @@
writeItems(mixedSectionOffsets.getAnnotationSetRefLists(),
layout::setAnnotationSetRefListsOffset, this::writeAnnotationSetRefList, 4);
// Write the annotation directories.
- writeItems(Arrays.asList(mapping.getClasses()), layout::setAnnotationDirectoriesOffset,
- this::writeAnnotationDirectoryForClass, 4);
+ writeItems(mixedSectionOffsets.getAnnotationDirectories(),
+ layout::setAnnotationDirectoriesOffset, this::writeAnnotationDirectory, 4);
// Write the rest.
writeItems(mixedSectionOffsets.getClassesWithData(), layout::setClassDataOffset,
this::writeClassData);
@@ -584,47 +586,21 @@
}
}
- private void writeAnnotationDirectoryForClass(DexProgramClass clazz) {
- if (clazz.hasAnnotations()) {
- mixedSectionOffsets.setOffsetForAnnotationsDirectory(clazz, dest.align(4));
- numberOfAnnotationDirectories++;
- assert isSorted(clazz.directMethods());
- assert isSorted(clazz.virtualMethods());
- OrderedMergingIterator<DexEncodedMethod, DexMethod> methods =
- new OrderedMergingIterator<>(clazz.directMethods(), clazz.virtualMethods());
- List<DexEncodedMethod> methodAnnotations = new ArrayList<>();
- List<DexEncodedMethod> parameterAnnotations = new ArrayList<>();
- while (methods.hasNext()) {
- DexEncodedMethod method = methods.next();
- if (!method.annotations.isEmpty()) {
- methodAnnotations.add(method);
- }
- if (!method.parameterAnnotations.isEmpty()) {
- parameterAnnotations.add(method);
- }
- }
- assert isSorted(clazz.staticFields());
- assert isSorted(clazz.instanceFields());
- OrderedMergingIterator<DexEncodedField, DexField> fields =
- new OrderedMergingIterator<>(clazz.staticFields(), clazz.instanceFields());
- List<DexEncodedField> fieldAnnotations = new ArrayList<>();
- while (fields.hasNext()) {
- DexEncodedField field = fields.next();
- if (!field.annotations.isEmpty()) {
- fieldAnnotations.add(field);
- }
- }
- dest.putInt(mixedSectionOffsets.getOffsetFor(clazz.annotations));
- dest.putInt(fieldAnnotations.size());
- dest.putInt(methodAnnotations.size());
- dest.putInt(parameterAnnotations.size());
- writeMemberAnnotations(fieldAnnotations,
- item -> mixedSectionOffsets.getOffsetFor(item.annotations));
- writeMemberAnnotations(methodAnnotations,
- item -> mixedSectionOffsets.getOffsetFor(item.annotations));
- writeMemberAnnotations(parameterAnnotations,
- item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotations));
- }
+ private void writeAnnotationDirectory(DexAnnotationDirectory annotationDirectory) {
+ mixedSectionOffsets.setOffsetForAnnotationsDirectory(annotationDirectory, dest.align(4));
+ dest.putInt(mixedSectionOffsets.getOffsetFor(annotationDirectory.getClazzAnnotations()));
+ List<DexEncodedMethod> methodAnnotations = annotationDirectory.getMethodAnnotations();
+ List<DexEncodedMethod> parameterAnnotations = annotationDirectory.getParameterAnnotations();
+ List<DexEncodedField> fieldAnnotations = annotationDirectory.getFieldAnnotations();
+ dest.putInt(fieldAnnotations.size());
+ dest.putInt(methodAnnotations.size());
+ dest.putInt(parameterAnnotations.size());
+ writeMemberAnnotations(fieldAnnotations,
+ item -> mixedSectionOffsets.getOffsetFor(item.annotations));
+ writeMemberAnnotations(methodAnnotations,
+ item -> mixedSectionOffsets.getOffsetFor(item.annotations));
+ writeMemberAnnotations(parameterAnnotations,
+ item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotations));
}
private void writeEncodedFields(DexEncodedField[] fields) {
@@ -788,7 +764,7 @@
mixedSectionOffsets.getAnnotationSetRefLists().size());
size += writeMapItem(Constants.TYPE_ANNOTATIONS_DIRECTORY_ITEM,
layout.getAnnotationDirectoriesOffset(),
- numberOfAnnotationDirectories);
+ mixedSectionOffsets.getAnnotationDirectories().size());
size += writeMapItem(Constants.TYPE_CLASS_DATA_ITEM, layout.getClassDataOffset(),
mixedSectionOffsets.getClassesWithData().size());
size += writeMapItem(Constants.TYPE_ENCODED_ARRAY_ITEM, layout.getEncodedArrarysOffset(),
@@ -1093,8 +1069,11 @@
private final Hashtable<DexAnnotationSetRefList, Integer> annotationSetRefLists
= new Hashtable<>();
private final List<DexAnnotationSetRefList> annotationSetRefListsList = new LinkedList<>();
- private final Hashtable<DexProgramClass, Integer> annotationDirectories
+ private final Hashtable<DexProgramClass, DexAnnotationDirectory> clazzToAnnotationDirectory
= new Hashtable<>();
+ private final Hashtable<DexAnnotationDirectory, Integer> annotationDirectories
+ = new Hashtable<>();
+ private final List<DexAnnotationDirectory> annotationDirectoriesList = new LinkedList<>();
private final Hashtable<DexProgramClass, Integer> classesWithData = new Hashtable<>();
private final List<DexProgramClass> classesWithDataList = new LinkedList<>();
private final Hashtable<DexEncodedArray, Integer> encodedArrays = new Hashtable<>();
@@ -1157,6 +1136,14 @@
return add(annotations, annotationsList, annotation);
}
+ @Override
+ public boolean setAnnotationsDirectoryForClass(DexProgramClass clazz,
+ DexAnnotationDirectory annotationDirectory) {
+ DexAnnotationDirectory previous = clazzToAnnotationDirectory.put(clazz, annotationDirectory);
+ assert previous == null;
+ return add(annotationDirectories, annotationDirectoriesList, annotationDirectory);
+ }
+
public boolean add(DexString string) {
return add(stringData, stringDataList, string);
}
@@ -1193,6 +1180,10 @@
return Collections.unmodifiableList(classesWithDataList);
}
+ public List<DexAnnotationDirectory> getAnnotationDirectories() {
+ return Collections.unmodifiableList(annotationDirectoriesList);
+ }
+
public List<DexEncodedArray> getEncodedArrays() {
return Collections.unmodifiableList(encodedArraysList);
}
@@ -1232,10 +1223,11 @@
public int getOffsetForAnnotationsDirectory(DexProgramClass clazz) {
- Integer offset = annotationDirectories.get(clazz);
- if (offset == null) {
+ if (!clazz.hasAnnotations()) {
return Constants.NO_OFFSET;
}
+ Integer offset = annotationDirectories.get(clazzToAnnotationDirectory.get(clazz));
+ assert offset != null;
return offset;
}
@@ -1292,10 +1284,8 @@
setOffsetFor(annotationSet, offset, annotationSets);
}
- void setOffsetForAnnotationsDirectory(DexProgramClass clazz,
- int offset) {
- Integer previous = annotationDirectories.put(clazz, offset);
- assert previous == null;
+ void setOffsetForAnnotationsDirectory(DexAnnotationDirectory annotationDirectory, int offset) {
+ setOffsetFor(annotationDirectory, offset, annotationDirectories);
}
void setOffsetFor(DexProgramClass aClassWithData, int offset) {
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index bc3fe98..eb7ae98 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.dex;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexCode;
@@ -95,4 +96,14 @@
* @return true if the item was not added before
*/
public abstract boolean add(DexAnnotation annotation);
+
+ /**
+ * Adds the given annotation directory to the collection.
+ *
+ * Add a dependency between the clazz and the annotation directory.
+ *
+ * @return true if the item was not added before
+ */
+ public abstract boolean setAnnotationsDirectoryForClass(DexProgramClass clazz,
+ DexAnnotationDirectory annotationDirectory);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
new file mode 100644
index 0000000..4cda153
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2016, 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.graph;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.OrderedMergingIterator;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+public class DexAnnotationDirectory extends DexItem {
+
+ private final DexProgramClass clazz;
+ private final List<DexEncodedMethod> methodAnnotations;
+ private final List<DexEncodedMethod> parameterAnnotations;
+ private final List<DexEncodedField> fieldAnnotations;
+
+ public DexAnnotationDirectory(DexProgramClass clazz) {
+ this.clazz = clazz;
+ assert isSorted(clazz.directMethods());
+ assert isSorted(clazz.virtualMethods());
+ OrderedMergingIterator<DexEncodedMethod, DexMethod> methods =
+ new OrderedMergingIterator<>(clazz.directMethods(), clazz.virtualMethods());
+ methodAnnotations = new ArrayList<>();
+ parameterAnnotations = new ArrayList<>();
+ while (methods.hasNext()) {
+ DexEncodedMethod method = methods.next();
+ if (!method.annotations.isEmpty()) {
+ methodAnnotations.add(method);
+ }
+ if (!method.parameterAnnotations.isEmpty()) {
+ parameterAnnotations.add(method);
+ }
+ }
+ assert isSorted(clazz.staticFields());
+ assert isSorted(clazz.instanceFields());
+ OrderedMergingIterator<DexEncodedField, DexField> fields =
+ new OrderedMergingIterator<>(clazz.staticFields(), clazz.instanceFields());
+ fieldAnnotations = new ArrayList<>();
+ while (fields.hasNext()) {
+ DexEncodedField field = fields.next();
+ if (!field.annotations.isEmpty()) {
+ fieldAnnotations.add(field);
+ }
+ }
+ }
+
+ public DexProgramClass getDexProgramClass() {
+ return clazz;
+ }
+
+ public DexAnnotationSet getClazzAnnotations() {
+ return clazz.annotations;
+ }
+
+ public List<DexEncodedMethod> getMethodAnnotations() {
+ return methodAnnotations;
+ }
+
+ public List<DexEncodedMethod> getParameterAnnotations() {
+ return parameterAnnotations;
+ }
+
+ public List<DexEncodedField> getFieldAnnotations() {
+ return fieldAnnotations;
+ }
+
+
+ /**
+ * DexAnnotationDirectory of a class can be canonicalized only if a clazz has annotations and
+ * does not contains annotations for his fields, methods or parameters. Indeed, if a field, method
+ * or parameter has annotations in this case, the DexAnnotationDirectory can not be shared since
+ * it will contains information about field, method and parameters that are only related to only
+ * one class.
+ */
+ @Override
+ public final boolean equals(Object obj) {
+ if (!(obj instanceof DexAnnotationDirectory)) {
+ return false;
+ }
+ if (clazz.hasInternalizableAnnotation()) {
+ return clazz.annotations
+ .equals(((DexAnnotationDirectory) obj).clazz.annotations);
+ }
+ return super.equals(obj);
+ }
+
+ @Override
+ public final int hashCode() {
+ if (clazz.hasInternalizableAnnotation()) {
+ return clazz.annotations.hashCode();
+ }
+ return super.hashCode();
+ }
+
+ @Override
+ public void collectIndexedItems(IndexedItemCollection collection) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void collectMixedSectionItems(MixedSectionCollection collection) {
+ throw new Unreachable();
+ }
+
+ private static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+ return isSorted(items, KeyedDexItem::getKey);
+ }
+
+ private static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+ T current = null;
+ for (S item : items) {
+ T next = getter.apply(item);
+ if (current != null && current.compareTo(next) >= 0) {
+ return false;
+ }
+ current = next;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 9327fb0..9b62842 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.code.SwitchPayload;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexSourceCode;
@@ -187,11 +188,14 @@
builder.append(": ")
.append(insn.toString(naming))
.append('\n');
- if (debugInfo != null && debugInfo.address == insn.getOffset()) {
+ while (debugInfo != null && debugInfo.address == insn.getOffset()) {
builder.append(" ").append(debugInfo).append("\n");
debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
}
}
+ if (debugInfoIterator.hasNext()) {
+ throw new Unreachable("Could not print all debug information.");
+ }
if (tries.length > 0) {
builder.append("Tries (numbers are offsets)\n");
for (Try atry : tries) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 094a039..f93a7da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -54,6 +54,13 @@
}
@Override
+ void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+ if (hasAnnotations()) {
+ mixedItems.setAnnotationsDirectoryForClass(this, new DexAnnotationDirectory(this));
+ }
+ }
+
+ @Override
public void addDependencies(MixedSectionCollection collector) {
// We only have a class data item if there are methods or fields.
if (hasMethodsOrFields()) {
@@ -106,6 +113,14 @@
|| hasAnnotations(instanceFields);
}
+ public boolean hasInternalizableAnnotation() {
+ return !annotations.isEmpty()
+ && !hasAnnotations(virtualMethods)
+ && !hasAnnotations(directMethods)
+ && !hasAnnotations(staticFields)
+ && !hasAnnotations(instanceFields);
+ }
+
private boolean hasAnnotations(DexEncodedField[] fields) {
return fields != null && Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotation);
}
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 ab2056d..3829154 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
@@ -262,6 +262,12 @@
assert false : "replaceSuccessor did not find the predecessor to replace";
}
+ public void swapSuccessorsByIndex(int index1, int index2) {
+ BasicBlock t = successors.get(index1);
+ successors.set(index1, successors.get(index2));
+ successors.set(index2, t);
+ }
+
public void removeSuccessorsByIndex(List<Integer> successorsToRemove) {
if (successorsToRemove.isEmpty()) {
return;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 400c30a..8506160 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -356,6 +356,10 @@
return createValue(moveType, null);
}
+ public ConstNumber createIntConstant(int value) {
+ return new ConstNumber(ConstType.INT, createValue(MoveType.SINGLE), value);
+ }
+
public ConstNumber createTrue() {
return new ConstNumber(ConstType.INT, createValue(MoveType.SINGLE), 1);
}
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 a30444e..6b47ba2 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
@@ -15,32 +15,72 @@
public class Switch extends JumpInstruction {
- public enum Type {
- PACKED, SPARSE
- }
-
- private final Type type;
private final int[] keys;
private final int[] targetBlockIndices;
private int fallthroughBlockIndex;
public Switch(
- Type type,
Value value,
int[] keys,
int[] targetBlockIndices,
int fallthroughBlockIndex) {
super(null, value);
- this.type = type;
this.keys = keys;
this.targetBlockIndices = targetBlockIndices;
this.fallthroughBlockIndex = fallthroughBlockIndex;
+ assert valid();
}
- private Value value() {
+ private boolean valid() {
+ assert keys.length <= Constants.U16BIT_MAX;
+ // Keys must be acceding, and cannot target the fallthrough.
+ assert keys.length == targetBlockIndices.length;
+ for (int i = 1; i < keys.length - 1; i++) {
+ assert keys[i - 1] < keys[i];
+ assert targetBlockIndices[i] != fallthroughBlockIndex;
+ }
+ assert targetBlockIndices[keys.length - 1] != fallthroughBlockIndex;
+ return true;
+ }
+
+ public Value value() {
return inValues.get(0);
}
+ // Number of targets if this switch is emitted as a packed switch.
+ private long numberOfTargetsIfPacked() {
+ return ((long) keys[keys.length - 1]) - ((long) keys[0]) + 1;
+ }
+
+ private boolean canBePacked() {
+ // The size of a switch payload is stored in an ushort in the Dex file.
+ return numberOfTargetsIfPacked() <= Constants.U16BIT_MAX;
+ }
+
+ // Number of targets if this switch is emitted as a packed switch.
+ private int numberOfTargetsForPacked() {
+ assert canBePacked();
+ return (int) numberOfTargetsIfPacked();
+ }
+
+ // Size of the switch payload if emitted as packed (in code units).
+ private long packedPayloadSize() {
+ return (numberOfTargetsForPacked() * 2) + 4;
+ }
+
+ // Size of the switch payload if emitted as sparse (in code units).
+ private long sparsePayloadSize() {
+ return (keys.length * 4) + 2;
+ }
+
+ private boolean emitPacked() {
+ return canBePacked() && packedPayloadSize() <= sparsePayloadSize();
+ }
+
+ public int getFirstKey() {
+ return keys[0];
+ }
+
@Override
public boolean isSwitch() {
return true;
@@ -66,7 +106,7 @@
@Override
public void buildDex(DexBuilder builder) {
int value = builder.allocatedRegister(value(), getNumber());
- if (type == Type.PACKED) {
+ if (emitPacked()) {
builder.addSwitch(this, new PackedSwitch(value));
} else {
builder.addSwitch(this, new SparseSwitch(value));
@@ -74,15 +114,11 @@
}
public int numberOfKeys() {
- return targetBlockIndices.length;
+ return keys.length;
}
public int getKey(int index) {
- if (type == Type.PACKED) {
- return keys[0] + index;
- } else {
- return keys[index];
- }
+ return keys[index];
}
public int[] targetBlockIndices() {
@@ -111,11 +147,33 @@
getBlock().getSuccessors().set(fallthroughBlockIndex, block);
}
- public Nop buildPayload(int[] targets) {
- if (type == Type.PACKED) {
- return new PackedSwitchPayload(numberOfKeys(), keys[0], targets);
+ public Nop buildPayload(int[] targets, int fallthroughTarget) {
+ assert keys.length == targets.length;
+ if (emitPacked()) {
+ int targetsCount = numberOfTargetsForPacked();
+ if (targets.length == targetsCount) {
+ // All targets are already present.
+ return new PackedSwitchPayload(getFirstKey(), targets);
+ } else {
+ // Generate the list of targets for all key values. Set the target for keys not present
+ // to the fallthrough.
+ int[] packedTargets = new int[targetsCount];
+ int originalIndex = 0;
+ for (int i = 0; i < targetsCount; i++) {
+ int key = getFirstKey() + i;
+ if (keys[originalIndex] == key) {
+ packedTargets[i] = targets[originalIndex];
+ originalIndex++;
+ } else {
+ packedTargets[i] = fallthroughTarget;
+ }
+ }
+ assert originalIndex == keys.length;
+ return new PackedSwitchPayload(getFirstKey(), packedTargets);
+ }
} else {
- return new SparseSwitchPayload(numberOfKeys(), keys, targets);
+ assert numberOfKeys() == keys.length;
+ return new SparseSwitchPayload(keys, targets);
}
}
@@ -131,15 +189,9 @@
@Override
public String toString() {
- StringBuilder builder = new StringBuilder(
- super.toString() + " (" + (type == Type.PACKED ? "PACKED" : "SPARSE") + ")\n");
+ StringBuilder builder = new StringBuilder(super.toString()+ "\n");
for (int i = 0; i < numberOfKeys(); i++) {
builder.append(" ");
- if (type == Type.PACKED) {
- builder.append(keys[0] + i);
- } else {
- builder.append(keys[i]);
- }
builder.append(" -> ");
builder.append(targetBlock(i).getNumber());
builder.append("\n");
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 a306bbd..fc1eab5 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
@@ -456,7 +456,13 @@
com.android.tools.r8.ir.code.Instruction targetInstruction = targetBlock.entry();
targets[i] = getInfo(targetInstruction).getOffset() - getInfo(ir).getOffset();
}
- return ir.buildPayload(targets);
+ BasicBlock fallthroughBlock = ir.fallthroughBlock();
+ com.android.tools.r8.ir.code.Instruction fallthroughTargetInstruction =
+ fallthroughBlock.entry();
+ int fallthroughTarget =
+ getInfo(fallthroughTargetInstruction).getOffset() - getInfo(ir).getOffset();
+
+ return ir.buildPayload(targets, fallthroughTarget);
}
// Helpers for computing the try items and handlers.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 118d0eb..9b1aa3f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,7 +40,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.MoveType;
-import com.android.tools.r8.ir.code.Switch.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -219,9 +218,9 @@
}
@Override
- public void resolveAndBuildSwitch(Type type, int value, int fallthroughOffset, int payloadOffset,
+ public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset,
IRBuilder builder) {
- builder.addSwitch(type, value, switchPayloadResolver.getKeys(payloadOffset), fallthroughOffset,
+ builder.addSwitch(value, switchPayloadResolver.getKeys(payloadOffset), fallthroughOffset,
switchPayloadResolver.absoluteTargets(payloadOffset));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 8cd9a2a..fba48c9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.conversion;
-import com.android.tools.r8.code.PackedSwitchPayload;
-import com.android.tools.r8.code.SparseSwitchPayload;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
@@ -87,6 +85,8 @@
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -461,9 +461,8 @@
}
// Helper to resolve switch payloads and build switch instructions (dex code only).
- public void resolveAndBuildSwitch(Switch.Type type, int value, int fallthroughOffset,
- int payloadOffset) {
- source.resolveAndBuildSwitch(type, value, fallthroughOffset, payloadOffset, this);
+ public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset) {
+ source.resolveAndBuildSwitch(value, fallthroughOffset, payloadOffset, this);
}
// Helper to resolve fill-array data and build new-array instructions (dex code only).
@@ -1204,10 +1203,9 @@
}
}
- public void addSwitch(Switch.Type type, int value, int[] keys, int fallthroughOffset,
- int[] labelOffsets) {
+ public void addSwitch(int value, int[] keys, int fallthroughOffset, int[] labelOffsets) {
int numberOfTargets = labelOffsets.length;
- assert (type == Switch.Type.PACKED && keys.length == 1) || (keys.length == numberOfTargets);
+ assert (keys.length == 1) || (keys.length == numberOfTargets);
// If the switch has no targets simply add a goto to the fallthrough.
if (numberOfTargets == 0) {
@@ -1217,73 +1215,51 @@
Value switchValue = readRegister(value, MoveType.SINGLE);
- // Change a switch with just one case to an if.
- // TODO(62247472): Move these into their own pass.
- if (numberOfTargets == 1) {
- addSwitchIf(keys[0], value, labelOffsets[0], fallthroughOffset);
- return;
- }
-
- // javac generates sparse switch (lookupwitch bytecode) for all two-case switch. Change
- // to packed switch for consecutive keys.
- if (numberOfTargets == 2 && type == Switch.Type.SPARSE && keys[0] + 1 == keys[1]) {
- addInstruction(createSwitch(
- Switch.Type.PACKED, switchValue, new int[]{keys[0]}, fallthroughOffset, labelOffsets));
- closeCurrentBlock();
- return;
- }
-
- // javac generates packed switches pretty aggressively. We convert to sparse switches
- // if the dex sparse switch payload takes up less space than the packed payload.
- if (type == Switch.Type.PACKED) {
- int numberOfFallthroughs = 0;
+ // Find the keys not targeting the fallthrough.
+ IntList nonFallthroughKeys = new IntArrayList(numberOfTargets);
+ IntList nonFallthroughOffsets = new IntArrayList(numberOfTargets);
+ int numberOfFallthroughs = 0;
+ if (keys.length == 1) {
+ int key = keys[0];
for (int i = 0; i < numberOfTargets; i++) {
- if (labelOffsets[i] == fallthroughOffset) {
- ++numberOfFallthroughs;
+ if (labelOffsets[i] != fallthroughOffset) {
+ nonFallthroughKeys.add(key);
+ nonFallthroughOffsets.add(labelOffsets[i]);
+ } else {
+ numberOfFallthroughs++;
}
+ key++;
}
- // If this was a packed switch with only fallthrough cases we can make it a goto.
- // Oddly, this does happen.
- if (numberOfFallthroughs == numberOfTargets) {
- BlockInfo info = targets.get(fallthroughOffset);
- info.block.decrementUnfilledPredecessorCount(numberOfFallthroughs);
- addGoto(fallthroughOffset);
- return;
- }
- int numberOfSparseTargets = numberOfTargets - numberOfFallthroughs;
- int sparseSwitchPayloadSize = SparseSwitchPayload.getSizeForTargets(numberOfSparseTargets);
- int packedSwitchPayloadSize = PackedSwitchPayload.getSizeForTargets(numberOfTargets);
- int bytesSaved = packedSwitchPayloadSize - sparseSwitchPayloadSize;
- // Perform the rewrite if we can reduce the payload size by more than 20%.
- if (bytesSaved > (packedSwitchPayloadSize / 5)) {
- BlockInfo info = targets.get(fallthroughOffset);
- info.block.decrementUnfilledPredecessorCount(numberOfFallthroughs);
- int nextCaseIndex = 0;
- int currentKey = keys[0];
- keys = new int[numberOfSparseTargets];
- int[] newLabelOffsets = new int[numberOfSparseTargets];
- for (int i = 0; i < numberOfTargets; i++) {
- if (labelOffsets[i] != fallthroughOffset) {
- keys[nextCaseIndex] = currentKey;
- newLabelOffsets[nextCaseIndex] = labelOffsets[i];
- ++nextCaseIndex;
- }
- ++currentKey;
+ } else {
+ assert keys.length == numberOfTargets;
+ for (int i = 0; i < numberOfTargets; i++) {
+ if (labelOffsets[i] != fallthroughOffset) {
+ nonFallthroughKeys.add(keys[i]);
+ nonFallthroughOffsets.add(labelOffsets[i]);
+ } else {
+ numberOfFallthroughs++;
}
- addInstruction(createSwitch(
- Switch.Type.SPARSE, switchValue, keys, fallthroughOffset, newLabelOffsets));
- closeCurrentBlock();
- return;
}
}
+ targets.get(fallthroughOffset).block.decrementUnfilledPredecessorCount(numberOfFallthroughs);
- addInstruction(createSwitch(type, switchValue, keys, fallthroughOffset, labelOffsets));
+ // If this was switch with only fallthrough cases we can make it a goto.
+ // Oddly, this does happen.
+ if (numberOfFallthroughs == numberOfTargets) {
+ assert nonFallthroughKeys.size() == 0;
+ addGoto(fallthroughOffset);
+ return;
+ }
+
+ // Create a switch with only the non-fallthrough targets.
+ keys = nonFallthroughKeys.toIntArray();
+ labelOffsets = nonFallthroughOffsets.toIntArray();
+ addInstruction(createSwitch(switchValue, keys, fallthroughOffset, labelOffsets));
closeCurrentBlock();
}
- private Switch createSwitch(Switch.Type type, Value value, int[] keys, int fallthroughOffset,
- int[] targetOffsets) {
- assert keys.length > 0;
+ private Switch createSwitch(Value value, int[] keys, int fallthroughOffset, int[] targetOffsets) {
+ assert keys.length == targetOffsets.length;
// Compute target blocks for all keys. Only add a successor block once even
// if it is hit by more of the keys.
int[] targetBlockIndices = new int[targetOffsets.length];
@@ -1313,7 +1289,7 @@
targetBlockIndices[i] = targetBlockIndex;
}
}
- return new Switch(type, value, keys, targetBlockIndices, fallthroughBlockIndex);
+ return new Switch(value, keys, targetBlockIndices, fallthroughBlockIndex);
}
public void addThrow(int value) {
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 a5e2df7..ff8bda8 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
@@ -451,6 +451,7 @@
codeRewriter.rewriteMoveResult(code);
codeRewriter.splitConstants(code);
codeRewriter.foldConstants(code);
+ codeRewriter.rewriteSwitch(code);
codeRewriter.simplifyIf(code);
if (Log.ENABLED) {
Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 12be2c2..dd68322 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -27,7 +27,6 @@
import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
import com.android.tools.r8.ir.conversion.JarState.Local;
import com.android.tools.r8.ir.conversion.JarState.Slot;
@@ -465,7 +464,7 @@
}
@Override
- public void resolveAndBuildSwitch(Switch.Type type, int value, int fallthroughOffset,
+ public void resolveAndBuildSwitch(int value, int fallthroughOffset,
int payloadOffset, IRBuilder builder) {
throw new Unreachable();
}
@@ -2694,7 +2693,7 @@
private void build(TableSwitchInsnNode insn, IRBuilder builder) {
processLocalVariablesAtControlEdge(insn, builder);
- buildSwitch(Switch.Type.PACKED, insn.dflt, insn.labels, new int[]{insn.min}, builder);
+ buildSwitch(insn.dflt, insn.labels, new int[]{insn.min}, builder);
}
private void build(LookupSwitchInsnNode insn, IRBuilder builder) {
@@ -2703,10 +2702,10 @@
for (int i = 0; i < insn.keys.size(); i++) {
keys[i] = (int) insn.keys.get(i);
}
- buildSwitch(Switch.Type.SPARSE, insn.dflt, insn.labels, keys, builder);
+ buildSwitch(insn.dflt, insn.labels, keys, builder);
}
- private void buildSwitch(Switch.Type type, LabelNode dflt, List labels, int[] keys,
+ private void buildSwitch(LabelNode dflt, List labels, int[] keys,
IRBuilder builder) {
int index = state.pop(Type.INT_TYPE).register;
int fallthroughOffset = getOffset(dflt);
@@ -2715,7 +2714,7 @@
int offset = getOffset((LabelNode) labels.get(i));
labelOffsets[i] = offset;
}
- builder.addSwitch(type, index, keys, fallthroughOffset, labelOffsets);
+ builder.addSwitch(index, keys, fallthroughOffset, labelOffsets);
}
private void build(MultiANewArrayInsnNode insn, IRBuilder builder) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index a36091d..b1f9a14 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.Switch.Type;
/**
* Abstraction of the input/source code for the IRBuilder.
@@ -51,7 +50,7 @@
void buildPostlude(IRBuilder builder);
// Helper to resolve switch payloads and build switch instructions (dex code only).
- void resolveAndBuildSwitch(Type type, int value, int fallthroughOffset, int payloadOffset,
+ void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset,
IRBuilder builder);
// Helper to resolve fill-array data and build new-array instructions (dex code only).
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 faed53d..ba5611a 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
@@ -23,11 +23,11 @@
import com.android.tools.r8.ir.code.Cmp.Bias;
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;
import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -46,7 +46,6 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Switch;
-import com.android.tools.r8.ir.code.Switch.Type;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.utils.InternalOptions;
@@ -60,10 +59,12 @@
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -320,6 +321,37 @@
}
}
+ public void rewriteSwitch(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ if (instruction.isSwitch()) {
+ Switch theSwitch = instruction.asSwitch();
+ if (theSwitch.numberOfKeys() == 1) {
+ // Rewrite the switch to an if.
+ int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
+ int caseBlockIndex = theSwitch.targetBlockIndices()[0];
+ if (fallthroughBlockIndex < caseBlockIndex) {
+ block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
+ }
+ if (theSwitch.getFirstKey() == 0) {
+ iterator.replaceCurrentInstruction(new If(Type.EQ, theSwitch.value()));
+ } else {
+ ConstNumber labelConst = code.createIntConstant(theSwitch.getFirstKey());
+ iterator.previous();
+ iterator.add(labelConst);
+ Instruction dummy = iterator.next();
+ assert dummy == theSwitch;
+ If theIf = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.dest()));
+ iterator.replaceCurrentInstruction(theIf);
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Inline the indirection of switch maps into the switch statement.
* <p>
@@ -381,18 +413,20 @@
Reference2IntMap ordinalsMap = extractOrdinalsMapFor(switchMapHolder);
if (ordinalsMap != null) {
Int2IntMap targetMap = new Int2IntArrayMap();
- int keys[] = new int[switchInsn.numberOfKeys()];
- for (int i = 0; i < keys.length; i++) {
- keys[i] = ordinalsMap.getInt(indexMap.get(switchInsn.getKey(i)));
- targetMap.put(keys[i], switchInsn.targetBlockIndices()[i]);
+ IntList keys = new IntArrayList(switchInsn.numberOfKeys());
+ for (int i = 0; i < switchInsn.numberOfKeys(); i++) {
+ assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex();
+ int key = ordinalsMap.getInt(indexMap.get(switchInsn.getKey(i)));
+ keys.add(key);
+ targetMap.put(key, switchInsn.targetBlockIndices()[i]);
}
- Arrays.sort(keys);
- int[] targets = new int[keys.length];
- for (int i = 0; i < keys.length; i++) {
- targets[i] = targetMap.get(keys[i]);
+ keys.sort(Comparator.naturalOrder());
+ int[] targets = new int[keys.size()];
+ for (int i = 0; i < keys.size(); i++) {
+ targets[i] = targetMap.get(keys.getInt(i));
}
- Switch newSwitch = new Switch(Type.SPARSE, ordinalInvoke.outValue(), keys,
+ Switch newSwitch = new Switch(ordinalInvoke.outValue(), keys.toIntArray(),
targets, switchInsn.getFallthroughBlockIndex());
// Replace the switch itself.
it.replaceCurrentInstruction(newSwitch);
@@ -413,6 +447,7 @@
}
}
+
/**
* Extracts the mapping from ordinal values to switch case constants.
* <p>
@@ -896,8 +931,6 @@
if (elementSize == 1) {
short[] result = new short[(size + 1) / 2];
for (int i = 0; i < size; i += 2) {
- assert values[i].getIntValue() <= Constants.S8BIT_MAX
- && values[i].getIntValue() >= Constants.S8BIT_MIN;
short value = (short) (values[i].getIntValue() & 0xFF);
if (i + 1 < size) {
value |= (short) ((values[i + 1].getIntValue() & 0xFF) << 8);
@@ -1268,15 +1301,15 @@
// Replace call to Throwable::getSuppressed() with new Throwable[0].
// First insert the constant value *before* the current instruction.
- Value zero = code.createValue(MoveType.SINGLE);
+ ConstNumber zero = code.createIntConstant(0);
assert iterator.hasPrevious();
iterator.previous();
- iterator.add(new ConstNumber(ConstType.INT, zero, 0));
+ iterator.add(zero);
// Then replace the invoke instruction with NewArrayEmpty instruction.
Instruction next = iterator.next();
assert current == next;
- NewArrayEmpty newArray = new NewArrayEmpty(destValue, zero,
+ NewArrayEmpty newArray = new NewArrayEmpty(destValue, zero.outValue(),
dexItemFactory.createType(dexItemFactory.throwableArrayDescriptor));
iterator.replaceCurrentInstruction(newArray);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 383605f..c2e804e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -39,7 +39,6 @@
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Rem;
import com.android.tools.r8.ir.code.Sub;
-import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
@@ -912,8 +911,8 @@
}
@Override
- public void resolveAndBuildSwitch(Switch.Type type, int value, int fallthroughOffset,
- int payloadOffset, IRBuilder builder) {
+ public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset,
+ IRBuilder builder) {
throw new Unreachable("Unexpected call to resolveAndBuildSwitch");
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
index 22cf6d5..eb66537 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.MoveType;
-import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
@@ -180,8 +179,7 @@
@Override
public final void resolveAndBuildSwitch(
- Switch.Type type, int value, int fallthroughOffset,
- int payloadOffset, IRBuilder builder) {
+ int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
throw new Unreachable("Unexpected call to resolveAndBuildSwitch");
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
index 2db35c4..8857e2c 100644
--- a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
+++ b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
-import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertEquals;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
diff --git a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
new file mode 100644
index 0000000..b5b2a3c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
@@ -0,0 +1,93 @@
+// 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class FillBooleanArrayTruncation extends JasminTestBase {
+
+ private void runTest(JasminBuilder builder, String main) throws Exception {
+ String javaResult = runOnJava(builder, main);
+ String artResult = runOnArt(builder, main);
+ assertEquals(javaResult, artResult);
+ String dxArtResult = runOnArtDx(builder, main);
+ assertEquals(javaResult, dxArtResult);
+ }
+
+ @Test
+ public void filledArray() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ // Corresponds to something like the following (which doesn't compile with javac):
+ //
+ // public static void foo() {
+ // byte[] bytes = new byte[5];
+ // bytes[0] = 257;
+ // bytes[1] = -257;
+ // bytes[2] = 88;
+ // bytes[3] = 129;
+ // bytes[4] = -129;
+ // for (int i = 0; i < bytes.length; i++) {
+ // System.out.println(bytes[i]);
+ // }
+ // }
+ clazz.addStaticMethod("foo", ImmutableList.of(), "V",
+ ".limit stack 10",
+ ".limit locals 10",
+ " iconst_5",
+ " newarray byte",
+ " astore_0",
+ " aload_0",
+ " iconst_0",
+ " sipush 257",
+ " bastore",
+ " aload_0",
+ " iconst_1",
+ " sipush -257",
+ " bastore",
+ " aload_0",
+ " iconst_2",
+ " bipush 88",
+ " bastore",
+ " aload_0",
+ " iconst_3",
+ " sipush 129",
+ " bastore",
+ " aload_0",
+ " iconst_4",
+ " sipush -129",
+ " bastore",
+ " iconst_0",
+ " istore_1",
+ "PrintLoop:",
+ " iload_1",
+ " aload_0",
+ " arraylength",
+ " if_icmpge Return",
+ " getstatic java/lang/System.out Ljava/io/PrintStream;",
+ " aload_0",
+ " iload_1",
+ " baload",
+ " invokevirtual java/io/PrintStream.println(I)V",
+ " iinc 1 1",
+ " goto PrintLoop",
+ "Return:",
+ " return");
+
+ // public static void main(String args[]) {
+ // foo();
+ // }
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 10",
+ " invokestatic Test/foo()V",
+ " return");
+
+ runTest(builder, clazz.name);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index a0a77d1..85d4fb2 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -33,7 +33,6 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Switch.Type;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -521,7 +520,7 @@
@Override
public void resolveAndBuildSwitch(
- Type type, int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
+ int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
throw new Unreachable();
}
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 12bb1f6..e49ea84 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -8,16 +8,18 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8;
+import com.android.tools.r8.code.Const;
import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstHigh16;
import com.android.tools.r8.code.IfEq;
import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.PackedSwitch;
import com.android.tools.r8.code.SparseSwitch;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -26,11 +28,20 @@
public class SwitchRewritingTest extends SmaliTestBase {
- private void runSingleCaseDexTest(Switch.Type type, int key) {
+ private boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
+ return Math.abs((long) key1 - (long) key2) <= 2;
+ }
+
+ private boolean some16BitConst(Instruction instruction) {
+ return instruction instanceof Const4
+ || instruction instanceof ConstHigh16
+ || instruction instanceof Const;
+ }
+ private void runSingleCaseDexTest(boolean packed, int key) {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String switchInstruction;
String switchData;
- if (type == Switch.Type.PACKED) {
+ if (packed) {
switchInstruction = "packed-switch";
switchData = StringUtils.join(
"\n",
@@ -82,17 +93,20 @@
assertTrue(code.instructions[0] instanceof IfEqz);
} else {
assertEquals(6, code.instructions.length);
- assertTrue(code.instructions[0] instanceof Const4);
+ assertTrue(some16BitConst(code.instructions[0]));
assertTrue(code.instructions[1] instanceof IfEq);
}
}
@Test
public void singleCaseDex() {
- runSingleCaseDexTest(Switch.Type.PACKED, 0);
- runSingleCaseDexTest(Switch.Type.SPARSE, 0);
- runSingleCaseDexTest(Switch.Type.PACKED, 1);
- runSingleCaseDexTest(Switch.Type.SPARSE, 1);
+ for (boolean packed : new boolean[]{true, false}) {
+ runSingleCaseDexTest(packed, Integer.MIN_VALUE);
+ runSingleCaseDexTest(packed, -1);
+ runSingleCaseDexTest(packed, 0);
+ runSingleCaseDexTest(packed, 1);
+ runSingleCaseDexTest(packed, Integer.MAX_VALUE);
+ }
}
private void runTwoCaseSparseToPackedDexTest(int key1, int key2) {
@@ -134,7 +148,7 @@
DexApplication processedApplication = processApplication(originalApplication, options);
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
- if (key1 + 1 == key2) {
+ if (twoCaseWillUsePackedSwitch(key1, key2)) {
assertTrue(code.instructions[0] instanceof PackedSwitch);
} else {
assertTrue(code.instructions[0] instanceof SparseSwitch);
@@ -143,22 +157,97 @@
@Test
public void twoCaseSparseToPackedDex() {
- runTwoCaseSparseToPackedDexTest(0, 1);
- runTwoCaseSparseToPackedDexTest(-1, 0);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + 1);
- runTwoCaseSparseToPackedDexTest(Integer.MAX_VALUE - 1, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedDexTest(0, 2);
+ 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);
+ }
runTwoCaseSparseToPackedDexTest(-1, 1);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + 2);
- runTwoCaseSparseToPackedDexTest(Integer.MAX_VALUE - 2, Integer.MAX_VALUE);
+ 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);
}
- private void runSingleCaseJarTest(Switch.Type type, int key) throws Exception {
+ private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
+ Integer additionalLastKey) throws Exception {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ StringBuilder switchSource = new StringBuilder();
+ StringBuilder targetCode = new StringBuilder();
+ for (int i = 0; i < totalKeys; i++) {
+ String caseLabel = "case_" + i;
+ switchSource.append(" " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n");
+ targetCode.append(" :" + caseLabel + "\n");
+ targetCode.append(" goto :return\n");
+ }
+ if (additionalLastKey != null) {
+ String caseLabel = "case_" + totalKeys;
+ switchSource.append(" " + additionalLastKey + " -> :" + caseLabel + "\n");
+ targetCode.append(" :" + caseLabel + "\n");
+ targetCode.append(" goto :return\n");
+ }
+
+ MethodSignature signature = builder.addStaticMethod(
+ "void",
+ DEFAULT_METHOD_NAME,
+ ImmutableList.of("int"),
+ 0,
+ " sparse-switch p0, :sparse_switch_data",
+ " goto :return",
+ targetCode.toString(),
+ " :return",
+ " return-void",
+ " :sparse_switch_data",
+ " .sparse-switch",
+ switchSource.toString(),
+ " .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)V",
+ " return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ options.verbose = true;
+ options.printTimes = true;
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+ DexEncodedMethod method = getMethod(processedApplication, signature);
+ DexCode code = method.getCode().asDexCode();
+ if (keyStep <= 2) {
+ assertTrue(code.instructions[0] instanceof PackedSwitch);
+ } else {
+ assertTrue(code.instructions[0] instanceof SparseSwitch);
+ }
+ }
+
+ @Test
+ public void twoMonsterSparseToPackedDex() throws Exception {
+ runLargerSwitchDexTest(0, 1, 100, null);
+ runLargerSwitchDexTest(0, 2, 100, null);
+ runLargerSwitchDexTest(0, 3, 100, null);
+ runLargerSwitchDexTest(100, 100, 100, null);
+ runLargerSwitchDexTest(-10000, 100, 100, null);
+ runLargerSwitchDexTest(-10000, 200, 100, 10000);
+ runLargerSwitchDexTest(
+ Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+ // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches.
+ // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null);
+ }
+
+ private void runSingleCaseJarTest(boolean packed, int key) throws Exception {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
String switchCode;
- if (type == Switch.Type.PACKED) {
+ if (packed) {
switchCode = StringUtils.join(
"\n",
" tableswitch " + key,
@@ -205,6 +294,7 @@
assertTrue(code.instructions[0] instanceof IfEqz);
} else {
assertEquals(6, code.instructions.length);
+ assertTrue(some16BitConst(code.instructions[0]));
assertTrue(code.instructions[1] instanceof IfEq);
assertTrue(code.instructions[2] instanceof Const4);
}
@@ -212,10 +302,13 @@
@Test
public void singleCaseJar() throws Exception {
- runSingleCaseJarTest(Switch.Type.PACKED, 0);
- runSingleCaseJarTest(Switch.Type.SPARSE, 0);
- runSingleCaseJarTest(Switch.Type.PACKED, 1);
- runSingleCaseJarTest(Switch.Type.SPARSE, 1);
+ for (boolean packed : new boolean[]{true, false}) {
+ runSingleCaseJarTest(packed, Integer.MIN_VALUE);
+ runSingleCaseJarTest(packed, -1);
+ runSingleCaseJarTest(packed, 0);
+ runSingleCaseJarTest(packed, 1);
+ runSingleCaseJarTest(packed, Integer.MAX_VALUE);
+ }
}
private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
@@ -256,7 +349,7 @@
MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
DexEncodedMethod method = getMethod(app, signature);
DexCode code = method.getCode().asDexCode();
- if (key1 + 1 == key2) {
+ if (twoCaseWillUsePackedSwitch(key1, key2)) {
assertTrue(code.instructions[0] instanceof PackedSwitch);
} else {
assertTrue(code.instructions[0] instanceof SparseSwitch);
@@ -265,13 +358,91 @@
@Test
public void twoCaseSparseToPackedJar() throws Exception {
- runTwoCaseSparseToPackedJarTest(0, 1);
- runTwoCaseSparseToPackedJarTest(-1, 0);
- runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + 1);
- runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - 1, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedJarTest(0, 2);
+ 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(Integer.MIN_VALUE, Integer.MIN_VALUE + 2);
- runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - 2, Integer.MAX_VALUE);
+ 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();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ StringBuilder switchSource = new StringBuilder();
+ StringBuilder targetCode = new StringBuilder();
+ for (int i = 0; i < totalKeys; i++) {
+ String caseLabel = "case_" + i;
+ switchSource.append(" " + (firstKey + i * keyStep) + " : " + caseLabel + "\n");
+ targetCode.append(" " + caseLabel + ":\n");
+ targetCode.append(" ldc " + i + "\n");
+ targetCode.append(" goto return_\n");
+ }
+ if (additionalLastKey != null) {
+ String caseLabel = "case_" + totalKeys;
+ switchSource.append(" " + additionalLastKey + " : " + caseLabel + "\n");
+ targetCode.append(" " + caseLabel + ":\n");
+ targetCode.append(" ldc " + totalKeys + "\n");
+ targetCode.append(" goto return_\n");
+ }
+
+ clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+ " .limit stack 1",
+ " .limit locals 1",
+ " iload 0",
+ " lookupswitch",
+ switchSource.toString(),
+ " default : case_default",
+ targetCode.toString(),
+ " 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");
+
+ DexApplication app = builder.read();
+ app = new R8(new InternalOptions()).optimize(app, new AppInfoWithSubtyping(app));
+
+ MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
+ DexEncodedMethod method = getMethod(app, signature);
+ DexCode code = method.getCode().asDexCode();
+ if (keyStep <= 2) {
+ assertTrue(code.instructions[0] instanceof PackedSwitch);
+ } else {
+ assertTrue(code.instructions[0] instanceof SparseSwitch);
+ }
+ }
+
+ @Test
+ public void largerSwitchJar() throws Exception {
+ runLargerSwitchJarTest(0, 1, 100, null);
+ runLargerSwitchJarTest(0, 2, 100, null);
+ runLargerSwitchJarTest(0, 3, 100, null);
+ runLargerSwitchJarTest(100, 100, 100, null);
+ runLargerSwitchJarTest(-10000, 100, 100, null);
+ runLargerSwitchJarTest(-10000, 200, 100, 10000);
+ runLargerSwitchJarTest(
+ Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+ // This is the maximal value possible with Jasmin with the generated code above. It depends on
+ // the source, so making smaller source can raise this limit. However we never get close to the
+ // class file max.
+ runLargerSwitchJarTest(0, 1, 5503, null);
}
}