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);
   }
 }