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