Merge "Add --adb-options to tools/apk-masseur.py"
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index a3d92ac..b4a83a4 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -26,7 +26,9 @@
  */
 public class D8Command extends BaseCommand {
 
-  /** Builder for constructing a D8Command. */
+  /**
+   * Builder for constructing a D8Command.
+   */
   public static class Builder extends BaseCommand.Builder<D8Command, Builder> {
 
     private Builder() {
@@ -42,7 +44,9 @@
       return this;
     }
 
-    /** Build the final D8Command. */
+    /**
+     * Build the final D8Command.
+     */
     @Override
     public D8Command build() throws CompilationException {
       if (isPrintHelp() || isPrintVersion()) {
@@ -148,6 +152,8 @@
     internal.allowAccessModification = false;
     assert internal.inlineAccessors;
     internal.inlineAccessors = false;
+    assert internal.removeSwitchMaps;
+    internal.removeSwitchMaps = false;
     assert internal.outline.enabled;
     internal.outline.enabled = false;
     internal.lazyClasspathLoading = true;
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..d12109e 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,9 @@
     if (memberValuePropagation != null) {
       memberValuePropagation.rewriteWithConstantValues(code);
     }
+    if (options.removeSwitchMaps) {
+      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/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 28b68b1..3ccf2b3 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -804,6 +804,9 @@
   }
 
   private int getSpillRegister(LiveIntervals intervals) {
+    if (intervals.isArgumentInterval()) {
+      return intervals.getSplitParent().getRegister();
+    }
     int registerNumber = nextUnusedRegisterNumber++;
     maxRegisterNumber = registerNumber;
     if (intervals.getType() == MoveType.WIDE) {
@@ -1331,6 +1334,8 @@
             inactive.add(splitOfSplit);
           } else if (intervals.getValue().isConstant()) {
             splitRangesForSpilledConstant(splitChild, registerNumber);
+          } else if (intervals.isArgumentInterval()) {
+            splitRangesForSpilledArgument(splitChild);
           } else {
             splitRangesForSpilledInterval(splitChild, registerNumber);
           }
@@ -1340,15 +1345,30 @@
     active.addAll(newActive);
   }
 
-  private void splitRangesForSpilledInterval(LiveIntervals splitChild, int registerNumber) {
+  private void splitRangesForSpilledArgument(LiveIntervals spilled) {
+    assert spilled.isSpilled();
+    assert spilled.isArgumentInterval();
+    // Argument intervals are spilled to the original argument register. We don't know what
+    // that is yet, and therefore we split before the next use to make sure we get a usable
+    // register at the next use.
+    if (!spilled.getUses().isEmpty()) {
+      LiveIntervals split = spilled.splitBefore(spilled.getUses().first().getPosition());
+      unhandled.add(split);
+    }
+  }
+
+  private void splitRangesForSpilledInterval(LiveIntervals spilled, int registerNumber) {
     // Spilling a non-pinned, non-rematerializable value. We use the value in the spill
     // register for as long as possible to avoid further moves.
-    assert splitChild.isSpilled();
-    assert !splitChild.getValue().isConstant();
-    assert !splitChild.isLinked() || splitChild.isArgumentInterval();
+    assert spilled.isSpilled();
+    assert !spilled.getValue().isConstant();
+    assert !spilled.isLinked() || spilled.isArgumentInterval();
+    if (spilled.isArgumentInterval()) {
+      registerNumber = Constants.U16BIT_MAX;
+    }
     LiveIntervalsUse firstUseWithLowerLimit = null;
     boolean hasUsesBeforeFirstUseWithLowerLimit = false;
-    for (LiveIntervalsUse use : splitChild.getUses()) {
+    for (LiveIntervalsUse use : spilled.getUses()) {
       if (registerNumber > use.getLimit()) {
         firstUseWithLowerLimit = use;
         break;
@@ -1357,10 +1377,10 @@
       }
     }
     if (hasUsesBeforeFirstUseWithLowerLimit) {
-      splitChild.setSpilled(false);
+      spilled.setSpilled(false);
     }
     if (firstUseWithLowerLimit != null) {
-      LiveIntervals splitOfSplit = splitChild.splitBefore(firstUseWithLowerLimit.getPosition());
+      LiveIntervals splitOfSplit = spilled.splitBefore(firstUseWithLowerLimit.getPosition());
       unhandled.add(splitOfSplit);
     }
   }
@@ -1722,7 +1742,20 @@
     // If the invoke instruction require more than 5 registers we link the inputs because they
     // need to be in consecutive registers.
     if (invoke.requiredArgumentRegisters() > 5) {
-      replaceFullArgumentList(invoke, insertAt);
+      List<Value> arguments = invoke.arguments();
+      Value previous = null;
+      for (int i = 0; i < arguments.size(); i++) {
+        Value argument = arguments.get(i);
+        Value newArgument = createValue(argument.outType(), argument.getDebugInfo());
+        Move move = new Move(newArgument, argument);
+        move.setBlock(invoke.getBlock());
+        replaceArgument(invoke, i, newArgument);
+        insertAt.add(move);
+        if (previous != null) {
+          previous.linkTo(newArgument);
+        }
+        previous = newArgument;
+      }
     }
   }
 
@@ -1789,33 +1822,6 @@
     }
   }
 
-  private void replaceFullArgumentList(Invoke invoke, InstructionListIterator insertAt) {
-    List<Value> arguments = invoke.arguments();
-    Value previous = null;
-    for (int i = 0; i < arguments.size(); i++) {
-      generateArgumentMove(invoke, i, insertAt);
-      if (previous != null) {
-        previous.linkTo(arguments.get(i));
-      }
-      previous = arguments.get(i);
-    }
-  }
-
-  private Move generateArgumentMove(Invoke invoke, int i) {
-    List<Value> arguments = invoke.arguments();
-    Value argument = arguments.get(i);
-    Value newArgument = createValue(argument.outType(), argument.getDebugInfo());
-    Move move = new Move(newArgument, argument);
-    move.setBlock(invoke.getBlock());
-    replaceArgument(invoke, i, newArgument);
-    return move;
-  }
-
-  private void generateArgumentMove(Invoke invoke, int i, InstructionListIterator insertAt) {
-    Move move = generateArgumentMove(invoke, i);
-    insertAt.add(move);
-  }
-
   private void computeNeedsRegister() {
     for (BasicBlock block : code.topologicallySortedBlocks()) {
       for (Instruction instruction : block.getInstructions()) {
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 f584e85..7f13315 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -70,6 +70,7 @@
   public String packagePrefix = "";
   public boolean allowAccessModification = true;
   public boolean inlineAccessors = true;
+  public boolean removeSwitchMaps = true;
   public final OutlineOptions outline = new OutlineOptions();
   public boolean debugKeepRules = false;
   public final AttributeRemovalOptions attributeRemoval = new AttributeRemovalOptions();
diff --git a/src/test/debugTestResources/Bridges.java b/src/test/debugTestResources/Bridges.java
new file mode 100644
index 0000000..e1a4380
--- /dev/null
+++ b/src/test/debugTestResources/Bridges.java
@@ -0,0 +1,27 @@
+// 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.
+
+public class Bridges {
+
+  interface GenericInterface<T> {
+
+    void get(T t);
+  }
+
+  static class StringImpl implements GenericInterface<String> {
+
+    @Override
+    public void get(String s) {
+      System.out.println(s);
+    }
+  }
+
+  public static void testGenericBridge(GenericInterface<String> obj) {
+    obj.get("Foo");
+  }
+
+  public static void main(String[] args) {
+    testGenericBridge(new StringImpl());
+  }
+}
\ No newline at end of file
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/examplesAndroidO/multidex004/ref-list-1.txt b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
index a29dc78..ac6e888 100644
--- a/src/test/examplesAndroidO/multidex004/ref-list-1.txt
+++ b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
@@ -1,4 +1,4 @@
-Lmultidex004/-$$Lambda$MainActivity$dRBNeLGjbsJicx27QgIJsf0cF7o;
+Lmultidex004/-$$Lambda$MainActivity$g120D_43GXrTOaB3kfYt6wSIJh4;
 Lmultidex004/MainActivity;
 Lmultidex004/VersionInterface;
 Lmultidex004/VersionStatic;
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 7f9ecb0..49c0837 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.TestCondition.D8_COMPILER;
 import static com.android.tools.r8.TestCondition.R8_COMPILER;
+import static com.android.tools.r8.TestCondition.R8DEBUG_AFTER_D8_COMPILER;
 import static com.android.tools.r8.TestCondition.any;
 import static com.android.tools.r8.TestCondition.match;
 import static com.android.tools.r8.TestCondition.runtimes;
@@ -4653,6 +4654,14 @@
           // 1) t04
           // java.lang.AssertionError
 
+          .put("lang.reflect.Field.getLjava_lang_Object.Field_get_A04", match(R8DEBUG_AFTER_D8_COMPILER))
+          // 1) t02
+          // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
+
+          .put("lang.reflect.Field.getLongLjava_lang_Object.Field_getLong_A04", match(R8DEBUG_AFTER_D8_COMPILER))
+          // 1)
+          // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
+
           .build(); // end of failuresToTriage
 
   public static final Multimap<String, TestCondition> flakyWithArt =
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index b026dcd..5c5975c 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.utils.ArtErrorParser;
 import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.JarBuilder;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OffOrAuto;
@@ -25,10 +26,10 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.ObjectArrays;
 import com.google.common.collect.Sets;
-import com.google.common.io.Files;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -54,7 +55,6 @@
 public abstract class R8RunArtTestsTest {
 
   private static final boolean DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE = true;
-  private static String[] D8_EXTRA_ARGS = {"--debug"};
 
   private final String name;
   private final DexTool toolchain;
@@ -70,7 +70,8 @@
 
   public enum CompilerUnderTest {
     D8,
-    R8
+    R8,
+    R8DEBUG_AFTER_D8 // refers to the R8/debug step but implies a previous D8 step as well
   }
 
   private static final String ART_TESTS_DIR = "tests/art";
@@ -937,13 +938,23 @@
   private void executeCompilerUnderTest(
       CompilerUnderTest compilerUnderTest, Collection<String> fileNames, String resultPath)
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
-    executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, null);
+    executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, null, null);
   }
 
   private void executeCompilerUnderTest(
       CompilerUnderTest compilerUnderTest,
       Collection<String> fileNames,
       String resultPath,
+      CompilationMode compilationMode)
+      throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+    executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, compilationMode, null);
+  }
+
+  private void executeCompilerUnderTest(
+      CompilerUnderTest compilerUnderTest,
+      Collection<String> fileNames,
+      String resultPath,
+      CompilationMode mode,
       String keepRulesFile)
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
     switch (compilerUnderTest) {
@@ -951,7 +962,7 @@
         assert keepRulesFile == null : "Keep-rules file specified for D8.";
         D8Command.Builder builder =
             D8Command.builder()
-                .setMode(CompilationMode.DEBUG)
+                .setMode(mode == null ? CompilationMode.DEBUG : mode)
                 .addProgramFiles(ListUtils.map(fileNames, Paths::get));
         Integer minSdkVersion = needMinSdkVersion.get(name);
         if (minSdkVersion != null) {
@@ -964,7 +975,7 @@
       case R8: {
         R8Command.Builder builder =
             R8Command.builder()
-                .setMode(CompilationMode.RELEASE)
+                .setMode(mode == null ? CompilationMode.RELEASE : mode)
                 .setOutputPath(Paths.get(resultPath))
                 .addProgramFiles(ListUtils.map(fileNames, Paths::get))
                 .setIgnoreMissingClasses(true);
@@ -1045,10 +1056,15 @@
 
     DexVm dexVm = ToolHelper.getDexVm();
 
-    File resultDir = temp.getRoot();
+    CompilerUnderTest firstCompilerUnderTest =
+        compilerUnderTest == CompilerUnderTest.R8DEBUG_AFTER_D8
+            ? CompilerUnderTest.D8
+            : compilerUnderTest;
+
+    File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output");
 
     JctfTestSpecifications.Outcome expectedOutcome = JctfTestSpecifications
-        .getExpectedOutcome(name, compilerUnderTest, dexVm);
+        .getExpectedOutcome(name, firstCompilerUnderTest, dexVm);
     TestSpecification specification = new TestSpecification(name, DexTool.NONE, resultDir,
         expectedOutcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
             || expectedOutcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
@@ -1116,7 +1132,53 @@
     for (File f : allClassFiles) {
       fileNames.add(f.getCanonicalPath());
     }
-    executeCompilerUnderTest(compilerUnderTest, fileNames, resultDir.getCanonicalPath());
+
+    runJctfTestDoRunOnArt(fileNames,
+        specification,
+        firstCompilerUnderTest,
+        fullClassName,
+        null,
+        dexVm,
+        resultDir);
+
+    // second pass if D8_R8Debug
+    if (compilerUnderTest == CompilerUnderTest.R8DEBUG_AFTER_D8) {
+      List<String> d8OutputFileNames =
+          Files.list(resultDir.toPath())
+              .filter(FileUtils::isDexFile)
+              .map(Path::toString)
+              .collect(Collectors.toList());
+      File r8ResultDir = temp.newFolder("r8-output");
+      expectedOutcome = JctfTestSpecifications
+          .getExpectedOutcome(name, CompilerUnderTest.R8DEBUG_AFTER_D8, dexVm);
+      specification = new TestSpecification(name, DexTool.DX, r8ResultDir,
+          expectedOutcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
+              || expectedOutcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
+          expectedOutcome == JctfTestSpecifications.Outcome.FAILS_WITH_ART);
+      if (specification.skipTest) {
+        return;
+      }
+      runJctfTestDoRunOnArt(
+          d8OutputFileNames,
+          specification,
+          CompilerUnderTest.R8,
+          fullClassName,
+          CompilationMode.DEBUG,
+          dexVm,
+          r8ResultDir);
+    }
+  }
+
+  private void runJctfTestDoRunOnArt(
+      Collection<String> fileNames,
+      TestSpecification specification,
+      CompilerUnderTest compilerUnderTest,
+      String fullClassName,
+      CompilationMode mode,
+      DexVm dexVm,
+      File resultDir)
+      throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+    executeCompilerUnderTest(compilerUnderTest, fileNames, resultDir.getAbsolutePath(), mode);
 
     if (!ToolHelper.artSupported()) {
       return;
@@ -1196,7 +1258,7 @@
     if (toolchain == DexTool.NONE) {
       File classes = new File(specification.directory, "classes");
       inputFiles =
-          Files.fileTreeTraverser().breadthFirstTraversal(classes).filter(
+          com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes).filter(
               (File f) -> !f.isDirectory()).toArray(File.class);
       File smali = new File(specification.directory, "smali");
       if (smali.exists()) {
@@ -1207,7 +1269,7 @@
       File classes2 = new File(specification.directory, "classes2");
       if (classes2.exists()) {
         inputFiles = ObjectArrays.concat(inputFiles,
-            Files.fileTreeTraverser().breadthFirstTraversal(classes2).filter(
+            com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes2).filter(
                 (File f) -> !f.isDirectory()).toArray(File.class), File.class);
       }
     } else {
@@ -1258,7 +1320,7 @@
       }
 
       File expectedFile = specification.resolveFile("expected.txt");
-      String expected = Files.toString(expectedFile, Charsets.UTF_8);
+      String expected = com.google.common.io.Files.toString(expectedFile, Charsets.UTF_8);
       if (specification.failsWithArt) {
         thrown.expect(AssertionError.class);
       }
@@ -1281,7 +1343,7 @@
       if (checkCommand.exists()) {
         // Run the Art test custom check command.
         File actualFile = temp.newFile();
-        Files.asByteSink(actualFile).write(output.getBytes(Charsets.UTF_8));
+        com.google.common.io.Files.asByteSink(actualFile).write(output.getBytes(Charsets.UTF_8));
         ProcessBuilder processBuilder = new ProcessBuilder();
         processBuilder.command(
             specification.resolveFile("check").toString(), expectedFile.toString(),
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/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index ef2eac3..5ffd779 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -40,6 +40,8 @@
 
   public static final CompilerSet D8_COMPILER = compilers(CompilerUnderTest.D8);
   public static final CompilerSet R8_COMPILER = compilers(CompilerUnderTest.R8);
+  public static final CompilerSet R8DEBUG_AFTER_D8_COMPILER =
+      compilers(CompilerUnderTest.R8DEBUG_AFTER_D8);
 
   private static final ToolSet ANY_TOOL = new ToolSet(EnumSet.allOf(DexTool.class));
   private static final CompilerSet ANY_COMPILER =
@@ -107,7 +109,13 @@
   }
 
   public boolean test(DexTool dexTool, CompilerUnderTest compilerUnderTest, DexVm dexVm) {
-    return dexTools.contains(dexTool) && compilers.contains(compilerUnderTest)
+    // R8DEBUG_AFTER_D8 will be set in the R8 phase of the D8-then-R8 tests. So R8DEBUG_AFTER_D8
+    // must match both with plain R8 and itself.
+    boolean compilerMatches = compilers.contains(compilerUnderTest)
+        || (compilerUnderTest == CompilerUnderTest.R8DEBUG_AFTER_D8
+            && compilers.contains(CompilerUnderTest.R8));
+    return dexTools.contains(dexTool)
+        && compilerMatches
         && dexVms.contains(dexVm);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 8f2e9ce..fa7443c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -29,12 +30,15 @@
 import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
 import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.Error;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.EventKind;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.StepDepth;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.StepSize;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.SuspendPolicy;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.TypeTag;
 import org.apache.harmony.jpda.tests.framework.jdwp.Location;
 import org.apache.harmony.jpda.tests.framework.jdwp.ParsedEvent;
 import org.apache.harmony.jpda.tests.framework.jdwp.ParsedEvent.EventThread;
@@ -73,14 +77,15 @@
   // Set to true to enable verbose logs
   private static final boolean DEBUG_TESTS = false;
 
-  private static final List<DexVm> UNSUPPORTED_ART_VERSIONS = ImmutableList.of(
+  private static final List<DexVm> UNSUPPORTED_ART_VERSIONS = ImmutableList.<DexVm>builder()
       // Dalvik does not support command ReferenceType.Methods which is used to set breakpoint.
       // TODO(shertz) use command ReferenceType.MethodsWithGeneric instead
-      DexVm.ART_4_4_4,
+      .add(DexVm.ART_4_4_4)
       // Older runtimes fail on buildbot
       // TODO(shertz) re-enable once issue is solved
-      DexVm.ART_5_1_1,
-      DexVm.ART_6_0_1);
+      .add(DexVm.ART_5_1_1)
+      .add(DexVm.ART_6_0_1)
+      .build();
 
   private static final Path JDWP_JAR = ToolHelper
       .getJdwpTestsJarPath(ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
@@ -200,7 +205,12 @@
   }
 
   protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
-    return new JUnit3Wrapper.Command.BreakpointCommand(className, methodName);
+    return breakpoint(className, methodName, null);
+  }
+
+  protected final JUnit3Wrapper.Command breakpoint(String className, String methodName,
+      String methodSignature) {
+    return new JUnit3Wrapper.Command.BreakpointCommand(className, methodName, methodSignature);
   }
 
   protected final JUnit3Wrapper.Command stepOver() {
@@ -244,12 +254,27 @@
     return inspect(t -> Assert.assertTrue(t.getLocalNames().isEmpty()));
   }
 
-  protected final JUnit3Wrapper.Command checkLine(int line) {
-    return inspect(t -> t.checkLine(line));
+  protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
+    return inspect(t -> {
+      Assert.assertEquals(sourceFile, t.getCurrentSourceFile());
+      Assert.assertEquals(line, t.getCurrentLineNumber());
+    });
   }
 
   protected final JUnit3Wrapper.Command checkMethod(String className, String methodName) {
-    return inspect(t -> t.checkMethod(className, methodName));
+    return checkMethod(className, methodName, null);
+  }
+
+  protected final JUnit3Wrapper.Command checkMethod(String className, String methodName,
+      String methodSignature) {
+    return inspect(t -> {
+      Assert.assertEquals("Incorrect class name", className, t.getCurrentClassName());
+      Assert.assertEquals("Incorrect method name", methodName, t.getCurrentMethodName());
+      if (methodSignature != null) {
+        Assert.assertEquals("Incorrect method signature", methodSignature,
+            t.getCurrentMethodSignature());
+      }
+    });
   }
 
   protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
@@ -477,26 +502,91 @@
         Assert.assertEquals(expectedValue, localValue);
       }
 
-      public void checkLine(int line) {
-        Location location = getLocation();
-        int currentLine = getMirror()
-            .getLineNumber(location.classID, location.methodID, location.index);
-        Assert.assertEquals(line, currentLine);
+      public int getCurrentLineNumber() {
+        ReplyPacket reply = getMirror().getLineTable(location.classID, location.methodID);
+        if (reply.getErrorCode() != 0) {
+          return -1;
+        }
+
+        long startCodeIndex = reply.getNextValueAsLong();
+        long endCodeIndex = reply.getNextValueAsLong();
+        int lines = reply.getNextValueAsInt();
+        int line = -1;
+        long previousLineCodeIndex = -1;
+        for (int i = 0; i < lines; ++i) {
+          long currentLineCodeIndex = reply.getNextValueAsLong();
+          int currentLineNumber = reply.getNextValueAsInt();
+
+          // Code indices are in ascending order.
+          assert currentLineCodeIndex >= startCodeIndex;
+          assert currentLineCodeIndex <= endCodeIndex;
+          assert currentLineCodeIndex > previousLineCodeIndex;
+          previousLineCodeIndex = currentLineCodeIndex;
+
+          if (location.index >= currentLineCodeIndex) {
+            line = currentLineNumber;
+          } else {
+            break;
+          }
+        }
+
+        return line;
+      }
+
+      public String getCurrentSourceFile() {
+        CommandPacket sourceFileCommand = new CommandPacket(
+            JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+            JDWPCommands.ReferenceTypeCommandSet.SourceFileCommand);
+        sourceFileCommand.setNextValueAsReferenceTypeID(location.classID);
+        ReplyPacket replyPacket = getMirror().performCommand(sourceFileCommand);
+        if (replyPacket.getErrorCode() != 0) {
+          return null;
+        } else {
+          return replyPacket.getNextValueAsString();
+        }
       }
 
       public List<String> getLocalNames() {
         return getVariablesAt(location).stream().map(v -> v.getName()).collect(Collectors.toList());
       }
 
-      public void checkMethod(String className, String methodName) {
-        String currentClassSig = getMirror().getClassSignature(location.classID);
-        assert currentClassSig.charAt(0) == 'L';
-        String currentClassName = currentClassSig.substring(1, currentClassSig.length() - 1)
-            .replace('/', '.');
-        Assert.assertEquals("Incorrect class name", className, currentClassName);
+      public String getCurrentClassName() {
+        String classSignature = getCurrentClassSignature();
+        assert classSignature.charAt(0) == 'L';
+        // Remove leading 'L' and trailing ';'
+        classSignature = classSignature.substring(1, classSignature.length() - 1);
+        // Return fully qualified name
+        return classSignature.replace('/', '.');
+      }
 
-        String currentMethodName = getMirror().getMethodName(location.classID, location.methodID);
-        Assert.assertEquals("Incorrect method name", methodName, currentMethodName);
+      public String getCurrentClassSignature() {
+        return getMirror().getClassSignature(location.classID);
+      }
+
+      public String getCurrentMethodName() {
+        return getMirror().getMethodName(location.classID, location.methodID);
+      }
+
+      public String getCurrentMethodSignature() {
+        CommandPacket command = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+            ReferenceTypeCommandSet.MethodsWithGenericCommand);
+        command.setNextValueAsReferenceTypeID(location.classID);
+
+        ReplyPacket reply = getMirror().performCommand(command);
+        assert reply.getErrorCode() == Error.NONE;
+        int methods = reply.getNextValueAsInt();
+
+        for (int i = 0; i < methods; ++i) {
+          long methodId = reply.getNextValueAsMethodID();
+          reply.getNextValueAsString(); // skip name
+          String methodSignature = reply.getNextValueAsString();
+          reply.getNextValueAsString(); // skip generic signature
+          reply.getNextValueAsInt();  // skip modifiers
+          if (methodId == location.methodID) {
+            return methodSignature;
+          }
+        }
+        throw new AssertionError("No method info for the current location");
       }
     }
 
@@ -564,7 +654,14 @@
     }
 
     private boolean installBreakpoint(BreakpointInfo breakpointInfo) {
-      final long classId = getMirror().getClassID(getClassSignature(breakpointInfo.className));
+      String classSignature = getClassSignature(breakpointInfo.className);
+      byte typeTag = TypeTag.CLASS;
+      long classId = getMirror().getClassID(classSignature);
+      if (classId == -1) {
+        // Is it an interface ?
+        classId = getMirror().getInterfaceID(classSignature);
+        typeTag = TypeTag.INTERFACE;
+      }
       if (classId == -1) {
         // The class is not ready yet. Request a CLASS_PREPARE to delay the installation of the
         // breakpoint.
@@ -575,14 +672,95 @@
             new ClassPrepareHandler(breakpointInfo, classPrepareRequestId));
         return false;
       } else {
-        int breakpointId = getMirror()
-            .setBreakpointAtMethodBegin(classId, breakpointInfo.methodName);
+        // Find the method.
+        long breakpointMethodId = findMethod(classId, breakpointInfo.methodName,
+            breakpointInfo.methodSignature);
+        long index = getMethodFirstCodeIndex(classId, breakpointMethodId);
+        Assert.assertTrue("No code in method", index >= 0);
+        // Install the breakpoint.
+        ReplyPacket replyPacket = getMirror()
+            .setBreakpoint(new Location(typeTag, classId, breakpointMethodId, index),
+                SuspendPolicy.ALL);
+        checkReplyPacket(replyPacket, "Breakpoint");
+        int breakpointId = replyPacket.getNextValueAsInt();
         // Nothing to do on breakpoint
         events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
         return true;
       }
     }
 
+    private long findMethod(long classId, String methodName, String methodSignature) {
+      class MethodInfo {
+
+        final long methodId;
+        final String methodName;
+        final String methodSignature;
+
+        MethodInfo(long methodId, String methodName, String methodSignature) {
+          this.methodId = methodId;
+          this.methodName = methodName;
+          this.methodSignature = methodSignature;
+        }
+      }
+
+      boolean withGenericSignature = true;
+      CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+          ReferenceTypeCommandSet.MethodsWithGenericCommand);
+      commandPacket.setNextValueAsReferenceTypeID(classId);
+      ReplyPacket replyPacket = getMirror().performCommand(commandPacket);
+      if (replyPacket.getErrorCode() != Error.NONE) {
+        // Retry with older command ReferenceType.Methods
+        withGenericSignature = false;
+        commandPacket.setCommand(ReferenceTypeCommandSet.MethodsCommand);
+        replyPacket = getMirror().performCommand(commandPacket);
+        assert replyPacket.getErrorCode() == Error.NONE;
+      }
+
+      int methodsCount = replyPacket.getNextValueAsInt();
+      List<MethodInfo> methodInfos = new ArrayList<>(methodsCount);
+      for (int i = 0; i < methodsCount; ++i) {
+        long currentMethodId = replyPacket.getNextValueAsMethodID();
+        String currentMethodName = replyPacket.getNextValueAsString();
+        String currentMethodSignature = replyPacket.getNextValueAsString();
+        if (withGenericSignature) {
+          replyPacket.getNextValueAsString(); // skip generic signature
+        }
+        replyPacket.getNextValueAsInt(); // skip modifiers
+        methodInfos
+            .add(new MethodInfo(currentMethodId, currentMethodName, currentMethodSignature));
+      }
+      Assert.assertTrue(replyPacket.isAllDataRead());
+
+      // Only keep methods with the expected name.
+      methodInfos = methodInfos.stream()
+          .filter(m -> m.methodName.equals(methodName)).collect(
+              Collectors.toList());
+      if (methodSignature != null) {
+        methodInfos = methodInfos.stream()
+            .filter(m -> methodSignature.equals(m.methodSignature)).collect(
+                Collectors.toList());
+      }
+      Assert.assertFalse("No method found", methodInfos.isEmpty());
+      // There must be only one matching method
+      Assert.assertEquals("More than 1 method found: please specify a signature", 1,
+          methodInfos.size());
+      return methodInfos.get(0).methodId;
+    }
+
+    private long getMethodFirstCodeIndex(long classId, long breakpointMethodId) {
+      ReplyPacket replyPacket = getMirror().getLineTable(classId, breakpointMethodId);
+      checkReplyPacket(replyPacket, "Failed to get method line table");
+      replyPacket.getNextValueAsLong(); // start
+      replyPacket.getNextValueAsLong(); // end
+      int linesCount = replyPacket.getNextValueAsInt();
+      if (linesCount == 0) {
+        return -1;
+      } else {
+        // Read only the 1st line because code indices are in ascending order
+        return replyPacket.getNextValueAsLong();
+      }
+    }
+
     //
     // Command processing
     //
@@ -603,22 +781,24 @@
         }
       }
 
-      // TODO(shertz) add method signature support (when multiple methods have the same name)
       class BreakpointCommand implements Command {
 
         private final String className;
         private final String methodName;
+        private final String methodSignature;
 
-        public BreakpointCommand(String className, String methodName) {
+        public BreakpointCommand(String className, String methodName,
+            String methodSignature) {
           assert className != null;
           assert methodName != null;
           this.className = className;
           this.methodName = methodName;
+          this.methodSignature = methodSignature;
         }
 
         @Override
         public void perform(JUnit3Wrapper testBase) {
-          testBase.installBreakpoint(new BreakpointInfo(className, methodName));
+          testBase.installBreakpoint(new BreakpointInfo(className, methodName, methodSignature));
         }
 
         @Override
@@ -740,10 +920,12 @@
 
       private final String className;
       private final String methodName;
+      private final String methodSignature;
 
-      private BreakpointInfo(String className, String methodName) {
+      private BreakpointInfo(String className, String methodName, String methodSignature) {
         this.className = className;
         this.methodName = methodName;
+        this.methodSignature = methodSignature;
       }
     }
 
diff --git a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
index ea116a7..e0eb5ea 100644
--- a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
@@ -11,6 +11,8 @@
 
 public class DefaultMethodTest extends DebugTestBase {
 
+  private static final String SOURCE_FILE = "DebugDefaultMethod.java";
+
   @Test
   public void testDefaultMethod() throws Throwable {
     String debuggeeClass = "DebugDefaultMethod";
@@ -21,7 +23,7 @@
     commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
     commands.add(run());
     commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
-    commands.add(checkLine(27));
+    commands.add(checkLine(SOURCE_FILE, 27));
     if (!supportsDefaultMethod()) {
       // We desugared default method. This means we're going to step through an extra (forward)
       // method first.
@@ -50,7 +52,7 @@
     commands.add(run());
     commands.add(run() /* resume after 1st breakpoint */);
     commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
-    commands.add(checkLine(27));
+    commands.add(checkLine(SOURCE_FILE, 27));
     commands.add(stepInto());
     commands.add(checkMethod("DebugDefaultMethod$OverrideImpl", "doSomething"));
     commands.add(checkLocal(parameterName));
@@ -61,5 +63,4 @@
 
     runDebugTestJava8(debuggeeClass, commands);
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/debug/ExceptionTest.java b/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
index 5f15f8f..3d4202b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
@@ -10,6 +10,8 @@
  */
 public class ExceptionTest extends DebugTestBase {
 
+  public static final String SOURCE_FILE = "Exceptions.java";
+
   @Test
   public void testStepOnCatch() throws Throwable {
     int catchLine;
@@ -24,9 +26,9 @@
     runDebugTest("Exceptions",
         breakpoint("Exceptions", "catchException"),
         run(),
-        checkLine(9), // line of the method call throwing the exception
+        checkLine(SOURCE_FILE, 9), // line of the method call throwing the exception
         stepOver(),
-        checkLine(catchLine), // line of the catch declaration
+        checkLine(SOURCE_FILE, catchLine), // line of the catch declaration
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 467ae09..52d9c70 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -8,6 +8,8 @@
 
 public class LambdaTest extends DebugTestBase {
 
+  public static final String SOURCE_FILE = "DebugLambda.java";
+
   @Test
   public void testLambdaDebugging() throws Throwable {
     String debuggeeClass = "DebugLambda";
@@ -17,9 +19,9 @@
         breakpoint(debuggeeClass, initialMethodName),
         run(),
         checkMethod(debuggeeClass, initialMethodName),
-        checkLine(12),
+        checkLine(SOURCE_FILE, 12),
         stepInto(INTELLIJ_FILTER),
-        checkLine(16),
+        checkLine(SOURCE_FILE, 16),
         run());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 0d72ab9..6132ace 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
-import com.android.tools.r8.debug.DebugTestBase;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
 import org.junit.Test;
 
@@ -12,6 +11,8 @@
  */
 public class LocalsTest extends DebugTestBase {
 
+  public static final String SOURCE_FILE = "Locals.java";
+
   @Test
   public void testNoLocal() throws Throwable {
     final String className = "Locals";
@@ -20,11 +21,11 @@
         breakpoint(className, methodName),
         run(),
         checkMethod(className, methodName),
-        checkLine(8),
+        checkLine(SOURCE_FILE, 8),
         checkNoLocal(),
         stepOver(),
         checkMethod(className, methodName),
-        checkLine(9),
+        checkLine(SOURCE_FILE, 9),
         checkNoLocal(),
         run());
   }
@@ -37,10 +38,10 @@
         breakpoint(className, methodName),
         run(),
         checkMethod(className, methodName),
-        checkLine(12),
+        checkLine(SOURCE_FILE, 12),
         checkNoLocal(),
         stepOver(),
-        checkLine(13),
+        checkLine(SOURCE_FILE, 13),
         checkLocal("i", Value.createInt(Integer.MAX_VALUE)),
         run());
   }
@@ -57,14 +58,14 @@
         breakpoint(className, methodName),
         run(),
         checkMethod(className, methodName),
-        checkLine(17),
+        checkLine(SOURCE_FILE, 17),
         checkLocal("p", pValue),
         stepOver(),
-        checkLine(18),
+        checkLine(SOURCE_FILE, 18),
         checkLocal("p", pValue),
         checkLocal("c", cValue),
         stepOver(),
-        checkLine(19),
+        checkLine(SOURCE_FILE, 19),
         checkLocal("p", pValue),
         checkLocal("c", cValue),
         checkLocal("v", vValue),
@@ -84,16 +85,16 @@
         breakpoint(className, methodName),
         run(),
         checkMethod(className, methodName),
-        checkLine(17),
+        checkLine(SOURCE_FILE, 17),
         checkLocal("p", pValue),
         stepOver(),
-        checkLine(18),
+        checkLine(SOURCE_FILE, 18),
         checkLocal("p", pValue),
         checkLocal("c", cValue),
         setLocal("c", newValue),
         checkLocal("c", newValue),  // we should see the updated value
         stepOver(),
-        checkLine(19),
+        checkLine(SOURCE_FILE, 19),
         checkLocal("p", pValue),
         checkLocal("c", newValue),
         checkLocal("v", vValue),
@@ -109,15 +110,15 @@
         breakpoint(className, methodName),
         run(),
         checkMethod(className, methodName),
-        checkLine(23),
+        checkLine(SOURCE_FILE, 23),
         checkNoLocal(),
         stepOver(),
         checkMethod(className, methodName),
-        checkLine(24),
+        checkLine(SOURCE_FILE, 24),
         checkLocal("i", Value.createInt(0)),
         setLocal("i", newValueForI),
         stepOver(),
-        checkLine(25),
+        checkLine(SOURCE_FILE, 25),
         checkLocal("i", newValueForI),
         checkLocal("f", Value.createFloat(0)),
         run());
@@ -133,15 +134,15 @@
         breakpoint(className, methodName),
         run(),
         checkMethod(className, methodName),
-        checkLine(29),
+        checkLine(SOURCE_FILE, 29),
         checkNoLocal(),
         stepOver(),
         checkMethod(className, methodName),
-        checkLine(30),
+        checkLine(SOURCE_FILE, 30),
         checkLocal("i", oldValueForI),
         setLocal("i", newValueForI),
         stepOver(),
-        checkLine(33),
+        checkLine(SOURCE_FILE, 33),
         checkLocal("i", newValueForI),
         run());
   }
diff --git a/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java b/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java
index 0b7973e..1d708cf 100644
--- a/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java
@@ -10,16 +10,18 @@
  */
 public class MultipleReturnsTest extends DebugTestBase {
 
+  public static final String SOURCE_FILE = "MultipleReturns.java";
+
   @Test
   public void testMultipleReturns() throws Throwable {
     runDebugTest("MultipleReturns",
         breakpoint("MultipleReturns", "multipleReturns"),
         run(),
         stepOver(),
-        checkLine(16), // this should be the 1st return statement
+        checkLine(SOURCE_FILE, 16), // this should be the 1st return statement
         run(),
         stepOver(),
-        checkLine(18), // this should be the 2nd return statement
+        checkLine(SOURCE_FILE, 18), // this should be the 2nd return statement
         run());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java b/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java
index a5b7d4e..fa5917e 100644
--- a/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java
@@ -11,22 +11,6 @@
 
 public class SyntheticMethodTest extends DebugTestBase {
 
-  private void debugInnerAccessors(StepFilter stepFilter) throws Throwable {
-    String debuggeeClass = "InnerAccessors";
-    List<Command> commands = new ArrayList<>();
-    commands.add(breakpoint("InnerAccessors$Inner", "callPrivateMethodInOuterClass"));
-    commands.add(run());
-    commands.add(checkLine(13));
-    commands.add(stepInto(stepFilter));  // skip synthetic accessor
-    if (stepFilter == NO_FILTER) {
-      commands.add(stepInto(stepFilter));
-    }
-    commands.add(checkMethod(debuggeeClass, "privateMethod"));
-    commands.add(checkLine(8));
-    commands.add(run());
-    runDebugTest(debuggeeClass, commands);
-  }
-
   @Test
   public void testInnerAccessors_NoFilter() throws Throwable {
     debugInnerAccessors(NO_FILTER);
@@ -37,4 +21,51 @@
     debugInnerAccessors(INTELLIJ_FILTER);
   }
 
+  @Test
+  public void testGenericBridges_NoFilter() throws Throwable {
+    debugGenericBridges(NO_FILTER);
+  }
+
+  @Test
+  public void testGenericBridges_IntelliJ() throws Throwable {
+    debugGenericBridges(INTELLIJ_FILTER);
+  }
+
+  private void debugInnerAccessors(StepFilter stepFilter) throws Throwable {
+    final String sourceFile = "InnerAccessors.java";
+    String debuggeeClass = "InnerAccessors";
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint("InnerAccessors$Inner", "callPrivateMethodInOuterClass"));
+    commands.add(run());
+    commands.add(checkLine(sourceFile, 13));
+    commands.add(stepInto(stepFilter));  // skip synthetic accessor
+    if (stepFilter == NO_FILTER) {
+      commands.add(stepInto(stepFilter));
+    }
+    commands.add(checkMethod(debuggeeClass, "privateMethod"));
+    commands.add(checkLine(sourceFile, 8));
+    commands.add(run());
+    runDebugTest(debuggeeClass, commands);
+  }
+
+  private void debugGenericBridges(StepFilter stepFilter) throws Throwable {
+    final String sourceFile = "Bridges.java";
+    String debuggeeClass = "Bridges";
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint(debuggeeClass, "testGenericBridge"));
+    commands.add(run());
+    commands.add(checkLine(sourceFile, 21));
+    commands.add(stepInto(stepFilter));  // skip synthetic accessor
+    String implementationClassName = "Bridges$StringImpl";
+    String methodName = "get";
+    if (stepFilter == NO_FILTER) {
+      commands.add(checkMethod(implementationClassName, methodName, "(Ljava/lang/Object;)V"));
+      commands.add(stepInto(stepFilter));
+    }
+    commands.add(checkMethod(implementationClassName, methodName, "(Ljava/lang/String;)V"));
+    commands.add(checkLine(sourceFile, 16));
+    commands.add(run());
+    runDebugTest(debuggeeClass, commands);
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index cab3291..ae0f650 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -4,41 +4,27 @@
 
 package com.android.tools.r8.maindexlist;
 
-import static com.android.tools.r8.ToolHelper.EXAMPLES_DIR;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationResult;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.R8Command.Builder;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OffOrAuto;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 
 public class MainDexTracingTest {
@@ -155,11 +141,21 @@
           .map(dexType -> dexType.descriptor.toString())
           .collect(Collectors.toList());
       Collections.sort(resultMainDexList);
-      StringBuilder resultString = new StringBuilder();
-      resultMainDexList.forEach(descriptor -> resultString.append(descriptor).append('\n'));
-      String refList = new String(Files.readAllBytes(
-          expectedMainDexList), StandardCharsets.UTF_8);
-      Assert.assertEquals(refList, resultString.toString());
+      String[] refList = new String(Files.readAllBytes(
+          expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+      for (int i = 0; i < refList.length; i++) {
+        String reference = refList[i];
+        String computed = resultMainDexList.get(i);
+        if (reference.contains("-$$Lambda$")) {
+          // For lambda classes we check that there is a lambda class for the right containing
+          // class. However, we do not check the hash for the generated lambda class. The hash
+          // changes for different compiler versions because different compiler versions generate
+          // different lambda implementation method names.
+          reference = reference.substring(0, reference.lastIndexOf('$'));
+          computed = computed.substring(0, computed.lastIndexOf('$'));
+        }
+        Assert.assertEquals(reference, computed);
+      }
     } catch (ExecutionException e) {
       throw e.getCause();
     }
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());
+  }
+}
diff --git a/tools/create_jctf_tests.py b/tools/create_jctf_tests.py
index 0f1ca98..52907fc 100755
--- a/tools/create_jctf_tests.py
+++ b/tools/create_jctf_tests.py
@@ -71,7 +71,8 @@
   raise IOError("Can't find package statement in java file: " + filepath)
 
 
-def generate_test(class_name, compiler_under_test, relative_package):
+def generate_test(class_name, compiler_under_test, compiler_under_test_enum,
+    relative_package):
   filename = join(DESTINATION_DIR, compiler_under_test,
       relative_package.replace('.', '/'), class_name + '.java')
   utils.makedirs_if_needed(dirname(filename))
@@ -83,7 +84,7 @@
       relativePackage = relative_package,
       name = full_class_name,
       testClassName = class_name,
-      compilerUnderTestEnum = compiler_under_test.upper(),
+      compilerUnderTestEnum = compiler_under_test_enum,
       classFile = full_class_name.replace('.', '/') + '.class',
       nameWithoutPackagePrefix = '{}.{}'.format(relative_package, class_name))
 
@@ -120,8 +121,8 @@
     assert idx >= 0
     relative_package = package[idx + len(dot_java_dot):]
 
-    for d in ['r8', 'd8']:
-      generate_test(class_name, d, relative_package)
+    generate_test(class_name, 'd8', 'R8DEBUG_AFTER_D8', relative_package)
+    generate_test(class_name, 'r8', 'R8', relative_package)
 
 
 if __name__ == '__main__':