Merge "Use ApiLevelException for Invoke Polymorphic error"
diff --git a/build.gradle b/build.gradle
index 9a48a4d..63513ce 100644
--- a/build.gradle
+++ b/build.gradle
@@ -114,7 +114,7 @@
 
 dependencies {
     compile 'net.sf.jopt-simple:jopt-simple:4.6'
-    compile 'com.googlecode.json-simple:json-simple:1.1.1'
+    compile 'com.googlecode.json-simple:json-simple:1.1'
     compile group: 'com.google.guava', name: 'guava', version: '19.0'
     compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
     compile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
@@ -326,12 +326,15 @@
     // In order to build without dependencies, pass the exclude_deps property using:
     // gradle -Pexclude_deps R8
     if (!project.hasProperty('exclude_deps')) {
-        // Relocating dependencies to avoid conflicts.
-        relocate 'com.google', 'com.android.tools.r8.com.google'
+        // Relocating dependencies to avoid conflicts. Keep this as precise as possible
+        // to avoid rewriting unrelated strings.
+        relocate 'com.google.common', 'com.android.tools.r8.com.google.common'
+        relocate 'com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'
         relocate 'joptsimple', 'com.android.tools.r8.joptsimple'
-        relocate 'org', 'com.android.tools.r8.org'
+        relocate 'org.apache.commons', 'com.android.tools.r8.org.apache.commons'
+        relocate 'org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'
+        relocate 'org.json.simple', 'com.android.tools.r8.org.json.simple'
         relocate 'it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'
-        relocate 'junit', 'com.android.tools.r8.junit'
         // Also include dependencies
         configurations = [project.configurations.compile]
     }
@@ -348,12 +351,15 @@
     // In order to build without dependencies, pass the exclude_deps property using:
     // gradle -Pexclude_deps D8
     if (!project.hasProperty('exclude_deps')) {
-        // Relocating dependencies to avoid conflicts.
-        relocate 'com.google', 'com.android.tools.r8.com.google'
+        // Relocating dependencies to avoid conflicts. Keep this as precise as possible
+        // to avoid rewriting unrelated strings.
+        relocate 'com.google.common', 'com.android.tools.r8.com.google.common'
+        relocate 'com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'
         relocate 'joptsimple', 'com.android.tools.r8.joptsimple'
-        relocate 'org', 'com.android.tools.r8.org'
+        relocate 'org.apache.commons', 'com.android.tools.r8.org.apache.commons'
+        relocate 'org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'
+        relocate 'org.json.simple', 'com.android.tools.r8.org.json.simple'
         relocate 'it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'
-        relocate 'junit', 'com.android.tools.r8.junit'
         // Also include dependencies
         configurations = [project.configurations.compile]
     }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2d2760e..5321808 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@
  */
 public final class D8 {
 
-  private static final String VERSION = "v0.1.0";
+  private static final String VERSION = "v0.2.0";
   private static final int STATUS_ERROR = 1;
 
   private D8() {}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 91d2337..328f215 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@
 
 public class R8 {
 
-  private static final String VERSION = "v0.1.0";
+  private static final String VERSION = "v0.2.0";
   private final Timing timing = new Timing("R8");
   private final InternalOptions options;
 
@@ -270,9 +270,10 @@
           proguardSeedsData = bytes.toByteArray();
         }
         if (options.useTreeShaking) {
-          application = new TreePruner(application, appInfo.withLiveness(), options).run();
+          TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+          application = pruner.run();
           // Recompute the subtyping information.
-          appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+          appInfo = appInfo.withLiveness().prunedCopyFrom(application, pruner.getRemovedClasses());
           new AbstractMethodRemover(appInfo).run();
           new AnnotationRemover(appInfo.withLiveness(), options).run();
         }
@@ -295,11 +296,13 @@
         // Class merging requires inlining.
         if (!options.skipClassMerging && options.inlineAccessors) {
           timing.begin("ClassMerger");
-          graphLense = new SimpleClassMerger(application, appInfo.withLiveness(), graphLense,
-              timing).run();
+          SimpleClassMerger classMerger = new SimpleClassMerger(application,
+              appInfo.withLiveness(), graphLense, timing);
+          graphLense = classMerger.run();
           timing.end();
+          appInfo = appInfo.withLiveness()
+              .prunedCopyFrom(application, classMerger.getRemovedClasses());
         }
-        appInfo = appInfo.withLiveness().prunedCopyFrom(application);
         appInfo = appInfo.withLiveness().rewrittenWithLense(graphLense);
         // Collect switch maps and ordinals maps.
         new SwitchMapCollector(appInfo.withLiveness(), options).run();
@@ -333,8 +336,10 @@
           Enqueuer enqueuer = new Enqueuer(appInfo);
           appInfo = enqueuer.traceApplication(rootSet, timing);
           if (options.useTreeShaking) {
-            application = new TreePruner(application, appInfo.withLiveness(), options).run();
-            appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+            TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+            application = pruner.run();
+            appInfo = appInfo.withLiveness()
+                .prunedCopyFrom(application, pruner.getRemovedClasses());
             // Print reasons on the application after pruning, so that we reflect the actual result.
             ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
             reasonPrinter.run(application);
@@ -345,7 +350,8 @@
       }
 
       // Only perform discard-checking if tree-shaking is turned on.
-      if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
+      if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()
+          && options.useDiscardedChecker) {
         new DiscardedChecker(rootSet, application).run();
       }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index ec040a5..775c1b3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -32,6 +31,7 @@
     private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
     private final List<Path> proguardConfigFiles = new ArrayList<>();
     private Optional<Boolean> treeShaking = Optional.empty();
+    private Optional<Boolean> discardedChecker = Optional.empty();
     private Optional<Boolean> minification = Optional.empty();
     private boolean ignoreMissingClasses = false;
     private Path packageDistributionFile = null;
@@ -58,6 +58,14 @@
     }
 
     /**
+     * Enable/disable discareded checker.
+     */
+    public Builder setDiscardedChecker(boolean useDiscardedChecker) {
+      discardedChecker = Optional.of(useDiscardedChecker);
+      return self();
+    }
+
+    /**
      * Enable/disable minification. This overrides any settings in proguard configuration files.
      */
     public Builder setMinification(boolean useMinification) {
@@ -194,11 +202,14 @@
         addLibraryFiles(configuration.getLibraryjars());
       }
 
+      // TODO(b/64802420): setProguardMapFile if configuration.hasApplyMappingFile
+
       if (packageDistributionFile != null) {
         getAppBuilder().setPackageDistributionFile(packageDistributionFile);
       }
 
       boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking());
+      boolean useDiscardedChecker = discardedChecker.orElse(true);
       boolean useMinification = minification.orElse(configuration.isObfuscating());
 
       return new R8Command(
@@ -211,6 +222,7 @@
           getMode(),
           getMinApiLevel(),
           useTreeShaking,
+          useDiscardedChecker,
           useMinification,
           ignoreMissingClasses);
     }
@@ -236,6 +248,7 @@
       "                           # shaking/minification).",
       "  --pg-map <file>          # Proguard map <file>.",
       "  --no-tree-shaking        # Force disable tree shaking of unreachable classes.",
+      "  --no-discarded-checker   # Force disable the discarded checker (when tree shaking).",
       "  --no-minification        # Force disable minification of names.",
       "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
       "                           # primary dex file.",
@@ -248,6 +261,7 @@
   private final Path mainDexListOutput;
   private final ProguardConfiguration proguardConfiguration;
   private final boolean useTreeShaking;
+  private final boolean useDiscardedChecker;
   private final boolean useMinification;
   private final boolean ignoreMissingClasses;
 
@@ -305,6 +319,8 @@
         builder.setMinApiLevel(Integer.valueOf(args[++i]));
       } else if (arg.equals("--no-tree-shaking")) {
         builder.setTreeShaking(false);
+      } else if (arg.equals("--no-discarded-checker")) {
+        builder.setDiscardedChecker(false);
       } else if (arg.equals("--no-minification")) {
         builder.setMinification(false);
       } else if (arg.equals("--main-dex-rules")) {
@@ -359,6 +375,7 @@
       CompilationMode mode,
       int minApiLevel,
       boolean useTreeShaking,
+      boolean useDiscardedChecker,
       boolean useMinification,
       boolean ignoreMissingClasses) {
     super(inputApp, outputPath, outputMode, mode, minApiLevel);
@@ -369,6 +386,7 @@
     this.mainDexListOutput = mainDexListOutput;
     this.proguardConfiguration = proguardConfiguration;
     this.useTreeShaking = useTreeShaking;
+    this.useDiscardedChecker = useDiscardedChecker;
     this.useMinification = useMinification;
     this.ignoreMissingClasses = ignoreMissingClasses;
   }
@@ -379,6 +397,7 @@
     mainDexListOutput = null;
     proguardConfiguration = null;
     useTreeShaking = false;
+    useDiscardedChecker = false;
     useMinification = false;
     ignoreMissingClasses = false;
   }
@@ -387,6 +406,10 @@
     return useTreeShaking;
   }
 
+  public boolean useDiscardedChecker() {
+    return useDiscardedChecker;
+  }
+
   public boolean useMinification() {
     return useMinification;
   }
@@ -401,6 +424,8 @@
     internal.skipMinification = !useMinification() || !proguardConfiguration.isObfuscating();
     assert internal.useTreeShaking;
     internal.useTreeShaking = useTreeShaking();
