Replace the use of switchmaps with direct use of ordinal.

Bug:
Change-Id: Iee5cd16ba848617a7eaac3d55283621c54b36ff1
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e98503f..f70a295 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -43,49 +43,65 @@
       return this;
     }
 
-    /** Enable/disable tree shaking. This overrides any settings in proguard configuration files. */
+    /**
+     * Enable/disable tree shaking. This overrides any settings in proguard configuration files.
+     */
     public Builder setTreeShaking(boolean useTreeShaking) {
       treeShaking = Optional.of(useTreeShaking);
       return this;
     }
 
-    /** Enable/disable minification. This overrides any settings in proguard configuration files. */
+    /**
+     * Enable/disable minification. This overrides any settings in proguard configuration files.
+     */
     public Builder setMinification(boolean useMinification) {
       minification = Optional.of(useMinification);
       return this;
     }
 
-    /** Add proguard configuration file resources for automatic main dex list calculation. */
+    /**
+     * Add proguard configuration file resources for automatic main dex list calculation.
+     */
     public Builder addMainDexRules(Path... paths) {
       Collections.addAll(mainDexRules, paths);
       return this;
     }
 
-    /** Add proguard configuration file resources for automatic main dex list calculation. */
+    /**
+     * Add proguard configuration file resources for automatic main dex list calculation.
+     */
     public Builder addMainDexRules(List<Path> paths) {
       mainDexRules.addAll(paths);
       return this;
     }
 
-    /** Add proguard configuration file resources. */
+    /**
+     * Add proguard configuration file resources.
+     */
     public Builder addProguardConfigurationFiles(Path... paths) {
       Collections.addAll(proguardConfigFiles, paths);
       return this;
     }
 
-    /** Add proguard configuration file resources. */
+    /**
+     * Add proguard configuration file resources.
+     */
     public Builder addProguardConfigurationFiles(List<Path> paths) {
       proguardConfigFiles.addAll(paths);
       return this;
     }
 
-    /** Set a proguard mapping file resource. */
+    /**
+     * Set a proguard mapping file resource.
+     */
     public Builder setProguardMapFile(Path path) {
       getAppBuilder().setProguardMapFile(path);
       return this;
     }
 
-    /** Set a package distribution file resource. */
+    /**
+     * Set a package distribution file resource.
+     */
     public Builder setPackageDistributionFile(Path path) {
       getAppBuilder().setPackageDistributionFile(path);
       return this;
@@ -154,6 +170,7 @@
 
   // Internal state to verify parsing properties not enforced by the builder.
   private static class ParseState {
+
     CompilationMode mode = null;
   }
 
@@ -313,6 +330,7 @@
     return useMinification;
   }
 