+    assert internal.useDiscardedChecker;
+    internal.useDiscardedChecker = useDiscardedChecker();
     assert !internal.ignoreMissingClasses;
     internal.ignoreMissingClasses = ignoreMissingClasses;
     for (String pattern : proguardConfiguration.getAttributesRemovalPatterns()) {
diff --git a/src/main/java/com/android/tools/r8/code/Base1Format.java b/src/main/java/com/android/tools/r8/code/Base1Format.java
index b62853d..9d88f53 100644
--- a/src/main/java/com/android/tools/r8/code/Base1Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base1Format.java
@@ -5,6 +5,8 @@
 
 public abstract class Base1Format extends Instruction {
 
+  public static final int SIZE = 1;
+
   public Base1Format(BytecodeStream stream) {
     super(stream);
   }
@@ -12,6 +14,6 @@
   protected Base1Format() {}
 
   public int getSize() {
-    return 1;
+    return SIZE;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Base2Format.java b/src/main/java/com/android/tools/r8/code/Base2Format.java
index f8a48ac..1241e6a 100644
--- a/src/main/java/com/android/tools/r8/code/Base2Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base2Format.java
@@ -5,6 +5,8 @@
 
 public abstract class Base2Format extends Instruction {
 
+  public static final int SIZE = 2;
+
   protected Base2Format() {}
 
   public Base2Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
   }
 
   public int getSize() {
-    return 2;
+    return SIZE;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Base3Format.java b/src/main/java/com/android/tools/r8/code/Base3Format.java
index 34bda57..c1618f5 100644
--- a/src/main/java/com/android/tools/r8/code/Base3Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base3Format.java
@@ -5,6 +5,8 @@
 
 public abstract class Base3Format extends Instruction {
 
+  public static final int SIZE = 3;
+
   protected Base3Format() {}
 
   public Base3Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
   }
 
   public int getSize() {
-    return 3;
+    return SIZE;
   }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/code/Base4Format.java b/src/main/java/com/android/tools/r8/code/Base4Format.java
index 7cdf1c5..f3448fa 100644
--- a/src/main/java/com/android/tools/r8/code/Base4Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base4Format.java
@@ -5,6 +5,8 @@
 
 public abstract class Base4Format extends Instruction {
 
+  public static final int SIZE = 4;
+
   protected Base4Format() {}
 
   public Base4Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
   }
 
   public int getSize() {
-    return 4;
+    return SIZE;
   }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/code/Base5Format.java b/src/main/java/com/android/tools/r8/code/Base5Format.java
index cc67572..10ddc5e 100644
--- a/src/main/java/com/android/tools/r8/code/Base5Format.java
+++ b/src/main/java/com/android/tools/r8/code/Base5Format.java
@@ -5,6 +5,8 @@
 
 public abstract class Base5Format extends Instruction {
 
+  public static final int SIZE = 5;
+
   protected Base5Format() {}
 
   public Base5Format(BytecodeStream stream) {
@@ -12,6 +14,6 @@
   }
 
   public int getSize() {
-    return 5;
+    return SIZE;
   }
 }
\ No newline at end of file
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 80dea00..7dc77de 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -317,6 +317,8 @@
     DexType result = types.get(descriptor);
     if (result == null) {
       result = new DexType(descriptor);
+      assert result.isArrayType() || result.isClassType() || result.isPrimitiveType() ||
+          result.isVoidType();
       assert !internalSentinels.contains(result);
       types.put(descriptor, result);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index a371684..0d81ea4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -288,8 +288,12 @@
   }
 
   public boolean isPrimitiveType() {
-    char firstChar = (char) descriptor.content[0];
-    return firstChar != 'L' && firstChar != '[';
+    return isPrimitiveType((char) descriptor.content[0]);
+  }
+
+  private boolean isPrimitiveType(char c) {
+    return c == 'Z' || c == 'B' || c == 'S' || c == 'C' || c == 'I' || c == 'F' || c == 'J'
+        || c == 'D';
   }
 
   public boolean isVoidType() {
@@ -310,19 +314,7 @@
     if (!isArrayType()) {
       return false;
     }
-    switch (descriptor.content[1]) {
-      case 'Z':  // boolean
-      case 'B':  // byte
-      case 'S':  // short
-      case 'C':  // char
-      case 'I':  // int
-      case 'F':  // float
-      case 'J':  // long
-      case 'D':  // double
-        return true;
-      default:
-        return false;
-    }
+    return isPrimitiveType((char) descriptor.content[1]);
   }
 
   public int elementSizeForPrimitiveArrayType() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index c7b55d5..fbf6658 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -17,7 +17,7 @@
 
   public Argument(Value outValue) {
     super(outValue);
-    outValue.markAsArgument();;
+    outValue.markAsArgument();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index 8a001ae..49adcb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -59,26 +59,26 @@
       int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
       int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
       int result = foldIntegers(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else if (type == NumericType.LONG) {
       long left = leftValue().getConstInstruction().asConstNumber().getLongValue();
       long right = rightValue().getConstInstruction().asConstNumber().getLongValue();
       long result = foldLongs(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     } else if (type == NumericType.FLOAT) {
       float left = leftValue().getConstInstruction().asConstNumber().getFloatValue();
       float right = rightValue().getConstInstruction().asConstNumber().getFloatValue();
       float result = foldFloat(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
     } else {
       assert type == NumericType.DOUBLE;
       double left = leftValue().getConstInstruction().asConstNumber().getDoubleValue();
       double right = rightValue().getConstInstruction().asConstNumber().getDoubleValue();
       double result = foldDouble(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
     }
   }
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 5f1b33f..c9e26b4 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
@@ -543,6 +543,13 @@
     return unlinkSingleSuccessor();
   }
 
+  public void detachAllSuccessors() {
+    for (BasicBlock successor : successors) {
+      successor.predecessors.remove(this);
+    }
+    successors.clear();
+  }
+
   public List<BasicBlock> unlink(BasicBlock successor, DominatorTree dominator) {
     assert successors.contains(successor);
     assert successor.predecessors.contains(this);
@@ -575,10 +582,6 @@
           for (Value value : instruction.getDebugValues()) {
             value.removeDebugUser(instruction);
           }
-          Value previousLocalValue = instruction.getPreviousLocalValue();
-          if (previousLocalValue != null) {
-            previousLocalValue.removeDebugUser(instruction);
-          }
         }
       }
     }
@@ -935,7 +938,8 @@
    *
    * <p>The constructed basic block has no predecessors and no successors.
    *
-   * @param blockNumber the block number of the goto block
+   * @param blockNumber the block number of the block
+   * @param theIf the if instruction
    */
   public static BasicBlock createIfBlock(int blockNumber, If theIf) {
     BasicBlock block = new BasicBlock();
@@ -945,6 +949,32 @@
     return block;
   }
 
+  /**
+   * Create a new basic block with an instruction followed by an if instruction.
+   *
+   * <p>The constructed basic block has no predecessors and no successors.
+   *
+   * @param blockNumber the block number of the block
+   * @param theIf the if instruction
+   * @param instruction the instruction to place before the if instruction
+   */
+  public static BasicBlock createIfBlock(int blockNumber, If theIf, Instruction instruction) {
+    BasicBlock block = new BasicBlock();
+    block.add(instruction);
+    block.add(theIf);
+    block.close(null);
+    block.setNumber(blockNumber);
+    return block;
+  }
+
+  public static BasicBlock createSwitchBlock(int blockNumber, Switch theSwitch) {
+    BasicBlock block = new BasicBlock();
+    block.add(theSwitch);
+    block.close(null);
+    block.setNumber(blockNumber);
+    return block;
+  }
+
   public boolean isTrivialGoto() {
     return instructions.size() == 1 && exit().isGoto();
   }
@@ -1144,7 +1174,7 @@
       // Remove the move-exception instruction.
       move = entry().asMoveException();
       position = move.getPosition();
-      assert move.getPreviousLocalValue() == null;
+      assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
     }
     // Create new predecessor blocks.
@@ -1158,8 +1188,7 @@
       BasicBlock newBlock = new BasicBlock();
       newPredecessors.add(newBlock);
       if (hasMoveException) {
-        Value value = new Value(
-            valueNumberGenerator.next(), MoveType.OBJECT, move.getDebugInfo());
+        Value value = new Value(valueNumberGenerator.next(), MoveType.OBJECT, move.getLocalInfo());
         values.add(value);
         MoveException newMove = new MoveException(value);
         newBlock.add(newMove);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 968e560..f5bc204 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.IteratorUtils;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
@@ -114,10 +115,6 @@
     for (Value value : current.getDebugValues()) {
       value.removeDebugUser(current);
     }
-    Value previousLocalValue = current.getPreviousLocalValue();
-    if (previousLocalValue != null) {
-      previousLocalValue.removeDebugUser(current);
-    }
     listIterator.remove();
     current = null;
   }
@@ -166,15 +163,9 @@
     }
   }
 
-  private BasicBlock peekPrevious(ListIterator<BasicBlock> blocksIterator) {
-    BasicBlock block = blocksIterator.previous();
-    blocksIterator.next();
-    return block;
-  }
-
   public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) {
     List<BasicBlock> blocks = code.blocks;
-    assert blocksIterator == null || peekPrevious(blocksIterator) == block;
+    assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block;
 
     int blockNumber = code.getHighestBlockNumber() + 1;
     BasicBlock newBlock;
@@ -209,6 +200,9 @@
       blocks.add(blocks.indexOf(block) + 1, newBlock);
     } else {
       blocksIterator.add(newBlock);
+      // Ensure that calling remove() will remove the block just added.
+      blocksIterator.previous();
+      blocksIterator.next();
     }
 
     return newBlock;
@@ -217,7 +211,7 @@
   public BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blocksIterator) {
     // Split at the current cursor position.
     BasicBlock newBlock = split(code, blocksIterator);
-    assert blocksIterator == null || peekPrevious(blocksIterator) == newBlock;
+    assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == newBlock;
     // Skip the requested number of instructions and split again.
     InstructionListIterator iterator = newBlock.listIterator();
     for (int i = 0; i < instructions; i++) {
@@ -350,7 +344,7 @@
     assert invoke.inValues().size() == arguments.size();
     for (int i = 0; i < invoke.inValues().size(); i++) {
       // TODO(zerny): Support inlining in --debug mode.
-      assert arguments.get(i).getDebugInfo() == null;
+      assert arguments.get(i).getLocalInfo() == null;
       if ((i == 0) && (downcast != null)) {
         Value invokeValue = invoke.inValues().get(0);
         Value receiverValue = arguments.get(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 9b54353..aeb0a51 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -180,7 +180,7 @@
       }
     }
     assert result == -1 || result == 0 || result == 1;
-    Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+    Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
     return new ConstNumber(ConstType.INT, value, result);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 1880668..e376361 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -33,10 +33,7 @@
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outType(),
-            original.getDebugInfo());
+        new Value(code.valueNumberGenerator.next(), original.outType(), original.getLocalInfo());
     return new ConstNumber(original.type, newValue, original.getRawValue());
   }
 
@@ -80,6 +77,14 @@
     return value == 0;
   }
 
+  public boolean isIntegerZero() {
+    return type == ConstType.INT && getIntValue() == 0;
+  }
+
+  public boolean isIntegerOne() {
+    return type == ConstType.INT && getIntValue() == 1;
+  }
+
   public boolean isIntegerNegativeOne(NumericType type) {
     assert type == NumericType.INT || type == NumericType.LONG;
     if (type == NumericType.INT) {
@@ -121,6 +126,33 @@
     }
   }
 
+  // Estimated size of the resulting dex instruction in code units.
+  public static int estimatedDexSize(ConstType type, long value) {
+    if (MoveType.fromConstType(type) == MoveType.SINGLE) {
+      assert NumberUtils.is32Bit(value);
+      if (NumberUtils.is4Bit(value)) {
+        return Const4.SIZE;
+      } else if (NumberUtils.is16Bit(value)) {
+        return Const16.SIZE;
+      } else if ((value & 0x0000ffffL) == 0) {
+        return ConstHigh16.SIZE;
+      } else {
+        return Const.SIZE;
+      }
+    } else {
+      assert MoveType.fromConstType(type) == MoveType.WIDE;
+      if (NumberUtils.is16Bit(value)) {
+        return ConstWide16.SIZE;
+      } else if ((value & 0x0000ffffffffffffL) == 0) {
+        return ConstWideHigh16.SIZE;
+      } else if (NumberUtils.is32Bit(value)) {
+        return ConstWide32.SIZE;
+      } else {
+        return ConstWide.SIZE;
+      }
+    }
+  }
+
   @Override
   public int maxInValueRegister() {
     assert false : "Const has no register arguments.";
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 824d75c..21ed16e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -155,14 +156,11 @@
         values.add(phi);
         for (Value value : phi.getOperands()) {
           values.add(value);
-          if (value.isPhi()) {
-            Phi phiOperand = value.asPhi();
-            assert phiOperand.getBlock().getPhis().contains(phiOperand);
-            assert phiOperand.uniquePhiUsers().contains(phi);
-          } else {
-            Instruction definition = value.definition;
-            assert definition.outValue() == value;
-          }
+          assert value.uniquePhiUsers().contains(phi);
+        }
+        for (Value value : phi.getDebugValues()) {
+          values.add(value);
+          value.debugPhiUsers().contains(phi);
         }
       }
       for (Instruction instruction : block.getInstructions()) {
@@ -171,33 +169,41 @@
         if (outValue != null) {
           values.add(outValue);
           assert outValue.definition == instruction;
-          Value previousLocalValue = outValue.getPreviousLocalValue();
-          if (previousLocalValue != null) {
-            values.add(previousLocalValue);
-            assert previousLocalValue.debugUsers().contains(instruction);
-          }
         }
         for (Value value : instruction.inValues()) {
           values.add(value);
           assert value.uniqueUsers().contains(instruction);
-          if (value.isPhi()) {
-            Phi phi = value.asPhi();
-            assert phi.getBlock().getPhis().contains(phi);
-          } else {
-            Instruction definition = value.definition;
-            assert definition.outValue() == value;
-          }
+        }
+        for (Value value : instruction.getDebugValues()) {
+          values.add(value);
+          assert value.debugUsers().contains(instruction);
         }
       }
     }
 
     for (Value value : values) {
+      assert verifyValue(value);
       assert consistentValueUses(value);
     }
 
     return true;
   }
 