+  @Override
   InternalOptions getInternalOptions() {
     InternalOptions internal = new InternalOptions(proguardConfiguration.getDexItemFactory());
     assert !internal.debug;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 40b0ead..cb9fb20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -82,6 +82,7 @@
   public DexString valueOfMethodName = createString("valueOf");
 
   public DexString getClassMethodName = createString("getClass");
+  public DexString ordinalMethodName = createString("ordinal");
 
   public DexString stringDescriptor = createString("Ljava/lang/String;");
   public DexString objectDescriptor = createString("Ljava/lang/Object;");
@@ -139,6 +140,7 @@
   }
 
   public class LongMethods {
+
     public DexMethod compare;
 
     private LongMethods() {
@@ -148,18 +150,20 @@
   }
 
   public class ThrowableMethods {
+
     public final DexMethod addSuppressed;
     public final DexMethod getSuppressed;
 
     private ThrowableMethods() {
       addSuppressed = createMethod(throwableDescriptor,
-          createString("addSuppressed"), voidDescriptor, new DexString[] { throwableDescriptor });
+          createString("addSuppressed"), voidDescriptor, new DexString[]{throwableDescriptor});
       getSuppressed = createMethod(throwableDescriptor,
           createString("getSuppressed"), throwableArrayDescriptor, DexString.EMPTY_ARRAY);
     }
   }
 
   public class ObjectMethods {
+
     public DexMethod getClass;
 
     private ObjectMethods() {
@@ -169,6 +173,7 @@
   }
 
   public class ObjectsMethods {
+
     public DexMethod requireNonNull;
 
     private ObjectsMethods() {
@@ -178,6 +183,7 @@
   }
 
   public class StringBuildingMethods {
+
     public DexMethod appendBoolean;
     public DexMethod appendChar;
     public DexMethod appendCharArray;
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 db44f9a..35cf808 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
@@ -13,6 +13,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -840,6 +841,15 @@
   }
 
   /**
+   * Remove an instruction.
+   */
+  public void removeInstruction(Instruction toRemove) {
+    int index = instructions.indexOf(toRemove);
+    assert index >= 0;
+    removeInstructions(Collections.singletonList(index));
+  }
+
+  /**
    * Create a new basic block with a single goto instruction.
    *
    * <p>The constructed basic block has no predecessors and has one
@@ -1101,7 +1111,8 @@
     }
   }
 
-  /** Append catch handlers from another block <code>fromBlock</code> (which must have catch
+  /**
+   * Append catch handlers from another block <code>fromBlock</code> (which must have catch
    * handlers) to the catch handlers of this block.
    *
    * Note that after appending catch handlers their targets are referenced by both
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 96e00f2..a30444e 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
@@ -73,10 +73,18 @@
     }
   }
 
-  private int numberOfKeys() {
+  public int numberOfKeys() {
     return targetBlockIndices.length;
   }
 
+  public int getKey(int index) {
+    if (type == Type.PACKED) {
+      return keys[0] + index;
+    } else {
+      return keys[index];
+    }
+  }
+
   public int[] targetBlockIndices() {
     return targetBlockIndices;
   }
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 12f0aa2..b92ff71 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
@@ -412,7 +412,8 @@
     if (!options.debug) {
       return;
     }
-    Value value = writeRegister(register, MoveType.fromConstType(type), ThrowingInfo.NO_THROW, null);
+    Value value = writeRegister(register, MoveType.fromConstType(type), ThrowingInfo.NO_THROW,
+        null);
     assert value.getLocalInfo() == null;
     addInstruction(new DebugLocalUninitialized(type, value));
   }
@@ -637,7 +638,8 @@
     boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
-    Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+    Value out = writeNumericRegister(dest, type,
+        canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
     Div instruction = new Div(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow() == canThrow;
     add(instruction);
@@ -648,7 +650,8 @@
     boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
     Value in1 = readNumericRegister(value, type);
     Value in2 = readLiteral(type, constant);
-    Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+    Value out = writeNumericRegister(dest, type,
+        canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
     Div instruction = new Div(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow() == canThrow;
     add(instruction);
@@ -697,7 +700,8 @@
     boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
-    Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+    Value out = writeNumericRegister(dest, type,
+        canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
     Rem instruction = new Rem(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow() == canThrow;
     addInstruction(instruction);
@@ -708,7 +712,8 @@
     boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
     Value in1 = readNumericRegister(value, type);
     Value in2 = readLiteral(type, constant);
-    Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+    Value out = writeNumericRegister(dest, type,
+        canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
     Rem instruction = new Rem(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow() == canThrow;
     addInstruction(instruction);
@@ -1119,6 +1124,7 @@
     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;
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 7aef78c..091714a 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
@@ -426,6 +426,7 @@
     if (memberValuePropagation != null) {
       memberValuePropagation.rewriteWithConstantValues(code);
     }
+    codeRewriter.removeSwitchMaps(code);
     if (options.inlineAccessors && inliner != null) {
       inliner.performInlining(method, code, callGraph);
     }
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 81a9e55..9034c37 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
@@ -9,9 +9,11 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Binop;
@@ -28,6 +30,7 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.JumpInstruction;
@@ -37,15 +40,26 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Return;
+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.utils.InternalOptions;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+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.objects.Reference2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -303,6 +317,244 @@
   }
 
   /**
+   * Inline the indirection of switch maps into the switch statement.
+   * <p>
+   * To ensure binary compatibility, javac generated code does not use ordinal values of enums
+   * directly in switch statements but instead generates a companion class that computes a mapping
+   * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can
+   * analyze these maps and inline the indirection into the switch map again.
+   * <p>
+   * In particular, we look for code of the form
+   *
+   * <blockquote><pre>
+   * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+   *   ...
+   * }
+   * </pre></blockquote>
+   * See {@link #extractIndexMapFrom} and {@link #extractOrdinalsMapFor} for
+   * details of the companion class and ordinals computation.
+   */
+  public void removeSwitchMaps(IRCode code) {
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator();
+      while (it.hasNext()) {
+        Instruction insn = it.next();
+        // Pattern match a switch on a switch map as input.
+        if (insn.isSwitch()) {
+          Switch switchInsn = insn.asSwitch();
+          Instruction input = switchInsn.inValues().get(0).definition;
+          if (input == null || !input.isArrayGet()) {
+            continue;
+          }
+          ArrayGet arrayGet = input.asArrayGet();
+          Instruction index = arrayGet.index().definition;
+          if (index == null || !index.isInvokeVirtual()) {
+            continue;
+          }
+          InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
+          DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
+          DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
+          if (enumClass == null
+              || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
+              || ordinalMethod.name != dexItemFactory.ordinalMethodName
+              || ordinalMethod.proto.returnType != dexItemFactory.intType
+              || !ordinalMethod.proto.parameters.isEmpty()) {
+            continue;
+          }
+          Instruction array = arrayGet.array().definition;
+          if (array == null || !array.isStaticGet()) {
+            continue;
+          }
+          StaticGet staticGet = array.asStaticGet();
+          if (staticGet.getField().name.toSourceString().startsWith("$SwitchMap$")) {
+            Int2ReferenceMap<DexField> indexMap = extractIndexMapFrom(staticGet.getField());
+            if (indexMap == null || indexMap.isEmpty()) {
+              continue;
+            }
+            // Due to member rebinding, only the fields are certain to provide the actual enums
+            // class.
+            DexType switchMapHolder = indexMap.values().iterator().next().getHolder();
+            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]);
+              }
+              Arrays.sort(keys);
+              int[] targets = new int[keys.length];
+              for (int i = 0; i < keys.length; i++) {
+                targets[i] = targetMap.get(keys[i]);
+              }
+
+              Switch newSwitch = new Switch(Type.SPARSE, ordinalInvoke.outValue(), keys,
+                  targets, switchInsn.getFallthroughBlockIndex());
+              // Replace the switch itself.
+              it.replaceCurrentInstruction(newSwitch);
+              // If the original input to the switch is now unused, remove it too. It is not dead
+              // as it might have side-effects but we ignore these here.
+              if (arrayGet.outValue().numberOfUsers() == 0) {
+                arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
+                arrayGet.getBlock().removeInstruction(arrayGet);
+              }
+              if (staticGet.outValue().numberOfUsers() == 0) {
+                assert staticGet.inValues().isEmpty();
+                staticGet.getBlock().removeInstruction(staticGet);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Extracts the mapping from ordinal values to switch case constants.
+   * <p>
+   * This is done by pattern-matching on the class initializer of the synthetic switch map class.
+   * For a switch
+   *
+   * <blockquote><pre>
+   * switch (day) {
+   *   case WEDNESDAY:
+   *   case FRIDAY:
+   *     System.out.println("3 or 5");
+   *     break;
+   *   case SUNDAY:
+   *     System.out.println("7");
+   *     break;
+   *   default:
+   *     System.out.println("other");
+   * }
+   * </pre></blockquote>
+   *
+   * the generated companing class initializer will have the form
+   *
+   * <blockquote><pre>
+   * class Switches$1 {
+   *   static {
+   *   $SwitchMap$switchmaps$Days[Days.WEDNESDAY.ordinal()] = 1;
+   *   $SwitchMap$switchmaps$Days[Days.FRIDAY.ordinal()] = 2;
+   *   $SwitchMap$switchmaps$Days[Days.SUNDAY.ordinal()] = 3;
+   * }
+   * </pre></blockquote>
+   *
+   * Note that one map per class is generated, so the map might contain additional entries as used
+   * by other switches in the class.
+   */
+  private Int2ReferenceMap<DexField> extractIndexMapFrom(DexField field) {
+    DexClass clazz = appInfo.definitionFor(field.getHolder());
+    if (!clazz.accessFlags.isSynthetic()) {
+      return null;
+    }
+    DexEncodedMethod initializer = clazz.getClassInitializer();
+    if (initializer == null || initializer.getCode() == null) {
+      return null;
+    }
+    IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions());
+    Int2ReferenceMap<DexField> switchMap = new Int2ReferenceArrayMap<>();
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator();
+      Instruction insn = it.nextUntil(i -> i.isStaticGet() && i.asStaticGet().getField() == field);
+      if (insn == null) {
+        continue;
+      }
+      for (Instruction use : insn.outValue().uniqueUsers()) {
+        if (use.isArrayPut()) {
+          Instruction index = use.asArrayPut().source().definition;
+          if (index == null || !index.isConstNumber()) {
+            return null;
+          }
+          int integerIndex = index.asConstNumber().getIntValue();
+          Instruction value = use.asArrayPut().index().definition;
+          if (value == null || !value.isInvokeVirtual()) {
+            return null;
+          }
+          InvokeVirtual invoke = value.asInvokeVirtual();
+          DexClass holder = appInfo.definitionFor(invoke.getInvokedMethod().holder);
+          if (holder == null ||
+              (!holder.accessFlags.isEnum() && holder.type != dexItemFactory.enumType)) {
+            return null;
+          }
+          Instruction enumGet = invoke.arguments().get(0).definition;
+          if (enumGet == null || !enumGet.isStaticGet()) {
+            return null;
+          }
+          DexField enumField = enumGet.asStaticGet().getField();
+          if (!appInfo.definitionFor(enumField.getHolder()).accessFlags.isEnum()) {
+            return null;
+          }
+          if (switchMap.put(integerIndex, enumField) != null) {
+            return null;
+          }
+        } else {
+          return null;
+        }
+      }
+    }
+    return switchMap;
+  }
+
+  /**
+   * Extracts the ordinal values for an Enum class from the classes static initializer.
+   * <p>
+   * An Enum class has a field for each value. In the class initializer, each field is initialized
+   * to a singleton object that represents the value. This code matches on the corresponding call
+   * to the constructor (instance initializer) and extracts the value of the second argument, which
+   * is the ordinal.
+   */
+  private Reference2IntMap<DexField> extractOrdinalsMapFor(DexType enumClass) {
+    DexClass clazz = appInfo.definitionFor(enumClass);
+    if (clazz == null || clazz.isLibraryClass()) {
+      // We have to keep binary compatibility in tact for libraries.
+      return null;
+    }
+    DexEncodedMethod initializer = clazz.getClassInitializer();
+    if (!clazz.accessFlags.isEnum() || initializer == null || initializer.getCode() == null) {
+      return null;
+    }
+    IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions());
+    Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
+    ordinalsMap.defaultReturnValue(-1);
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction insn = it.next();
+      if (!insn.isStaticPut()) {
+        continue;
+      }
+      StaticPut staticPut = insn.asStaticPut();
+      if (staticPut.getField().type != enumClass) {
+        continue;
+      }
+      Instruction newInstance = staticPut.inValue().definition;
+      if (newInstance == null || !newInstance.isNewInstance()) {
+        continue;
+      }
+      Instruction ordinal = null;
+      for (Instruction ctorCall : newInstance.outValue().uniqueUsers()) {
+        if (!ctorCall.isInvokeDirect()) {
+          continue;
+        }
+        InvokeDirect invoke = ctorCall.asInvokeDirect();
+        if (!dexItemFactory.isConstructor(invoke.getInvokedMethod())
+            || invoke.arguments().size() < 3) {
+          continue;
+        }
+        ordinal = invoke.arguments().get(2).definition;
+        break;
+      }
+      if (ordinal == null || !ordinal.isConstNumber()) {
+        return null;
+      }
+      if (ordinalsMap.put(staticPut.getField(), ordinal.asConstNumber().getIntValue()) != -1) {
+        return null;
+      }
+    }
+    return ordinalsMap;
+  }
+
+  /**
    * Rewrite all branch targets to the destination of trivial goto chains when possible.
    * Does not rewrite fallthrough targets as that would require block reordering and the
    * transformation only makes sense after SSA destruction where there are no phis.
@@ -920,11 +1172,11 @@
       if (current.isInvokeMethod()) {
         DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod();
         if (invokedMethod == dexItemFactory.longMethods.compare) {
-            List<Value> inValues = current.inValues();
-            assert inValues.size() == 2;
-            iterator.replaceCurrentInstruction(
-                new Cmp(NumericType.LONG, Bias.NONE, current.outValue(), inValues.get(0),
-                    inValues.get(1)));
+          List<Value> inValues = current.inValues();
+          assert inValues.size() == 2;
+          iterator.replaceCurrentInstruction(
+              new Cmp(NumericType.LONG, Bias.NONE, current.outValue(), inValues.get(0),
+                  inValues.get(1)));
         } else if (!canUseObjectsNonNull
             && invokedMethod == dexItemFactory.objectsMethods.requireNonNull) {
           // Rewrite calls to Objects.requireNonNull(Object) because Javac 9 start to use it for
diff --git a/src/test/examples/switchmaps/Colors.java b/src/test/examples/switchmaps/Colors.java
new file mode 100644
index 0000000..b2c8dc1
--- /dev/null
+++ b/src/test/examples/switchmaps/Colors.java
@@ -0,0 +1,19 @@
+// 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 switchmaps;
+
+public enum Colors {
+  RED("rar"), BLUE("blew"), GREEN("soylent"), GRAY("fifty");
+
+  private String aField;
+
+  Colors(String string) {
+    aField = string;
+  }
+
+  @Override
+  public String toString() {
+    return aField;
+  }
+}
diff --git a/src/test/examples/switchmaps/Days.java b/src/test/examples/switchmaps/Days.java
new file mode 100644
index 0000000..b484bb5
--- /dev/null
+++ b/src/test/examples/switchmaps/Days.java
@@ -0,0 +1,8 @@
+// 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 switchmaps;
+
+public enum Days {
+  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
+}
diff --git a/src/test/examples/switchmaps/Switches.java b/src/test/examples/switchmaps/Switches.java
new file mode 100644
index 0000000..bf16856
--- /dev/null
+++ b/src/test/examples/switchmaps/Switches.java
@@ -0,0 +1,66 @@
+// 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 switchmaps;
+
+public class Switches {
+
+  public static void main(String... args) {
+    for (Days value : Days.values()) {
+      switchWithDefault(value);
+      switchFull(value);
+    }
+    for (Colors color : Colors.values()) {
+      switchOnColors(color);
+    }
+  }
+
+  private static void switchOnColors(Colors color) {
+    System.out.println(color.toString());
+    switch (color) {
+      case GRAY:
+        System.out.println("not really");
+        break;
+      case GREEN:
+        System.out.println("sooo green");
+        break;
+      default:
+        System.out.println("colorful");
+    }
+  }
+
+  private static void switchWithDefault(Days day) {
+    switch (day) {
+      case WEDNESDAY:
+      case FRIDAY:
+        System.out.println("3 or 5");
+        break;
+      case SUNDAY:
+        System.out.println("7");
+        break;
+      default:
+        System.out.println("other");
+    }
+  }
+
+  private static void switchFull(Days day) {
+    switch (day) {
+      case MONDAY:
+      case WEDNESDAY:
+      case THURSDAY:
+        System.out.println("1, 3 or 4");
+      case TUESDAY:
+      case FRIDAY:
+        System.out.println("2 or 5");
+        break;
+      case SUNDAY:
+        System.out.println("7");
+        break;
+      case SATURDAY:
+        System.out.println("6");
+        break;
+      default:
+        System.out.println("other");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 9e7588d..4a06085 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -100,6 +100,7 @@
         {"minification.Minification", null},
         {"enclosingmethod.Main", null},
         {"interfaceinlining.Main", null},
+        {"switchmaps.Switches", null},
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a93fb12..bcb9f7d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -34,7 +34,7 @@
   /**
    * Write lines of text to a temporary file.
    */
-  protected Path writeTextToTempFile(String... lines) throws IOException{
+  protected Path writeTextToTempFile(String... lines) throws IOException {
     Path file = temp.newFile().toPath();
     FileUtils.writeTextFile(file, lines);
     return file;
@@ -86,7 +86,7 @@
   /**
    * Compile an application with R8 using the supplied proguard configuration.
    */
-  protected  AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
+  protected AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
     return compileWithR8(readClasses(classes), proguardConfig);
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
new file mode 100644
index 0000000..4e8881a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -0,0 +1,36 @@
+// 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.rewrite.switchmaps;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RewriteSwitchMapsTest extends TestBase {
+
+  private static final String JAR_FILE = "switchmaps.jar";
+  private static final String SWITCHMAP_CLASS_NAME = "switchmaps.Switches$1";
+  private static final String PG_CONFIG =
+      "-keep class switchmaps.Switches { public static void main(...); } " +
+          "-dontobfuscate";
+
+  @Test
+  public void checkSwitchMapsRemoved()
+      throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()));
+    builder.addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR).resolve(JAR_FILE));
+    AndroidApp result = compileWithR8(builder.build(), writeTextToTempFile(PG_CONFIG));
+    DexInspector inspector = new DexInspector(result);
+    Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
+  }
+}