+  private boolean verifyValue(Value value) {
+    assert value.isPhi() ? verifyPhi(value.asPhi()) : verifyDefinition(value);
+    return true;
+  }
+
+  private boolean verifyPhi(Phi phi) {
+    assert phi.getBlock().getPhis().contains(phi);
+    return true;
+  }
+
+  private boolean verifyDefinition(Value value) {
+    assert value.definition.outValue() == value;
+    return true;
+  }
+
   private boolean consistentValueUses(Value value) {
     for (Instruction user : value.uniqueUsers()) {
       assert user.inValues().contains(value);
@@ -206,10 +212,13 @@
       assert phiUser.getOperands().contains(value);
       assert phiUser.getBlock().getPhis().contains(phiUser);
     }
-    if (value.debugUsers() != null) {
+    if (value.getLocalInfo() != null) {
       for (Instruction debugUser : value.debugUsers()) {
-        assert debugUser.getPreviousLocalValue() == value
-            || debugUser.getDebugValues().contains(value);
+        assert debugUser.getDebugValues().contains(value);
+      }
+      for (Phi phiUser : value.debugPhiUsers()) {
+        assert verifyPhi(phiUser);
+        assert phiUser.getDebugValues().contains(value);
       }
     }
     return true;
@@ -367,8 +376,8 @@
     return arguments;
   }
 
-  public Value createValue(MoveType moveType, Value.DebugInfo debugInfo) {
-    return new Value(valueNumberGenerator.next(), moveType, debugInfo);
+  public Value createValue(MoveType moveType, DebugLocalInfo local) {
+    return new Value(valueNumberGenerator.next(), moveType, local);
   }
 
   public Value createValue(MoveType moveType) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index b533452..3a068f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -115,6 +115,11 @@
     builder.addIf(this);
   }
 
+  // Estimated size of the resulting dex instruction in code units.
+  public static int estimatedDexSize() {
+    return 2;
+  }
+
   @Override
   public String toString() {
     return super.toString() + " " + type + " block " + getTrueTarget().getNumber()
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e33c796..e99dbf1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -15,9 +14,11 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 public abstract class Instruction {
 
@@ -25,7 +26,7 @@
   protected final List<Value> inValues = new ArrayList<>();
   private BasicBlock block = null;
   private int number = -1;
-  private List<Value> debugValues = null;
+  private Set<Value> debugValues = null;
 
   protected Instruction(Value outValue) {
     setOutValue(outValue);
@@ -65,17 +66,13 @@
     outValue = value;
     if (outValue != null) {
       outValue.definition = this;
-      Value previousLocalValue = getPreviousLocalValue();
-      if (previousLocalValue != null) {
-        previousLocalValue.addDebugUser(this);
-      }
     }
   }
 
   public void addDebugValue(Value value) {
     assert value.getLocalInfo() != null;
     if (debugValues == null) {
-      debugValues = new ArrayList<>();
+      debugValues = new HashSet<>();
     }
     debugValues.add(value);
     value.addDebugUser(this);
@@ -97,36 +94,24 @@
 
   public abstract void buildDex(DexBuilder builder);
 
-  public void replaceValue(Value oldValue, Value newValue) {
+  public void replaceValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
     for (int i = 0; i < inValues.size(); i++) {
       if (oldValue == inValues.get(i)) {
         inValues.set(i, newValue);
         newValue.addUser(this);
-        oldValue.removeUser(this);
+        toRemove.add(this);
       }
     }
   }
 
-  public void replaceDebugPhi(Phi phi, Value value) {
-    if (debugValues != null) {
-      for (int i = 0; i < debugValues.size(); i++) {
-        if (phi == debugValues.get(i)) {
-          if (value.getLocalInfo() == null) {
-            debugValues.remove(i);
-          } else {
-            debugValues.set(i, value);
-            value.addDebugUser(this);
-          }
-        }
+  public void replaceDebugValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
+    if (debugValues.remove(oldValue)) {
+      toRemove.add(this);
+      if (newValue.getLocalInfo() != null) {
+        // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
+        addDebugValue(newValue);
       }
-    }
-    if (phi == getPreviousLocalValue()) {
-      if (value.getDebugInfo() == null) {
-        replacePreviousLocalValue(null);
-      } else {
-        replacePreviousLocalValue(value);
-        value.addDebugUser(this);
-      }
+      // TODO(zerny): Else: Insert a write if replacing a phi with associated debug-local info.
     }
   }
 
@@ -323,24 +308,12 @@
 
   public abstract int maxOutValueRegister();
 
-  public DebugInfo getDebugInfo() {
-    return outValue == null ? null : outValue.getDebugInfo();
-  }
-
   public DebugLocalInfo getLocalInfo() {
     return outValue == null ? null : outValue.getLocalInfo();
   }
 
-  public Value getPreviousLocalValue() {
-    return outValue == null ? null : outValue.getPreviousLocalValue();
-  }
-
-  public List<Value> getDebugValues() {
-    return debugValues != null ? debugValues : ImmutableList.of();
-  }
-
-  public void replacePreviousLocalValue(Value value) {
-    outValue.replacePreviousLocalValue(value);
+  public Set<Value> getDebugValues() {
+    return debugValues != null ? debugValues : ImmutableSet.of();
   }
 
   public boolean isArrayGet() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 431d338..97f1885 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -95,7 +95,8 @@
    * @param code the IR code for the block this iterator originates from.
    * @param blockIterator basic block iterator used to iterate the blocks. This must be positioned
    * just after the block for which this is the instruction iterator. After this method returns it
-   * will be positioned just after the basic block returned.
+   * will be positioned just after the basic block returned. Calling {@link #remove} without
+   * further navigation will remove that block.
    * @return Returns the new block with the instructions after the cursor.
    */
   BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator);
@@ -118,9 +119,9 @@
    * @param code the IR code for the block this iterator originates from.
    * @param blockIterator basic block iterator used to iterate the blocks. This must be positioned
    * just after the block for this is the instruction iterator. After this method returns it will be
-   * positioned just after the second block inserted. That is after the successor of the block
-   * returned.
-   * @return Returns the new block with the instructions right after the cursor.
+   * positioned just after the second block inserted. Calling {@link #remove} without further
+   * navigation will remove that block.
+   * @return Returns the new block with the instructions after the cursor.
    */
   // TODO(sgjesse): Refactor to avoid the need for passing code and blockIterator.
   BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blockIterator);
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 52a0f55..770278a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -37,7 +37,7 @@
       int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
       int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
       int result = foldIntegers(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else {
       assert type == NumericType.LONG;
@@ -50,7 +50,7 @@
         right = rightValue().getConstInstruction().asConstNumber().getLongValue();
       }
       long result = foldLongs(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 59022de..f858d70 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -31,20 +31,20 @@
     assert canBeFolded();
     if (type == NumericType.INT) {
       int result = -source().getConstInstruction().asConstNumber().getIntValue();
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else if (type == NumericType.LONG) {
       long result = -source().getConstInstruction().asConstNumber().getLongValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     } else if (type == NumericType.FLOAT) {
       float result = -source().getConstInstruction().asConstNumber().getFloatValue();
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
     } else {
       assert type == NumericType.DOUBLE;
       double result = -source().getConstInstruction().asConstNumber().getDoubleValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index cae6118..4c42b80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -27,12 +27,12 @@
     assert canBeFolded();
     if (type == NumericType.INT) {
       int result = ~(source().getConstInstruction().asConstNumber().getIntValue());
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else {
       assert type == NumericType.LONG;
       long result = ~source().getConstInstruction().asConstNumber().getLongValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 7e05dc9..96069bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -10,16 +10,19 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 public class Phi extends Value {
 
   private final BasicBlock block;
   private final List<Value> operands = new ArrayList<>();
+  private Set<Value> debugValues = null;
 
   // Trivial phis are eliminated during IR construction. When a trivial phi is eliminated
   // we need to update all references to it. A phi can be referenced from phis, instructions
@@ -36,7 +39,7 @@
   private MoveType outType = null;
 
   public Phi(int number, BasicBlock block, MoveType type, DebugLocalInfo local) {
-    super(number, type, local == null ? null : new DebugInfo(local, null));
+    super(number, type, local);
     this.block = block;
     block.addPhi(this);
   }
@@ -96,6 +99,15 @@
     removeTrivialPhi();
   }
 
+  public void addDebugValue(Value value) {
+    assert value.getLocalInfo() != null;
+    if (debugValues == null) {
+      debugValues = new HashSet<>();
+    }
+    debugValues.add(value);
+    value.addDebugPhiUser(this);
+  }
+
   private void throwUndefinedValueError() {
     throw new CompilationError(
         "Undefined value encountered during compilation. "
@@ -143,20 +155,25 @@
     current.removePhiUser(this);
   }
 
-  // Removing the phi user from the current value leads to concurrent modification errors
-  // during trivial phi elimination. It is safe to not remove the phi user from current
-  // since current will be unreachable after trivial phi elimination.
-  // TODO(ager): can we unify the these replace methods and avoid the concurrent modification
-  // issue?
-  private void replaceTrivialPhi(Value current, Value newValue) {
+  void replaceTrivialPhi(Value current, Value newValue, List<Phi> toRemove) {
     for (int i = 0; i < operands.size(); i++) {
       if (operands.get(i) == current) {
         operands.set(i, newValue);
         newValue.addPhiUser(this);
+        toRemove.add(this);
       }
     }
   }
 
+  void replaceTrivialDebugPhi(Value current, Value newValue, List<Phi> toRemove) {
+    assert current.getLocalInfo() != null;
+    assert current.getLocalInfo() == newValue.getLocalInfo();
+    if (debugValues.remove(current)) {
+      addDebugValue(newValue);
+      toRemove.add(this);
+    }
+  }
+
   public boolean isTrivialPhi() {
     Value same = null;
     for (Value op : operands) {
@@ -188,28 +205,17 @@
       same = op;
     }
     assert isTrivialPhi();
+    assert same != null : "ill-defined phi";
     // Removing this phi, so get rid of it as a phi user from all of the operands to avoid
     // recursively getting back here with the same phi. If the phi has itself as an operand
     // that also removes the self-reference.
     for (Value op : operands) {
       op.removePhiUser(this);
     }
-    // Replace this phi with the unique value in all users.
-    for (Instruction user : uniqueUsers()) {
-      user.replaceValue(this, same);
-    }
-    for (Phi user : uniquePhiUsers()) {
-      user.replaceTrivialPhi(this, same);
-    }
-    if (debugUsers() != null) {
-      for (Instruction user : debugUsers()) {
-        user.replaceDebugPhi(this, same);
-      }
-    }
     // If IR construction is taking place, update the definition users.
     if (definitionUsers != null) {
       for (Map<Integer, Value> user : definitionUsers) {
-        for (Map.Entry<Integer, Value> entry : user.entrySet()) {
+        for (Entry<Integer, Value> entry : user.entrySet()) {
           if (entry.getValue() == this) {
             entry.setValue(same);
             if (same.isPhi()) {
@@ -219,9 +225,14 @@
         }
       }
     }
-    // Try to simplify phi users that might now have become trivial.
-    for (Phi user : uniquePhiUsers()) {
-      user.removeTrivialPhi();
+    {
+      Set<Phi> phiUsersToSimplify = uniquePhiUsers();
+      // Replace this phi with the unique value in all users.
+      replaceInUsers(same);
+      // Try to simplify phi users that might now have become trivial.
+      for (Phi user : phiUsersToSimplify) {
+        user.removeTrivialPhi();
+      }
     }
     // Get rid of the phi itself.
     block.removePhi(this);
@@ -316,4 +327,8 @@
   public boolean needsRegister() {
     return true;
   }
+
+  public Set<Value> getDebugValues() {
+    return debugValues != null ? debugValues : ImmutableSet.of();
+  }
 }
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 6b47ba2..6def63a 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
@@ -12,6 +12,8 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
+import com.google.common.primitives.Ints;
+import java.util.List;
 
 public class Switch extends JumpInstruction {
 
@@ -48,29 +50,73 @@
   }
 
   // Number of targets if this switch is emitted as a packed switch.
-  private long numberOfTargetsIfPacked() {
+  private static long numberOfTargetsIfPacked(int keys[]) {
     return ((long) keys[keys.length - 1]) - ((long) keys[0]) + 1;
   }
 
-  private boolean canBePacked() {
+  public static boolean canBePacked(int keys[]) {
     // The size of a switch payload is stored in an ushort in the Dex file.
-    return numberOfTargetsIfPacked() <= Constants.U16BIT_MAX;
+    return numberOfTargetsIfPacked(keys) <= Constants.U16BIT_MAX;
+  }
+
+  // Number of targets if this switch is emitted as a packed switch.
+  public static int numberOfTargetsForPacked(int keys[]) {
+    assert canBePacked(keys);
+    return (int) numberOfTargetsIfPacked(keys);
+  }
+
+  // Size of the switch payload if emitted as packed (in code units).
+  static public long packedPayloadSize(int keys[]) {
+    return (numberOfTargetsForPacked(keys) * 2) + 4;
+  }
+
+  // Size of the switch payload if emitted as sparse (in code units).
+  public static long sparsePayloadSize(int keys[]) {
+    return (keys.length * 4) + 2;
+  }
+
+  /**
+   * Size of the switch payload instruction for the given keys. This will be the payload
+   * size for the smallest encoding of the provided keys.
+   *
+   * @param keys the switch keys
+   * @return Size of the switch payload instruction in code units
+   */
+  public static long payloadSize(List<Integer> keys) {
+    return payloadSize(Ints.toArray(keys));
+  }
+
+  /**
+   * Size of the switch payload instruction for the given keys.
+   *
+   * @see #payloadSize(List)
+   */
+  public static long payloadSize(int keys[]) {
+    long sparse = sparsePayloadSize(keys);
+    if (canBePacked(keys)) {
+      return Math.min(sparse, packedPayloadSize(keys));
+    } else {
+      return sparse;
+    }
+  }
+
+  private boolean canBePacked() {
+    return canBePacked(keys);
   }
 
   // Number of targets if this switch is emitted as a packed switch.
   private int numberOfTargetsForPacked() {
-    assert canBePacked();
-    return (int) numberOfTargetsIfPacked();
+    return numberOfTargetsForPacked(keys);
   }
 
   // Size of the switch payload if emitted as packed (in code units).
   private long packedPayloadSize() {
-    return (numberOfTargetsForPacked() * 2) + 4;
+    return packedPayloadSize(keys);
   }
 
   // Size of the switch payload if emitted as sparse (in code units).
   private long sparsePayloadSize() {
-    return (keys.length * 4) + 2;
+    return sparsePayloadSize(keys);
   }
 
   private boolean emitPacked() {
@@ -121,6 +167,10 @@
     return keys[index];
   }
 
+  public int[] getKeys() {
+    return keys;
+  }
+
   public int[] targetBlockIndices() {
     return targetBlockIndices;
   }
@@ -192,6 +242,7 @@
     StringBuilder builder = new StringBuilder(super.toString()+ "\n");
     for (int i = 0; i < numberOfKeys(); i++) {
       builder.append("          ");
+      builder.append(getKey(i));
       builder.append(" -> ");
       builder.append(targetBlock(i).getNumber());
       builder.append("\n");
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 44bd9d4..dfa8548 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -18,39 +18,17 @@
 
 public class Value {
 
-  /**
-   * Immutable view of the debug info associated with an SSA value.
-   *
-   * Used during IR building and to construct replacement values.
-   */
-  public static class DebugInfo {
-    private final DebugLocalInfo local;
-    private final Value previousLocalValue;
-
-    public DebugInfo(DebugLocalInfo local, Value previousLocalValue) {
-      assert local != null;
-      this.local = local;
-      this.previousLocalValue = previousLocalValue;
-    }
-  }
-
-  // Actual internal data for the debug information of locals.
+  // Lazily allocated internal data for the debug information of locals.
   // This is wrapped in a class to avoid multiple pointers in the value structure.
   private static class DebugData {
     final DebugLocalInfo local;
-    Value previousLocalValue;
-    Set<Instruction> debugUsers = new HashSet<>();
+    Set<Instruction> users = new HashSet<>();
+    Set<Phi> phiUsers = new HashSet<>();
     List<Instruction> localStarts = new ArrayList<>();
     List<Instruction> localEnds = new ArrayList<>();
 
-    DebugData(DebugInfo info) {
-      this(info.local, info.previousLocalValue);
-    }
-
-    DebugData(DebugLocalInfo local, Value previousLocalValue) {
-      assert previousLocalValue == null || !previousLocalValue.isUninitializedLocal();
+    DebugData(DebugLocalInfo local) {
       this.local = local;
-      this.previousLocalValue = previousLocalValue;
     }
   }
 
@@ -70,13 +48,14 @@
   private boolean neverNull = false;
   private boolean isThis = false;
   private boolean isArgument = false;
+  private boolean knownToBeBoolean = false;
   private LongInterval valueRange;
   private final DebugData debugData;
 
-  public Value(int number, MoveType type, DebugInfo debugInfo) {
+  public Value(int number, MoveType type, DebugLocalInfo local) {
     this.number = number;
     this.type = type;
-    this.debugData = debugInfo == null ? null : new DebugData(debugInfo);
+    this.debugData = local == null ? null : new DebugData(local);
   }
 
   public boolean isFixedRegisterValue() {
@@ -95,26 +74,10 @@
     return type.requiredRegisters();
   }
 
-  public DebugInfo getDebugInfo() {
-    return debugData == null ? null : new DebugInfo(debugData.local, debugData.previousLocalValue);
-  }
-
   public DebugLocalInfo getLocalInfo() {
     return debugData == null ? null : debugData.local;
   }
 
-  public Value getPreviousLocalValue() {
-    return debugData == null ? null : debugData.previousLocalValue;
-  }
-
-  public void replacePreviousLocalValue(Value value) {
-    if (value == null || value.isUninitializedLocal()) {
-      debugData.previousLocalValue = null;
-    } else {
-      debugData.previousLocalValue = value;
-    }
-  }
-
   public List<Instruction> getDebugLocalStarts() {
     return debugData.localStarts;
   }
@@ -189,10 +152,11 @@
   }
 
   public Set<Instruction> debugUsers() {
-    if (debugData == null) {
-      return null;
-    }
-    return Collections.unmodifiableSet(debugData.debugUsers);
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
+  }
+
+  public Set<Phi> debugPhiUsers() {
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.phiUsers);
   }
 
   public int numberOfUsers() {
@@ -211,18 +175,30 @@
     return uniquePhiUsers().size();
   }
 
+  public int numberOfAllNonDebugUsers() {
+    return numberOfUsers() + numberOfPhiUsers();
+  }
+
   public int numberOfDebugUsers() {
-    return debugData == null ? 0 : debugData.debugUsers.size();
+    return debugData == null ? 0 : debugData.users.size();
+  }
+
+  public int numberOfDebugPhiUsers() {
+    return debugData == null ? 0 : debugData.phiUsers.size();
+  }
+
+  public int numberOfAllDebugUsers() {
+    return numberOfDebugUsers() + numberOfDebugPhiUsers();
   }
 
   public int numberOfAllUsers() {
-    return numberOfUsers() + numberOfPhiUsers() + numberOfDebugUsers();
+    return numberOfAllNonDebugUsers() + numberOfAllDebugUsers();
   }
 
   public boolean isUsed() {
     return !users.isEmpty()
         || !phiUsers.isEmpty()
-        || ((debugData != null) && !debugData.debugUsers.isEmpty());
+        || ((debugData != null) && !debugData.users.isEmpty());
   }
 
   public boolean usedInMonitorOperation() {
@@ -250,7 +226,7 @@
     phiUsers.clear();
     uniquePhiUsers = null;
     if (debugData != null) {
-      debugData.debugUsers.clear();
+      debugData.users.clear();
     }
   }
 
@@ -268,7 +244,14 @@
     if (isUninitializedLocal()) {
       return;
     }
-    debugData.debugUsers.add(user);
+    debugData.users.add(user);
+  }
+
+  public void addDebugPhiUser(Phi user) {
+    if (isUninitializedLocal()) {
+      return;
+    }
+    debugData.phiUsers.add(user);
   }
 
   public boolean isUninitializedLocal() {
@@ -280,7 +263,11 @@
   }
 
   public void removeDebugUser(Instruction user) {
-    debugData.debugUsers.remove(user);
+    debugData.users.remove(user);
+  }
+
+  public void removeDebugPhiUser(Phi user) {
+    debugData.phiUsers.remove(user);
   }
 
   public boolean hasUsersInfo() {
@@ -293,7 +280,8 @@
     phiUsers = null;
     uniquePhiUsers = null;
     if (debugData != null) {
-      debugData.debugUsers = null;
+      debugData.users = null;
+      debugData.phiUsers = null;
     }
   }
 
@@ -321,22 +309,52 @@
     }
     if (debugData != null) {
       for (Instruction user : debugUsers()) {
-        user.getDebugValues().replaceAll(v -> {
-          if (v == this) {
-            newValue.addDebugUser(user);
-            return newValue;
-          }
-          return v;
-        });
-        if (user.getPreviousLocalValue() == this) {
-          newValue.addDebugUser(user);
-          user.replacePreviousLocalValue(newValue);
+        if (user.getDebugValues().remove(this)) {
+          user.addDebugValue(newValue);
+        }
+      }
+      for (Phi user : debugPhiUsers()) {
+        if (user.getDebugValues().remove(this)) {
+          user.addDebugValue(newValue);
         }
       }
     }
     clearUsers();
   }
 
+  public void replaceInUsers(Value newValue) {
+    if (!uniqueUsers().isEmpty()) {
+      List<Instruction> toRemove = new ArrayList<>(uniqueUsers().size());
+      for (Instruction user : uniqueUsers()) {
+        user.replaceValue(this, newValue, toRemove);
+      }
+      toRemove.forEach(this::removeUser);
+    }
+    if (!uniquePhiUsers().isEmpty()) {
+      List<Phi> toRemove = new ArrayList<>(uniquePhiUsers().size());
+      for (Phi user : uniquePhiUsers()) {
+        user.replaceTrivialPhi(this, newValue, toRemove);
+      }
+      toRemove.forEach(this::removePhiUser);
+    }
+    if (debugData != null) {
+      if (!debugUsers().isEmpty()) {
+        List<Instruction> toRemove = new ArrayList<>(debugUsers().size());
+        for (Instruction user : debugUsers()) {
+          user.replaceDebugValue(this, newValue, toRemove);
+        }
+        toRemove.forEach(this::removeDebugUser);
+      }
+      if (!debugPhiUsers().isEmpty()) {
+        List<Phi> toRemove = new ArrayList<>(debugPhiUsers().size());
+        for (Phi user : debugPhiUsers()) {
+          user.replaceTrivialDebugPhi(this, newValue, toRemove);
+        }
+        toRemove.forEach(this::removeDebugPhiUser);
+      }
+    }
+  }
+
   public void setLiveIntervals(LiveIntervals intervals) {
     assert liveIntervals == null;
     liveIntervals = intervals;
@@ -477,6 +495,14 @@
     return isArgument;
   }
 
+  public void setKnownToBeBoolean(boolean knownToBeBoolean) {
+    this.knownToBeBoolean = knownToBeBoolean;
+  }
+
+  public boolean knownToBeBoolean() {
+    return knownToBeBoolean;
+  }
+
   public void markAsThis() {
     assert isArgument;
     assert !isThis;
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 4b7de62..e10c690 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
@@ -76,7 +76,6 @@
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Ushr;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.utils.InternalOptions;
@@ -270,6 +269,7 @@
   private final InternalOptions options;
 
   // Pending local changes.
+  private Value previousLocalValue = null;
   private List<Value> debugLocalStarts = new ArrayList<>();
   private List<Value> debugLocalReads = new ArrayList<>();
   private List<Value> debugLocalEnds = new ArrayList<>();
@@ -490,16 +490,21 @@
 
   public void addThisArgument(int register) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = local == null ? null : new DebugInfo(local, null);
-    Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, info);
+    Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
     value.markAsThis();
   }
 
   public void addNonThisArgument(int register, MoveType moveType) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = local == null ? null : new DebugInfo(local, null);
-    Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, info);
+    Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, local);
+    addInstruction(new Argument(value));
+  }
+
+  public void addBooleanNonThisArgument(int register) {
+    DebugLocalInfo local = getCurrentLocal(register);
+    Value value = writeRegister(register, MoveType.SINGLE, ThrowingInfo.NO_THROW, local);
+    value.setKnownToBeBoolean(true);
     addInstruction(new Argument(value));
   }
 
@@ -616,6 +621,7 @@
     Value in1 = readRegister(array, MoveType.OBJECT);
     Value in2 = readRegister(index, MoveType.SINGLE);
     Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+    out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     ArrayGet instruction = new ArrayGet(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow();
     add(instruction);
@@ -886,6 +892,7 @@
       DexField field) {
     Value in = readRegister(object, MoveType.OBJECT);
     Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+    out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     InstanceGet instruction = new InstanceGet(type, out, in, field);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1104,7 +1111,7 @@
 
   public void addMoveException(int dest) {
     Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.NO_THROW);
-    assert out.getDebugInfo() == null;
+    assert out.getLocalInfo() == null;
     MoveException instruction = new MoveException(out);
     assert !instruction.instructionTypeCanThrow();
     if (!currentBlock.getInstructions().isEmpty()) {
@@ -1124,6 +1131,16 @@
     invoke.setOutValue(writeRegister(dest, type, ThrowingInfo.CAN_THROW));
   }
 
+  public void addBooleanMoveResult(MoveType type, int dest) {
+    List<Instruction> instructions = currentBlock.getInstructions();
+    Invoke invoke = instructions.get(instructions.size() - 1).asInvoke();
+    assert invoke.outValue() == null;
+    assert invoke.instructionTypeCanThrow();
+    Value outValue = writeRegister(dest, type, ThrowingInfo.CAN_THROW);
+    outValue.setKnownToBeBoolean(true);
+    invoke.setOutValue(outValue);
+  }
+
   public void addNeg(NumericType type, int dest, int value) {
     Value in = readNumericRegister(value, type);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
@@ -1175,6 +1192,7 @@
 
   public void addStaticGet(MemberType type, int dest, DexField field) {
     Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+    out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     StaticGet instruction = new StaticGet(type, out, field);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1509,21 +1527,18 @@
 
   // This special write register is needed when changing the scoping of a local variable.
   // See addDebugLocalStart and addDebugLocalEnd.
-  private Value writeRegister(int register, MoveType type, ThrowingInfo throwing, DebugInfo info) {
+  private Value writeRegister(
+      int register, MoveType type, ThrowingInfo throwing, DebugLocalInfo local) {
     checkRegister(register);
-    Value value = new Value(valueNumberGenerator.next(), type, info);
+    Value value = new Value(valueNumberGenerator.next(), type, local);
     currentBlock.writeCurrentDefinition(register, value, throwing);
     return value;
   }
 
   public Value writeRegister(int register, MoveType type, ThrowingInfo throwing) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = null;
-    if (local != null) {
-      Value previousLocal = readRegisterIgnoreLocal(register, type);
-      info = new DebugInfo(local, previousLocal.getLocalInfo() != local ? null : previousLocal);
-    }
-    return writeRegister(register, type, throwing, info);
+    previousLocalValue = local == null ? null : readRegisterIgnoreLocal(register, type);
+    return writeRegister(register, type, throwing, local);
   }
 
   public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
@@ -1604,6 +1619,12 @@
     if (!options.debug) {
       return;
     }
+    // Add a use if this instruction is overwriting a previous value of the same local.
+    if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) {
+      assert ir.outValue() != null;
+      ir.addDebugValue(previousLocalValue);
+    }
+    previousLocalValue = null;
     if (debugLocalStarts.isEmpty() && debugLocalReads.isEmpty() && debugLocalEnds.isEmpty()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 116a2f0..668e220 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.Descriptor;
@@ -261,7 +262,8 @@
   @Override
   public void buildPrelude(IRBuilder builder) {
     // Record types for arguments.
-    Map<Integer, MoveType> initializedLocals = recordArgumentTypes();
+    Int2ReferenceMap<MoveType> argumentLocals = recordArgumentTypes();
+    Int2ReferenceMap<MoveType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
     // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
@@ -295,7 +297,11 @@
     }
     // Add debug information for all locals at the initial label.
     if (initialLabel != null) {
-      state.openLocals(initialLabel);
+      for (Local local : state.openLocals(initialLabel)) {
+        if (!argumentLocals.containsKey(local.slot.register)) {
+          builder.addDebugLocalStart(local.slot.register, local.info);
+        }
+      }
     }
     // Build the actual argument instructions now that type and debug information is known
     // for arguments.
@@ -332,7 +338,11 @@
     for (Type type : parameterTypes) {
       MoveType moveType = moveType(type);
       Slot slot = state.readLocal(argumentRegister, type);
-      builder.addNonThisArgument(slot.register, moveType);
+      if (type == Type.BOOLEAN_TYPE) {
+        builder.addBooleanNonThisArgument(slot.register);
+      } else {
+        builder.addNonThisArgument(slot.register, moveType);
+      }
       argumentRegister += moveType.requiredRegisters();
     }
   }
@@ -1731,9 +1741,10 @@
       state.push(Type.DOUBLE_TYPE);
     } else if (insn.cst instanceof Integer) {
       state.push(Type.INT_TYPE);
-    } else {
-      assert insn.cst instanceof Float;
+    } else if (insn.cst instanceof Float) {
       state.push(Type.FLOAT_TYPE);
+    } else {
+      throw new CompilationError("Unsupported constant: " + insn.cst.toString());
     }
   }
 
@@ -2549,7 +2560,11 @@
     // Move the result to the "top of stack".
     Type returnType = Type.getReturnType(methodDesc);
     if (returnType != Type.VOID_TYPE) {
-      builder.addMoveResult(moveType(returnType), state.push(returnType));
+      if (returnType == Type.BOOLEAN_TYPE) {
+        builder.addBooleanMoveResult(moveType(returnType), state.push(returnType));
+      } else {
+        builder.addMoveResult(moveType(returnType), state.push(returnType));
+      }
     }
   }
 
@@ -2721,10 +2736,11 @@
     } else if (insn.cst instanceof Integer) {
       int dest = state.push(Type.INT_TYPE);
       builder.addIntConst(dest, (Integer) insn.cst);
-    } else {
-      assert insn.cst instanceof Float;
+    } else if (insn.cst instanceof Float) {
       int dest = state.push(Type.FLOAT_TYPE);
       builder.addFloatConst(dest, Float.floatToRawIntBits((Float) insn.cst));
+    } else {
+      throw new CompilationError("Unsupported constant: " + insn.cst.toString());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index a7c40d1..71de2b6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -51,7 +51,7 @@
     if (insn.outValue() == null) {
       return null;
     } else {
-      return code.createValue(insn.outType(), insn.getDebugInfo());
+      return code.createValue(insn.outType(), insn.getLocalInfo());
     }
   }
 
@@ -105,7 +105,7 @@
             // Fix up the return type if needed.
             if (actualTarget.proto.returnType != invokedMethod.proto.returnType
                 && newInvoke.outValue() != null) {
-              Value newValue = code.createValue(newInvoke.outType(), invoke.getDebugInfo());
+              Value newValue = code.createValue(newInvoke.outType(), invoke.getLocalInfo());
               newInvoke.outValue().replaceUsers(newValue);
               CheckCast cast = new CheckCast(
                   newValue,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index bfaee32..a4de54f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -134,6 +134,11 @@
     }
 
     if (b.isPrimitiveType()) {
+      if (a == factory.objectType) {
+        // `a` is java.lang.Object in which case we assume it represented by
+        // proper boxed type.
+        return true;
+      }
       // `a` is a boxed type for `a*` which can be
       // widened to primitive type `b`.
       DexType unboxedA = getPrimitiveFromBoxed(a);
@@ -313,9 +318,16 @@
     // type, the value must be unboxed and converted to the resulting type via primitive
     // widening conversion.
     if (toTypePrimitive) {
-      DexType fromTypeAsPrimitive = getPrimitiveFromBoxed(fromType);
+      DexType boxedType = fromType;
+      if (boxedType == factory().objectType) {
+        // We are in situation when from(=java.lang.Object) is being adjusted to a
+        // primitive type, in which case we assume it is of proper box type.
+        boxedType = getBoxedForPrimitiveType(toType);
+        register = castToBoxedType(register, boxedType);
+      }
+      DexType fromTypeAsPrimitive = getPrimitiveFromBoxed(boxedType);
       if (fromTypeAsPrimitive != null) {
-        int unboxedRegister = addPrimitiveUnboxing(register, fromTypeAsPrimitive, fromType);
+        int unboxedRegister = addPrimitiveUnboxing(register, fromTypeAsPrimitive, boxedType);
         return addPrimitiveWideningConversion(unboxedRegister, fromTypeAsPrimitive, toType);
       }
     }
@@ -461,6 +473,11 @@
     return result;
   }
 
+  private int castToBoxedType(int register, DexType boxType) {
+    add(builder -> builder.addCheckCast(register, boxType));
+    return register;
+  }
+
   private int addPrimitiveBoxing(int register, DexType primitiveType, DexType boxType) {
     // Generate factory method fo boxing.
     DexItemFactory factory = factory();
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 815efe4..d44ecd9 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
@@ -37,6 +37,7 @@
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.ConstType;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
@@ -63,6 +64,7 @@
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
@@ -72,8 +74,13 @@
 import com.google.common.collect.Maps;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -336,8 +343,128 @@
     }
   }
 
+  // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
+  public static class SwitchBuilder {
+    private Value value;
+    private Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
+    private BasicBlock fallthrough;
+    private int blockNumber;
+
+    public SwitchBuilder setValue(Value value) {
+      this.value = value;
+      return  this;
+    }
+
+    public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) {
+      keyToTarget.put(key, target);
+      return this;
+    }
+
+    public SwitchBuilder setFallthrough(BasicBlock fallthrough) {
+      this.fallthrough = fallthrough;
+      return this;
+    }
+
+    public SwitchBuilder setBlockNumber(int blockNumber) {
+      this.blockNumber = blockNumber;
+      return  this;
+    }
+
+    public BasicBlock build() {
+      final int NOT_FOUND = -1;
+      Object2IntMap<BasicBlock> targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>();
+      targetToSuccessorIndex.defaultReturnValue(NOT_FOUND);
+
+      int[] keys = new int[keyToTarget.size()];
+      int[] targetBlockIndices = new int[keyToTarget.size()];
+      // Sort keys descending.
+      int count = 0;
+      IntIterator iter = keyToTarget.keySet().iterator();
+      while (iter.hasNext()) {
+        int key = iter.nextInt();
+        BasicBlock target = keyToTarget.get(key);
+        Integer targetIndex =
+            targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size());
+        keys[count] = key;
+        targetBlockIndices[count] = targetIndex;
+        count++;
+      }
+      Integer fallthroughIndex =
+          targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size());
+      Switch newSwitch = new Switch(value, keys, targetBlockIndices, fallthroughIndex);
+      BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch);
+      for (BasicBlock successor : targetToSuccessorIndex.keySet()) {
+        newSwitchBlock.link(successor);
+      }
+      return newSwitchBlock;
+    }
+  }
+
+  /**
+   * Covert the switch instruction to a sequence of if instructions checking for a specified
+   * set of keys, followed by a new switch with the remaining keys.
+   */
+  private void convertSwitchToSwitchAndIfs(
+      IRCode code, ListIterator<BasicBlock> blocksIterator, BasicBlock originalBlock,
+      InstructionListIterator iterator, Switch theSwitch, List<Integer> keysToRemove) {
+    // Split the switch instruction into its own block and remove it.
+    iterator.previous();
+    BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator);
+    assert !originalSwitchBlock.hasCatchHandlers();
+    assert originalSwitchBlock.getInstructions().size() == 1;
+    blocksIterator.remove();
+
+    int nextBlockNumber = code.getHighestBlockNumber() + 1;
+
+    // Collect targets for the keys to peel off, and create a new switch instruction without
+    // these keys.
+    SwitchBuilder switchBuilder = new SwitchBuilder();
+    List<BasicBlock> peeledOffTargets = new ArrayList<>();
+    for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
+      BasicBlock target = theSwitch.targetBlock(i);
+      if (!keysToRemove.contains(theSwitch.getKey(i))) {
+        switchBuilder.addKeyAndTarget(theSwitch.getKey(i), theSwitch.targetBlock(i));
+      } else {
+        peeledOffTargets.add(target);
+      }
+    }
+    assert peeledOffTargets.size() == keysToRemove.size();
+    switchBuilder.setValue(theSwitch.value());
+    switchBuilder.setFallthrough(theSwitch.fallthroughBlock());
+    switchBuilder.setBlockNumber(nextBlockNumber++);
+    theSwitch.getBlock().detachAllSuccessors();
+    BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor();
+    assert theSwitch.getBlock().getPredecessors().size() == 0;
+    assert theSwitch.getBlock().getSuccessors().size() == 0;
+    assert block == originalBlock;
+
+    BasicBlock newSwitchBlock = switchBuilder.build();
+
+    // Create if blocks for each of the peeled off keys, and link them into the graph.
+    BasicBlock predecessor = originalBlock;
+    for (int i = 0; i < keysToRemove.size(); i++) {
+      int key = keysToRemove.get(i);
+      BasicBlock peeledOffTarget = peeledOffTargets.get(i);
+      ConstNumber keyConst = code.createIntConstant(key);
+      If theIf = new If(Type.EQ, ImmutableList.of(keyConst.dest(), theSwitch.value()));
+      BasicBlock ifBlock = BasicBlock.createIfBlock(nextBlockNumber++, theIf, keyConst);
+      predecessor.link(ifBlock);
+      ifBlock.link(peeledOffTarget);
+      predecessor = ifBlock;
+      blocksIterator.add(ifBlock);
+      assert !peeledOffTarget.getPredecessors().contains(theSwitch.getBlock());
+    }
+    predecessor.link(newSwitchBlock);
+    blocksIterator.add(newSwitchBlock);
+
+    // The switch fallthrough block is still the same, and it is right after the new switch block.
+    assert newSwitchBlock.exit().fallthroughBlock() == IteratorUtils.peekNext(blocksIterator);
+  }
+
   public void rewriteSwitch(IRCode code) {
-    for (BasicBlock block : code.blocks) {
+    ListIterator<BasicBlock> blocksIterator = code.listIterator();
+    while (blocksIterator.hasNext()) {
+      BasicBlock block = blocksIterator.next();
       InstructionListIterator iterator = block.listIterator();
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
@@ -361,6 +488,54 @@
               If theIf = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.dest()));
               iterator.replaceCurrentInstruction(theIf);
             }
+          } else {
+            // Split keys into outliers and sequences.
+            List<List<Integer>> sequences = new ArrayList<>();
+            List<Integer> outliers = new ArrayList<>();
+
+            List<Integer> current = new ArrayList<>();
+            int[] keys = theSwitch.getKeys();
+            int previousKey = keys[0];
+            current.add(previousKey);
+            for (int i = 1; i < keys.length; i++) {
+              assert current.size() > 0;
+              assert current.get(current.size() - 1) == previousKey;
+              int key = keys[i];
+              if (((long) key - (long) previousKey) > 1) {
+                if (current.size() == 1) {
+                  outliers.add(previousKey);
+                } else {
+                  sequences.add(current);;
+                }
+                current = new ArrayList<>();
+              }
+              current.add(key);
+              previousKey = key;
+            }
+            if (current.size() == 1) {
+              outliers.add(previousKey);
+            } else {
+              sequences.add(current);
+            }
+
+            // Only check for rewrite if there is one sequence and one or two outliers.
+            if (sequences.size() == 1 && outliers.size() <= 2) {
+              // Get the existing dex size for the payload (excluding the switch itself).
+              long currentSize = Switch.payloadSize(keys);
+              // Estimate the dex size of the rewritten payload and the additional if instructions.
+              long rewrittenSize = Switch.payloadSize(sequences.get(0));
+              for (Integer outlier : outliers) {
+                rewrittenSize += ConstNumber.estimatedDexSize(
+                    ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+                rewrittenSize += If.estimatedDexSize();
+              }
+              // Rewrite if smaller.
+              if (rewrittenSize < currentSize) {
+                convertSwitchToSwitchAndIfs(
+                    code, blocksIterator, block, iterator, theSwitch, outliers);
+                assert code.isConsistentSSA();
+              }
+            }
           }
         }
       }
@@ -797,6 +972,7 @@
     while (it.hasNext()) {
       Instruction current = it.next();
       if (current.isCheckCast()
+          && current.getLocalInfo() == null
           && current.outValue() != null && current.outValue().isUsed()
           && current.outValue().numberOfPhiUsers() == 0) {
         CheckCast checkCast = current.asCheckCast();
@@ -928,13 +1104,15 @@
             assert instruction.outValue().numberOfUsers() != 0;
             ConstNumber constNumber = instruction.asConstNumber();
             Value constantValue = instruction.outValue();
+            List<Instruction> toRemove = new ArrayList<>(constantValue.uniqueUsers().size());
             for (Instruction user : constantValue.uniqueUsers()) {
               ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
               InstructionListIterator iterator = user.getBlock().listIterator(user);
               iterator.previous();
               iterator.add(newCstNum);
-              user.replaceValue(constantValue, newCstNum.outValue());
+              user.replaceValue(constantValue, newCstNum.outValue(), toRemove);
             }
+            toRemove.forEach(constantValue::removeUser);
           }
         }
       } else {
@@ -1257,10 +1435,16 @@
         // First rewrite zero comparison.
         rewriteIfWithConstZero(block);
 
+        if (simplifyKnownBooleanCondition(dominator, block)) {
+          continue;
+        }
+
         // Simplify if conditions when possible.
         If theIf = block.exit().asIf();
         List<Value> inValues = theIf.inValues();
+
         int cond;
+
         if (inValues.get(0).isConstNumber()
             && (theIf.isZeroTest() || inValues.get(1).isConstNumber())) {
           // Zero test with a constant of comparison between between two constants.
@@ -1297,22 +1481,83 @@
         BasicBlock target = theIf.targetFromCondition(cond);
         BasicBlock deadTarget =
             target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
-        List<BasicBlock> removedBlocks = block.unlink(deadTarget, dominator);
-        for (BasicBlock removedBlock : removedBlocks) {
-          if (!removedBlock.isMarked()) {
-            removedBlock.mark();
-          }
-        }
-        assert theIf == block.exit();
-        block.replaceLastInstruction(new Goto());
-        assert block.exit().isGoto();
-        assert block.exit().asGoto().getTarget() == target;
+        rewriteIfToGoto(dominator, block, theIf, target, deadTarget);
       }
     }
     code.removeMarkedBlocks();
     assert code.isConsistentSSA();
   }
 
+
+  /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms:
+   *
+   *   ifeqz booleanValue       ifnez booleanValue
+   *      /        \              /        \
+   *      \        /              \        /
+   *      phi(0, 1)                phi(1, 0)
+   *
+   * which can be replaced by a fallthrough and the phi value can be replaced
+   * with the boolean value itself.
+   */
+  private boolean simplifyKnownBooleanCondition(DominatorTree dominator, BasicBlock block) {
+    If theIf = block.exit().asIf();
+    Value testValue = theIf.inValues().get(0);
+    if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
+      BasicBlock trueBlock = theIf.getTrueTarget();
+      BasicBlock falseBlock = theIf.fallthroughBlock();
+      if (trueBlock.isTrivialGoto() &&
+          falseBlock.isTrivialGoto() &&
+          trueBlock.getSuccessors().get(0) == falseBlock.getSuccessors().get(0)) {
+        BasicBlock targetBlock = trueBlock.getSuccessors().get(0);
+        if (targetBlock.getPredecessors().size() == 2) {
+          int trueIndex = targetBlock.getPredecessors().indexOf(trueBlock);
+          int falseIndex = trueIndex == 0 ? 1 : 0;
+          int deadPhis = 0;
+          // Locate the phis that have the same value as the boolean and replace them
+          // by the boolean in all users.
+          for (Phi phi : targetBlock.getPhis()) {
+            Value trueValue = phi.getOperand(trueIndex);
+            Value falseValue = phi.getOperand(falseIndex);
+            if (trueValue.isConstNumber() && falseValue.isConstNumber()) {
+              ConstNumber trueNumber = trueValue.getConstInstruction().asConstNumber();
+              ConstNumber falseNumber = falseValue.getConstInstruction().asConstNumber();
+              if ((theIf.getType() == Type.EQ &&
+                  trueNumber.isIntegerZero() &&
+                  falseNumber.isIntegerOne()) ||
+                  (theIf.getType() == Type.NE &&
+                      trueNumber.isIntegerOne() &&
+                      falseNumber.isIntegerZero())) {
+                phi.replaceUsers(testValue);
+                deadPhis++;
+              }
+            }
+          }
+          // If all phis were removed, there is no need for the diamond shape anymore
+          // and it can be rewritten to a goto to one of the branches.
+          if (deadPhis == targetBlock.getPhis().size()) {
+            rewriteIfToGoto(dominator, block, theIf, trueBlock, falseBlock);
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  private void rewriteIfToGoto(DominatorTree dominator, BasicBlock block, If theIf,
+      BasicBlock target, BasicBlock deadTarget) {
+    List<BasicBlock> removedBlocks = block.unlink(deadTarget, dominator);
+    for (BasicBlock removedBlock : removedBlocks) {
+      if (!removedBlock.isMarked()) {
+        removedBlock.mark();
+      }
+    }
+    assert theIf == block.exit();
+    block.replaceLastInstruction(new Goto());
+    assert block.exit().isGoto();
+    assert block.exit().asGoto().getTarget() == target;
+  }
+
   private void rewriteIfWithConstZero(BasicBlock block) {
     If theIf = block.exit().asIf();
     if (theIf.isZeroTest()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 3a1902e..50f224c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -61,10 +61,6 @@
     for (Value debugValue : instruction.getDebugValues()) {
       updateWorklist(worklist, debugValue);
     }
-    Value previousLocalValue = instruction.getPreviousLocalValue();
-    if (previousLocalValue != null) {
-      updateWorklist(worklist, previousLocalValue);
-    }
   }
 
   private static void removeDeadPhis(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index a34c8cb..241106c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -75,7 +75,7 @@
     MoveType moveType = instruction.outValue().outType();
     if (rule != null && rule.hasReturnValue() && rule.getReturnValue().isSingleValue()) {
       assert moveType != MoveType.OBJECT;
-      Value value = code.createValue(moveType, instruction.getDebugInfo());
+      Value value = code.createValue(moveType, instruction.getLocalInfo());
       replacement = new ConstNumber(
           ConstType.fromMoveType(moveType), value, rule.getReturnValue().getSingleValue());
     }
@@ -84,7 +84,7 @@
       DexField field = rule.getReturnValue().getField();
       DexEncodedField staticField = appInfo.lookupStaticTarget(field.clazz, field);
       if (staticField != null) {
-        Value value = code.createValue(moveType, instruction.getDebugInfo());
+        Value value = code.createValue(moveType, instruction.getLocalInfo());
         replacement = staticField.staticValue.asConstInstruction(false, value);
       } else {
         throw new CompilationError(field.clazz.toSourceString() + "." + field.name.toString() +
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 460fe92..6cd453b 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
@@ -1841,6 +1841,8 @@
         int predIndex = succ.getPredecessors().indexOf(block);
         for (Phi phi : succ.getPhis()) {
           live.add(phi.getOperand(predIndex));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          live.addAll(phi.getDebugValues());
         }
       }
       ListIterator<Instruction> iterator =
@@ -1855,17 +1857,8 @@
             live.add(use);
           }
         }
-        if (options.debug) {
-          for (Value use : instruction.getDebugValues()) {
-            assert use.needsRegister();
-            live.add(use);
-          }
-          Value use = instruction.getPreviousLocalValue();
-          if (use != null) {
-            assert use.needsRegister();
-            live.add(use);
-          }
-        }
+        assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
+        live.addAll(instruction.getDebugValues());
       }
       for (Phi phi : block.getPhis()) {
         live.remove(phi);
@@ -1942,6 +1935,8 @@
         for (Phi phi : successor.getPhis()) {
           live.remove(phi);
           phiOperands.add(phi.getOperand(successor.getPredecessors().indexOf(block)));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          phiOperands.addAll(phi.getDebugValues());
         }
       }
       live.addAll(phiOperands);
@@ -1990,14 +1985,6 @@
               addLiveRange(use, block, number);
             }
           }
-          Value use = instruction.getPreviousLocalValue();
-          if (use != null) {
-            assert use.needsRegister();
-            if (!live.contains(use)) {
-              live.add(use);
-              addLiveRange(use, block, number);
-            }
-          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index c1e8515..f4bd577 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -9,7 +9,10 @@
 
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
@@ -40,6 +43,8 @@
   private final AppInfoWithLiveness appInfo;
   private final RootSet rootSet;
   private final PackageObfuscationMode packageObfuscationMode;
+  private final boolean isAccessModificationAllowed;
+  private final Set<String> noObfuscationPrefixes = Sets.newHashSet();
   private final Set<String> usedPackagePrefixes = Sets.newHashSet();
   private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
 
@@ -63,6 +68,7 @@
     this.appInfo = appInfo;
     this.rootSet = rootSet;
     this.packageObfuscationMode = options.proguardConfiguration.getPackageObfuscationMode();
+    this.isAccessModificationAllowed = options.proguardConfiguration.isAccessModificationAllowed();
     this.packageDictionary = options.proguardConfiguration.getPackageObfuscationDictionary();
     this.classDictionary = options.proguardConfiguration.getClassObfuscationDictionary();
     this.keepInnerClassStructure = options.attributeRemoval.signature;
@@ -78,7 +84,7 @@
     // Collect names we have to keep.
     timing.begin("reserve");
     for (DexClass clazz : classes) {
-      if (rootSet.noObfuscation.contains(clazz)) {
+      if (rootSet.noObfuscation.contains(clazz.type)) {
         assert !renaming.containsKey(clazz.type);
         registerClassAsUsed(clazz.type);
       }
@@ -94,6 +100,12 @@
     }
     timing.end();
 
+    timing.begin("rename-dangling-types");
+    for (DexClass clazz : classes) {
+      renameDanglingTypes(clazz);
+    }
+    timing.end();
+
     timing.begin("rename-generic");
     renameTypesInGenericSignatures();
     timing.end();
@@ -105,6 +117,35 @@
     return Collections.unmodifiableMap(renaming);
   }
 
+  private void renameDanglingTypes(DexClass clazz) {
+    clazz.forEachMethod(this::renameDanglingTypesInMethod);
+    clazz.forEachField(this::renameDanglingTypesInField);
+  }
+
+  private void renameDanglingTypesInField(DexEncodedField field) {
+    renameDanglingType(field.field.type);
+  }
+
+  private void renameDanglingTypesInMethod(DexEncodedMethod method) {
+    DexProto proto = method.method.proto;
+    renameDanglingType(proto.returnType);
+    for (DexType type : proto.parameters.values) {
+      renameDanglingType(type);
+    }
+  }
+
+  private void renameDanglingType(DexType type) {
+    if (appInfo.wasPruned(type)
+        && !renaming.containsKey(type)
+        && !rootSet.noObfuscation.contains(type)) {
+      // We have a type that is defined in the program source but is only used in a proto or
+      // return type. As we don't need the class, we can rename it to anything as long as it is
+      // unique.
+      assert appInfo.definitionFor(type) == null;
+      renaming.put(type, topLevelState.nextTypeName());
+    }
+  }
+
   private void renameTypesInGenericSignatures() {
     for (DexClass clazz : appInfo.classes()) {
       rewriteGenericSignatures(clazz.annotations.annotations,
@@ -164,6 +205,11 @@
    * Registers the given package prefix and all of parent packages as used.
    */
   private void registerPackagePrefixesAsUsed(String packagePrefix) {
+    // If -allowaccessmodification is not set, we may keep classes in their original packages,
+    // accounting for package-private accesses.
+    if (!isAccessModificationAllowed) {
+      noObfuscationPrefixes.add(packagePrefix);
+    }
     String usedPrefix = packagePrefix;
     while (usedPrefix.length() > 0) {
       usedPackagePrefixes.add(usedPrefix);
@@ -209,7 +255,9 @@
   private Namespace getStateForClass(DexClass clazz) {
     String packageName = getPackageBinaryNameFromJavaType(clazz.type.getPackageDescriptor());
     // Check whether the given class should be kept.
-    if (rootSet.keepPackageName.contains(clazz)) {
+    // or check whether the given class belongs to a package that is kept for another class.
+    if (rootSet.keepPackageName.contains(clazz)
+        || noObfuscationPrefixes.contains(packageName)) {
       return states.computeIfAbsent(packageName, Namespace::new);
     }
     Namespace state = topLevelState;
@@ -320,6 +368,7 @@
       do {
         candidate = appInfo.dexItemFactory.createString(nextSuggestedNameForClass());
       } while (usedTypeNames.contains(candidate));
+      usedTypeNames.add(candidate);
       return candidate;
     }
 
@@ -342,6 +391,7 @@
       do {
         candidate = nextSuggestedNameForSubpackage();
       } while (usedPackagePrefixes.contains(candidate));
+      usedPackagePrefixes.add(candidate);
       return candidate;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 951790c..a26e135 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -36,10 +36,6 @@
 
   public NamingLens run(Timing timing) {
     assert !options.skipMinification;
-    // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
-    if (!options.proguardConfiguration.isAccessModificationAllowed()) {
-      throw new CompilationError("Minification requires allowaccessmodification.");
-    }
     timing.begin("MinifyClasses");
     Map<DexType, DexString> classRenaming =
         new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 524207e..ff8adee 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -927,13 +928,13 @@
   SortedSet<DexField> collectFieldsRead() {
     return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
         Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
-        collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
+            collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
   }
 
   SortedSet<DexField> collectFieldsWritten() {
     return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
         Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
-        collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
+            collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
   }
 
   private static class Action {
@@ -1099,6 +1100,10 @@
      * Map from the class of an extension to the state it produced.
      */
     public final Map<Class, Object> extensions;
+    /**
+     * A set of types that have been removed by the {@link TreePruner}.
+     */
+    public final Set<DexType> prunedTypes;
 
     private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
       super(appInfo);
@@ -1124,11 +1129,13 @@
       this.assumedValues = enqueuer.rootSet.assumedValues;
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
       this.extensions = enqueuer.extensionsState;
+      this.prunedTypes = Collections.emptySet();
       assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
       assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
     }
 
-    private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application) {
+    private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application,
+        Collection<DexType> removedClasses) {
       super(application);
       this.liveTypes = previous.liveTypes;
       this.instantiatedTypes = previous.instantiatedTypes;
@@ -1151,6 +1158,7 @@
       this.staticInvokes = previous.staticInvokes;
       this.extensions = previous.extensions;
       this.alwaysInline = previous.alwaysInline;
+      this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
       assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
       assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
     }
@@ -1178,6 +1186,7 @@
       this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
       this.alwaysInline = previous.alwaysInline;
       this.extensions = previous.extensions;
+      this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
       assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
       assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
     }
@@ -1209,6 +1218,13 @@
       return builder.build();
     }
 
+    private static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
+      ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+      builder.addAll(first);
+      builder.addAll(second);
+      return builder.build();
+    }
+
     @SuppressWarnings("unchecked")
     public <T> T getExtension(Class extension, T defaultValue) {
       if (extensions.containsKey(extension)) {
@@ -1237,14 +1253,23 @@
      * Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the
      * given DexApplication object.
      */
-    public AppInfoWithLiveness prunedCopyFrom(DexApplication application) {
-      return new AppInfoWithLiveness(this, application);
+    public AppInfoWithLiveness prunedCopyFrom(DexApplication application,
+        Collection<DexType> removedClasses) {
+      return new AppInfoWithLiveness(this, application, removedClasses);
     }
 
     public AppInfoWithLiveness rewrittenWithLense(GraphLense lense) {
       assert lense.isContextFree();
       return new AppInfoWithLiveness(this, lense);
     }
+
+    /**
+     * Returns true if the given type was part of the original program but has been removed during
+     * tree shaking.
+     */
+    public boolean wasPruned(DexType type) {
+      return prunedTypes.contains(type);
+    }
   }
 
   private static class SetWithReason<T> {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index e39faa0..f47adbd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -31,6 +31,7 @@
     private Path printUsageFile;
     private boolean printMapping;
     private Path printMappingFile;
+    private Path applyMappingFile = null;
     private boolean verbose = false;
     private final List<String> attributesRemovalPatterns = new ArrayList<>();
     private final Set<ProguardTypeMatcher> dontWarnPatterns = new HashSet<>();
@@ -104,6 +105,10 @@
       this.printMappingFile = file;
     }
 
+    public void setApplyMappingFile(Path file) {
+      this.applyMappingFile = file;
+    }
+
     public void setVerbose(boolean verbose) {
       this.verbose = verbose;
     }
@@ -155,6 +160,7 @@
           printUsageFile,
           printMapping,
           printMappingFile,
+          applyMappingFile,
           verbose,
           attributesRemovalPatterns,
           dontWarnPatterns,
@@ -180,6 +186,7 @@
   private final Path printUsageFile;
   private final boolean printMapping;
   private final Path printMappingFile;
+  private final Path applyMappingFile;
   private final boolean verbose;
   private final ImmutableList<String> attributesRemovalPatterns;
   private final ImmutableSet<ProguardTypeMatcher> dontWarnPatterns;
@@ -204,6 +211,7 @@
       Path printUsageFile,
       boolean printMapping,
       Path printMappingFile,
+      Path applyMappingFile,
       boolean verbose,
       List<String> attributesRemovalPatterns,
       Set<ProguardTypeMatcher> dontWarnPatterns,
@@ -226,6 +234,7 @@
     this.printUsageFile = printUsageFile;
     this.printMapping = printMapping;
     this.printMappingFile = printMappingFile;
+    this.applyMappingFile = applyMappingFile;
     this.verbose = verbose;
     this.attributesRemovalPatterns = ImmutableList.copyOf(attributesRemovalPatterns);
     this.dontWarnPatterns = ImmutableSet.copyOf(dontWarnPatterns);
@@ -280,6 +289,14 @@
     return printMappingFile;
   }
 
+  public boolean hasApplyMappingFile() {
+    return applyMappingFile != null;
+  }
+
+  public Path getApplyMappingFile() {
+    return applyMappingFile;
+  }
+
   public boolean isIgnoreWarnings() {
     return ignoreWarnings;
   }
@@ -347,7 +364,8 @@
           false                 /* printUsage */,
           null                  /* printUsageFile */,
           false                 /* printMapping */,
-          null                  /* outputMapping */,
+          null                  /* printMappingFile */,
+          null                  /* applyMapping */,
           false                 /* verbose */,
           ImmutableList.of()    /* attributesRemovalPatterns */,
           ImmutableSet.of()     /* dontWarnPatterns */,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 4fb7357..5292a45 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -219,6 +219,10 @@
         if (isOptionalArgumentGiven()) {
           configurationBuilder.setPrintMappingFile(parseFileName());
         }
+      } else if (acceptString("applymapping")) {
+        configurationBuilder.setApplyMappingFile(parseFileName());
+        // TODO(b/64802420): warn until it is fully implemented.
+        warnIgnoringOptions("applymapping");
       } else if (acceptString("assumenosideeffects")) {
         ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule();
         configurationBuilder.addRule(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 0fa5051..5e7b42d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -452,7 +452,7 @@
     dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
         .put(definition, context);
     // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
-    noObfuscation.add(definition);
+    noObfuscation.add(type);
   }
 
   private void includeDescriptorClasses(DexItem item, ProguardKeepRule context) {
@@ -487,7 +487,11 @@
         noOptimization.add(item);
       }
       if (!modifiers.allowsObfuscation) {
-        noObfuscation.add(item);
+        if (item instanceof DexClass) {
+          noObfuscation.add(((DexClass) item).type);
+        } else {
+          noObfuscation.add(item);
+        }
       }
       if (modifiers.includeDescriptorClasses) {
         includeDescriptorClasses(item, keepRule);
@@ -520,38 +524,24 @@
     public final Map<DexItem, ProguardMemberRule> assumedValues;
     private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
 
-    private boolean legalNoObfuscationItem(DexItem item) {
-      if (!(item instanceof DexProgramClass
-          || item instanceof DexLibraryClass
-          || item instanceof DexEncodedMethod
-          || item instanceof DexEncodedField)) {
-      }
-      assert item instanceof DexProgramClass
-          || item instanceof DexLibraryClass
-          || item instanceof DexEncodedMethod
-          || item instanceof DexEncodedField;
-      return true;
-    }
-
-    private boolean legalNoObfuscationItems(Set<DexItem> items) {
-      items.forEach(this::legalNoObfuscationItem);
-      return true;
-    }
-
-    private boolean legalDependentNoShrinkingItem(DexItem item) {
-      if (!(item instanceof DexType
-          || item instanceof DexEncodedMethod
-          || item instanceof DexEncodedField)) {
-      }
+    private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
       assert item instanceof DexType
           || item instanceof DexEncodedMethod
           || item instanceof DexEncodedField;
+      return item instanceof DexType
+          || item instanceof DexEncodedMethod
+          || item instanceof DexEncodedField;
+    }
+
+    private boolean legalNoObfuscationItems(Set<DexItem> items) {
+      assert items.stream().allMatch(this::isTypeEncodedMethodOrEncodedField);
       return true;
     }
 
     private boolean legalDependentNoShrinkingItems(
         Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
-      dependentNoShrinking.keySet().forEach(this::legalDependentNoShrinkingItem);
+      assert dependentNoShrinking.keySet().stream()
+          .allMatch(this::isTypeEncodedMethodOrEncodedField);
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index c04b29a..193ebdf 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -655,4 +655,8 @@
       return result;
     }
   }
+
+  public Collection<DexType> getRemovedClasses() {
+    return Collections.unmodifiableCollection(mergedClasses.keySet());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 64294bd..153f604 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,13 +8,17 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -23,7 +27,8 @@
   private DexApplication application;
   private final AppInfoWithLiveness appInfo;
   private final InternalOptions options;
-  private UsagePrinter usagePrinter;
+  private final UsagePrinter usagePrinter;
+  private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
 
   public TreePruner(
       DexApplication application, AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -64,6 +69,7 @@
         if (Log.ENABLED) {
           Log.debug(getClass(), "Removing class: " + clazz);
         }
+        prunedTypes.add(clazz.type);
         usagePrinter.printUnusedClass(clazz);
       } else {
         newClasses.add(clazz);
@@ -190,4 +196,8 @@
     }
     return reachableFields.toArray(new DexEncodedField[reachableFields.size()]);
   }
+
+  public Collection<DexType> getRemovedClasses() {
+    return Collections.unmodifiableCollection(prunedTypes);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 97a3679..4c6c56f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -586,6 +586,15 @@
     }
 
     /**
+     * Inform whether ProGuard map has already been set or not.
+     *
+     * ProGuard option -applymapping will override R8/Dissemble option -pg-map.
+     */
+    public boolean hasProguardMap() {
+      return proguardMap != null;
+    }
+
+    /**
      * Set proguard-map file.
      */
     public Builder setProguardMapFile(Path file) {
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 b03721f..9f3488c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -91,6 +91,7 @@
   public OutputMode outputMode = OutputMode.Indexed;
 
   public boolean useTreeShaking = true;
+  public boolean useDiscardedChecker = true;
 
   public boolean printCfg = false;
   public String printCfgFile;
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
new file mode 100644
index 0000000..3cc3245
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -0,0 +1,23 @@
+// 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.utils;
+
+import java.util.ListIterator;
+
+public class IteratorUtils {
+  public static <T> T peekPrevious(ListIterator<T> iterator) {
+    T previous = iterator.previous();
+    T next = iterator.next();
+    assert previous == next;
+    return previous;
+  }
+
+  public static <T> T peekNext(ListIterator<T> iterator) {
+    T next = iterator.next();
+    T previous = iterator.previous();
+    assert previous == next;
+    return next;
+  }
+}
diff --git a/src/test/examples/naming101/keep-rules-102.txt b/src/test/examples/naming101/keep-rules-102.txt
new file mode 100644
index 0000000..b1c0cda
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-102.txt
@@ -0,0 +1,13 @@
+# 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.
+
+-repackageclasses 'naming101.a'
+
+-keep public class **.a {
+  *;
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/naming101/keep-rules-104.txt b/src/test/examples/naming101/keep-rules-104.txt
new file mode 100644
index 0000000..6866dee
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-104.txt
@@ -0,0 +1,13 @@
+# 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.
+
+-flattenpackagehierarchy 'naming101'
+
+-keep public class **.a {
+  *;
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/shaking17/AbstractProgramClass.java b/src/test/examples/shaking17/AbstractProgramClass.java
new file mode 100644
index 0000000..d3b696b
--- /dev/null
+++ b/src/test/examples/shaking17/AbstractProgramClass.java
@@ -0,0 +1,9 @@
+// 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 shaking17;
+
+public abstract class AbstractProgramClass extends shakinglib.AbstractLibraryClass {
+
+  public abstract int abstractMethod();
+}
diff --git a/src/test/examples/shaking17/Shaking.java b/src/test/examples/shaking17/Shaking.java
new file mode 100644
index 0000000..78cc495
--- /dev/null
+++ b/src/test/examples/shaking17/Shaking.java
@@ -0,0 +1,16 @@
+// 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 shaking17;
+
+public class Shaking {
+
+  public static void main(String[] args) {
+    Subclass subclass = new Subclass();
+    callTheMethod(subclass);
+  }
+
+  private static void callTheMethod(AbstractProgramClass abstractProgramClass) {
+    System.out.print(abstractProgramClass.abstractMethod());
+  }
+}
diff --git a/src/test/examples/shaking17/Subclass.java b/src/test/examples/shaking17/Subclass.java
new file mode 100644
index 0000000..237dca9
--- /dev/null
+++ b/src/test/examples/shaking17/Subclass.java
@@ -0,0 +1,12 @@
+// 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 shaking17;
+
+public class Subclass extends AbstractProgramClass {
+
+  @Override
+  public int abstractMethod() {
+    return 42;
+  }
+}
diff --git a/src/test/examples/shaking17/keep-rules.txt b/src/test/examples/shaking17/keep-rules.txt
new file mode 100644
index 0000000..27dd8f3
--- /dev/null
+++ b/src/test/examples/shaking17/keep-rules.txt
@@ -0,0 +1,9 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class shaking17.Shaking {
+  public static void main(...);
+}
diff --git a/src/test/examples/shakinglib/AbstractLibraryClass.java b/src/test/examples/shakinglib/AbstractLibraryClass.java
new file mode 100644
index 0000000..614b53e
--- /dev/null
+++ b/src/test/examples/shakinglib/AbstractLibraryClass.java
@@ -0,0 +1,9 @@
+// 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 shakinglib;
+
+public abstract class AbstractLibraryClass {
+
+  public abstract int abstractMethod();
+}
diff --git a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
index b9d2752..18f8d64 100644
--- a/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
+++ b/src/test/examplesAndroidO/lambdadesugaring/ValueAdjustments.java
@@ -13,6 +13,10 @@
         int i, Integer I, long l, Long L, float f, Float F, double d, Double D);
   }
 
+  interface it<T> {
+    T t();
+  }
+
   interface iz {
     boolean f();
   }
@@ -135,6 +139,11 @@
 
   private static void checkDouble(StringBuffer builder) {
     builder
+        .append(((id) new it<Double>() {
+          @Override public Double t() {
+            return (double) (Integer.MAX_VALUE) + 1;
+          }
+        }::t).f()).append(' ')
         .append(((id) ValueAdjustments::b).f()).append(' ')
         .append(((id) ValueAdjustments::B).f()).append(' ')
         .append(((id) ValueAdjustments::s).f()).append(' ')
@@ -153,6 +162,11 @@
 
   private static void checkFloat(StringBuffer builder) {
     builder
+        .append(((if_) new it<Float>() {
+          @Override public Float t() {
+            return (float) (Short.MAX_VALUE) + 1;
+          }
+        }::t).f()).append(' ')
         .append(((if_) ValueAdjustments::b).f()).append(' ')
         .append(((if_) ValueAdjustments::B).f()).append(' ')
         .append(((if_) ValueAdjustments::s).f()).append(' ')
@@ -169,6 +183,11 @@
 
   private static void checkLong(StringBuffer builder) {
     builder
+        .append(((ij) new it<Long>() {
+          @Override public Long t() {
+            return (long) (Integer.MAX_VALUE) + 1;
+          }
+        }::t).f()).append(' ')
         .append(((ij) ValueAdjustments::b).f()).append(' ')
         .append(((ij) ValueAdjustments::B).f()).append(' ')
         .append(((ij) ValueAdjustments::s).f()).append(' ')
@@ -183,6 +202,11 @@
 
   private static void checkInt(StringBuffer builder) {
     builder
+        .append(((ii) new it<Integer>() {
+          @Override public Integer t() {
+            return Short.MAX_VALUE + 1;
+          }
+        }::t).f()).append(' ')
         .append(((ii) ValueAdjustments::b).f()).append(' ')
         .append(((ii) ValueAdjustments::B).f()).append(' ')
         .append(((ii) ValueAdjustments::s).f()).append(' ')
@@ -195,6 +219,11 @@
 
   private static void checkShort(StringBuffer builder) {
     builder
+        .append(((is) new it<Short>() {
+          @Override public Short t() {
+            return 256;
+          }
+        }::t).f()).append(' ')
         .append(((is) ValueAdjustments::b).f()).append(' ')
         .append(((is) ValueAdjustments::B).f()).append(' ')
         .append(((is) ValueAdjustments::s).f()).append(' ')
@@ -203,18 +232,33 @@
 
   private static void checkChar(StringBuffer builder) {
     builder
+        .append(((ic) new it<Character>() {
+          @Override public Character t() {
+            return 'C';
+          }
+        }::t).f()).append(' ')
         .append(((ic) ValueAdjustments::c).f()).append(' ')
         .append(((ic) ValueAdjustments::C).f()).append('\n');
   }
 
   private static void checkByte(StringBuffer builder) {
     builder
+        .append(((ib) new it<Byte>() {
+          @Override public Byte t() {
+            return 11;
+          }
+        }::t).f()).append(' ')
         .append(((ib) ValueAdjustments::b).f()).append(' ')
         .append(((ib) ValueAdjustments::B).f()).append('\n');
   }
 
   private static void checkBoolean(StringBuffer builder) {
     builder
+        .append(((iz) new it<Boolean>() {
+          @Override public Boolean t() {
+            return true;
+          }
+        }::t).f()).append(' ')
         .append(((iz) ValueAdjustments::z).f()).append(' ')
         .append(((iz) ValueAdjustments::Z).f()).append('\n');
   }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 3ca9b1b..4d707a8 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -19,6 +19,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
@@ -30,7 +31,7 @@
 public class TestBase {
 
   @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   /**
    * Write lines of text to a temporary file.
@@ -114,6 +115,15 @@
   /**
    * Compile an application with R8.
    */
+  protected AndroidApp compileWithR8(AndroidApp app)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
+    return ToolHelper.runR8(command);
+  }
+
+  /**
+   * Compile an application with R8.
+   */
   protected AndroidApp compileWithR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
     R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
@@ -215,6 +225,13 @@
    * Run application on Art with the specified main class and provided arguments.
    */
   protected String runOnArt(AndroidApp app, Class mainClass, String... args) throws IOException {
+    return runOnArt(app, mainClass, Arrays.asList(args));
+  }
+
+  /**
+   * Run application on Art with the specified main class and provided arguments.
+   */
+  protected String runOnArt(AndroidApp app, Class mainClass, List<String> args) throws IOException {
     Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
     app.writeToZip(out, OutputMode.Indexed);
     return ToolHelper.runArtNoVerificationErrors(
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
rename to src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
index a449e21..3aadc15 100644
--- a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
@@ -16,7 +16,12 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
-
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -24,20 +29,12 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-
 /**
- * Simple test that compiles framework.jar with D8 a number of times with
- * various number of threads available to the compiler.
- * This test also tests the hidden marker inserted into classes.dex.
+ * Simple test that compiles framework.jar with D8 a number of times with various number of threads
+ * available to the compiler. This test also tests the hidden marker inserted into classes.dex.
  */
-@RunWith( Parameterized.class )
-public class D8FrameworkTest {
+@RunWith(Parameterized.class)
+public class D8FrameworkDexPassthroughMarkerTest {
 
   private static final Path FRAMEWORK_JAR =
       Paths.get("tools/linux/art-5.1.1/product/mako/system/framework/framework.jar");
@@ -52,7 +49,7 @@
 
   private final int threads;
 
-  public D8FrameworkTest(int threads) {
+  public D8FrameworkDexPassthroughMarkerTest(int threads) {
     this.threads = threads;
   }
 
@@ -72,7 +69,9 @@
       options.numberOfThreads = threads;
     });
     DexApplication dexApp =
-        new ApplicationReader(app, new InternalOptions(), new Timing("D8FrameworkTest")).read();
+        new ApplicationReader(
+                app, new InternalOptions(), new Timing("D8FrameworkDexPassthroughMarkerTest"))
+            .read();
     Marker readMarker = dexApp.dexItemFactory.extractMarker();
     assertEquals(marker, readMarker);
   }
diff --git a/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java
new file mode 100644
index 0000000..4cc5818
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java
@@ -0,0 +1,42 @@
+// 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.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8Framework14082017DesugaredVerificationTest extends CompilationTestBase {
+  private static final int MIN_SDK = 24;
+  private static final String JAR = "third_party/framework/framework_14082017_desugared.jar";
+
+  @Test
+  public void verifyDebugBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        D8Command.builder()
+            .addProgramFiles(Paths.get(JAR))
+            .setMode(CompilationMode.DEBUG)
+            .setMinApiLevel(MIN_SDK)
+            .build(),
+        JAR);
+  }
+
+  @Test
+  public void verifyReleaseBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        D8Command.builder()
+            .addProgramFiles(Paths.get(JAR))
+            .setMode(CompilationMode.RELEASE)
+            .setMinApiLevel(MIN_SDK)
+            .build(),
+        JAR);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
new file mode 100644
index 0000000..87786df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
@@ -0,0 +1,42 @@
+// 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.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8Framework14082017VerificationTest extends CompilationTestBase {
+  private static final int MIN_SDK = 24;
+  private static final String JAR = "third_party/framework/framework_14082017.jar";
+
+  @Test
+  public void verifyDebugBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        D8Command.builder()
+            .addProgramFiles(Paths.get(JAR))
+            .setMode(CompilationMode.DEBUG)
+            .setMinApiLevel(MIN_SDK)
+            .build(),
+        JAR);
+  }
+
+  @Test
+  public void verifyReleaseBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        D8Command.builder()
+            .addProgramFiles(Paths.get(JAR))
+            .setMode(CompilationMode.RELEASE)
+            .setMinApiLevel(MIN_SDK)
+            .build(),
+        JAR);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index f88ea49..74e4e9b 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
index efa812a..8c72ddd 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationMode;
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 08e90c0..6eebf6b 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.ConstNumber;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 671698b..813ea1b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.CompilationError;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 87d350c..4710e69 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.errors.CompilationError;
 import java.util.Arrays;
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index e1ace71..cbf40a2 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
-import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 7f65c60..ba5e370 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
@@ -141,11 +140,17 @@
     return out.toByteArray();
   }
 
+  public List<byte[]> buildClasses() throws Exception {
+    List<byte[]> result = new ArrayList<>();
+    for (ClassBuilder clazz : classes) {
+      result.add(compile(clazz));
+    }
+    return result;
+  }
+
   public AndroidApp build() throws Exception {
     AndroidApp.Builder builder = AndroidApp.builder();
-    for (ClassBuilder clazz : classes) {
-      builder.addClassProgramData(compile(clazz));
-    }
+    builder.addClassProgramData(buildClasses());
     return builder.build();
   }
 
@@ -154,7 +159,6 @@
   }
 
   public DexApplication read(InternalOptions options) throws Exception {
-    DexItemFactory factory = new DexItemFactory();
     Timing timing = new Timing("JasminTest");
     return new ApplicationReader(build(), options, timing).read();
   }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 801f6d1..619f808 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -302,7 +302,7 @@
         Path secondaryDexFile = testDir.resolve("classes2.dex");
         assertTrue(Files.exists(primaryDexFile));
         boolean hasSecondaryDexFile = !allClasses && mode == CompilationMode.DEBUG;
-        assertEquals(hasSecondaryDexFile, Files.exists(testDir.resolve(secondaryDexFile)));
+        assertEquals(hasSecondaryDexFile, Files.exists(secondaryDexFile));
         byte[] content = Files.readAllBytes(primaryDexFile);
         if (ref == null) {
           ref = content;
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index b8e22d2..5975483 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -17,12 +17,14 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.BiConsumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,6 +47,16 @@
     inspection.accept(dexItemFactory, naming);
   }
 
+  private static final List<String> CLASSES_IN_NAMING044 = ImmutableList.of(
+      "Lnaming044/A;", "Lnaming044/B;", "Lnaming044/sub/SubA;", "Lnaming044/sub/SubB;");
+
+  private static final List<String> CLASSES_IN_NAMING101 = ImmutableList.of(
+      "Lnaming101/c;", "Lnaming101/d;",
+      "Lnaming101/a/a;", "Lnaming101/a/c;",
+      "Lnaming101/a/b/c",
+      "Lnaming101/b/a'", "Lnaming101/b/b;"
+  );
+
   @Parameters(name = "test: {0} keep: {1}")
   public static Collection<Object[]> data() {
     List<String> tests = Arrays.asList("naming044", "naming101");
@@ -60,6 +72,8 @@
     inspections.put("naming101:keep-rules-003.txt", PackageNamingTest::test101_rule003);
     inspections.put("naming101:keep-rules-004.txt", PackageNamingTest::test101_rule004);
     inspections.put("naming101:keep-rules-005.txt", PackageNamingTest::test101_rule005);
+    inspections.put("naming101:keep-rules-102.txt", PackageNamingTest::test101_rule102);
+    inspections.put("naming101:keep-rules-104.txt", PackageNamingTest::test101_rule104);
 
     return createTests(tests, inspections);
   }
@@ -68,8 +82,23 @@
     return CharMatcher.is('/').countIn(descriptor);
   }
 
+  private static void verifyUniqueNaming(
+      DexItemFactory dexItemFactory,
+      NamingLens naming,
+      List<String> klasses) {
+    Set<String> renamedNames = Sets.newHashSet();
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String rename = naming.lookupDescriptor(k).toSourceString();
+      assertFalse(renamedNames.contains(rename));
+      renamedNames.add(rename);
+    }
+  }
+
   // repackageclasses ''
   private static void test044_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All classes are moved to the top-level package, hence no package separator.
     DexType b = dexItemFactory.createType("Lnaming044/B;");
     assertFalse(naming.lookupDescriptor(b).toSourceString().contains("/"));
@@ -86,6 +115,8 @@
 
   // repackageclasses 'p44.x'
   private static void test044_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All classes are moved to a single package, so they all have the same package prefix.
     DexType a = dexItemFactory.createType("Lnaming044/A;");
     assertTrue(naming.lookupDescriptor(a).toSourceString().startsWith("Lp44/x/"));
@@ -103,6 +134,8 @@
 
   // flattenpackagehierarchy ''
   private static void test044_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All packages are moved to the top-level package, hence only one package separator.
     DexType b = dexItemFactory.createType("Lnaming044/B;");
     assertEquals(1, countPackageDepth(naming.lookupDescriptor(b).toSourceString()));
@@ -145,6 +178,8 @@
   }
 
   private static void test044_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All packages are renamed somehow. Need to check package hierarchy is consistent.
     DexType a = dexItemFactory.createType("Lnaming044/A;");
     assertEquals(1, countPackageDepth(naming.lookupDescriptor(a).toSourceString()));
@@ -171,6 +206,8 @@
   }
 
   private static void test101_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
     // All classes are moved to the top-level package, hence no package separator.
     DexType c = dexItemFactory.createType("Lnaming101/c;");
     assertFalse(naming.lookupDescriptor(c).toSourceString().contains("/"));
@@ -183,9 +220,14 @@
   }
 
   private static void test101_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
-    // Check naming101.a.a is kept due to **.a
-    DexType a = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+
     // Repackaged to naming101.a, but naming101.a.a exists to make a name conflict.
     // Thus, everything else should not be renamed to 'a',
     // except for naming101.b.a, which is also kept due to **.a
@@ -204,6 +246,8 @@
   }
 
   private static void test101_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
     // All packages are moved to the top-level package, hence only one package separator.
     DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
     assertEquals(1, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
@@ -217,9 +261,14 @@
   }
 
   private static void test101_rule004(DexItemFactory dexItemFactory, NamingLens naming) {
-    // Check naming101.a.a is kept due to **.a
-    DexType a = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+
     // Flattened to naming101, hence all other classes will be in naming101.* package.
     // Due to naming101.a.a, prefix naming101.a is already used. So, any other classes,
     // except for naming101.a.c, should not have naming101.a as package.
@@ -237,6 +286,8 @@
   }
 
   private static void test101_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
     // All packages are renamed somehow. Need to check package hierarchy is consistent.
     DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
     assertEquals(2, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
@@ -250,4 +301,60 @@
         getParentPackagePrefix(
             getParentPackagePrefix(naming.lookupDescriptor(abc).toSourceString())));
   }
+
+  private static void test101_rule102(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    // Due to package-private access, classes in the same package should remain as-is.
+    DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
+    assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+    DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+    assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+
+    // All other classes can be repackaged to naming101.a, but naming101.a.a exists to make a name
+    // conflict. Thus, those should not be renamed to 'a'.
+    List<String> klasses = ImmutableList.of(
+        "Lnaming101/c;",
+        "Lnaming101/d;",
+        "Lnaming101/a/b/c;");
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String renamedName = naming.lookupDescriptor(k).toSourceString();
+      assertEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+      assertNotEquals("Lnaming101/a/a;", renamedName);
+    }
+  }
+
+  private static void test101_rule104(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    // Due to package-private access, classes in the same package should remain as-is.
+    DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
+    assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+    DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+    assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+
+    // All other packages are flattened to naming101, hence all other classes will be in
+    // naming101.* package. Due to naming101.a.a, prefix naming101.a is already used. So,
+    // remaining classes should not have naming101.a as package.
+    List<String> klasses = ImmutableList.of(
+        "Lnaming101/c;",
+        "Lnaming101/d;",
+        "Lnaming101/a/b/c;");
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String renamedName = naming.lookupDescriptor(k).toSourceString();
+      assertNotEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index ea28214..ac0178c 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -69,6 +69,10 @@
       VALID_PROGUARD_DIR + "package-obfuscation-5.flags";
   private static final String PACKAGE_OBFUSCATION_6 =
       VALID_PROGUARD_DIR + "package-obfuscation-6.flags";
+  private static final String APPLY_MAPPING =
+      VALID_PROGUARD_DIR + "applymapping.flags";
+  private static final String APPLY_MAPPING_WITHOUT_FILE =
+      INVALID_PROGUARD_DIR + "applymapping-without-file.flags";
   private static final String DONT_SHRINK =
       VALID_PROGUARD_DIR + "dontshrink.flags";
   private static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES =
@@ -339,6 +343,25 @@
   }
 
   @Test
+  public void parseApplyMapping() throws IOException, ProguardRuleParserException {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(Paths.get(APPLY_MAPPING));
+    ProguardConfiguration config = parser.getConfig();
+    assertTrue(config.hasApplyMappingFile());
+  }
+
+  @Test
+  public void parseApplyMappingWithoutFile() throws IOException, ProguardRuleParserException {
+    try {
+      ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+      parser.parse(Paths.get(APPLY_MAPPING_WITHOUT_FILE));
+      fail("Expect to fail due to the lack of file name.");
+    } catch (ProguardRuleParserException e) {
+      assertTrue(e.getMessage().contains("File name expected"));
+    }
+  }
+
+  @Test
   public void parseIncluding() throws IOException, ProguardRuleParserException {
     new ProguardConfigurationParser(new DexItemFactory()).parse(Paths.get(INCLUDING));
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index be28b38..0ab229f 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 51b6ab5..ab6d6e2 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -478,6 +478,12 @@
     inspector.forAllClasses((clazz) -> checkClassAndMemberInDictionary(clazz));
   }
 
+  private static void abstractMethodRemains(DexInspector inspector) {
+    ClassSubject programClass = inspector.clazz("shaking17.AbstractProgramClass");
+    Assert.assertTrue(programClass.isPresent());
+    Assert.assertTrue(
+        programClass.method("int", "abstractMethod", Collections.emptyList()).isPresent());
+  }
 
   private static void assumenosideeffects1CheckOutput(String output1, String output2) {
     Assert.assertEquals("noSideEffectVoid\nnoSideEffectInt\n", output1);
@@ -620,6 +626,7 @@
             "shaking14",
             "shaking15",
             "shaking16",
+            "shaking17",
             "inlining",
             "minification",
             "minifygeneric",
@@ -684,22 +691,17 @@
         .put("shaking11:keep-rules.txt", TreeShakingTest::shaking11OnlyOneClassKept);
     inspections
         .put("shaking11:keep-rules-keep-method.txt", TreeShakingTest::shaking11BothMethodsKept);
-    inspections
-        .put("shaking12:keep-rules.txt",
+    inspections.put("shaking12:keep-rules.txt",
             TreeShakingTest::shaking12OnlyInstantiatedClassesHaveConstructors);
-    inspections
-        .put("shaking13:keep-rules.txt",
+    inspections.put("shaking13:keep-rules.txt",
             TreeShakingTest::shaking13EnsureFieldWritesCorrect);
-    inspections
-        .put("shaking14:keep-rules.txt",
+    inspections.put("shaking14:keep-rules.txt",
             TreeShakingTest::shaking14EnsureRightStaticMethodsLive);
-    inspections.put("shaking15:keep-rules.txt",
-        TreeShakingTest::shaking15testDictionary);
-    inspections
-        .put("annotationremoval:keep-rules.txt",
+    inspections.put("shaking15:keep-rules.txt", TreeShakingTest::shaking15testDictionary);
+    inspections.put("shaking17:keep-rules.txt", TreeShakingTest::abstractMethodRemains);
+    inspections.put("annotationremoval:keep-rules.txt",
             TreeShakingTest::annotationRemovalHasNoInnerClassAnnotations);
-    inspections
-        .put("annotationremoval:keep-rules-keep-innerannotation.txt",
+    inspections.put("annotationremoval:keep-rules-keep-innerannotation.txt",
             TreeShakingTest::annotationRemovalHasAllInnerClassAnnotations);
     inspections
         .put("simpleproto1:keep-rules.txt", TreeShakingTest::simpleproto1UnusedFieldIsGone);
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
new file mode 100644
index 0000000..f47025a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
@@ -0,0 +1,43 @@
+// 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.smali;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CheckSwitchInTestClass {
+  public static void main(String[] args) throws Exception {
+    // Load the generated Jasmin class, and get the test method.
+    Class<?> test = CheckSwitchInTestClass.class.getClassLoader().loadClass("Test");
+    Method method = test.getMethod("test", int.class);
+
+    // Get keys and default value from arguments.
+    List<Integer> keys = new ArrayList<>();
+    for (int i = 0; i < args.length - 1; i++) {
+      keys.add(Integer.parseInt(args[i]));
+    }
+    int defaultValue = Integer.parseInt(args[args.length - 1]);
+
+    // Run over all keys and test a small interval around each.
+    long delta = 2;
+    for (Integer key : keys) {
+      for (long potential = key - delta; potential < key + delta; potential++) {
+        if (Integer.MIN_VALUE <= potential && potential <= Integer.MAX_VALUE) {
+          int testKey = (int) potential;
+          int result = ((Integer) method.invoke(null, testKey));
+          int expect = defaultValue;
+          if (keys.contains(testKey)) {
+            expect = testKey;
+          }
+          if (result != expect) {
+            System.out.println("Expected " + expect + " but got " + result);
+            System.exit(1);
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 27ee140..f35dd21 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.R8;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
@@ -46,18 +47,13 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import org.antlr.runtime.RecognitionException;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
 
-public class SmaliTestBase {
+public class SmaliTestBase extends TestBase {
 
   public static final String DEFAULT_CLASS_NAME = "Test";
   public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME;
   public static final String DEFAULT_METHOD_NAME = "method";
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
   public static class MethodSignature {
 
     public final String clazz;
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index 7f89da7..c256920 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -21,9 +21,14 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 
 public class SwitchRewritingTest extends SmaliTestBase {
@@ -456,4 +461,77 @@
     // class file max.
     runLargerSwitchJarTest(0, 1, 5503, null);
   }
-}
+
+  private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs)
+      throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    StringBuilder x = new StringBuilder();
+    StringBuilder y = new StringBuilder();
+    for (Integer key : keys) {
+      x.append(key).append(" : case_").append(key).append("\n");
+      y.append("case_").append(key).append(":\n");
+      y.append("    ldc ").append(key).append("\n");
+      y.append("    goto return_\n");
+    }
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload_0",
+        "    lookupswitch",
+        x.toString(),
+        "      default : case_default",
+        y.toString(),
+        "  case_default:",
+        "    ldc " + defaultValue,
+        "  return_:",
+        "    ireturn");
+
+    // Add the Jasmin class and a class from Java source with the main method.
+    AndroidApp.Builder appBuilder = AndroidApp.builder();
+    appBuilder.addClassProgramData(builder.buildClasses());
+    appBuilder.addProgramFiles(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class));
+    AndroidApp app = compileWithR8(appBuilder.build());
+
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
+    DexCode code = method.getMethod().getCode().asDexCode();
+
+    int packedSwitches = 0;
+    int sparseSwitches = 0;
+    int ifs = 0;
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof PackedSwitch) {
+        packedSwitches++;
+      }
+      if (instruction instanceof SparseSwitch) {
+        sparseSwitches++;
+      }
+      if (instruction instanceof IfEq || instruction instanceof IfEqz) {
+        ifs++;
+      }
+    }
+
+    assertEquals(1, packedSwitches);
+    assertEquals(0, sparseSwitches);
+    assertEquals(expectedIfs, ifs);
+
+    // Run the code
+    List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
+    args.add(Integer.toString(defaultValue));
+    runOnArt(app, CheckSwitchInTestClass.class, args);
+  }
+
+  @Test
+  public void convertCasesToIf() throws Exception {
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1);
+    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1);
+    runConvertCasesToIf(ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1);
+    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2);
+    runConvertCasesToIf(ImmutableList.of(
+        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/utils/R8InliningTest.java b/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
index 7f3c3e1..7af5708 100644
--- a/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8InliningTest.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.R8Command;
diff --git a/src/test/proguard/invalid/applymapping-without-file.flags b/src/test/proguard/invalid/applymapping-without-file.flags
new file mode 100644
index 0000000..ef5abe5
--- /dev/null
+++ b/src/test/proguard/invalid/applymapping-without-file.flags
@@ -0,0 +1,6 @@
+# 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.
+
+# file name should be followed.
+-applymapping
diff --git a/src/test/proguard/valid/applymapping.flags b/src/test/proguard/valid/applymapping.flags
new file mode 100644
index 0000000..8d427f9
--- /dev/null
+++ b/src/test/proguard/valid/applymapping.flags
@@ -0,0 +1,5 @@
+# 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.
+
+-applymapping bogus-file-name.ext
diff --git a/tools/gmscore_data.py b/tools/gmscore_data.py
index 47d9024..80e7853 100644
--- a/tools/gmscore_data.py
+++ b/tools/gmscore_data.py
@@ -90,6 +90,7 @@
     },
     'proguarded' : {
       'inputs': ['%s_proguard.jar' % V9_PREFIX],
+      'main-dex-list': os.path.join(V9_BASE, 'main_dex_list.txt'),
       'pgmap': '%s_proguard.map' % V9_PREFIX,
       'min-api' : ANDROID_L_API,
      }
@@ -109,6 +110,7 @@
     },
     'proguarded' : {
       'inputs': ['%s_proguard.jar' % V10_PREFIX],
+      'main-dex-list': os.path.join(V10_BASE, 'main_dex_list.txt') ,
       'pgmap': '%s_proguard.map' % V10_PREFIX,
       'min-api' : ANDROID_L_API,
     }
@@ -121,5 +123,11 @@
           '%s/proguardsettings/GmsCore_proguard.config' % THIRD_PARTY],
       'min-api' : ANDROID_L_API,
     },
+    'proguarded' : {
+      'inputs': ['%s_proguard.jar' % LATEST_PREFIX],
+      'main-dex-list': os.path.join(LATEST_BASE, 'main_dex_list.txt') ,
+      'pgmap': '%s_proguard.map' % LATEST_PREFIX,
+      'min-api' : ANDROID_L_API,
+    }
   },
 }
diff --git a/tools/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index e150278..63865d0 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -25,7 +25,7 @@
   result.add_option('--version',
                     help = '',
                     default = 'v9',
-                    choices = ['v9', 'v10'])
+                    choices = ['v9', 'v10', 'latest'])
   result.add_option('--type',
                     help = '',
                     default = 'proguarded',
@@ -58,6 +58,9 @@
   if not os.path.exists(outdir):
     os.makedirs(outdir)
 
+  if 'main-dex-list' in values:
+    args.extend(['--main-dex-list', values['main-dex-list']])
+
   if options.d8_flags:
     args.extend(options.d8_flags.split(' '))