Merge "Revert "Loosen desugaring constraints""
diff --git a/build.gradle b/build.gradle
index 63513ce..7362238 100644
--- a/build.gradle
+++ b/build.gradle
@@ -565,6 +565,7 @@
             executable file("third_party/kotlin/kotlinc/bin/kotlinc");
         }
         args "-include-runtime"
+        args "-nowarn"
         args "-d"
         args "build/test/${kotlinHostJar}"
         args fileTree(dir: kotlinResourcesDir, include: '**/*.kt')
diff --git a/src/main/java/com/android/tools/r8/ApiLevelException.java b/src/main/java/com/android/tools/r8/ApiLevelException.java
index ec9fe68..14a530c 100644
--- a/src/main/java/com/android/tools/r8/ApiLevelException.java
+++ b/src/main/java/com/android/tools/r8/ApiLevelException.java
@@ -3,27 +3,28 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.utils.AndroidApiLevel;
+
 /**
  * Exception to signal features that are not supported until a given API level.
  */
 public class ApiLevelException extends CompilationException {
 
   public ApiLevelException(
-      int minApiLevel, String minApiLevelString, String unsupportedFeatures, String sourceString) {
-    super(makeMessage(minApiLevel, minApiLevelString, unsupportedFeatures, sourceString));
-    assert minApiLevel > 0;
-    assert minApiLevelString != null;
+      AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
+    super(makeMessage(minApiLevel, unsupportedFeatures, sourceString));
+    assert minApiLevel != null;
     assert unsupportedFeatures != null;
   }
 
   private static String makeMessage(
-      int minApiLevel, String minApiLevelString, String unsupportedFeatures, String sourceString) {
+      AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
     String message =
         unsupportedFeatures
             + " are only supported starting with "
-            + minApiLevelString
+            + minApiLevel.getName()
             + " (--min-api "
-            + minApiLevel
+            + minApiLevel.getLevel()
             + ")";
     message = (sourceString != null) ? message + ": " + sourceString : message;
     return message;
diff --git a/src/main/java/com/android/tools/r8/CompilationException.java b/src/main/java/com/android/tools/r8/CompilationException.java
index 4fd3654..8e1e56c 100644
--- a/src/main/java/com/android/tools/r8/CompilationException.java
+++ b/src/main/java/com/android/tools/r8/CompilationException.java
@@ -36,5 +36,17 @@
   public CompilationException(Throwable cause) {
     super(cause.getMessage(), cause);
   }
+
+  protected CompilationException() {
+    super();
+  }
+
+  public String getMessageForD8() {
+    return super.getMessage();
+  }
+
+  public String getMessageForR8() {
+    return super.getMessage();
+  }
 }
 
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 38b7acc..6598ae4 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -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() {}
@@ -118,7 +118,7 @@
   }
 
   /** Command-line entry to D8. */
-  public static void main(String[] args) throws IOException {
+  public static void main(String[] args) {
     if (args.length == 0) {
       System.err.println(USAGE_MESSAGE);
       System.exit(STATUS_ERROR);
@@ -140,8 +140,7 @@
       cause.printStackTrace();
       System.exit(STATUS_ERROR);
     } catch (CompilationException e) {
-      System.err.println("Compilation failed: " + e.getMessage());
-      System.err.println(USAGE_MESSAGE);
+      System.err.println("Compilation failed: " + e.getMessageForD8());
       System.exit(STATUS_ERROR);
     }
   }
@@ -199,8 +198,6 @@
 
       options.printWarnings();
       return output;
-    } catch (MainDexError mainDexError) {
-      throw new CompilationError(mainDexError.getMessageForD8());
     } catch (ExecutionException e) {
       R8.unwrapExecutionException(e);
       throw new AssertionError(e); // unwrapping method should have thrown
@@ -210,7 +207,7 @@
   private static DexApplication optimize(
       DexApplication application, AppInfo appInfo, InternalOptions options,
       Timing timing, ExecutorService executor)
-      throws IOException, ExecutionException {
+      throws IOException, ExecutionException, ApiLevelException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
     IRConverter converter = new IRConverter(timing, application, appInfo, options, printer);
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 3827cae..4e4a19b 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.dex.DexFileReader;
 import com.android.tools.r8.dex.Segment;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
@@ -16,7 +15,6 @@
 import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
 
 public class DexSegments {
   private static class Command extends BaseCommand {
@@ -99,7 +97,7 @@
   }
 
   public static void main(String[] args)
-      throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+      throws IOException, CompilationException {
     Command.Builder builder = Command.parse(args);
     Command command = builder.build();
     if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 2f1d523..a7bfdc9 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
@@ -133,7 +132,7 @@
   }
 
   public static void main(String[] args)
-      throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+      throws IOException, CompilationException, ExecutionException {
     DisassembleCommand.Builder builder = DisassembleCommand.parse(args);
     DisassembleCommand command = builder.build();
     if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index c1234b8..0db183f 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
@@ -100,7 +99,7 @@
   }
 
   public static void main(String[] args)
-      throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+      throws IOException, CompilationException, ExecutionException {
     ExtractMarker.Command.Builder builder = ExtractMarker.Command.parse(args);
     ExtractMarker.Command command = builder.build();
     if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index f28a07a..b4c19ea 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -7,11 +7,10 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.ClassAndMemberPublicizer;
@@ -41,6 +40,7 @@
 import com.android.tools.r8.shaking.SimpleClassMerger;
 import com.android.tools.r8.shaking.TreePruner;
 import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.FileUtils;
@@ -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;
 
@@ -99,7 +99,7 @@
       byte[] proguardSeedsData,
       PackageDistribution packageDistribution,
       InternalOptions options)
-      throws ExecutionException {
+      throws ExecutionException, DexOverflowException {
     try {
       Marker marker = getMarker(options);
       return new ApplicationWriter(
@@ -114,12 +114,12 @@
       DexApplication application,
       AppInfoWithSubtyping appInfo,
       InternalOptions options)
-      throws ProguardRuleParserException, ExecutionException, IOException {
+      throws ApiLevelException, ExecutionException, IOException {
     return new R8(options).optimize(application, appInfo);
   }
 
   private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
-      throws IOException, ProguardRuleParserException, ExecutionException {
+      throws IOException, ApiLevelException, ExecutionException {
     return optimize(application, appInfo, GraphLense.getIdentityLense(),
         Executors.newSingleThreadExecutor());
   }
@@ -129,7 +129,7 @@
       AppInfoWithSubtyping appInfo,
       GraphLense graphLense,
       ExecutorService executorService)
-      throws IOException, ProguardRuleParserException, ExecutionException {
+      throws IOException, ApiLevelException, ExecutionException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
     timing.begin("Create IR");
@@ -207,7 +207,7 @@
   }
 
   static CompilationResult runForTesting(AndroidApp app, InternalOptions options)
-      throws ProguardRuleParserException, IOException, CompilationException {
+      throws IOException, CompilationException {
     ExecutorService executor = ThreadUtils.getExecutorService(options);
     try {
       return runForTesting(app, options, executor);
@@ -220,20 +220,21 @@
       AndroidApp app,
       InternalOptions options,
       ExecutorService executor)
-      throws ProguardRuleParserException, IOException, CompilationException {
+      throws IOException, CompilationException {
     return new R8(options).run(app, executor);
   }
 
   private CompilationResult run(AndroidApp inputApp, ExecutorService executorService)
-      throws IOException, ProguardRuleParserException, CompilationException {
+      throws IOException, CompilationException {
     if (options.quiet) {
       System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
     }
     try {
-      if (options.minApiLevel >= Constants.ANDROID_O_API
+      AndroidApiLevel oLevel = AndroidApiLevel.O;
+      if (options.minApiLevel >= oLevel.getLevel()
           && !options.mainDexKeepRules.isEmpty()) {
-        throw new CompilationError("Automatic main dex list is not supported when compiling for"
-            + " android O and later (--min-api " + Constants.ANDROID_O_API + ")");
+        throw new CompilationError("Automatic main dex list is not supported when compiling for "
+            + oLevel.getName() + " and later (--min-api " + oLevel.getLevel() + ")");
       }
       DexApplication application =
           new ApplicationReader(inputApp, options, timing).read(executorService);
@@ -270,9 +271,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 +297,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 +337,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 +351,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();
       }
 
@@ -386,8 +393,6 @@
 
       options.printWarnings();
       return new CompilationResult(androidApp, application, appInfo);
-    } catch (MainDexError mainDexError) {
-      throw new CompilationError(mainDexError.getMessageForR8());
     } catch (ExecutionException e) {
       unwrapExecutionException(e);
       throw new AssertionError(e); // unwrapping method should have thrown
@@ -439,7 +444,7 @@
    * @return the compilation result.
    */
   public static AndroidApp run(R8Command command)
-      throws IOException, CompilationException, ProguardRuleParserException {
+      throws IOException, CompilationException {
     ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
     try {
       return run(command, executorService);
@@ -510,7 +515,7 @@
    * @return the compilation result.
    */
   public static AndroidApp run(R8Command command, ExecutorService executor)
-      throws IOException, CompilationException, ProguardRuleParserException {
+      throws IOException, CompilationException {
     InternalOptions options = command.getInternalOptions();
     AndroidApp outputApp =
         runForTesting(command.getInputApp(), options, executor).androidApp;
@@ -556,8 +561,7 @@
       cause.printStackTrace();
       System.exit(1);
     } catch (CompilationException e) {
-      System.err.println("Compilation failed: " + e.getMessage());
-      System.err.println(USAGE_MESSAGE);
+      System.err.println("Compilation failed: " + e.getMessageForR8());
       System.exit(1);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 0959167..318b3e6 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -7,6 +7,9 @@
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
@@ -17,7 +20,6 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Consumer;
@@ -26,11 +28,12 @@
 
   public static class Builder extends BaseCommand.Builder<R8Command, Builder> {
 
-    private final List<Path> mainDexRules = new ArrayList<>();
+    private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
     private Path mainDexListOutput = null;
     private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
-    private final List<Path> proguardConfigFiles = new ArrayList<>();
+    private final List<ProguardConfigurationSource> proguardConfigs = 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;
@@ -57,6 +60,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) {
@@ -67,16 +78,28 @@
     /**
      * Add proguard configuration file resources for automatic main dex list calculation.
      */
-    public Builder addMainDexRules(Path... paths) {
-      Collections.addAll(mainDexRules, paths);
+    public Builder addMainDexRulesFiles(Path... paths) {
+      for (Path path : paths) {
+        mainDexRules.add(new ProguardConfigurationSourceFile(path));
+      }
       return self();
     }
 
     /**
      * Add proguard configuration file resources for automatic main dex list calculation.
      */
-    public Builder addMainDexRules(List<Path> paths) {
-      mainDexRules.addAll(paths);
+    public Builder addMainDexRulesFiles(List<Path> paths) {
+      for (Path path : paths) {
+        mainDexRules.add(new ProguardConfigurationSourceFile(path));
+      }
+      return self();
+    }
+
+    /**
+     * Add proguard configuration file resources for automatic main dex list calculation.
+     */
+    public Builder addMainDexRules(List<String> lines) {
+      mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
       return self();
     }
 
@@ -89,7 +112,9 @@
      * Add proguard configuration file resources.
      */
     public Builder addProguardConfigurationFiles(Path... paths) {
-      Collections.addAll(proguardConfigFiles, paths);
+      for (Path path : paths) {
+        proguardConfigs.add(new ProguardConfigurationSourceFile(path));
+      }
       return self();
     }
 
@@ -97,7 +122,17 @@
      * Add proguard configuration file resources.
      */
     public Builder addProguardConfigurationFiles(List<Path> paths) {
-      proguardConfigFiles.addAll(paths);
+      for (Path path : paths) {
+        proguardConfigs.add(new ProguardConfigurationSourceFile(path));
+      }
+      return self();
+    }
+
+    /**
+     * Add proguard configuration.
+     */
+    public Builder addProguardConfiguration(List<String> lines) {
+      proguardConfigs.add(new ProguardConfigurationSourceStrings(lines));
       return self();
     }
 
@@ -175,12 +210,12 @@
         mainDexKeepRules = parser.getConfig().getRules();
       }
       ProguardConfiguration configuration;
-      if (proguardConfigFiles.isEmpty()) {
+      if (proguardConfigs.isEmpty()) {
         configuration = ProguardConfiguration.defaultConfiguration(factory);
       } else {
         ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
         try {
-          parser.parse(proguardConfigFiles);
+          parser.parse(proguardConfigs);
         } catch (ProguardRuleParserException e) {
           throw new CompilationException(e.getMessage(), e.getCause());
         }
@@ -200,6 +235,7 @@
       }
 
       boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking());
+      boolean useDiscardedChecker = discardedChecker.orElse(true);
       boolean useMinification = minification.orElse(configuration.isObfuscating());
 
       return new R8Command(
@@ -212,6 +248,7 @@
           getMode(),
           getMinApiLevel(),
           useTreeShaking,
+          useDiscardedChecker,
           useMinification,
           ignoreMissingClasses);
     }
@@ -237,6 +274,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.",
@@ -249,6 +287,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;
 
@@ -306,10 +345,12 @@
         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")) {
-        builder.addMainDexRules(Paths.get(args[++i]));
+        builder.addMainDexRulesFiles(Paths.get(args[++i]));
       } else if (arg.equals("--main-dex-list")) {
         builder.addMainDexListFiles(Paths.get(args[++i]));
       } else if (arg.equals("--main-dex-list-output")) {
@@ -360,6 +401,7 @@
       CompilationMode mode,
       int minApiLevel,
       boolean useTreeShaking,
+      boolean useDiscardedChecker,
       boolean useMinification,
       boolean ignoreMissingClasses) {
     super(inputApp, outputPath, outputMode, mode, minApiLevel);
@@ -370,6 +412,7 @@
     this.mainDexListOutput = mainDexListOutput;
     this.proguardConfiguration = proguardConfiguration;
     this.useTreeShaking = useTreeShaking;
+    this.useDiscardedChecker = useDiscardedChecker;
     this.useMinification = useMinification;
     this.ignoreMissingClasses = ignoreMissingClasses;
   }
@@ -380,6 +423,7 @@
     mainDexListOutput = null;
     proguardConfiguration = null;
     useTreeShaking = false;
+    useDiscardedChecker = false;
     useMinification = false;
     ignoreMissingClasses = false;
   }
@@ -388,6 +432,10 @@
     return useTreeShaking;
   }
 
+  public boolean useDiscardedChecker() {
+    return useDiscardedChecker;
+  }
+
   public boolean useMinification() {
     return useMinification;
   }
@@ -402,6 +450,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/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index ee4477d..81d5f9a 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -174,7 +175,7 @@
   }
 
   private static void writeApp(DexApplication app, Path output, ExecutorService executor)
-      throws IOException, ExecutionException {
+      throws IOException, ExecutionException, DexOverflowException {
     InternalOptions options = new InternalOptions();
     AppInfo appInfo = new AppInfo(app);
     ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null, null);
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectOptions.java b/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
index d0051dc..8bd2c90 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
@@ -121,8 +121,7 @@
     return new BisectOptions(goodBuild, badBuild, stateFile, command, output, result);
   }
 
-  private static <T> T require(OptionSet options, OptionSpec<T> option, String flag)
-      throws IOException {
+  private static <T> T require(OptionSet options, OptionSpec<T> option, String flag) {
     T value = options.valueOf(option);
     if (value != null) {
       return value;
@@ -130,7 +129,7 @@
     throw new CompilationError("Missing required option: --" + flag);
   }
 
-  private static File exists(String path, String flag) throws IOException {
+  private static File exists(String path, String flag) {
     File file = new File(path);
     if (file.exists()) {
       return file;
@@ -138,7 +137,7 @@
     throw new CompilationError("File --" + flag + ": " + file + " does not exist");
   }
 
-  private static File directoryExists(String path, String flag) throws IOException {
+  private static File directoryExists(String path, String flag) {
     File file = new File(path);
     if (file.exists() && file.isDirectory()) {
       return file;
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArray.java b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
index 085b6fa..21402a8 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -38,7 +39,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeNewArray(getType(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
index 25a941f..cb2bd6f 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -38,7 +39,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRangeNewArray(getType(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index a87217b..504b605 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexCallSite;
@@ -179,7 +180,7 @@
     return NO_TARGETS;
   }
 
-  public abstract void buildIR(IRBuilder builder);
+  public abstract void buildIR(IRBuilder builder) throws ApiLevelException;
 
   public DexCallSite getCallSite() {
     return null;
@@ -227,6 +228,7 @@
     throw new InternalCompilerError("Instruction " + payloadUser + " is not a payload user");
   }
 
+  @Override
   public String toString() {
     return toString(null);
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
index 58fb8c1..e52b1ed 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -47,7 +48,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRegisters(Type.DIRECT, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
index 742546d..f7f0c93 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -45,7 +46,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRange(Type.DIRECT, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
index 512f6bc..e8cff27 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -47,7 +48,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRegisters(
         Type.INTERFACE, getMethod(), getProto(), A, new int[] {C, D, E, F, G});
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
index 06eb69e..b40e177 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRange(Type.INTERFACE, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
index d5ecd11..891dcca 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -29,7 +30,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRegisters(
         Type.POLYMORPHIC, getMethod(), getProto(), A, new int[] {C, D, E, F, G});
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
index e6a16f5..4068d80 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -48,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRange(Type.POLYMORPHIC, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
index 8fbf95c..27c3255 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRegisters(Type.STATIC, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
index 92a4047..69394b3 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRange(Type.STATIC, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
index 7a6e32a..0382872 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -47,7 +48,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRegisters(Type.SUPER, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
index 9ee05b7..e12bccb 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRange(Type.SUPER, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
index 1fbf9d9..4de66f6 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRegisters(Type.VIRTUAL, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index c5c8249..fbacf0b 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) {
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
     builder.addInvokeRange(Type.VIRTUAL, getMethod(), getProto(), AA, CCCC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 9bcf510..fdd5964 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -36,11 +36,11 @@
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 import joptsimple.OptionParser;
 import joptsimple.OptionSet;
@@ -250,7 +250,7 @@
       }
     }
 
-    private DxCompatOptions(OptionSet options, Spec spec) throws DxParseError {
+    private DxCompatOptions(OptionSet options, Spec spec) {
       help = options.has(spec.help);
       debug = options.has(spec.debug);
       verbose = options.has(spec.verbose);
@@ -383,7 +383,7 @@
     }
 
     if (dexArgs.dumpTo != null) {
-      throw new Unimplemented("dump-to file not yet supported");
+      System.out.println("dump-to file not yet supported");
     }
 
     if (dexArgs.positions == PositionInfo.NONE) {
@@ -529,16 +529,16 @@
         // For each input archive file, add all class files within.
         for (Path input : inputs) {
           if (isArchive(input)) {
-            try (ZipInputStream in = new ZipInputStream(Files.newInputStream(input))) {
-              ZipEntry entry;
-              while ((entry = in.getNextEntry()) != null) {
+            try (ZipFile zipFile = new ZipFile(input.toFile())) {
+              final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+              while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
                 if (isClassFile(Paths.get(entry.getName()))) {
-                  addEntry(entry.getName(), in, out);
+                  try (InputStream entryStream = zipFile.getInputStream(entry)) {
+                    addEntry(entry.getName(), entryStream, out);
+                  }
                 }
               }
-            } catch (ZipException e) {
-              throw new CompilationError(
-                  "Zip error while reading '" + input + "': " + e.getMessage(), e);
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index cca3520..a07a4a3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -35,7 +35,6 @@
 import com.android.tools.r8.utils.MainDexList;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.io.Closer;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -130,8 +129,7 @@
   }
 
   private void readProguardMap(DexApplication.Builder builder, ExecutorService executorService,
-      List<Future<?>> futures)
-      throws IOException {
+      List<Future<?>> futures) {
     // Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
     if (inputApp.hasProguardMap()) {
       futures.add(executorService.submit(() -> {
@@ -145,8 +143,7 @@
   }
 
   private void readMainDexList(DexApplication.Builder builder, ExecutorService executorService,
-      List<Future<?>> futures)
-      throws IOException {
+      List<Future<?>> futures) {
     if (inputApp.hasMainDexList()) {
       futures.add(executorService.submit(() -> {
         for (Resource resource : inputApp.getMainDexListResources()) {
@@ -184,7 +181,7 @@
     }
 
     private <T extends DexClass> void readDexSources(List<Resource> dexSources,
-        ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
+        ClassKind classKind, Queue<T> classes) throws IOException {
       if (dexSources.size() > 0) {
         List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
         int computedMinApiLevel = options.minApiLevel;
@@ -211,7 +208,7 @@
     }
 
     private <T extends DexClass> void readClassSources(List<Resource> classSources,
-        ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
+        ClassKind classKind, Queue<T> classes) {
       JarClassFileReader reader = new JarClassFileReader(
           application, classKind.bridgeConsumer(classes::add));
       // Read classes in parallel.
@@ -227,7 +224,7 @@
       }
     }
 
-    void readSources() throws IOException, ExecutionException {
+    void readSources() throws IOException {
       readDexSources(inputApp.getDexProgramResources(), PROGRAM, programClasses);
       readClassSources(inputApp.getClassProgramResources(), PROGRAM, programClasses);
     }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 412b291..46ee85c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -4,10 +4,8 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.ApiLevelException;
-import com.android.tools.r8.dex.VirtualFile.FilePerClassDistributor;
-import com.android.tools.r8.dex.VirtualFile.FillFilesDistributor;
-import com.android.tools.r8.dex.VirtualFile.PackageMapDistributor;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -129,7 +127,7 @@
   }
 
   public AndroidApp write(PackageDistribution packageDistribution, ExecutorService executorService)
-      throws IOException, ExecutionException {
+      throws IOException, ExecutionException, DexOverflowException {
     application.timing.begin("DexApplication.write");
     try {
       application.dexItemFactory.sort(namingLens);
@@ -243,7 +241,7 @@
         .replace('.', '/') + ".class";
   }
 
-  private byte[] writeMainDexList() throws IOException {
+  private byte[] writeMainDexList() {
     if (application.mainDexList.isEmpty()) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
index 09b3c33..7bf5e8c 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -81,7 +81,7 @@
     return parseMapFrom(new DexFile(stream));
   }
 
-  private static Segment[] parseMapFrom(DexFile dex) throws IOException {
+  private static Segment[] parseMapFrom(DexFile dex) {
     DexFileReader reader = new DexFileReader(dex, ClassKind.PROGRAM, new DexItemFactory());
     return reader.segments;
   }
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index d47c57d..d820747 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -47,8 +47,10 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LebUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -295,8 +297,7 @@
     if (method.accessFlags.isStatic()) {
       if (!options.canUseDefaultAndStaticInterfaceMethods()) {
         throw new ApiLevelException(
-            Constants.ANDROID_N_API,
-            "Android N",
+            AndroidApiLevel.N,
             "Static interface methods",
             method.method.toSourceString());
       }
@@ -309,8 +310,7 @@
       if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
           !options.canUseDefaultAndStaticInterfaceMethods()) {
         throw new ApiLevelException(
-            Constants.ANDROID_N_API,
-            "Android N",
+            AndroidApiLevel.N,
             "Default interface methods",
             method.method.toSourceString());
       }
@@ -321,8 +321,7 @@
         return;
       }
       throw new ApiLevelException(
-          Constants.ANDROID_N_API,
-          "Android N",
+          AndroidApiLevel.N,
           "Private interface methods",
           method.method.toSourceString());
     }
@@ -365,21 +364,13 @@
   }
 
   private <T extends DexItem> void writeFixedSectionItems(T[] items, int offset,
-      ItemWriter<T> writer) throws ApiLevelException {
+      ThrowingConsumer<T, ApiLevelException> writer) throws ApiLevelException {
     assert dest.position() == offset;
     for (T item : items) {
       writer.accept(item);
     }
   }
 
-  /**
-   * Similar to a {@link Consumer} but throws an {@link ApiLevelException}.
-   */
-  @FunctionalInterface
-  private interface ItemWriter<T> {
-    void accept(T t) throws ApiLevelException;
-  }
-
   private <T extends DexItem> void writeItems(Collection<T> items, Consumer<Integer> offsetSetter,
       Consumer<T> writer) {
     writeItems(items, offsetSetter, writer, 1);
@@ -452,10 +443,10 @@
 
   private void writeFieldItem(DexField field) {
     int classIdx = mapping.getOffsetFor(field.clazz);
-    assert (short) classIdx == classIdx;
+    assert (classIdx & 0xFFFF) == classIdx;
     dest.putShort((short) classIdx);
     int typeIdx = mapping.getOffsetFor(field.type);
-    assert (short) typeIdx == typeIdx;
+    assert (typeIdx & 0xFFFF) == typeIdx;
     dest.putShort((short) typeIdx);
     DexString name = namingLens.lookupName(field);
     dest.putInt(mapping.getOffsetFor(name));
@@ -463,10 +454,10 @@
 
   private void writeMethodItem(DexMethod method) {
     int classIdx = mapping.getOffsetFor(method.holder);
-    assert (short) classIdx == classIdx;
+    assert (classIdx & 0xFFFF) == classIdx;
     dest.putShort((short) classIdx);
     int protoIdx = mapping.getOffsetFor(method.proto);
-    assert (short) protoIdx == protoIdx;
+    assert (protoIdx & 0xFFFF) == protoIdx;
     dest.putShort((short) protoIdx);
     DexString name = namingLens.lookupName(method);
     dest.putInt(mapping.getOffsetFor(name));
@@ -701,7 +692,7 @@
       assert methodHandle.isFieldHandle();
       fieldOrMethodIdx = mapping.getOffsetFor(methodHandle.asField());
     }
-    assert (short) fieldOrMethodIdx == fieldOrMethodIdx;
+    assert (fieldOrMethodIdx & 0xFFFF) == fieldOrMethodIdx;
     dest.putShort((short) fieldOrMethodIdx);
     dest.putShort((short) 0); // unused
   }
@@ -1359,8 +1350,7 @@
   private void checkThatInvokeCustomIsAllowed() throws ApiLevelException {
     if (!options.canUseInvokeCustom()) {
       throw new ApiLevelException(
-          Constants.ANDROID_O_API,
-          "Android O",
+          AndroidApiLevel.O,
           "Invoke-customs",
           null /* sourceString */);
     }
diff --git a/src/main/java/com/android/tools/r8/dex/Segment.java b/src/main/java/com/android/tools/r8/dex/Segment.java
index 0d782d6..7836438 100644
--- a/src/main/java/com/android/tools/r8/dex/Segment.java
+++ b/src/main/java/com/android/tools/r8/dex/Segment.java
@@ -71,6 +71,7 @@
     }
   }
 
+  @Override
   public String toString() {
     return typeName() + " @" + offset + " " + length;
   }
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index cbca177..c40d5a5 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -173,11 +173,11 @@
     return isFull(transaction.getNumberOfMethods(), transaction.getNumberOfFields(), MAX_ENTRIES);
   }
 
-  void throwIfFull(boolean hasMainDexList) {
+  void throwIfFull(boolean hasMainDexList) throws DexOverflowException {
     if (!isFull()) {
       return;
     }
-    throw new MainDexError(
+    throw new DexOverflowException(
         hasMainDexList,
         transaction.getNumberOfMethods(),
         transaction.getNumberOfFields(),
@@ -217,7 +217,8 @@
       this.writer = writer;
     }
 
-    public abstract Map<Integer, VirtualFile> run() throws ExecutionException, IOException;
+    public abstract Map<Integer, VirtualFile> run()
+        throws ExecutionException, IOException, DexOverflowException;
   }
 
   public static class FilePerClassDistributor extends Distributor {
@@ -226,7 +227,8 @@
       super(writer);
     }
 
-    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+    @Override
+    public Map<Integer, VirtualFile> run() {
       // Assign dedicated virtual files for all program classes.
       for (DexProgramClass clazz : application.classes()) {
         VirtualFile file = new VirtualFile(nameToFileMap.size(), writer.namingLens);
@@ -258,7 +260,7 @@
       originalNames = computeOriginalNameMapping(classes, application.getProguardMap());
     }
 
-    protected void fillForMainDexList(Set<DexProgramClass> classes) {
+    protected void fillForMainDexList(Set<DexProgramClass> classes) throws DexOverflowException {
       if (!application.mainDexList.isEmpty()) {
         VirtualFile mainDexFile = nameToFileMap.get(0);
         for (DexType type : application.mainDexList) {
@@ -322,7 +324,8 @@
       this.fillStrategy = FillStrategy.FILL_MAX;
     }
 
-    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+    @Override
+    public Map<Integer, VirtualFile> run() throws IOException, DexOverflowException {
       // First fill required classes into the main dex file.
       fillForMainDexList(classes);
       if (classes.isEmpty()) {
@@ -357,7 +360,8 @@
     }
 
     @Override
-    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+    public Map<Integer, VirtualFile> run()
+        throws ExecutionException, IOException, DexOverflowException {
       // Add all classes to the main dex file.
       for (DexProgramClass programClass : classes) {
         mainDexFile.addClass(programClass);
@@ -381,7 +385,9 @@
       this.executorService = executorService;
     }
 
-    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+    @Override
+    public Map<Integer, VirtualFile> run()
+        throws ExecutionException, IOException, DexOverflowException {
       // Strategy for distributing classes for write out:
       // 1. Place all files in the package distribution file in the proposed files (if any).
       // 2. Place the remaining files based on their packages in sorted order.
diff --git a/src/main/java/com/android/tools/r8/errors/DexOverflowException.java b/src/main/java/com/android/tools/r8/errors/DexOverflowException.java
new file mode 100644
index 0000000..a94d206
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/DexOverflowException.java
@@ -0,0 +1,91 @@
+// 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.errors;
+
+import com.android.tools.r8.CompilationException;
+
+/**
+ * Signals when there were too many items to fit in a given dex file.
+ */
+public class DexOverflowException extends CompilationException {
+
+  private final boolean hasMainDexList;
+  private final long numOfMethods;
+  private final long numOfFields;
+  private final long maxNumOfEntries;
+
+  public DexOverflowException(
+      boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
+    super();
+    this.hasMainDexList = hasMainDexList;
+    this.numOfMethods = numOfMethods;
+    this.numOfFields = numOfFields;
+    this.maxNumOfEntries = maxNumOfEntries;
+  }
+
+  private StringBuilder getGeneralMessage() {
+    StringBuilder messageBuilder = new StringBuilder();
+    // General message: Cannot fit.
+    messageBuilder.append("Cannot fit requested classes in ");
+    messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
+    messageBuilder.append("dex file");
+
+    return messageBuilder;
+  }
+
+  private String getNumberRelatedMessage() {
+    StringBuilder messageBuilder = new StringBuilder();
+    // Show the numbers of methods and/or fields that exceed the limit.
+    if (numOfMethods > maxNumOfEntries) {
+      messageBuilder.append("# methods: ");
+      messageBuilder.append(numOfMethods);
+      messageBuilder.append(" > ").append(maxNumOfEntries);
+      if (numOfFields > maxNumOfEntries) {
+        messageBuilder.append(" ; ");
+      }
+    }
+    if (numOfFields > maxNumOfEntries) {
+      messageBuilder.append("# fields: ");
+      messageBuilder.append(numOfFields);
+      messageBuilder.append(" > ").append(maxNumOfEntries);
+    }
+
+    return messageBuilder.toString();
+  }
+
+  @Override
+  public String getMessage() {
+    // Default message
+    return getGeneralMessage()
+        .append(" (")
+        .append(getNumberRelatedMessage())
+        .append(")")
+        .toString();
+  }
+
+  @Override
+  public String getMessageForD8() {
+    StringBuilder messageBuilder = getGeneralMessage();
+    if (!hasMainDexList) {
+      messageBuilder.append(". ");
+      messageBuilder.append("Try supplying a main-dex list");
+    }
+    messageBuilder.append(".").append(System.getProperty("line.separator"));
+    messageBuilder.append(getNumberRelatedMessage());
+    return messageBuilder.toString();
+  }
+
+  @Override
+  public String getMessageForR8() {
+    StringBuilder messageBuilder = getGeneralMessage();
+    if (!hasMainDexList) {
+      messageBuilder.append(". ");
+      messageBuilder.append("Try supplying a main-dex list or main dex rules");
+    }
+    messageBuilder.append(".").append(System.getProperty("line.separator"));
+    messageBuilder.append(getNumberRelatedMessage());
+    return messageBuilder.toString();
+  }
+
+}
diff --git a/src/main/java/com/android/tools/r8/errors/MainDexError.java b/src/main/java/com/android/tools/r8/errors/MainDexError.java
deleted file mode 100644
index 4c23aca..0000000
--- a/src/main/java/com/android/tools/r8/errors/MainDexError.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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.errors;
-
-/**
- * Exception regarding main-dex list and main dex rules.
- *
- * Depending on tool kind, this exception should be massaged, e.g., adding appropriate suggestions,
- * and re-thrown as {@link CompilationError}, which will be in turn informed to the user as an
- * expected compilation error.
- */
-public class MainDexError extends RuntimeException {
-
-  private final boolean hasMainDexList;
-  private final long numOfMethods;
-  private final long numOfFields;
-  private final long maxNumOfEntries;
-
-  public MainDexError(
-      boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
-    this.hasMainDexList = hasMainDexList;
-    this.numOfMethods = numOfMethods;
-    this.numOfFields = numOfFields;
-    this.maxNumOfEntries = maxNumOfEntries;
-  }
-
-  private String getGeneralMessage() {
-    StringBuilder messageBuilder = new StringBuilder();
-    // General message: Cannot fit.
-    messageBuilder.append("Cannot fit requested classes in ");
-    messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
-    messageBuilder.append("dex file.\n");
-
-    return messageBuilder.toString();
-  }
-
-  private String getNumberRelatedMessage() {
-    StringBuilder messageBuilder = new StringBuilder();
-    // Show the numbers of methods and/or fields that exceed the limit.
-    if (numOfMethods > maxNumOfEntries) {
-      messageBuilder.append("# methods: ");
-      messageBuilder.append(numOfMethods);
-      messageBuilder.append(" > ").append(maxNumOfEntries).append('\n');
-    }
-    if (numOfFields > maxNumOfEntries) {
-      messageBuilder.append("# fields: ");
-      messageBuilder.append(numOfFields);
-      messageBuilder.append(" > ").append(maxNumOfEntries).append('\n');
-    }
-
-    return messageBuilder.toString();
-  }
-
-  @Override
-  public String getMessage() {
-    // Placeholder to generate a general error message for other (minor) utilities:
-    //   Bisect, disassembler, dexsegments.
-    // Implement tool-specific error message generator, like D8 and R8 below, if necessary.
-    return getGeneralMessage() + getNumberRelatedMessage();
-  }
-
-  public String getMessageForD8() {
-    StringBuilder messageBuilder = new StringBuilder();
-    messageBuilder.append(getGeneralMessage());
-    if (hasMainDexList) {
-      messageBuilder.append("Classes required by the main-dex list ");
-      messageBuilder.append("do not fit in one dex.\n");
-    } else {
-      messageBuilder.append("Try supplying a main-dex list.\n");
-    }
-    messageBuilder.append(getNumberRelatedMessage());
-    return messageBuilder.toString();
-  }
-
-  public String getMessageForR8() {
-    StringBuilder messageBuilder = new StringBuilder();
-    messageBuilder.append(getGeneralMessage());
-    if (hasMainDexList) {
-      messageBuilder.append("Classes required by main dex rules and the main-dex list ");
-      messageBuilder.append("do not fit in one dex.\n");
-    } else {
-      messageBuilder.append("Try supplying a main-dex list or main dex rules.\n");
-    }
-    messageBuilder.append(getNumberRelatedMessage());
-    return messageBuilder.toString();
-  }
-
-}
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 3845507..0ae2cbc 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
@@ -13,7 +14,8 @@
 
 public abstract class Code extends CachedHashValueDexItem {
 
-  public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options);
+  public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+      throws ApiLevelException;
 
   public abstract void registerReachableDefinitions(UseRegistry registry);
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 3f492b5..50c22b8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.MoreObjects;
 import java.util.Arrays;
 import java.util.function.Consumer;
@@ -91,6 +92,16 @@
     }
   }
 
+  public <E extends Throwable> void forEachMethodThrowing(
+      ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
+    for (DexEncodedMethod method : directMethods()) {
+      consumer.accept(method);
+    }
+    for (DexEncodedMethod method : virtualMethods()) {
+      consumer.accept(method);
+    }
+  }
+
   public DexEncodedMethod[] allMethodsSorted() {
     int vLen = virtualMethods().length;
     int dLen = directMethods().length;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index f62dda0..8da2178 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SwitchPayload;
@@ -81,6 +82,17 @@
     this.debugInfo = debugInfo;
   }
 
+  public boolean hasDebugPositions() {
+    if (debugInfo != null) {
+      for (DexDebugEvent event : debugInfo.events) {
+        if (event instanceof DexDebugEvent.Default) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
     if (debugInfo == null) {
       return null;
@@ -142,12 +154,13 @@
     return false;
   }
 
-  boolean isEmptyVoidMethod() {
+  public boolean isEmptyVoidMethod() {
     return instructions.length == 1 && instructions[0] instanceof ReturnVoid;
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+      throws ApiLevelException {
     DexSourceCode source = new DexSourceCode(this, encodedMethod);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options);
     return builder.build();
@@ -156,7 +169,8 @@
   public IRCode buildIR(
       DexEncodedMethod encodedMethod,
       ValueNumberGenerator valueNumberGenerator,
-      InternalOptions options) {
+      InternalOptions options)
+      throws ApiLevelException {
     DexSourceCode source = new DexSourceCode(this, encodedMethod);
     IRBuilder builder = new IRBuilder(encodedMethod, source, valueNumberGenerator, options);
     return builder.build();
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index ffaf42b..3d68ace 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -91,8 +91,8 @@
       // If this is the end of the block clear out the pending state.
       pendingLocals = null;
       pendingLocalChanges = false;
-    } else {
-      // For non-exit / pc-advancing instructions emit any pending changes.
+    } else if (pc != emittedPc) {
+      // For non-exit / pc-advancing instructions emit any pending changes once possible.
       emitLocalChanges(pc);
     }
   }
@@ -224,6 +224,7 @@
       DexString nextFile,
       List<DexDebugEvent> events,
       DexItemFactory factory) {
+    assert previousPc != nextPc;
     int pcDelta = previousPc == NO_PC_INFO ? nextPc : nextPc - previousPc;
     int lineDelta = nextLine == NO_LINE_INFO ? 0 : nextLine - previousLine;
     assert pcDelta >= 0;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index e66b984..b76177b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.code.Const;
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.ConstStringJumbo;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -106,13 +108,13 @@
     return accessFlags.isConstructor() && accessFlags.isStatic();
   }
 
-  public boolean isInliningCandidate(DexEncodedMethod container, boolean alwaysInline,
+  public boolean isInliningCandidate(DexEncodedMethod container, Reason inliningReason,
       AppInfoWithSubtyping appInfo) {
     if (isClassInitializer()) {
       // This will probably never happen but never inline a class initializer.
       return false;
     }
-    if (alwaysInline) {
+    if (inliningReason == Reason.FORCE) {
       return true;
     }
     switch (compilationState) {
@@ -155,11 +157,12 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(InternalOptions options) {
+  public IRCode buildIR(InternalOptions options) throws ApiLevelException {
     return code == null ? null : code.buildIR(this, options);
   }
 
-  public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options) {
+  public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options)
+      throws ApiLevelException {
     return code == null
         ? null
         : code.asDexCode().buildIR(this, valueNumberGenerator, options);
@@ -206,6 +209,11 @@
     code = null;
   }
 
+  public boolean hasDebugPositions() {
+    assert code != null && code.isDexCode();
+    return code.asDexCode().hasDebugPositions();
+  }
+
   public String qualifiedName() {
     return method.qualifiedName();
   }
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 7dc77de..527f27b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -162,7 +162,7 @@
       "Ldalvik/annotation/SourceDebugExtension;");
   public final DexType annotationThrows = createType("Ldalvik/annotation/Throws;");
 
-  public void clearSubtypeInformation() {
+  public synchronized void clearSubtypeInformation() {
     types.values().forEach(DexType::clearSubtypeInformation);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 08c8a1c..73b3242 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -151,6 +151,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueByte(value);
     }
 
+    public byte getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeHeader(VALUE_BYTE, 0, dest);
@@ -196,6 +200,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueShort(value);
     }
 
+    public short getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeIntegerTo(VALUE_SHORT, value, Short.BYTES, dest);
@@ -240,6 +248,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueChar(value);
     }
 
+    public char getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
@@ -288,6 +300,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueInt(value);
     }
 
+    public int getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeIntegerTo(VALUE_INT, value, Integer.BYTES, dest);
@@ -332,6 +348,10 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueLong(value);
     }
 
+    public long getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeIntegerTo(VALUE_LONG, value, Long.BYTES, dest);
@@ -376,6 +396,10 @@
       return Float.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueFloat(value);
     }
 
+    public float getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
@@ -420,6 +444,10 @@
       return Double.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueDouble(value);
     }
 
+    public double getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       dest.forward(1);
@@ -508,6 +536,10 @@
       super(value);
     }
 
+    public DexString getValue() {
+      return value;
+    }
+
     @Override
     protected byte getValueKind() {
       return VALUE_STRING;
@@ -710,6 +742,10 @@
     private DexValueNull() {
     }
 
+    public Object getValue() {
+      return null;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeHeader(VALUE_NULL, 0, dest);
@@ -751,6 +787,10 @@
       return value ? TRUE : FALSE;
     }
 
+    public boolean getValue() {
+      return value;
+    }
+
     @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeHeader(VALUE_BOOLEAN, value ? 1 : 0, dest);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index b9173dd..3abe7c5 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -118,18 +118,21 @@
     @Override
     public DexType lookupType(DexType type, DexEncodedMethod context) {
       if (type.isArrayType()) {
-        DexType result = arrayTypeCache.get(type);
-        if (result == null) {
-          DexType baseType = type.toBaseType(dexItemFactory);
-          DexType newType = lookupType(baseType, context);
-          if (baseType == newType) {
-            result = type;
-          } else {
-            result = type.replaceBaseType(newType, dexItemFactory);
+        synchronized(this) {
+          // This block need to be synchronized due to arrayTypeCache.
+          DexType result = arrayTypeCache.get(type);
+          if (result == null) {
+            DexType baseType = type.toBaseType(dexItemFactory);
+            DexType newType = lookupType(baseType, context);
+            if (baseType == newType) {
+              result = type;
+            } else {
+              result = type.replaceBaseType(newType, dexItemFactory);
+            }
+            arrayTypeCache.put(type, result);
           }
-          arrayTypeCache.put(type, result);
+          return result;
         }
-        return result;
       }
       DexType previous = previousLense.lookupType(type, context);
       return typeMap.getOrDefault(previous, previous);
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 6b9c06d..1dc5964 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -80,7 +81,8 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+      throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
     return options.debug
         ? internalBuildWithLocals(encodedMethod, null, options)
@@ -88,7 +90,8 @@
   }
 
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+      throws ApiLevelException {
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
@@ -97,7 +100,8 @@
   }
 
   private IRCode internalBuildWithLocals(
-      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+      throws ApiLevelException {
     try {
       return internalBuild(encodedMethod, generator, options);
     } catch (InvalidDebugInfoException e) {
@@ -108,7 +112,8 @@
   }
 
   private IRCode internalBuild(
-      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+      throws ApiLevelException {
     if (!options.debug) {
       node.localVariables.clear();
     }
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/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index f5bc204..40a4776 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
@@ -140,29 +140,13 @@
       assert newInstruction.outValue() != null;
       current.outValue().replaceUsers(newInstruction.outValue());
     }
-    for (Value value : current.getDebugValues()) {
-      replaceInstructionInList(current, newInstruction, value.getDebugLocalStarts());
-      replaceInstructionInList(current, newInstruction, value.getDebugLocalEnds());
-      value.removeDebugUser(current);
-      newInstruction.addDebugValue(value);
-    }
+    current.moveDebugValues(newInstruction);
     newInstruction.setBlock(block);
     listIterator.remove();
     listIterator.add(newInstruction);
     current.clearBlock();
   }
 
-  private static void replaceInstructionInList(
-      Instruction instruction,
-      Instruction newInstruction,
-      List<Instruction> instructions) {
-    for (int i = 0; i < instructions.size(); i++) {
-      if (instructions.get(i) == instruction) {
-        instructions.set(i, newInstruction);
-      }
-    }
-  }
-
   public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) {
     List<BasicBlock> blocks = code.blocks;
     assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block;
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 661a602..ef5a025 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
@@ -26,7 +26,6 @@
     // are all for fixed register values. All other values that are used as the destination for
     // const number instructions should be marked as constants.
     assert dest.isFixedRegisterValue() || dest.definition.isConstNumber();
-    assert type != ConstType.OBJECT;
     this.type = type;
     this.value = value;
   }
@@ -50,7 +49,7 @@
   }
 
   public int getIntValue() {
-    assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
+    assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT || type == ConstType.OBJECT;
     return (int) value;
   }
 
@@ -77,6 +76,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) {
@@ -93,7 +100,7 @@
     }
 
     int register = builder.allocatedRegister(dest(), getNumber());
-    if (MoveType.fromConstType(type) == MoveType.SINGLE) {
+    if (MoveType.fromConstType(type) == MoveType.SINGLE || type == ConstType.OBJECT) {
       assert NumberUtils.is32Bit(value);
       if ((register & 0xf) == register && NumberUtils.is4Bit(value)) {
         builder.add(this, new Const4(register, (int) value));
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index 8fd9bf0..7dc5e75 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -88,7 +88,7 @@
 
   @Override
   public boolean canBeFolded() {
-    return super.canBeFolded() && !rightValue().getConstInstruction().asConstNumber().isZero();
+    return super.canBeFolded() && !rightValue().isZero();
   }
 
   @Override
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 13325d8..1cc21a3 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
@@ -14,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 {
 
@@ -24,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);
@@ -70,10 +72,11 @@
   public void addDebugValue(Value value) {
     assert value.getLocalInfo() != null;
     if (debugValues == null) {
-      debugValues = new ArrayList<>();
+      debugValues = new HashSet<>();
     }
-    debugValues.add(value);
-    value.addDebugUser(this);
+    if (debugValues.add(value)) {
+      value.addDebugUser(this);
+    }
   }
 
   public static void clearUserInfo(Instruction instruction) {
@@ -97,34 +100,34 @@
       if (oldValue == inValues.get(i)) {
         inValues.set(i, newValue);
         newValue.addUser(this);
-        oldValue.removeUser(this);
       }
     }
   }
 
-  // Similar to Phi::replaceTrivialPhi, removal can cause a concurrent modification error.
-  // TODO(ager): Consider unifying with other replace methods and avoid the C.M.E.
-  public boolean replaceDebugValue(Value oldValue, Value newValue) {
-    if (debugValues == null) {
-      return false;
-    }
-    int found = -1;
-    for (int i = 0; i < debugValues.size(); i++) {
-      if (oldValue == debugValues.get(i)) {
-        assert found == -1;
-        found = i;
-        if (newValue.getLocalInfo() != null) {
-          // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
-          debugValues.set(i, newValue);
-          newValue.addDebugUser(this);
-        }
+  public void replaceDebugValue(Value oldValue, Value newValue) {
+    if (debugValues.remove(oldValue)) {
+      if (newValue.getLocalInfo() != null) {
+        // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
+        addDebugValue(newValue);
       }
+      // TODO(zerny): Else: Insert a write if replacing a phi with associated debug-local info.
     }
-    if (found >= 0 && newValue.getLocalInfo() == null) {
-      // TODO(zerny): Insert a write if replacing a phi with associated debug-local info.
-      debugValues.remove(found);
+  }
+
+  public void moveDebugValues(Instruction target) {
+    if (debugValues == null) {
+      return;
     }
-    return found >= 0;
+    for (Value value : debugValues) {
+      value.replaceDebugUser(this, target);
+    }
+    debugValues.clear();
+  }
+
+  public void moveDebugValue(Value value, Instruction target) {
+    assert debugValues.contains(value);
+    value.replaceDebugUser(this, target);
+    debugValues.remove(value);
   }
 
   /**
@@ -324,8 +327,8 @@
     return outValue == null ? null : outValue.getLocalInfo();
   }
 
-  public List<Value> getDebugValues() {
-    return debugValues != null ? debugValues : ImmutableList.of();
+  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/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 3dff4f3..61ed928 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -27,6 +27,7 @@
   }
 
   public void setPosition(DebugPosition position) {
+    assert this.position == null;
     this.position = position;
   }
 
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 185d2fd..65995ff 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,18 +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.ImmutableList;
+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 List<Value> debugValues = null;
+  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
@@ -101,7 +102,7 @@
   public void addDebugValue(Value value) {
     assert value.getLocalInfo() != null;
     if (debugValues == null) {
-      debugValues = new ArrayList<>();
+      debugValues = new HashSet<>();
     }
     debugValues.add(value);
     value.addDebugPhiUser(this);
@@ -154,12 +155,7 @@
     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 replaceOperand(Value current, Value newValue) {
     for (int i = 0; i < operands.size(); i++) {
       if (operands.get(i) == current) {
         operands.set(i, newValue);
@@ -168,17 +164,11 @@
     }
   }
 
-  private void replaceTrivialDebugPhi(Value current, Value newValue) {
-    if (debugValues == null) {
-      return;
-    }
+  void replaceDebugValue(Value current, Value newValue) {
     assert current.getLocalInfo() != null;
     assert current.getLocalInfo() == newValue.getLocalInfo();
-    for (int i = 0; i < debugValues.size(); i++) {
-      if (debugValues.get(i) == current) {
-        debugValues.set(i, newValue);
-        newValue.addDebugPhiUser(this);
-      }
+    if (debugValues.remove(current)) {
+      addDebugValue(newValue);
     }
   }
 
@@ -213,35 +203,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) {
-      List<Instruction> removed = new ArrayList<>();
-      for (Instruction user : debugUsers()) {
-        if (user.replaceDebugValue(this, same)) {
-          removed.add(user);
-        }
-      }
-      removed.forEach(this::removeDebugUser);
-      for (Phi user : debugPhiUsers()) {
-        user.replaceTrivialDebugPhi(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()) {
@@ -251,9 +223,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.
+      replaceUsers(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);
@@ -296,9 +273,9 @@
   }
 
   private boolean isSingleConstZero(Value value) {
-    return value.definition != null && value.definition.isConstNumber() &&
-        value.definition.asConstNumber().isZero() &&
-        value.outType() == MoveType.SINGLE;
+    return value.definition != null
+        && value.outType() == MoveType.SINGLE
+        && value.isZero();
   }
 
   private MoveType computeOutType(Set<Phi> active) {
@@ -349,7 +326,7 @@
     return true;
   }
 
-  public List<Value> getDebugValues() {
-    return debugValues != null ? debugValues : ImmutableList.of();
+  public Set<Value> getDebugValues() {
+    return debugValues != null ? debugValues : ImmutableSet.of();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Rem.java b/src/main/java/com/android/tools/r8/ir/code/Rem.java
index 6521ae7..a257109 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Rem.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Rem.java
@@ -88,7 +88,7 @@
 
   @Override
   public boolean canBeFolded() {
-    return super.canBeFolded() && !rightValue().getConstInstruction().asConstNumber().isZero();
+    return super.canBeFolded() && !rightValue().isZero();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 6def63a..08ab002 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
@@ -13,6 +13,8 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.google.common.primitives.Ints;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.List;
 
 public class Switch extends JumpInstruction {
@@ -159,6 +161,11 @@
     }
   }
 
+  // Estimated size of the resulting dex instruction in code units (excluding the payload).
+  public static int estimatedDexSize() {
+    return 3;
+  }
+
   public int numberOfKeys() {
     return keys.length;
   }
@@ -175,6 +182,14 @@
     return targetBlockIndices;
   }
 
+  public Int2ReferenceSortedMap<BasicBlock> getKeyToTargetMap() {
+    Int2ReferenceSortedMap<BasicBlock> result = new Int2ReferenceAVLTreeMap<>();
+    for (int i = 0; i < keys.length; i++) {
+      result.put(getKey(i), targetBlock(i));
+    }
+    return result;
+  }
+
   @Override
   public BasicBlock fallthroughBlock() {
     return getBlock().getSuccessors().get(fallthroughBlockIndex);
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 78cb4a5..2880c87 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
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.utils.InternalOptions;
@@ -11,9 +12,12 @@
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 public class Value {
@@ -21,17 +25,49 @@
   // 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;
-    Set<Instruction> users = new HashSet<>();
+    Map<Instruction, DebugUse> users = new HashMap<>();
     Set<Phi> phiUsers = new HashSet<>();
-    List<Instruction> localStarts = new ArrayList<>();
-    List<Instruction> localEnds = new ArrayList<>();
 
     DebugData(DebugLocalInfo local) {
       this.local = local;
     }
   }
 
+  // A debug-value user represents a point where the value is live, ends or starts.
+  // If a point is marked as both ending and starting then it is simply live, but we maintain
+  // the marker so as not to unintentionally end it if marked again.
+  private enum DebugUse {
+    LIVE, START, END, LIVE_FINAL;
+
+    DebugUse start() {
+      switch (this) {
+        case LIVE:
+        case START:
+          return START;
+        case END:
+        case LIVE_FINAL:
+          return LIVE_FINAL;
+        default:
+          throw new Unreachable();
+      }
+    }
+
+    DebugUse end() {
+      switch (this) {
+        case LIVE:
+        case END:
+          return END;
+        case START:
+        case LIVE_FINAL:
+          return LIVE_FINAL;
+        default:
+          throw new Unreachable();
+      }
+    }
+  }
+
   public static final Value UNDEFINED = new Value(-1, MoveType.OBJECT, null);
 
   protected final int number;
@@ -48,6 +84,7 @@
   private boolean neverNull = false;
   private boolean isThis = false;
   private boolean isArgument = false;
+  private boolean knownToBeBoolean = false;
   private LongInterval valueRange;
   private final DebugData debugData;
 
@@ -78,21 +115,49 @@
   }
 
   public List<Instruction> getDebugLocalStarts() {
-    return debugData.localStarts;
+    if (debugData == null) {
+      return Collections.emptyList();
+    }
+    List<Instruction> starts = new ArrayList<>(debugData.users.size());
+    for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
+      if (entry.getValue() == DebugUse.START) {
+        starts.add(entry.getKey());
+      }
+    }
+    return starts;
   }
 
   public List<Instruction> getDebugLocalEnds() {
-    return debugData.localEnds;
+    if (debugData == null) {
+      return Collections.emptyList();
+    }
+    List<Instruction> ends = new ArrayList<>(debugData.users.size());
+    for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
+      if (entry.getValue() == DebugUse.END) {
+        ends.add(entry.getKey());
+      }
+    }
+    return ends;
   }
 
   public void addDebugLocalStart(Instruction start) {
     assert start != null;
-    debugData.localStarts.add(start);
+    debugData.users.put(start, markStart(debugData.users.get(start)));
+  }
+
+  private DebugUse markStart(DebugUse use) {
+    assert use != null;
+    return use == null ? DebugUse.START : use.start();
   }
 
   public void addDebugLocalEnd(Instruction end) {
     assert end != null;
-    debugData.localEnds.add(end);
+    debugData.users.put(end, markEnd(debugData.users.get(end)));
+  }
+
+  private DebugUse markEnd(DebugUse use) {
+    assert use != null;
+    return use == null ? DebugUse.END : use.end();
   }
 
   public void linkTo(Value other) {
@@ -151,7 +216,7 @@
   }
 
   public Set<Instruction> debugUsers() {
-    return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.users.keySet());
   }
 
   public Set<Phi> debugPhiUsers() {
@@ -243,7 +308,7 @@
     if (isUninitializedLocal()) {
       return;
     }
-    debugData.users.add(user);
+    debugData.users.putIfAbsent(user, DebugUse.LIVE);
   }
 
   public void addDebugPhiUser(Phi user) {
@@ -262,11 +327,19 @@
   }
 
   public void removeDebugUser(Instruction user) {
-    debugData.users.remove(user);
+    if (debugData != null && debugData.users != null) {
+      debugData.users.remove(user);
+      return;
+    }
+    assert false;
   }
 
   public void removeDebugPhiUser(Phi user) {
-    debugData.phiUsers.remove(user);
+    if (debugData != null && debugData.phiUsers != null) {
+      debugData.phiUsers.remove(user);
+      return;
+    }
+    assert false;
   }
 
   public boolean hasUsersInfo() {
@@ -289,46 +362,30 @@
       return;
     }
     for (Instruction user : uniqueUsers()) {
-      user.inValues.replaceAll(v -> {
-        if (v == this) {
-          newValue.addUser(user);
-          return newValue;
-        }
-        return v;
-      });
+      user.replaceValue(this, newValue);
     }
     for (Phi user : uniquePhiUsers()) {
-      user.getOperands().replaceAll(v -> {
-        if (v == this) {
-          newValue.addPhiUser(user);
-          return newValue;
-        }
-        return v;
-      });
+      user.replaceOperand(this, newValue);
     }
     if (debugData != null) {
       for (Instruction user : debugUsers()) {
-        user.getDebugValues().replaceAll(v -> {
-          if (v == this) {
-            newValue.addDebugUser(user);
-            return newValue;
-          }
-          return v;
-        });
+        user.replaceDebugValue(this, newValue);
       }
       for (Phi user : debugPhiUsers()) {
-        user.getDebugValues().replaceAll(v -> {
-          if (v == this) {
-            newValue.addDebugPhiUser(user);
-            return newValue;
-          }
-          return v;
-        });
+        user.replaceDebugValue(this, newValue);
       }
     }
     clearUsers();
   }
 
+  public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
+    DebugUse use = debugData.users.remove(oldUser);
+    if (use != null) {
+      newUser.addDebugValue(this);
+      debugData.users.put(newUser, use);
+    }
+  }
+
   public void setLiveIntervals(LiveIntervals intervals) {
     assert liveIntervals == null;
     liveIntervals = intervals;
@@ -469,6 +526,14 @@
     return isArgument;
   }
 
+  public void setKnownToBeBoolean(boolean knownToBeBoolean) {
+    this.knownToBeBoolean = knownToBeBoolean;
+  }
+
+  public boolean knownToBeBoolean() {
+    return knownToBeBoolean;
+  }
+
   public void markAsThis() {
     assert isArgument;
     assert !isThis;
@@ -549,4 +614,10 @@
     }
     return true;
   }
+
+  public boolean isZero() {
+    return isConstant()
+        && getConstInstruction().isConstNumber()
+        && getConstInstruction().asConstNumber().isZero();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index dad58d8..b32f34d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -18,11 +18,11 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -31,8 +31,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
-import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
@@ -50,7 +50,7 @@
  * <p>
  * Recursive calls are not present.
  */
-public class CallGraph {
+public class CallGraph extends CallSiteInformation {
 
   private CallGraph(InternalOptions options) {
     this.shuffle = options.testing.irOrdering;
@@ -131,15 +131,7 @@
   }
 
   private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
-  private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>();
-  private final Function<List<DexEncodedMethod>, List<DexEncodedMethod>> shuffle;
-
-  // Returns whether the method->callee edge has been removed from the call graph
-  // to break a cycle in the call graph.
-  public boolean isBreaker(DexEncodedMethod method, DexEncodedMethod callee) {
-    Set<DexEncodedMethod> value = breakers.get(method);
-    return (value != null) && value.contains(callee);
-  }
+  private final Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> shuffle;
 
   private Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
   private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
@@ -169,10 +161,12 @@
    * For pinned methods (methods kept through Proguard keep rules) this will always answer
    * <code>false</code>.
    */
+  @Override
   public boolean hasSingleCallSite(DexEncodedMethod method) {
     return singleCallSite.contains(method);
   }
 
+  @Override
   public boolean hasDoubleCallSite(DexEncodedMethod method) {
     return doubleCallSite.contains(method);
   }
@@ -210,12 +204,10 @@
    * All nodes in the graph are extracted if called repeatedly until null is returned.
    * Please note that there are no cycles in this graph (see {@link #breakCycles}).
    * <p>
-   *
-   * @return List of {@link DexEncodedMethod}.
    */
-  private List<DexEncodedMethod> extractLeaves() {
+  private Set<DexEncodedMethod> extractLeaves() {
     if (isEmpty()) {
-      return Collections.emptyList();
+      return Collections.emptySet();
     }
     // First identify all leaves before removing them from the graph.
     List<Node> leaves = nodes.values().stream().filter(Node::isLeaf).collect(Collectors.toList());
@@ -223,7 +215,8 @@
       leaf.callers.forEach(caller -> caller.callees.remove(leaf));
       nodes.remove(leaf.method);
     });
-    return shuffle.apply(leaves.stream().map(leaf -> leaf.method).collect(Collectors.toList()));
+    return shuffle.apply(leaves.stream().map(leaf -> leaf.method)
+        .collect(Collectors.toCollection(LinkedHashSet::new)));
   }
 
   private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
@@ -245,8 +238,6 @@
           // We have a cycle; break it by removing node->callee.
           toBeRemoved.add(callee);
           callee.callers.remove(node);
-          breakers.computeIfAbsent(node.method,
-              ignore -> Sets.newIdentityHashSet()).add(callee.method);
         } else {
           numberOfCycles += traverse(callee, stack, marked);
         }
@@ -263,7 +254,6 @@
 
   private int breakCycles() {
     // Break cycles in this call graph by removing edges causing cycles.
-    // The remove edges are stored in @breakers.
     int numberOfCycles = 0;
     Set<Node> stack = Sets.newIdentityHashSet();
     Set<Node> marked = Sets.newIdentityHashSet();
@@ -293,15 +283,24 @@
     return nodes.size() == 0;
   }
 
-  public void forEachMethod(Consumer<DexEncodedMethod> consumer, ExecutorService executorService)
+  /**
+   * Applies the given method to all leaf nodes of the graph.
+   * <p>
+   * As second parameter, a predicate that can be used to decide whether another method is
+   * processed at the same time is passed. This can be used to avoid races in concurrent processing.
+   */
+  public <E extends Exception> void forEachMethod(
+      ThrowingBiConsumer<DexEncodedMethod, Predicate<DexEncodedMethod>, E> consumer,
+      ExecutorService executorService)
       throws ExecutionException {
     while (!isEmpty()) {
-      List<DexEncodedMethod> methods = extractLeaves();
+      Set<DexEncodedMethod> methods = extractLeaves();
       assert methods.size() > 0;
       List<Future<?>> futures = new ArrayList<>();
       for (DexEncodedMethod method : methods) {
         futures.add(executorService.submit(() -> {
-          consumer.accept(method);
+          consumer.accept(method, methods::contains);
+          return null; // we want a Callable not a Runnable to be able to throw
         }));
       }
       ThreadUtils.awaitFutures(futures);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
new file mode 100644
index 0000000..651fb6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -0,0 +1,38 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+
+public abstract class CallSiteInformation {
+
+  /**
+   * Check if the <code>method</code> is guaranteed to only have a single call site.
+   * <p>
+   * For pinned methods (methods kept through Proguard keep rules) this will always answer
+   * <code>false</code>.
+   */
+  public abstract boolean hasSingleCallSite(DexEncodedMethod method);
+
+  public abstract boolean hasDoubleCallSite(DexEncodedMethod method);
+
+  public static CallSiteInformation empty() {
+    return EmptyCallSiteInformation.EMPTY_INFO;
+  }
+
+  private static class EmptyCallSiteInformation extends CallSiteInformation {
+
+    private static EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation();
+
+    @Override
+    public boolean hasSingleCallSite(DexEncodedMethod method) {
+      return false;
+    }
+
+    @Override
+    public boolean hasDoubleCallSite(DexEncodedMethod method) {
+      return false;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 9e07ad6..17b0e36 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.code.FillArrayData;
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.code.FilledNewArray;
@@ -26,6 +27,7 @@
 import com.android.tools.r8.code.MoveResultWide;
 import com.android.tools.r8.code.SwitchPayload;
 import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAccessFlags;
 import com.android.tools.r8.graph.DexCode;
@@ -39,6 +41,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.MoveType;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -148,7 +151,7 @@
   }
 
   @Override
-  public void buildInstruction(IRBuilder builder, int instructionIndex) {
+  public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
     updateCurrentCatchHandlers(instructionIndex);
     emitDebugPosition(instructionIndex, builder);
     currentDexInstruction = code.instructions[instructionIndex];
@@ -161,6 +164,17 @@
   }
 
   @Override
+  public int getMoveExceptionRegister() {
+    // No register, move-exception is manually entered during construction.
+    return -1;
+  }
+
+  @Override
+  public DebugPosition getDebugPositionAtOffset(int offset) {
+    throw new Unreachable();
+  }
+
+  @Override
   public boolean verifyCurrentInstructionCanThrow() {
     return currentDexInstruction.canThrow();
   }
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 1f29d26..245a75a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.conversion;
 
+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.InternalCompilerError;
@@ -77,7 +78,9 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -92,6 +95,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -123,6 +127,15 @@
     }
   }
 
+  private static class MoveExceptionWorklistItem extends WorklistItem {
+    private final int targetOffset;
+
+    private MoveExceptionWorklistItem(BasicBlock block, int targetOffset) {
+      super(block, -1);
+      this.targetOffset = targetOffset;
+    }
+  }
+
   /**
    * Representation of lists of values that can be used as keys in maps. A list of
    * values is equal to another list of values if it contains exactly the same values
@@ -311,7 +324,7 @@
    *
    * @return The list of basic blocks. First block is the main entry.
    */
-  public IRCode build() {
+  public IRCode build() throws ApiLevelException {
     assert source != null;
     source.setUp();
 
@@ -441,7 +454,7 @@
     return true;
   }
 
-  private void processWorklist() {
+  private void processWorklist() throws ApiLevelException {
     for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) {
       if (item.block.isFilled()) {
         continue;
@@ -449,6 +462,11 @@
       setCurrentBlock(item.block);
       blocks.add(currentBlock);
       currentBlock.setNumber(nextBlockNumber++);
+      // Process synthesized move-exception block specially.
+      if (item instanceof MoveExceptionWorklistItem) {
+        processMoveExceptionItem((MoveExceptionWorklistItem) item);
+        continue;
+      }
       // Build IR for each dex instruction in the block.
       for (int i = item.firstInstructionIndex; i < source.instructionCount(); ++i) {
         if (currentBlock == null) {
@@ -467,6 +485,22 @@
     }
   }
 
+  private void processMoveExceptionItem(MoveExceptionWorklistItem moveExceptionItem) {
+    // TODO(zerny): Link with outer try-block handlers, if any. b/65203529
+    int moveExceptionDest = source.getMoveExceptionRegister();
+    assert moveExceptionDest >= 0;
+    int targetIndex = source.instructionIndex(moveExceptionItem.targetOffset);
+    Value out = writeRegister(moveExceptionDest, MoveType.OBJECT, ThrowingInfo.NO_THROW, null);
+    MoveException moveException = new MoveException(out);
+    moveException.setPosition(source.getDebugPositionAtOffset(moveExceptionItem.targetOffset));
+    currentBlock.add(moveException);
+    currentBlock.add(new Goto());
+    BasicBlock targetBlock = getTarget(moveExceptionItem.targetOffset);
+    currentBlock.link(targetBlock);
+    addToWorklist(targetBlock, targetIndex);
+    closeCurrentBlock();
+  }
+
   // Helper to resolve switch payloads and build switch instructions (dex code only).
   public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset) {
     source.resolveAndBuildSwitch(value, fallthroughOffset, payloadOffset, this);
@@ -500,6 +534,13 @@
     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));
+  }
+
   public void addDebugUninitialized(int register, ConstType type) {
     if (!options.debug) {
       return;
@@ -613,6 +654,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);
@@ -714,7 +756,7 @@
   }
 
   public void addNullConst(int dest, long value) {
-    canonicalizeAndAddConst(ConstType.INT, dest, value, nullConstants);
+    canonicalizeAndAddConst(ConstType.OBJECT, dest, value, nullConstants);
   }
 
   public void addConstClass(int dest, DexType type) {
@@ -883,6 +925,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);
@@ -908,12 +951,13 @@
     add(instruction);
   }
 
-  public void addInvoke(
-      Type type, DexItem item, DexProto callSiteProto, List<Value> arguments) {
+  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments)
+      throws ApiLevelException {
     if (type == Invoke.Type.POLYMORPHIC && !options.canUseInvokePolymorphic()) {
-      throw new CompilationError(
-          "MethodHandle.invoke and MethodHandle.invokeExact is unsupported before "
-              + "Android O (--min-api " + Constants.ANDROID_O_API + ")");
+      throw new ApiLevelException(
+          AndroidApiLevel.O,
+          "MethodHandle.invoke and MethodHandle.invokeExact",
+          null /* sourceString */);
     }
     add(Invoke.create(type, item, callSiteProto, null, arguments));
   }
@@ -923,7 +967,8 @@
       DexItem item,
       DexProto callSiteProto,
       List<MoveType> types,
-      List<Integer> registers) {
+      List<Integer> registers)
+      throws ApiLevelException {
     assert types.size() == registers.size();
     List<Value> arguments = new ArrayList<>(types.size());
     for (int i = 0; i < types.size(); i++) {
@@ -991,7 +1036,8 @@
       DexMethod method,
       DexProto callSiteProto,
       int argumentRegisterCount,
-      int[] argumentRegisters) {
+      int[] argumentRegisters)
+      throws ApiLevelException {
     // The value of argumentRegisterCount is the number of registers - not the number of values,
     // but it is an upper bound on the number of arguments.
     List<Value> arguments = new ArrayList<>(argumentRegisterCount);
@@ -1018,7 +1064,8 @@
     addInvoke(type, method, callSiteProto, arguments);
   }
 
-  public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters) {
+  public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters)
+      throws ApiLevelException {
     String descriptor = type.descriptor.toString();
     assert descriptor.charAt(0) == '[';
     assert descriptor.length() >= 2;
@@ -1042,7 +1089,8 @@
       DexMethod method,
       DexProto callSiteProto,
       int argumentCount,
-      int firstArgumentRegister) {
+      int firstArgumentRegister)
+      throws ApiLevelException {
     // The value of argumentCount is the number of registers - not the number of values, but it
     // is an upper bound on the number of arguments.
     List<Value> arguments = new ArrayList<>(argumentCount);
@@ -1069,7 +1117,8 @@
     addInvoke(type, method, callSiteProto, arguments);
   }
 
-  public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister) {
+  public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister)
+      throws ApiLevelException {
     String descriptor = type.descriptor.toString();
     assert descriptor.charAt(0) == '[';
     assert descriptor.length() >= 2;
@@ -1114,6 +1163,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);
@@ -1165,6 +1224,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);
@@ -1562,9 +1622,7 @@
   // Private instruction helpers.
   private void addInstruction(Instruction ir) {
     attachLocalChanges(ir);
-    if (currentDebugPosition != null && !ir.isMoveException()) {
-      flushCurrentDebugPosition();
-    }
+    flushCurrentDebugPosition(ir);
     currentBlock.add(ir);
     if (ir.instructionTypeCanThrow()) {
       assert source.verifyCurrentInstructionCanThrow();
@@ -1573,18 +1631,33 @@
         assert !throwingInstructionInCurrentBlock;
         throwingInstructionInCurrentBlock = true;
         List<BasicBlock> targets = new ArrayList<>(catchHandlers.getAllTargets().size());
-        for (int targetOffset : catchHandlers.getAllTargets()) {
-          BasicBlock target = getTarget(targetOffset);
-          addToWorklist(target, source.instructionIndex(targetOffset));
-          targets.add(target);
+        int moveExceptionDest = source.getMoveExceptionRegister();
+        if (moveExceptionDest < 0) {
+          for (int targetOffset : catchHandlers.getAllTargets()) {
+            BasicBlock target = getTarget(targetOffset);
+            addToWorklist(target, source.instructionIndex(targetOffset));
+            targets.add(target);
+          }
+        } else {
+          // If there is a well-defined move-exception destination register (eg, compiling from
+          // Java-bytecode) then we construct move-exception header blocks for each unique target.
+          Map<BasicBlock, BasicBlock> moveExceptionHeaders =
+              new IdentityHashMap<>(catchHandlers.getUniqueTargets().size());
+          for (int targetOffset : catchHandlers.getAllTargets()) {
+            BasicBlock target = getTarget(targetOffset);
+            BasicBlock header = moveExceptionHeaders.get(target);
+            if (header == null) {
+              header = new BasicBlock();
+              header.incrementUnfilledPredecessorCount();
+              moveExceptionHeaders.put(target, header);
+              ssaWorklist.add(new MoveExceptionWorklistItem(header, targetOffset));
+            }
+            targets.add(header);
+          }
         }
         currentBlock.linkCatchSuccessors(catchHandlers.getGuards(), targets);
       }
     }
-    if (currentDebugPosition != null) {
-      assert ir.isMoveException();
-      flushCurrentDebugPosition();
-    }
   }
 
   private void attachLocalChanges(Instruction ir) {
@@ -1761,6 +1834,26 @@
         assert returnType == MoveType.fromDexType(method.method.proto.returnType);
       }
       closeCurrentBlock();
+
+      // Collect the debug values which are live on all returns.
+      Set<Value> debugValuesForReturn = Sets.newIdentityHashSet();
+      for (Value value : exitBlocks.get(0).exit().getDebugValues()) {
+        boolean include = true;
+        for (int i = 1; i < exitBlocks.size() && include; i++) {
+          include = exitBlocks.get(i).exit().getDebugValues().contains(value);
+        }
+        if (include) {
+          debugValuesForReturn.add(value);
+        }
+      }
+
+      // Move all these debug values to the new return.
+      for (Value value : debugValuesForReturn) {
+        for (BasicBlock block : exitBlocks) {
+          block.exit().moveDebugValue(value, normalExitBlock.exit());
+        }
+      }
+
       // Replace each return instruction with a goto to the new exit block.
       List<Value> operands = new ArrayList<>();
       for (BasicBlock block : exitBlocks) {
@@ -1772,12 +1865,7 @@
         }
         Goto gotoExit = new Goto();
         gotoExit.setBlock(block);
-        if (options.debug) {
-          for (Value value : ret.getDebugValues()) {
-            gotoExit.addDebugValue(value);
-            value.removeDebugUser(ret);
-          }
-        }
+        ret.moveDebugValues(gotoExit);
         instructions.set(instructions.size() - 1, gotoExit);
         block.link(normalExitBlock);
         gotoExit.setTarget(normalExitBlock);
@@ -1977,26 +2065,23 @@
 
   public void updateCurrentDebugPosition(int line, DexString file) {
     // Stack-trace support requires position information in both debug and release mode.
-    flushCurrentDebugPosition();
+    flushCurrentDebugPosition(null);
     currentDebugPosition = new DebugPosition(line, file);
     attachLocalChanges(currentDebugPosition);
   }
 
-  private void flushCurrentDebugPosition() {
+  private void flushCurrentDebugPosition(Instruction instruction) {
     if (currentDebugPosition != null) {
       DebugPosition position = currentDebugPosition;
       currentDebugPosition = null;
-      if (!currentBlock.getInstructions().isEmpty()) {
-        MoveException move = currentBlock.getInstructions().getLast().asMoveException();
-        if (move != null && move.getPosition() == null) {
-          // Set the position on the move-exception instruction.
-          // ART/DX associates the move-exception pc with the catch-declaration line.
-          // See debug.ExceptionTest.testStepOnCatch().
-          move.setPosition(position);
-          return;
-        }
+      if (instruction != null && instruction.isMoveException()) {
+        // Set the position on the move-exception instruction.
+        // ART/DX associates the move-exception pc with the catch-declaration line.
+        // See debug.ExceptionTest.testStepOnCatch().
+        instruction.asMoveException().setPosition(position);
+      } else {
+        addInstruction(position);
       }
-      addInstruction(position);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index fad378e..5b5d382 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -47,6 +48,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.function.BiConsumer;
+import java.util.function.Predicate;
 
 public class IRConverter {
 
@@ -66,7 +68,6 @@
   private final LensCodeRewriter lensCodeRewriter;
   private final Inliner inliner;
   private final ProtoLitePruner protoLiteRewriter;
-  private CallGraph callGraph;
 
   private OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
   private DexString highestSortingString;
@@ -196,7 +197,7 @@
     }
   }
 
-  private void synthesizeLambdaClasses(Builder builder) {
+  private void synthesizeLambdaClasses(Builder builder) throws ApiLevelException {
     if (lambdaRewriter != null) {
       lambdaRewriter.adjustAccessibility();
       lambdaRewriter.synthesizeLambdaClasses(builder);
@@ -204,13 +205,15 @@
   }
 
   private void desugarInterfaceMethods(
-      Builder builder, InterfaceMethodRewriter.Flavor includeAllResources) {
+      Builder builder, InterfaceMethodRewriter.Flavor includeAllResources)
+      throws ApiLevelException {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(builder, includeAllResources);
     }
   }
 
-  public DexApplication convertToDex(ExecutorService executor) throws ExecutionException {
+  public DexApplication convertToDex(ExecutorService executor)
+      throws ExecutionException, ApiLevelException {
     removeLambdaDeserializationMethods();
 
     convertClassesToDex(application.classes(), executor);
@@ -229,28 +232,31 @@
       ExecutorService executor) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
-      futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
+      futures.add(
+          executor.submit(
+              () -> {
+                clazz.forEachMethodThrowing(this::convertMethodToDex);
+                return null; // we want a Callable not a Runnable to be able to throw
+              }));
     }
-
     ThreadUtils.awaitFutures(futures);
-
-    // Get rid of <clinit> methods with no code.
-    removeEmptyClassInitializers();
   }
 
-  private void convertMethodToDex(DexEncodedMethod method) {
+  void convertMethodToDex(DexEncodedMethod method) throws ApiLevelException {
     if (method.getCode() != null) {
       boolean matchesMethodFilter = options.methodMatchesFilter(method);
       if (matchesMethodFilter) {
         if (method.getCode().isJarCode()) {
-          rewriteCode(method, ignoreOptimizationFeedback, Outliner::noProcessing);
+          // We do not process in call graph order, so anything could be a leaf.
+          rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+              Outliner::noProcessing);
         }
         updateHighestSortingStrings(method);
       }
     }
   }
 
-  public DexApplication optimize() throws ExecutionException {
+  public DexApplication optimize() throws ExecutionException, ApiLevelException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
       return optimize(executor);
@@ -259,13 +265,10 @@
     }
   }
 
-  public DexApplication optimize(ExecutorService executorService) throws ExecutionException {
+  public DexApplication optimize(ExecutorService executorService)
+      throws ExecutionException, ApiLevelException {
     removeLambdaDeserializationMethods();
 
-    timing.begin("Build call graph");
-    callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense, options);
-    timing.end();
-
     // The process is in two phases.
     // 1) Subject all DexEncodedMethods to optimization (except outlining).
     //    - a side effect is candidates for outlining are identified.
@@ -273,16 +276,19 @@
     // Ideally, we should outline eagerly when threshold for a template has been reached.
 
     // Process the application identifying outlining candidates.
-    timing.begin("IR conversion phase 1");
     OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
-    callGraph.forEachMethod(method -> {
-          processMethod(method, directFeedback,
-              outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
-    }, executorService);
-    timing.end();
-
-    // Get rid of <clinit> methods with no code.
-    removeEmptyClassInitializers();
+    {
+      timing.begin("Build call graph");
+      CallGraph callGraph = CallGraph
+          .build(application, appInfo.withSubtyping(), graphLense, options);
+      timing.end();
+      timing.begin("IR conversion phase 1");
+      callGraph.forEachMethod((method, isProcessedConcurrently) -> {
+        processMethod(method, directFeedback, isProcessedConcurrently, callGraph,
+            outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
+      }, executorService);
+      timing.end();
+    }
 
     // Build a new application with jumbo string info.
     Builder builder = new Builder(application);
@@ -304,16 +310,17 @@
       if (outlineClass != null) {
         // We need a new call graph to ensure deterministic order and also processing inside out
         // to get maximal inlining. Use a identity lense, as the code has been rewritten.
-        callGraph = CallGraph
+        CallGraph callGraph = CallGraph
             .build(application, appInfo.withSubtyping(), GraphLense.getIdentityLense(), options);
         Set<DexEncodedMethod> outlineMethods = outliner.getMethodsSelectedForOutlining();
-        callGraph.forEachMethod(method -> {
+        callGraph.forEachMethod((method, isProcessedConcurrently) -> {
           if (!outlineMethods.contains(method)) {
             return;
           }
           // This is the second time we compile this method first mark it not processed.
           assert !method.getCode().isOutlineCode();
-          processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate);
+          processMethod(method, ignoreOptimizationFeedback, isProcessedConcurrently, callGraph,
+              outliner::applyOutliningCandidate);
           assert method.isProcessed();
         }, executorService);
         builder.addSynthesizedClass(outlineClass, true);
@@ -325,16 +332,6 @@
     return builder.build();
   }
 
-  private void removeEmptyClassInitializers() {
-    application.classes().forEach(this::removeEmptyClassInitializer);
-  }
-
-  private void removeEmptyClassInitializer(DexProgramClass clazz) {
-    if (clazz.hasTrivialClassInitializer()) {
-      clazz.removeStaticMethod(clazz.getClassInitializer());
-    }
-  }
-
   private void clearDexMethodCompilationState() {
     application.classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -380,7 +377,7 @@
     return result;
   }
 
-  private DexProgramClass prepareOutlining() {
+  private DexProgramClass prepareOutlining() throws ApiLevelException {
     if (!outliner.selectMethodsForOutlining()) {
       return null;
     }
@@ -389,14 +386,15 @@
     return outlineClass;
   }
 
-  public void optimizeSynthesizedClass(DexProgramClass clazz) {
+  public void optimizeSynthesizedClass(DexProgramClass clazz) throws ApiLevelException {
     // Process the generated class, but don't apply any outlining.
-    clazz.forEachMethod(this::optimizeSynthesizedMethod);
+    clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
   }
 
-  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
+  public void optimizeSynthesizedMethod(DexEncodedMethod method) throws ApiLevelException {
     // Process the generated method, but don't apply any outlining.
-    processMethod(method, ignoreOptimizationFeedback, Outliner::noProcessing);
+    processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
+        Outliner::noProcessing);
   }
 
   private String logCode(InternalOptions options, DexEncodedMethod method) {
@@ -405,11 +403,14 @@
 
   public void processMethod(DexEncodedMethod method,
       OptimizationFeedback feedback,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      CallSiteInformation callSiteInformation,
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
+          throws ApiLevelException {
     Code code = method.getCode();
     boolean matchesMethodFilter = options.methodMatchesFilter(method);
     if (code != null && matchesMethodFilter) {
-      rewriteCode(method, feedback, outlineHandler);
+      rewriteCode(method, feedback, isProcessedConcurrently, callSiteInformation, outlineHandler);
     } else {
       // Mark abstract methods as processed as well.
       method.markProcessed(Constraint.NEVER);
@@ -418,7 +419,10 @@
 
   private void rewriteCode(DexEncodedMethod method,
       OptimizationFeedback feedback,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      CallSiteInformation callSiteInformation,
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
+      throws ApiLevelException {
     if (options.verbose) {
       System.out.println("Processing: " + method.toSourceString());
     }
@@ -462,7 +466,7 @@
     if (options.inlineAccessors && inliner != null) {
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       assert !options.debug;
-      inliner.performInlining(method, code, callGraph);
+      inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
     }
     codeRewriter.removeCastChains(code);
     codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
@@ -473,7 +477,9 @@
     codeRewriter.foldConstants(code);
     codeRewriter.rewriteSwitch(code);
     codeRewriter.simplifyIf(code);
-    codeRewriter.collectClassInitializerDefaults(method, code);
+    if (!options.debug) {
+      codeRewriter.collectClassInitializerDefaults(method, code);
+    }
     if (Log.ENABLED) {
       Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
           method.toSourceString(), code);
@@ -549,7 +555,6 @@
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
     registerAllocator.allocateRegisters(options.debug);
     printMethod(code, "After register allocation (non-SSA)");
-    printLiveRanges(registerAllocator, "Final live ranges.");
     if (!options.debug) {
       CodeRewriter.removedUnneededDebugPositions(code);
     }
@@ -584,10 +589,4 @@
       printer.end("cfg");
     }
   }
-
-  private void printLiveRanges(LinearScanRegisterAllocator allocator, String title) {
-    if (printer != null) {
-      allocator.print(printer, title);
-    }
-  }
 }
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 dc49630..2ab087c 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
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+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;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.ConstType;
+import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.MemberType;
@@ -32,7 +34,9 @@
 import com.android.tools.r8.ir.conversion.JarState.Local;
 import com.android.tools.r8.ir.conversion.JarState.Slot;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.io.PrintWriter;
@@ -46,7 +50,6 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
-import java.util.function.BiConsumer;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -135,10 +138,10 @@
       "([Ljava/lang/Object;)Ljava/lang/Object;";
 
   // Language types.
-  private static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class");
-  private static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
-  private static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC);
-  private static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable");
+  static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class");
+  static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
+  static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC);
+  static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable");
 
   private static final int[] NO_TARGETS = {};
 
@@ -263,6 +266,7 @@
     // Record types for arguments.
     Int2ReferenceMap<MoveType> argumentLocals = recordArgumentTypes();
     Int2ReferenceMap<MoveType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
+    Int2ReferenceMap<ConstType> uninitializedLocals = new Int2ReferenceOpenHashMap<>();
     // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
@@ -291,20 +295,34 @@
         int localRegister2 = state.writeLocal(local.index, localType);
         assert localRegister == localRegister2;
         initializedLocals.put(localRegister, moveType(localType));
-        builder.addDebugUninitialized(localRegister, constType(localType));
+        uninitializedLocals.put(localRegister, constType(localType));
       }
     }
+
+    // TODO(zerny): This is getting a little out of hands. Clean it up.
+
     // Add debug information for all locals at the initial label.
+    List<Local> locals = null;
     if (initialLabel != null) {
-      for (Local local : state.openLocals(initialLabel)) {
+      locals = state.openLocals(initialLabel);
+    }
+
+    // Build the actual argument instructions now that type and debug information is known
+    // for arguments.
+    buildArgumentInstructions(builder);
+
+    for (Entry<ConstType> entry : uninitializedLocals.int2ReferenceEntrySet()) {
+      builder.addDebugUninitialized(entry.getIntKey(), entry.getValue());
+    }
+
+    if (locals != null) {
+      for (Local local : locals) {
         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.
-    buildArgumentInstructions(builder);
+
     if (isSynchronized()) {
       generatingMethodSynchronization = true;
       Type clazzType = Type.getType(clazz.toDescriptorString());
@@ -337,7 +355,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();
     }
   }
@@ -371,14 +393,6 @@
     state.recordStateForTarget(0, this);
     for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) {
       state.restoreState(item.instructionIndex);
-      // If the block being restored is a try-catch handler push the exception on the stack.
-      for (int i = 0; i < node.tryCatchBlocks.size(); i++) {
-        TryCatchBlockNode tryCatchBlockNode = (TryCatchBlockNode) node.tryCatchBlocks.get(i);
-        if (tryCatchBlockNode.handler == getInstruction(item.instructionIndex)) {
-          state.push(THROWABLE_TYPE);
-          break;
-        }
-      }
       // Iterate each of the instructions in the block to compute the outgoing JarState.
       for (int i = item.instructionIndex; i <= instructionCount(); ++i) {
         // If we are at the end of the instruction stream or if we have reached the start
@@ -436,10 +450,8 @@
   private void buildExceptionalPostlude(IRBuilder builder) {
     assert isSynchronized();
     generatingMethodSynchronization = true;
-    int exceptionRegister = 0; // We are exiting the method so we just overwrite register 0.
-    builder.addMoveException(exceptionRegister);
     buildMonitorExit(builder);
-    builder.addThrow(exceptionRegister);
+    builder.addThrow(getMoveExceptionRegister());
     generatingMethodSynchronization = false;
   }
 
@@ -457,7 +469,7 @@
   }
 
   @Override
-  public void buildInstruction(IRBuilder builder, int instructionIndex) {
+  public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
     if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
       buildExceptionalPostlude(builder);
       return;
@@ -469,14 +481,6 @@
     // If a new block is starting here, we restore the computed JarState.
     if (builder.getCFG().containsKey(instructionIndex) || instructionIndex == 0) {
       state.restoreState(instructionIndex);
-      // If the block being restored is a try-catch handler push the exception on the stack.
-      for (int i = 0; i < node.tryCatchBlocks.size(); i++) {
-        TryCatchBlockNode tryCatchBlockNode = (TryCatchBlockNode) node.tryCatchBlocks.get(i);
-        if (tryCatchBlockNode.handler == insn) {
-          builder.addMoveException(state.push(THROWABLE_TYPE));
-          break;
-        }
-      }
     }
 
     String preInstructionState;
@@ -547,6 +551,11 @@
   }
 
   @Override
+  public int getMoveExceptionRegister() {
+    return state.startOfStack;
+  }
+
+  @Override
   public boolean verifyCurrentInstructionCanThrow() {
     return generatingMethodSynchronization || canThrow(currentInstruction);
   }
@@ -1773,7 +1782,7 @@
 
   // IR instruction building procedures.
 
-  private void build(AbstractInsnNode insn, IRBuilder builder) {
+  private void build(AbstractInsnNode insn, IRBuilder builder) throws ApiLevelException {
     switch (insn.getType()) {
       case AbstractInsnNode.INSN:
         build((InsnNode) insn, builder);
@@ -2286,8 +2295,19 @@
     }
   }
 
+  private boolean isExitingThrow(InsnNode insn) {
+    List<TryCatchBlock> handlers = getTryHandlers(insn);
+    if (handlers.isEmpty()) {
+      return true;
+    }
+    if (!isSynchronized() || handlers.size() > 1) {
+      return false;
+    }
+    return handlers.get(0) == EXCEPTIONAL_SYNC_EXIT;
+  }
+
   private void addThrow(InsnNode insn, int register, IRBuilder builder) {
-    if (getTryHandlers(insn).isEmpty()) {
+    if (isExitingThrow(insn)) {
       processLocalVariablesAtExit(insn, builder);
     } else {
       processLocalVariablesAtControlEdge(insn, builder);
@@ -2501,7 +2521,7 @@
     }
   }
 
-  private void build(MethodInsnNode insn, IRBuilder builder) {
+  private void build(MethodInsnNode insn, IRBuilder builder) throws ApiLevelException {
     // Resolve the target method of the invoke.
     DexMethod method = application.getMethod(insn.owner, insn.name, insn.desc);
 
@@ -2524,8 +2544,12 @@
   }
 
   private void buildInvoke(
-      String methodDesc, Type methodOwner, boolean addImplicitReceiver,
-      IRBuilder builder, BiConsumer<List<MoveType>, List<Integer>> creator) {
+      String methodDesc,
+      Type methodOwner,
+      boolean addImplicitReceiver,
+      IRBuilder builder,
+      ThrowingBiConsumer<List<MoveType>, List<Integer>, ApiLevelException> creator)
+      throws ApiLevelException {
 
     // Build the argument list of the form [owner, param1, ..., paramN].
     // The arguments are in reverse order on the stack, so we pop off the parameters here.
@@ -2551,7 +2575,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));
+      }
     }
   }
 
@@ -2562,7 +2590,7 @@
     registers.add(slot.register);
   }
 
-  private void build(InvokeDynamicInsnNode insn, IRBuilder builder) {
+  private void build(InvokeDynamicInsnNode insn, IRBuilder builder) throws ApiLevelException {
     // Bootstrap method
     Handle bsmHandle = insn.bsm;
     if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC &&
@@ -2640,8 +2668,10 @@
       case Opcodes.H_PUTSTATIC:
         return MethodHandleType.STATIC_PUT;
       case Opcodes.H_INVOKESPECIAL:
+        assert !handle.getName().equals(Constants.INSTANCE_INITIALIZER_NAME);
+        assert !handle.getName().equals(Constants.CLASS_INITIALIZER_NAME);
         DexType owner = application.getTypeFromName(handle.getOwner());
-        if (owner == clazz || handle.getName().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+        if (owner == clazz) {
           return MethodHandleType.INVOKE_DIRECT;
         } else {
           return MethodHandleType.INVOKE_SUPER;
@@ -2762,7 +2792,7 @@
     builder.addSwitch(index, keys, fallthroughOffset, labelOffsets);
   }
 
-  private void build(MultiANewArrayInsnNode insn, IRBuilder builder) {
+  private void build(MultiANewArrayInsnNode insn, IRBuilder builder) throws ApiLevelException {
     // Type of the full array.
     Type arrayType = Type.getObjectType(insn.desc);
     DexType dexArrayType = application.getType(arrayType);
@@ -2804,6 +2834,23 @@
     builder.updateCurrentDebugPosition(insn.line, null);
   }
 
+  @Override
+  public DebugPosition getDebugPositionAtOffset(int offset) {
+    int index = instructionIndex(offset);
+    if (index < 0 || instructionCount() <= index) {
+      return null;
+    }
+    AbstractInsnNode insn = node.instructions.get(index);
+    if (insn instanceof LabelNode) {
+      insn = insn.getNext();
+    }
+    if (insn instanceof LineNumberNode) {
+      LineNumberNode line = (LineNumberNode) insn;
+      return new DebugPosition(line.line, null);
+    }
+    return null;
+  }
+
   // Printing helpers.
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 03c614a..12b62ad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -134,8 +134,7 @@
       return (sort == Type.OBJECT && otherSort == Type.ARRAY)
           || (sort == Type.ARRAY && otherSort == Type.OBJECT)
           || (sort == Type.OBJECT && otherSort == Type.OBJECT)
-          || (sort == Type.ARRAY && otherSort == Type.ARRAY
-              && isReferenceCompatible(getArrayElementType(type), getArrayElementType(other)));
+          || (sort == Type.ARRAY && otherSort == Type.ARRAY);
     }
   }
 
@@ -166,7 +165,7 @@
     }
   }
 
-  private final int startOfStack;
+  final int startOfStack;
   private int topOfStack;
 
   // Locals are split into three parts based on types:
@@ -344,6 +343,11 @@
 
   private Local setLocalInfoForRegister(int register, DebugLocalInfo info) {
     Local existingLocal = getLocalForRegister(register);
+    Type type = Type.getType(info.type.toDescriptorString());
+    if (!existingLocal.slot.isCompatibleWith(type)) {
+      throw new InvalidDebugInfoException(
+          "Attempt to define local of type " + prettyType(existingLocal.slot.type) + " as " + info);
+    }
     Local local = new Local(existingLocal.slot, info);
     locals[register] = local;
     return local;
@@ -420,12 +424,7 @@
 
   public Slot pop(Type type) {
     Slot slot = pop();
-    boolean compatible = slot.isCompatibleWith(type);
-    if (!compatible && !localVariables.isEmpty()) {
-      throw new InvalidDebugInfoException("Expected to read stack value of type " + prettyType(type)
-          + " but found value of type " + prettyType(slot.type));
-    }
-    assert compatible;
+    assert slot.isCompatibleWith(type);
     return slot;
   }
 
@@ -464,7 +463,10 @@
   }
 
   public boolean recordStateForExceptionalTarget(int target, JarSourceCode source) {
-    return recordStateForTarget(target, locals.clone(), ImmutableList.of(), source);
+    return recordStateForTarget(target,
+        locals.clone(),
+        ImmutableList.of(new Slot(startOfStack, JarSourceCode.THROWABLE_TYPE)),
+        source);
   }
 
   private boolean recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index b1f9a14..16e124a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
 
 /**
  * Abstraction of the input/source code for the IRBuilder.
@@ -26,6 +28,8 @@
 
   DebugLocalInfo getCurrentLocal(int register);
 
+  DebugPosition getDebugPositionAtOffset(int offset);
+
   /**
    * Trace block structure of the source-program.
    *
@@ -46,7 +50,7 @@
 
   // Delegates for IR building.
   void buildPrelude(IRBuilder builder);
-  void buildInstruction(IRBuilder builder, int instructionIndex);
+  void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException;
   void buildPostlude(IRBuilder builder);
 
   // Helper to resolve switch payloads and build switch instructions (dex code only).
@@ -57,6 +61,7 @@
   void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, IRBuilder builder);
 
   CatchHandlers<Integer> getCurrentCatchHandlers();
+  int getMoveExceptionRegister();
 
   // For debugging/verification purpose.
   boolean verifyRegister(int register);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index c3f2d08..cbeda1e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
@@ -193,7 +194,7 @@
    * Move static and default interface methods to companion classes,
    * add missing methods to forward to moved default methods implementation.
    */
-  public void desugarInterfaceMethods(Builder builder, Flavor flavour) {
+  public void desugarInterfaceMethods(Builder builder, Flavor flavour) throws ApiLevelException {
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
     forwardingMethods.addAll(processClasses(builder, flavour));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 353f866..f03a619 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
@@ -387,7 +388,7 @@
     }
 
     // Ensure access of the referenced symbol(s).
-    abstract boolean ensureAccessibility();
+    abstract boolean ensureAccessibility() throws ApiLevelException;
 
     DexClass definitionFor(DexType type) {
       return rewriter.converter.appInfo.app.definitionFor(type);
@@ -482,7 +483,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() {
+    boolean ensureAccessibility() throws ApiLevelException {
       // Create a static accessor with proper accessibility.
       DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
       assert accessorClass != null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index e322717..bba7b23 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index f6f8f2c..86ad391 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication.Builder;
@@ -191,7 +192,7 @@
    * Adjust accessibility of referenced application symbols or
    * creates necessary accessors.
    */
-  public void adjustAccessibility() {
+  public void adjustAccessibility() throws ApiLevelException {
     // For each lambda class perform necessary adjustment of the
     // referenced symbols to make them accessible. This can result in
     // method access relaxation or creation of accessor method.
@@ -201,7 +202,7 @@
   }
 
   /** Generates lambda classes and adds them to the builder. */
-  public void synthesizeLambdaClasses(Builder builder) {
+  public void synthesizeLambdaClasses(Builder builder) throws ApiLevelException {
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
       DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
       converter.optimizeSynthesizedClass(synthesizedClass);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 9f833f0..8820c61 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
 import java.util.Arrays;
-import java.util.Comparator;
 import java.util.List;
 
 class BasicBlockInstructionsEquivalence extends Equivalence<BasicBlock> {
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 3a89759..8ae2147 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
@@ -64,27 +64,32 @@
 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;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 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.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntLists;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -344,11 +349,25 @@
   }
 
   // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
-  public static class SwitchBuilder {
+  public abstract static class InstructionBuilder<T> {
+    protected int blockNumber;
+
+    public abstract T self();
+
+    public T setBlockNumber(int blockNumber) {
+      this.blockNumber = blockNumber;
+      return self();
+    }
+  }
+
+  public static class SwitchBuilder extends InstructionBuilder<SwitchBuilder> {
     private Value value;
     private Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
     private BasicBlock fallthrough;
-    private int blockNumber;
+
+    public SwitchBuilder self() {
+      return this;
+    }
 
     public SwitchBuilder setValue(Value value) {
       this.value = value;
@@ -365,11 +384,6 @@
       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<>();
@@ -400,65 +414,134 @@
     }
   }
 
+  public static class IfBuilder extends InstructionBuilder<IfBuilder> {
+    private final IRCode code;
+    private Value left;
+    private int right;
+    private BasicBlock target;
+    private BasicBlock fallthrough;
+    private int blockNumber;
+
+    public IfBuilder(IRCode code) {
+      this.code = code;
+    }
+
+    public IfBuilder self() {
+      return this;
+    }
+
+    public IfBuilder setLeft(Value left) {
+      this.left = left;
+      return  this;
+    }
+
+    public IfBuilder setRight(int right) {
+      this.right = right;
+      return  this;
+    }
+
+    public IfBuilder setTarget(BasicBlock target) {
+      this.target = target;
+      return this;
+    }
+
+    public IfBuilder setFallthrough(BasicBlock fallthrough) {
+      this.fallthrough = fallthrough;
+      return this;
+    }
+
+    public BasicBlock build() {
+      assert target != null;
+      assert fallthrough != null;
+      If newIf;
+      BasicBlock ifBlock;
+      if (right != 0) {
+        ConstNumber rightConst = code.createIntConstant(right);
+        newIf = new If(Type.EQ, ImmutableList.of(left, rightConst.dest()));
+        ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, rightConst);
+      } else {
+        newIf = new If(Type.EQ, left);
+        ifBlock = BasicBlock.createIfBlock(blockNumber, newIf);
+      }
+      ifBlock.link(target);
+      ifBlock.link(fallthrough);
+      return ifBlock;
+    }
+  }
+
   /**
    * 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) {
+      InstructionListIterator iterator, Switch theSwitch,
+      List<IntList> switches, IntList keysToRemove) {
+
+    // Extract the information from the switch before removing it.
+    Int2ReferenceSortedMap<BasicBlock> keyToTarget = theSwitch.getKeyToTargetMap();
+    Set<Value> originalDebugValues = ImmutableSet.copyOf(theSwitch.getDebugValues());
+
+    // Keep track of the current fallthrough, starting with the original.
+    BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
+
     // 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();
+    // Collect the new blocks for adding to the block list.
+    int nextBlockNumber = code.getHighestBlockNumber() + 1;
+    LinkedList<BasicBlock> newBlocks = new LinkedList<>();
 
-    // 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());
+    // Build the switch-blocks backwards, to always have the fallthrough block in hand.
+    for (int i = switches.size() - 1; i >= 0; i--) {
+      SwitchBuilder switchBuilder = new SwitchBuilder();
+      switchBuilder.setValue(theSwitch.value());
+      IntList keys = switches.get(i);
+      for (int j = 0; j < keys.size(); j++) {
+        int key = keys.getInt(j);
+        switchBuilder.addKeyAndTarget(key, keyToTarget.get(key));
+      }
+      switchBuilder
+          .setFallthrough(fallthroughBlock)
+          .setBlockNumber(nextBlockNumber++);
+      BasicBlock newSwitchBlock = switchBuilder.build();
+      newBlocks.addFirst(newSwitchBlock);
+      fallthroughBlock = newSwitchBlock;
     }
-    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);
+    // Build the if-blocks backwards, to always have the fallthrough block in hand.
+    for (int i = keysToRemove.size() - 1; i >= 0; i--) {
+      int key = keysToRemove.getInt(i);
+      BasicBlock peeledOffTarget = keyToTarget.get(key);
+      IfBuilder ifBuilder = new IfBuilder(code);
+      ifBuilder
+          .setLeft(theSwitch.value())
+          .setRight(key)
+          .setTarget(peeledOffTarget)
+          .setFallthrough(fallthroughBlock)
+          .setBlockNumber(nextBlockNumber++);
+      BasicBlock ifBlock = ifBuilder.build();
+      newBlocks.addFirst(ifBlock);
+      fallthroughBlock = ifBlock;
+    }
+
+    // Attach the debug values from the original switch to the first of the new instructions,
+    originalDebugValues.forEach(fallthroughBlock.exit()::addDebugValue);
+
+    // Finally link the block before the original switch to the new block sequence.
+    originalBlock.link(fallthroughBlock);
+
+    // Finally add the blocks.
+    newBlocks.forEach(blocksIterator::add);
   }
 
   public void rewriteSwitch(IRCode code) {
@@ -490,16 +573,16 @@
             }
           } else {
             // Split keys into outliers and sequences.
-            List<List<Integer>> sequences = new ArrayList<>();
-            List<Integer> outliers = new ArrayList<>();
+            List<IntList> sequences = new ArrayList<>();
+            IntList outliers = new IntArrayList();
 
-            List<Integer> current = new ArrayList<>();
+            IntList current = new IntArrayList();
             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;
+              assert current.getInt(current.size() - 1) == previousKey;
               int key = keys[i];
               if (((long) key - (long) previousKey) > 1) {
                 if (current.size() == 1) {
@@ -507,7 +590,7 @@
                 } else {
                   sequences.add(current);;
                 }
-                current = new ArrayList<>();
+                current = new IntArrayList();
               }
               current.add(key);
               previousKey = key;
@@ -518,22 +601,45 @@
               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));
+            // Get the existing dex size for the payload and switch.
+            long currentSize = Switch.payloadSize(keys) + Switch.estimatedDexSize();
+
+            // Never replace with more than 10 if/switch instructions.
+            if (outliers.size() + sequences.size() <= 10) {
+              // Calculate estimated size for splitting into ifs and switches.
+              long rewrittenSize = 0;
               for (Integer outlier : outliers) {
-                rewrittenSize += ConstNumber.estimatedDexSize(
-                    ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+                if (outlier != 0) {
+                  rewrittenSize += ConstNumber.estimatedDexSize(
+                      ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+                }
                 rewrittenSize += If.estimatedDexSize();
               }
-              // Rewrite if smaller.
+              for (List<Integer> sequence : sequences) {
+                rewrittenSize += Switch.payloadSize(sequence);
+              }
+              rewrittenSize += Switch.estimatedDexSize() * sequences.size();
+
               if (rewrittenSize < currentSize) {
                 convertSwitchToSwitchAndIfs(
-                    code, blocksIterator, block, iterator, theSwitch, outliers);
-                assert code.isConsistentSSA();
+                    code, blocksIterator, block, iterator, theSwitch, sequences, outliers);
+              }
+            } else if (outliers.size() > 1) {
+              // Calculate estimated size for splitting into switches (packed for the sequences
+              // and sparse for the outliers).
+              long rewrittenSize = 0;
+              for (List<Integer> sequence : sequences) {
+                rewrittenSize += Switch.payloadSize(sequence);
+              }
+              rewrittenSize += Switch.payloadSize(outliers);
+              rewrittenSize += Switch.estimatedDexSize() * (sequences.size() + 1);
+
+              if (rewrittenSize < currentSize) {
+                // Create a copy to not modify sequences.
+                List<IntList> seqs = new ArrayList<>(sequences);
+                seqs.add(outliers);
+                convertSwitchToSwitchAndIfs(
+                    code, blocksIterator, block, iterator, theSwitch, seqs, IntLists.EMPTY_LIST);
               }
             }
           }
@@ -722,7 +828,7 @@
                   assert (invoke.outType() == argument.outType()) ||
                       (invoke.outType() == MoveType.OBJECT
                           && argument.outType() == MoveType.SINGLE
-                          && argument.getConstInstruction().asConstNumber().isZero());
+                          && argument.isZero());
                   invoke.outValue().replaceUsers(argument);
                   invoke.setOutValue(null);
                 }
@@ -834,7 +940,8 @@
     if (exit == null) {
       return;
     }
-    Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+    Set<StaticPut> puts = Sets.newIdentityHashSet();
+    Map<DexField, StaticPut> dominatingPuts = Maps.newIdentityHashMap();
     for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
       InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
       while (iterator.hasPrevious()) {
@@ -845,25 +952,29 @@
           if (field.getHolder() == method.method.getHolder()) {
             if (put.inValue().isConstant()) {
               if ((field.type.isClassType() || field.type.isArrayType())
-                  && put.inValue().getConstInstruction().isConstNumber() &&
-                  put.inValue().getConstInstruction().asConstNumber().isZero()) {
+                  && put.inValue().isZero()) {
                 // Collect put of zero as a potential default value.
-                puts.put(put.getField(), put);
+                dominatingPuts.putIfAbsent(put.getField(), put);
+                puts.add(put);
               } else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
                 // Collect put as a potential default value.
-                puts.put(put.getField(), put);
+                dominatingPuts.putIfAbsent(put.getField(), put);
+                puts.add(put);
               }
             } else if (isClassNameConstant(method, put)) {
               // Collect put of class name constant as a potential default value.
-              puts.put(put.getField(), put);
+              dominatingPuts.putIfAbsent(put.getField(), put);
+              puts.add(put);
             }
           }
         }
         if (current.isStaticGet()) {
           // If a static field is read, any collected potential default value cannot be a
           // default value.
-          if (puts.containsKey(current.asStaticGet().getField())) {
-            puts.remove(current.asStaticGet().getField());
+          DexField field = current.asStaticGet().getField();
+          if (dominatingPuts.containsKey(field)) {
+            dominatingPuts.remove(field);
+            Iterables.removeIf(puts, put -> put.getField() == field);
           }
         }
       }
@@ -871,13 +982,13 @@
 
     if (!puts.isEmpty()) {
       // Set initial values for static fields from the definitive static put instructions collected.
-      for (StaticPut put : puts.values()) {
+      for (StaticPut put : dominatingPuts.values()) {
         DexField field = put.getField();
         DexEncodedField encodedField = appInfo.definitionFor(field);
         if (field.type == dexItemFactory.stringType) {
           if (put.inValue().isConstant()) {
-            if (put.inValue().getConstInstruction().isConstNumber()) {
-              assert put.inValue().getConstInstruction().asConstNumber().isZero();
+            if (put.inValue().isConstNumber()) {
+              assert put.inValue().isZero();
               encodedField.staticValue = DexValueNull.NULL;
             } else {
               ConstString cnst = put.inValue().getConstInstruction().asConstString();
@@ -896,8 +1007,7 @@
             }
           }
         } else if (field.type.isClassType() || field.type.isArrayType()) {
-          if (put.inValue().getConstInstruction().isConstNumber()
-              && put.inValue().getConstInstruction().asConstNumber().isZero()) {
+          if (put.inValue().isZero()) {
             encodedField.staticValue = DexValueNull.NULL;
           } else {
             throw new Unreachable("Unexpected default value for field type " + field.type + ".");
@@ -932,7 +1042,7 @@
         InstructionListIterator iterator = block.listIterator();
         while (iterator.hasNext()) {
           Instruction current = iterator.next();
-          if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+          if (current.isStaticPut() && puts.contains(current.asStaticPut())) {
             iterator.remove();
             // Collect, for removal, the instruction that created the value for the static put,
             // if all users are gone. This is done even if these instructions can throw as for
@@ -972,6 +1082,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();
@@ -1020,7 +1131,7 @@
         if (current.isInvoke() && current.asInvoke().requiredArgumentRegisters() > 5) {
           Invoke invoke = current.asInvoke();
           it.previous();
-          Map<ConstNumber, ConstNumber> oldToNew = new HashMap<>();
+          Map<ConstNumber, ConstNumber> oldToNew = new IdentityHashMap<>();
           for (int i = 0; i < invoke.inValues().size(); i++) {
             Value value = invoke.inValues().get(i);
             if (value.isConstNumber() && value.numberOfUsers() > 1) {
@@ -1100,9 +1211,10 @@
             // Add constant into the dominator block of usages.
             insertConstantInBlock(instruction, entry.getKey());
           } else {
-            assert instruction.outValue().numberOfUsers() != 0;
             ConstNumber constNumber = instruction.asConstNumber();
             Value constantValue = instruction.outValue();
+            assert constantValue.numberOfUsers() != 0;
+            assert constantValue.numberOfUsers() == constantValue.numberOfAllUsers();
             for (Instruction user : constantValue.uniqueUsers()) {
               ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
               InstructionListIterator iterator = user.getBlock().listIterator(user);
@@ -1110,6 +1222,7 @@
               iterator.add(newCstNum);
               user.replaceValue(constantValue, newCstNum.outValue());
             }
+            constantValue.clearUsers();
           }
         }
       } else {
@@ -1432,10 +1545,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.
@@ -1472,22 +1591,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/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index b2ebecc..38c8da8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -47,14 +48,16 @@
     return ordinalsMaps.get(enumClass);
   }
 
-  public void run() {
-    appInfo.classes().forEach(this::processClasses);
+  public void run() throws ApiLevelException {
+    for (DexProgramClass clazz : appInfo.classes()) {
+      processClasses(clazz);
+    }
     if (!ordinalsMaps.isEmpty()) {
       appInfo.setExtension(EnumOrdinalMapCollector.class, ordinalsMaps);
     }
   }
 
-  private void processClasses(DexProgramClass clazz) {
+  private void processClasses(DexProgramClass clazz) throws ApiLevelException {
     // Enum classes are flagged as such. Also, for library classes, the ordinals are not known.
     if (!clazz.accessFlags.isEnum() || clazz.isLibraryClass() || !clazz.hasClassInitializer()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 814812c..a11d924 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexAccessFlags;
 import com.android.tools.r8.graph.DexClass;
@@ -22,7 +23,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.CallGraph;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
@@ -35,6 +36,7 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 public class Inliner {
@@ -127,7 +129,7 @@
   }
 
   public synchronized void processDoubleInlineCallers(IRConverter converter,
-      OptimizationFeedback feedback) {
+      OptimizationFeedback feedback) throws ApiLevelException {
     if (doubleInlineCallers.size() > 0) {
       applyDoubleInlining = true;
       List<DexEncodedMethod> methods = doubleInlineCallers
@@ -135,7 +137,8 @@
           .sorted(DexEncodedMethod::slowCompare)
           .collect(Collectors.toList());
       for (DexEncodedMethod method : methods) {
-        converter.processMethod(method, feedback, Outliner::noProcessing);
+        converter.processMethod(method, feedback, x -> false, CallSiteInformation.empty(),
+            Outliner::noProcessing);
         assert method.isProcessed();
       }
     }
@@ -221,12 +224,12 @@
       this.reason = reason;
     }
 
-    boolean forceInline() {
+    boolean ignoreInstructionBudget() {
       return reason != Reason.SIMPLE;
     }
 
     public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
-        GraphLense graphLense, InternalOptions options) {
+        GraphLense graphLense, InternalOptions options) throws ApiLevelException {
       if (target.isProcessed()) {
         assert target.getCode().isDexCode();
         return target.buildIR(generator, options);
@@ -314,14 +317,18 @@
     return true;
   }
 
-  public void performInlining(DexEncodedMethod method, IRCode code, CallGraph callGraph) {
+  public void performInlining(DexEncodedMethod method, IRCode code,
+      Predicate<DexEncodedMethod> isProcessedConcurrently,
+      CallSiteInformation callSiteInformation)
+      throws ApiLevelException {
     int instruction_allowance = 1500;
     instruction_allowance -= numberOfInstructions(code);
     if (instruction_allowance < 0) {
       return;
     }
     computeReceiverMustBeNonNull(code);
-    InliningOracle oracle = new InliningOracle(this, method, callGraph);
+    InliningOracle oracle = new InliningOracle(this, method, callSiteInformation,
+        isProcessedConcurrently);
 
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -337,15 +344,7 @@
           InvokeMethod invoke = current.asInvokeMethod();
           InlineAction result = invoke.computeInlining(oracle);
           if (result != null) {
-            DexEncodedMethod target = appInfo.lookup(invoke.getType(), invoke.getInvokedMethod());
-            if (target == null) {
-              // The declared target cannot be found so skip inlining.
-              continue;
-            }
-            if (!(target.isProcessed() || result.reason == Reason.FORCE)) {
-              // Do not inline code that was not processed unless we have to force inline.
-              continue;
-            }
+            DexEncodedMethod target = result.target;
             IRCode inlinee = result
                 .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
             if (inlinee != null) {
@@ -353,10 +352,6 @@
               if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
                 continue;
               }
-              if (callGraph.isBreaker(method, target)) {
-                // Make sure we don't inline a call graph breaker.
-                continue;
-              }
               // If this code did not go through the full pipeline, apply inlining to make sure
               // that force inline targets get processed.
               if (!target.isProcessed()) {
@@ -364,7 +359,8 @@
                 if (Log.ENABLED) {
                   Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
                 }
-                performInlining(target, inlinee, callGraph);
+                performInlining(target, inlinee, isProcessedConcurrently,
+                    callSiteInformation);
               }
               // Make sure constructor inlining is legal.
               assert !target.isClassInitializer();
@@ -376,7 +372,7 @@
               if (invoke.isInvokeMethodWithReceiver()) {
                 // If the invoke has a receiver but the declared method holder is different
                 // from the computed target holder, inlining requires a downcast of the receiver.
-                if (result.target.method.getHolder() != target.method.getHolder()) {
+                if (target.method.getHolder() != invoke.getInvokedMethod().getHolder()) {
                   downcast = result.target.method.getHolder();
                 }
               }
@@ -384,7 +380,7 @@
               // Back up before the invoke instruction.
               iterator.previous();
               instruction_allowance -= numberOfInstructions(inlinee);
-              if (instruction_allowance >= 0 || result.forceInline()) {
+              if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
                 iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
               }
               // If we inlined the invoke from a bridge method, it is no longer a bridge method.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 91f0a61..8b7a05e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -11,10 +11,11 @@
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.CallGraph;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.logging.Log;
+import java.util.function.Predicate;
 
 /**
  * The InliningOracle contains information needed for when inlining
@@ -26,16 +27,19 @@
 
   private final Inliner inliner;
   private final DexEncodedMethod method;
-  private final CallGraph callGraph;
+  private final CallSiteInformation callSiteInformation;
+  private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final InliningInfo info;
 
   InliningOracle(
       Inliner inliner,
       DexEncodedMethod method,
-      CallGraph callGraph) {
+      CallSiteInformation callSiteInformation,
+      Predicate<DexEncodedMethod> isProcessedConcurrently) {
     this.inliner = inliner;
     this.method = method;
-    this.callGraph = callGraph;
+    this.callSiteInformation = callSiteInformation;
+    this.isProcessedConcurrently = isProcessedConcurrently;
     info = Log.ENABLED ? new InliningInfo(method) : null;
   }
 
@@ -66,7 +70,7 @@
         && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
       return Reason.ALWAYS;
     }
-    if (callGraph.hasSingleCallSite(target)) {
+    if (callSiteInformation.hasSingleCallSite(target)) {
       return Reason.SINGLE_CALLER;
     }
     if (isDoubleInliningTarget(target)) {
@@ -91,18 +95,13 @@
 
   private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
     // 10 is found from measuring.
-    return callGraph.hasDoubleCallSite(candidate)
+    return callSiteInformation.hasDoubleCallSite(candidate)
         && candidate.getCode().isDexCode()
         && (candidate.getCode().asDexCode().instructions.length <= 10);
   }
 
   private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
       Reason reason) {
-    if (callGraph.isBreaker(method, candidate)) {
-      // Cycle breaker so abort to preserve compilation order.
-      return false;
-    }
-
     if (method == candidate) {
       // Cannot handle recursive inlining at this point.
       // Force inlined method should never be recursive.
@@ -113,6 +112,13 @@
       return false;
     }
 
+    if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
+      if (info != null) {
+        info.exclude(invoke, "is processed in parallel");
+      }
+      return false;
+    }
+
     // Abort inlining attempt if method -> target access is not right.
     if (!inliner.hasInliningAccess(method, candidate)) {
       if (info != null) {
@@ -176,7 +182,7 @@
     }
 
     Reason reason = computeInliningReason(candidate);
-    if (!candidate.isInliningCandidate(method, reason == Reason.FORCE, inliner.appInfo)) {
+    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
       // Abort inlining attempt if the single target is not an inlining candidate.
       if (info != null) {
         info.exclude(invoke, "target is not identified for inlining");
@@ -202,7 +208,7 @@
 
     Reason reason = computeInliningReason(candidate);
     // Determine if this should be inlined no matter how big it is.
-    if (!candidate.isInliningCandidate(method, reason == Reason.FORCE, inliner.appInfo)) {
+    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
       // Abort inlining attempt if the single target is not an inlining candidate.
       if (info != null) {
         info.exclude(invoke, "target is not identified for inlining");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 3857c1f..ade7537 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.Div;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -928,6 +930,16 @@
     }
 
     @Override
+    public int getMoveExceptionRegister() {
+      throw new Unreachable();
+    }
+
+    @Override
+    public DebugPosition getDebugPositionAtOffset(int offset) {
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean verifyCurrentInstructionCanThrow() {
       // TODO(sgjesse): Check more here?
       return true;
@@ -969,7 +981,8 @@
     }
 
     @Override
-    public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+    public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+        throws ApiLevelException {
       OutlineSourceCode source = new OutlineSourceCode(outline);
       IRBuilder builder = new IRBuilder(encodedMethod, source, options);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index a7d6e8d..35cadf2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -84,7 +84,9 @@
             commonSuffixSize = Math.min(
                 commonSuffixSize, sharedSuffixSizeExcludingExit(firstPred, pred, allocator));
           }
-          assert commonSuffixSize >= 1;
+          if (commonSuffixSize == 0) {
+            continue;
+          }
           int blockNumber = startNumberOfNewBlock + newBlocks.size();
           BasicBlock newBlock = createAndInsertBlockForSuffix(
               blockNumber, commonSuffixSize, predsWithSameLastInstruction, block);
@@ -160,7 +162,9 @@
       Instruction i0 = it0.previous();
       Instruction i1 = it1.previous();
       if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
-        return suffixSize;
+        // If the shared suffix follows a debug position at least one instruction must remain
+        // unshared to ensure the debug position is at a different pc than the shared suffix.
+        return i0.isDebugPosition() || i1.isDebugPosition() ? suffixSize - 1 : suffixSize;
       }
       suffixSize++;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 44f0759..58bf0bb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -75,8 +76,10 @@
     intArrayType = appInfo.dexItemFactory.createType("[I");
   }
 
-  public void run() {
-    appInfo.classes().forEach(this::processClasses);
+  public void run() throws ApiLevelException {
+    for (DexProgramClass clazz : appInfo.classes()) {
+      processClasses(clazz);
+    }
     if (!switchMaps.isEmpty()) {
       appInfo.setExtension(SwitchMapCollector.class, switchMaps);
     }
@@ -89,7 +92,7 @@
     return switchMaps.get(field);
   }
 
-  private void processClasses(DexProgramClass clazz) {
+  private void processClasses(DexProgramClass clazz) throws ApiLevelException {
     // Switchmap classes are synthetic and have a class initializer.
     if (!clazz.accessFlags.isSynthetic() && !clazz.hasClassInitializer()) {
       return;
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 6cd453b..52653a7 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.ArithmeticBinop;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.IRCode;
@@ -24,7 +25,6 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.regalloc.RegisterPositions.Type;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -39,7 +39,6 @@
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntSet;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -139,8 +138,7 @@
   // List of intervals where the current instruction falls into one of their live range holes.
   private List<LiveIntervals> inactive = new LinkedList<>();
   // List of intervals that no register has been allocated to sorted by first live range.
-  private PriorityQueue<LiveIntervals> unhandled =
-      new PriorityQueue<>(Comparator.comparingInt(LiveIntervals::getStart));
+  private PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
 
   // The first register used for parallel moves. After register allocation the parallel move
   // temporary registers are [firstParallelMoveTemporary, maxRegisterNumber].
@@ -197,7 +195,6 @@
       }
     }
 
-    clearUserInfo();
     assert code.isConsistentGraph();
     if (Log.ENABLED) {
       Log.debug(this.getClass(), toString());
@@ -206,6 +203,7 @@
     if (debug) {
       computeDebugInfo(blocks);
     }
+    clearUserInfo();
     clearState();
   }
 
@@ -814,6 +812,9 @@
     // Go through each unhandled live interval and find a register for it.
     while (!unhandled.isEmpty()) {
       LiveIntervals unhandledInterval = unhandled.poll();
+
+      setHintToPromote2AddrInstruction(unhandledInterval);
+
       // If this interval value is the src of an argument move. Fix the registers for the
       // consecutive arguments now and add hints to the move sources. This looks forward
       // and propagate hints backwards to avoid many moves in connection with ranged invokes.
@@ -865,6 +866,31 @@
     return true;
   }
 
+  /*
+   * This method tries to promote arithmetic binary instruction to use the 2Addr form.
+   * To achieve this goal the output interval of the binary instruction is set with an hint
+   * that is the left interval or the right interval if possible when intervals do not overlap.
+   */
+  private void setHintToPromote2AddrInstruction(LiveIntervals unhandledInterval) {
+    if (unhandledInterval.getHint() == null &&
+        unhandledInterval.getValue().definition instanceof ArithmeticBinop) {
+      ArithmeticBinop binOp = unhandledInterval.getValue().definition.asArithmeticBinop();
+      Value left = binOp.leftValue();
+      assert left != null;
+      if (left.getLiveIntervals() != null &&
+          !left.getLiveIntervals().overlaps(unhandledInterval)) {
+        unhandledInterval.setHint(left.getLiveIntervals());
+      } else {
+        Value right = binOp.rightValue();
+        assert right != null;
+        if (binOp.isCommutative() && right.getLiveIntervals() != null &&
+            !right.getLiveIntervals().overlaps(unhandledInterval)) {
+          unhandledInterval.setHint(right.getLiveIntervals());
+        }
+      }
+    }
+  }
+
   /**
    * Perform look-ahead and allocate registers for linked argument chains that have the argument
    * interval as an argument move source.
@@ -2216,25 +2242,6 @@
     return true;
   }
 
-  public void print(CfgPrinter printer, String title) {
-    printer.begin("intervals");
-    printer.print("name \"").append(title).append("\"").ln();
-    PriorityQueue<LiveIntervals> sortedIntervals =
-        new PriorityQueue<>((o1, o2) -> Integer.compare(o1.getStart(), o2.getStart()));
-    sortedIntervals.addAll(liveIntervals);
-    for (LiveIntervals interval = sortedIntervals.poll();
-        interval != null;
-        interval = sortedIntervals.poll()) {
-      Value value = interval.getValue();
-      if (interval.getRanges().get(0).isInfinite()) {
-        // Skip argument sentinels.
-        continue;
-      }
-      interval.print(printer, value.getNumber(), value.getNumber());
-    }
-    printer.end("intervals");
-  }
-
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder("Live ranges:\n");
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 65628c3..0d8c6b8 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -15,7 +15,7 @@
 import java.util.List;
 import java.util.TreeSet;
 
-public class LiveIntervals {
+public class LiveIntervals implements Comparable<LiveIntervals> {
 
   private final Value value;
   private LiveIntervals nextConsecutive;
@@ -454,6 +454,12 @@
   }
 
   @Override
+  public int compareTo(LiveIntervals other) {
+    int startDiff = getStart() - other.getStart();
+    return startDiff != 0 ? startDiff : (value.getNumber() - other.value.getNumber());
+  }
+
+  @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     builder.append("(cons ");
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index 9cb873e..4a21e8a 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -10,7 +10,7 @@
 
 // Register moves used by the spilling register allocator. These are used both for spill and
 // for phi moves and they are moves between actual registers represented by their register number.
-public class RegisterMove {
+public class RegisterMove implements Comparable<RegisterMove> {
   MoveType type;
   int dst;
   int src;
@@ -70,4 +70,30 @@
     RegisterMove o = (RegisterMove) other;
     return o.src == src && o.dst == dst && o.type == type && o.definition == definition;
   }
+
+  @Override
+  public int compareTo(RegisterMove o) {
+    int srcDiff = src - o.src;
+    if (srcDiff != 0) {
+      return srcDiff;
+    }
+    int dstDiff = dst - o.dst;
+    if (dstDiff != 0) {
+      return dstDiff;
+    }
+    int typeDiff = o.type.ordinal() - type.ordinal();
+    if (typeDiff != 0) {
+      return typeDiff;
+    }
+    if (definition == null) {
+      if (o.definition != null) {
+        return -1;
+      }
+      return 0;
+    }
+    if (o.definition == null) {
+      return 1;
+    }
+    return definition.getNumber() - o.definition.getNumber();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index cc26729..007aa6f 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -14,15 +14,15 @@
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 public class RegisterMoveScheduler {
   // The set of moves to schedule.
-  private Set<RegisterMove> moveSet = new LinkedHashSet<>();
+  private Set<RegisterMove> moveSet = new TreeSet<>();
   // Mapping to keep track of which values currently corresponds to each other.
   // This is initially an identity map but changes as we insert moves.
   private Map<Integer, Integer> valueMap = new HashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
index bd7efce..391b479 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
@@ -6,19 +6,21 @@
 
 import static com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo.NO_THROW;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Consumer;
 
 public abstract class SingleBlockSourceCode implements SourceCode {
 
@@ -38,7 +40,7 @@
   private Value[] paramValues;
 
   // Instruction constructors
-  private List<Consumer<IRBuilder>> constructors = new ArrayList<>();
+  private List<ThrowingConsumer<IRBuilder, ApiLevelException>> constructors = new ArrayList<>();
 
   protected SingleBlockSourceCode(DexType receiver, DexProto proto) {
     assert proto != null;
@@ -57,7 +59,7 @@
     }
   }
 
-  protected final void add(Consumer<IRBuilder> constructor) {
+  protected final void add(ThrowingConsumer<IRBuilder, ApiLevelException> constructor) {
     constructors.add(constructor);
   }
 
@@ -174,7 +176,8 @@
   }
 
   @Override
-  public final void buildInstruction(IRBuilder builder, int instructionIndex) {
+  public final void buildInstruction(IRBuilder builder, int instructionIndex)
+      throws ApiLevelException {
     constructors.get(instructionIndex).accept(builder);
   }
 
@@ -196,6 +199,16 @@
   }
 
   @Override
+  public int getMoveExceptionRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public DebugPosition getDebugPositionAtOffset(int offset) {
+    throw new Unreachable();
+  }
+
+  @Override
   public final boolean verifyCurrentInstructionCanThrow() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 3d3f9f1..0de034a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.synthetic;
 
+import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -31,7 +32,8 @@
   }
 
   @Override
-  public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+  public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+      throws ApiLevelException {
     return new IRBuilder(encodedMethod, sourceCode, options).build();
   }
 
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 3ef67e6..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;
@@ -81,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);
       }
@@ -97,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();
@@ -108,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,
diff --git a/src/main/java/com/android/tools/r8/naming/DictionaryReader.java b/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
index 146d531..1d05d25 100644
--- a/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
+++ b/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
@@ -9,7 +9,6 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.List;
 
 public class DictionaryReader implements AutoCloseable {
 
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 a26e135..6a58866 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 564e6de..0b683e2 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -64,13 +64,14 @@
 
   private final BufferedReader reader;
 
+  @Override
   public void close() throws IOException {
     if (reader != null) {
       reader.close();
     }
   }
 
-  private ProguardMapReader(BufferedReader reader) throws IOException {
+  private ProguardMapReader(BufferedReader reader) {
     this.reader = reader;
   }
 
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/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5292a45..22fdf45 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -22,11 +22,8 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -85,31 +82,32 @@
   }
 
   public void parse(Path path) throws ProguardRuleParserException, IOException {
-    parse(Collections.singletonList(path));
+    parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
   }
 
-  public void parse(List<Path> pathList) throws ProguardRuleParserException, IOException {
-    for (Path path : pathList) {
-      new ProguardFileParser(path).parse();
+  public void parse(ProguardConfigurationSource source)
+      throws ProguardRuleParserException, IOException {
+    parse(ImmutableList.of(source));
+  }
+
+  public void parse(List<ProguardConfigurationSource> sources)
+      throws ProguardRuleParserException, IOException {
+    for (ProguardConfigurationSource source : sources) {
+      new ProguardFileParser(source).parse();
     }
   }
 
   private class ProguardFileParser {
 
-    private final Path path;
+    private final String name;
     private final String contents;
     private int position = 0;
     private Path baseDirectory;
 
-    ProguardFileParser(Path path) throws IOException {
-      this.path = path;
-      contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
-      baseDirectory = path.getParent();
-      if (baseDirectory == null) {
-        // path parent can be null only if it's root dir or if its a one element path relative to
-        // current directory.
-        baseDirectory = Paths.get(".");
-      }
+    ProguardFileParser(ProguardConfigurationSource source) throws IOException {
+      contents = source.get();
+      baseDirectory = source.getBaseDirectory();
+      name = source.getName();
     }
 
     public void parse() throws ProguardRuleParserException {
@@ -271,7 +269,7 @@
     private void parseInclude() throws ProguardRuleParserException {
       Path included = parseFileName();
       try {
-        new ProguardFileParser(included).parse();
+        new ProguardFileParser(new ProguardConfigurationSourceFile(included)).parse();
       } catch (FileNotFoundException | NoSuchFileException e) {
         throw parseError("Included file '" + included.toString() + "' not found", e);
       } catch (IOException e) {
@@ -1037,12 +1035,12 @@
         String line = lines[lineNumber];
         if (remaining <= line.length() || lineNumber == lines.length - 1) {
           String arrow = CharBuffer.allocate(remaining).toString().replace( '\0', ' ' ) + '^';
-          return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
+          return name + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
               + '\n' + arrow;
         }
         remaining -= (line.length() + 1); // Include newline.
       }
-      return path.toString();
+      return name;
     }
 
     private ProguardRuleParserException parseError(String message) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java
new file mode 100644
index 0000000..b0375c0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java
@@ -0,0 +1,14 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface ProguardConfigurationSource {
+  String get() throws IOException;
+  Path getBaseDirectory();
+  String getName();
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java
new file mode 100644
index 0000000..058c14f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java
@@ -0,0 +1,37 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class ProguardConfigurationSourceFile implements ProguardConfigurationSource {
+  private final Path path;
+
+  public ProguardConfigurationSourceFile(Path path) {
+    this.path = path;
+  }
+
+  public String get() throws IOException{
+    return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+  }
+
+  public Path getBaseDirectory() {
+    Path baseDirectory = path.getParent();
+    if (baseDirectory == null) {
+      // Path parent can be null only if it's root dir or if its a one element path relative to
+      // current directory.
+      baseDirectory = Paths.get(".");
+    }
+    return baseDirectory;
+  }
+
+  public String getName() {
+    return path.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
new file mode 100644
index 0000000..ac5f0ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
@@ -0,0 +1,31 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import joptsimple.internal.Strings;
+
+public class ProguardConfigurationSourceStrings implements ProguardConfigurationSource {
+  private final List<String> config;
+
+  public ProguardConfigurationSourceStrings(List<String> config) {
+    this.config = config;
+  }
+
+  public String get() throws IOException{
+    return Strings.join(config, "\n");
+  }
+
+  public Path getBaseDirectory() {
+    return Paths.get(".");
+  }
+
+  public String getName() {
+    return "<no file>";
+  }
+}
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..3decec1 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,16 @@
 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 java.io.IOException;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -23,7 +26,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) {
@@ -34,7 +38,7 @@
         ? new UsagePrinter() : UsagePrinter.DONT_PRINT;
   }
 
-  public DexApplication run() throws IOException {
+  public DexApplication run() {
     application.timing.begin("Pruning application...");
     if (options.debugKeepRules && !options.skipMinification) {
       System.out.println(
@@ -64,6 +68,7 @@
         if (Log.ENABLED) {
           Log.debug(getClass(), "Removing class: " + clazz);
         }
+        prunedTypes.add(clazz.type);
         usagePrinter.printUnusedClass(clazz);
       } else {
         newClasses.add(clazz);
@@ -190,4 +195,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/shaking/protolite/ProtoLitePruner.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
index 4610f41..09bbad6 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
@@ -137,8 +137,7 @@
   }
 
   private boolean isDefinedAsNull(Value value) {
-    return value.definition != null && value.definition.isConstNumber()
-        && value.definition.asConstNumber().isZero();
+    return value.definition != null && value.isZero();
   }
 
   private boolean isComputeSizeMethod(DexMethod invokedMethod) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
new file mode 100644
index 0000000..d72fb2b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -0,0 +1,50 @@
+// 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;
+
+/**
+ * Android API level description
+ */
+public enum AndroidApiLevel {
+  O(26),
+  N_MR1(25),
+  N(24),
+  M(23),
+  L_MR1(22),
+  L(21),
+  K_WATCH(20),
+  K(19),
+  J_MR2(18),
+  J_MR1(17),
+  J(16),
+  I_MR1(15),
+  I(14),
+  H_MR2(13),
+  H_MR1(12),
+  H(11),
+  G_MR1(10),
+  G(9),
+  F(8),
+  E_MR1(7),
+  E_0_1(6),
+  E(5),
+  D(4),
+  C(3),
+  B_1_1(2),
+  B(1);
+
+  private final int level;
+
+  AndroidApiLevel(int level) {
+    this.level = level;
+  }
+
+  public int getLevel() {
+    return level;
+  }
+
+  public String getName() {
+    return "Android " + name();
+  }
+}
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 4c6c56f..a4c2c62 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -32,6 +32,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -302,9 +303,11 @@
    */
   public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException {
     if (outputMode == OutputMode.Indexed) {
-      for (Path path : Files.list(directory).collect(Collectors.toList())) {
-        if (isClassesDexFile(path)) {
-          Files.delete(path);
+      try (Stream<Path> filesInDir = Files.list(directory)) {
+        for (Path path : filesInDir.collect(Collectors.toList())) {
+          if (isClassesDexFile(path)) {
+            Files.delete(path);
+          }
         }
       }
     }
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..c241bd7 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -14,6 +14,7 @@
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Function;
 
 public class InternalOptions {
@@ -91,6 +92,7 @@
   public OutputMode outputMode = OutputMode.Indexed;
 
   public boolean useTreeShaking = true;
+  public boolean useDiscardedChecker = true;
 
   public boolean printCfg = false;
   public String printCfgFile;
@@ -196,7 +198,7 @@
 
   public static class TestingOptions {
 
-    public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering =
+    public Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> irOrdering =
         Function.identity();
   }
 
@@ -320,37 +322,37 @@
   }
 
   public boolean canUseInvokePolymorphic() {
-    return minApiLevel >= Constants.ANDROID_O_API;
+    return minApiLevel >= AndroidApiLevel.O.getLevel();
   }
 
   public boolean canUseInvokeCustom() {
-    return minApiLevel >= Constants.ANDROID_O_API;
+    return minApiLevel >= AndroidApiLevel.O.getLevel();
   }
 
   public boolean canUseDefaultAndStaticInterfaceMethods() {
-    return minApiLevel >= Constants.ANDROID_N_API;
+    return minApiLevel >= AndroidApiLevel.N.getLevel();
   }
 
   public boolean canUsePrivateInterfaceMethods() {
-    return minApiLevel >= Constants.ANDROID_N_API;
+    return minApiLevel >= AndroidApiLevel.N.getLevel();
   }
 
   public boolean canUseMultidex() {
-    return intermediate || minApiLevel >= Constants.ANDROID_L_API;
+    return intermediate || minApiLevel >= AndroidApiLevel.L.getLevel();
   }
 
   public boolean canUseLongCompareAndObjectsNonNull() {
-    return minApiLevel >= Constants.ANDROID_K_API;
+    return minApiLevel >= AndroidApiLevel.K.getLevel();
   }
 
   public boolean canUseSuppressedExceptions() {
-    return minApiLevel >= Constants.ANDROID_K_API;
+    return minApiLevel >= AndroidApiLevel.K.getLevel();
   }
 
   // APIs for accessing parameter names annotations are not available before Android O, thus does
   // not emit them to avoid wasting space in Dex files because runtimes before Android O will ignore
   // them.
   public boolean canUseParameterNameAnnotations() {
-    return minApiLevel >= Constants.ANDROID_O_API;
+    return minApiLevel >= AndroidApiLevel.O.getLevel();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
index 07c136d..5ea2a30 100644
--- a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
@@ -16,15 +16,16 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
 
 /** Lazy Java class file resource provider based on preloaded/prebuilt context. */
 public final class PreloadedClassFileProvider implements ClassFileResourceProvider {
@@ -52,17 +53,17 @@
   public static ClassFileResourceProvider fromArchive(Path archive) throws IOException {
     assert isArchive(archive);
     Builder builder = builder();
-    try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
-      ZipEntry entry;
-      while ((entry = stream.getNextEntry()) != null) {
+    try (ZipFile zipFile = new ZipFile(archive.toFile())) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
         String name = entry.getName();
         if (isClassFile(Paths.get(name))) {
-          builder.addResource(guessTypeDescriptor(name), ByteStreams.toByteArray(stream));
+          try (InputStream entryStream = zipFile.getInputStream(entry)) {
+            builder.addResource(guessTypeDescriptor(name), ByteStreams.toByteArray(entryStream));
+          }
         }
       }
-    } catch (ZipException e) {
-      throw new CompilationError(
-          "Zip error while reading '" + archive + "': " + e.getMessage(), e);
     }
 
     return builder.build();
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 8341921..79ab853 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -10,17 +10,18 @@
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.io.ByteStreams;
-import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Enumeration;
 import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
 
 class ProgramFileArchiveReader {
 
@@ -38,21 +39,24 @@
     assert isArchive(archive);
     dexResources = new ArrayList<>();
     classResources = new ArrayList<>();
-    try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
-      ZipEntry entry;
-      while ((entry = stream.getNextEntry()) != null) {
-        Path name = Paths.get(entry.getName());
-        if (isDexFile(name)) {
-          if (!ignoreDexInArchive) {
-            Resource resource =
-                new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
-            dexResources.add(resource);
+    try (ZipFile zipFile = new ZipFile(archive.toFile())) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        try (InputStream stream = zipFile.getInputStream(entry)) {
+          Path name = Paths.get(entry.getName());
+          if (isDexFile(name)) {
+            if (!ignoreDexInArchive) {
+              Resource resource =
+                  new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
+              dexResources.add(resource);
+            }
+          } else if (isClassFile(name)) {
+            String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
+            Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
+                ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
+            classResources.add(resource);
           }
-        } else if (isClassFile(name)) {
-          String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
-          Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
-              ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
-          classResources.add(resource);
         }
       }
     } catch (ZipException e) {
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java b/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java
new file mode 100644
index 0000000..d7da332
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java
@@ -0,0 +1,15 @@
+package com.android.tools.r8.utils;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Similar to a {@link BiConsumer} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the first argument
+ * @param <U> the type of the second argument
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingBiConsumer<T, U, E extends Throwable> {
+  void accept(T t, U u) throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java b/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java
new file mode 100644
index 0000000..8fb8c84
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java
@@ -0,0 +1,14 @@
+package com.android.tools.r8.utils;
+
+import java.util.function.Consumer;
+
+/**
+ * Similar to a {@link Consumer} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the input
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingConsumer<T, E extends Throwable> {
+  void accept(T t) throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 47d8e01..ca9a404 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -6,27 +6,31 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.io.ByteStreams;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.List;
 import java.util.function.Predicate;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
 
 public class ZipUtils {
 
   public interface OnEntryHandler {
-    void onEntry(ZipEntry entry, ZipInputStream input) throws IOException;
+    void onEntry(ZipEntry entry, InputStream input) throws IOException;
   }
 
-  public static void iter(String zipFile, OnEntryHandler handler) throws IOException {
-    try (ZipInputStream input = new ZipInputStream(new FileInputStream(zipFile))){
-      ZipEntry entry;
-      while ((entry = input.getNextEntry()) != null) {
-        handler.onEntry(entry, input);
+  public static void iter(String zipFileStr, OnEntryHandler handler) throws IOException {
+    try (ZipFile zipFile = new ZipFile(zipFileStr)) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        try (InputStream entryStream = zipFile.getInputStream(entry)) {
+          handler.onEntry(entry, entryStream);
+        }
       }
     }
   }
diff --git a/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
new file mode 100644
index 0000000..fa6078a
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.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.
+
+public class ClassInitializerAssignmentInitialization {
+
+  static int x = 1;
+  static int y;
+
+  static int z = 2;
+
+  public static void main(String[] args) {
+    System.out.println("x=" + x);
+    System.out.println("y=" + y);
+  }
+}
diff --git a/src/test/debugTestResources/ClassInitializerEmpty.java b/src/test/debugTestResources/ClassInitializerEmpty.java
new file mode 100644
index 0000000..9062d62
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerEmpty.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.
+
+public class ClassInitializerEmpty {
+
+  static {
+  }
+
+  public static void main(String[] args) {
+  }
+}
diff --git a/src/test/debugTestResources/ClassInitializerMixedInitialization.java b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
new file mode 100644
index 0000000..504db6c
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+public class ClassInitializerMixedInitialization {
+
+  static boolean b;
+  static int x = 1;
+  static int y;
+
+  static {
+    x = 2;
+    if (b) {
+      y = 1;
+    } else {
+      y = 2;
+    }
+    x = 3;
+  }
+
+  public static void main(String[] args) {
+    System.out.println("x=" + x);
+    System.out.println("y=" + y);
+  }
+}
diff --git a/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
new file mode 100644
index 0000000..78fe9a0
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+public class ClassInitializerStaticBlockInitialization {
+
+  static boolean b;
+  static int x;
+  static int y;
+
+  static {
+    x = 1;
+    x = 2;
+    if (b) {
+      y = 1;
+    } else {
+      y = 2;
+    }
+    x = 3;
+  }
+
+  public static void main(String[] args) {
+    System.out.println("x=" + x);
+    System.out.println("y=" + y);
+  }
+}
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index d42dd61..5afcabb 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -244,6 +244,80 @@
     return sum + x + y;
   }
 
+  public static int argumentLiveAtReturn(int x) {
+    switch (x) {
+      case 0:
+        return 0;
+      case 1:
+        return 0;
+      case 2:
+        return 0;
+      case 100:
+        return 1;
+      case 101:
+        return 1;
+      case 102:
+        return 1;
+    }
+    return -1;
+  }
+
+  public static int switchRewriteToIfs(int x) {
+    {
+      int t = x + 1;
+      x = t;
+      x = x + x;
+    }
+    switch (x) {
+      case 0:
+        return 0;
+      case 100:
+        return 1;
+    }
+    return -1;
+  }
+
+  public static int switchRewriteToSwitches(int x) {
+    {
+      int t = x + 1;
+      x = t;
+      x = x + x;
+    }
+    switch (x) {
+      case 0:
+        return 0;
+      case 1:
+        return 0;
+      case 2:
+        return 0;
+      case 100:
+        return 1;
+      case 101:
+        return 1;
+      case 102:
+        return 1;
+    }
+    return -1;
+  }
+
+  public static String regression65039701(boolean createIntNotLong) {
+    Object a = createIntNotLong ? new int[1] : new long[1];
+    if (a instanceof int []) {
+      ((int [])a)[0] = 0;
+    }
+    return "OK";
+  }
+
+  public static void regression65066975(boolean bit) {
+    nop();
+    if (bit) {
+      nop();
+    } else {
+      nop();
+    }
+    nop();
+  }
+
   public static void main(String[] args) {
     noLocals();
     unusedLocals();
@@ -259,5 +333,10 @@
     stepNonEmptyForLoopBody(3);
     tempInCase(42);
     localSwap(1, 2);
+    argumentLiveAtReturn(-1);
+    switchRewriteToIfs(1);
+    switchRewriteToSwitches(1);
+    regression65039701(true);
+    regression65066975(false);
   }
 }
diff --git a/src/test/debugTestResourcesKotlin/KotlinApp.kt b/src/test/debugTestResourcesKotlin/KotlinApp.kt
index 7fa2be1..094407b 100644
--- a/src/test/debugTestResourcesKotlin/KotlinApp.kt
+++ b/src/test/debugTestResourcesKotlin/KotlinApp.kt
@@ -3,42 +3,27 @@
 // BSD-style license that can be found in the LICENSE file.
 
 class KotlinApp {
+
+    fun ifElse(cond: Boolean) {
+        val a = 10
+        if (cond) {
+            val b = a * 2
+            printInt(b)
+        } else {
+            val c = a / 2
+            print(c)
+        }
+    }
+
+    fun printInt(i: Int) {
+        println(i)
+    }
+
     companion object {
         @JvmStatic fun main(args: Array<String>) {
-            println("Hello world!")
             val instance = KotlinApp()
-            instance.processObject(instance, instance::printObject)
-            instance.invokeInlinedFunctions()
+            instance.ifElse(true)
+            instance.ifElse(false)
         }
     }
-
-    fun processObject(obj: Any, func: (Any) -> Unit) {
-        func(obj)
-    }
-
-    fun printObject(obj: Any) {
-        println(obj)
-    }
-
-    fun invokeInlinedFunctions() {
-        inlinedA {
-            val inA = 1
-            inlinedB {
-                val inB = 2
-                foo(inA, inB)
-            }
-        }
-    }
-
-    inline fun inlinedA(f: () -> Unit) {
-        f()
-    }
-
-    inline fun inlinedB(f: () -> Unit) {
-        f()
-    }
-
-    fun foo(a: Int, b: Int) {
-        println("a=$a, b=$b")
-    }
 }
\ No newline at end of file
diff --git a/src/test/debugTestResourcesKotlin/KotlinInline.kt b/src/test/debugTestResourcesKotlin/KotlinInline.kt
new file mode 100644
index 0000000..7f914e4
--- /dev/null
+++ b/src/test/debugTestResourcesKotlin/KotlinInline.kt
@@ -0,0 +1,59 @@
+// 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.
+
+class KotlinInline {
+
+    fun processObject(obj: Any, func: (Any) -> Unit) {
+        func(obj)
+    }
+
+    fun printObject(obj: Any) {
+        println(obj)
+    }
+
+    fun invokeInlinedFunctions() {
+        inlinedA {
+            val inA = 1
+            inlinedB {
+                val inB = 2
+                foo(inA, inB)
+            }
+        }
+    }
+
+    inline fun inlinedA(f: () -> Unit) {
+        f()
+    }
+
+    inline fun inlinedB(f: () -> Unit) {
+        f()
+    }
+
+    fun foo(a: Int, b: Int) {
+        println("a=$a, b=$b")
+    }
+
+    fun emptyMethod(unused: Int) {
+    }
+
+    fun singleInline() {
+        emptyMethod(0)
+        inlined()
+        emptyMethod(1)
+    }
+
+    inline fun inlined() {
+        emptyMethod(-1)
+    }
+
+    companion object {
+        @JvmStatic fun main(args: Array<String>) {
+            println("Hello world!")
+            val instance = KotlinInline()
+            instance.processObject(instance, instance::printObject)
+            instance.invokeInlinedFunctions()
+            instance.singleInline()
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index aa30615..8ffab3c 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.TestCondition.compilers;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.JctfTestSpecifications.Outcome;
+import com.android.tools.r8.TestCondition.RuntimeSet;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -82,6 +82,12 @@
   private static final String ART_TESTS_NATIVE_LIBRARY_DIR = "tests/2017-07-27/art/lib64";
   private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64";
 
+  private static final RuntimeSet LEGACY_RUNTIME = TestCondition.runtimes(
+      DexVm.ART_4_4_4,
+      DexVm.ART_5_1_1,
+      DexVm.ART_6_0_1,
+      DexVm.ART_7_0_0);
+
   // Input jar for jctf tests.
   private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
 
@@ -527,8 +533,6 @@
           // keeps the instances alive is dead and could be garbage collected. The compiler reuses
           // the register for the list and therefore there are no live instances.
           .put("099-vmdebug", TestCondition.any())
-          // This test relies on output on stderr, which we currently do not collect.
-          .put("143-string-value", TestCondition.any())
           .put(
               "800-smali",
               TestCondition.match(
@@ -577,7 +581,7 @@
               "370-dex-v37",
               TestCondition.match(
                   TestCondition.tools(DexTool.JACK, DexTool.DX),
-                  compilers(CompilerUnderTest.R8, CompilerUnderTest.D8),
+                  TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.D8),
                   TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1)))
           // Array index out of bounds exception.
           .put("449-checker-bce", TestCondition.any())
@@ -681,21 +685,15 @@
           // iput on a static field.
           .put("600-verifier-fails", TestCondition.match(TestCondition.R8_COMPILER))
           // Contains a method that falls off the end without a return.
-          .put("606-erroneous-class", TestCondition
-              .match(TestCondition.tools(DexTool.DX, DexTool.JACK),
-                  TestCondition.R8_NOT_AFTER_D8_COMPILER))
-          .put("064-field-access", TestCondition
-              .match(TestCondition.tools(DexTool.NONE), TestCondition.D8_COMPILER))
+          .put("606-erroneous-class", TestCondition.match(
+              TestCondition.tools(DexTool.DX, DexTool.JACK),
+              TestCondition.R8_NOT_AFTER_D8_COMPILER,
+              LEGACY_RUNTIME))
           .build();
 
   // Tests that are invalid dex files and on which R8/D8 fails and that is OK.
   private static final Multimap<String, TestCondition> expectedToFailWithCompiler =
       new ImmutableListMultimap.Builder<String, TestCondition>()
-          // When starting from the Dex frontend we see two definitions of the same class coming
-          // from two differents dex files.
-          .put("064-field-access",
-              TestCondition.match(TestCondition.tools(DexTool.DX, DexTool.JACK),
-                  TestCondition.runtimes(DexVm.ART_DEFAULT)))
           // When starting from the Jar frontend we see the A$B class both from the Java source
           // code and from the smali dex code. We reject that because there are then two definitions
           // of the same class in the application. When running from the final dex files there is
@@ -703,18 +701,8 @@
           .put("121-modifiers", TestCondition.match(TestCondition.tools(DexTool.NONE)))
           // This test uses register r1 in method that is declared to only use 1 register (r0).
           .put("142-classloader2", TestCondition.match(TestCondition.R8_COMPILER))
-          // When starting from the Dex frontend we see two definitions of the same class coming
-          // from two differents dex files.
-          .put("162-method-resolution",
-              TestCondition.match(TestCondition.tools(DexTool.DX, DexTool.JACK)))
           // This test uses an uninitialized register.
           .put("471-uninitialized-locals", TestCondition.match(TestCondition.R8_COMPILER))
-          // When starting from the Dex frontend we see two definitions of the same class coming
-          // from two differents dex files.
-          .put("606-erroneous-class",
-              TestCondition
-                  .match(TestCondition.tools(DexTool.DX, DexTool.JACK), TestCondition.D8_COMPILER,
-                      TestCondition.runtimes(DexVm.ART_DEFAULT)))
           // This test is starting from invalid dex code. It splits up a double value and uses
           // the first register of a double with the second register of another double.
           .put("800-smali", TestCondition.match(TestCondition.R8_COMPILER))
@@ -730,9 +718,7 @@
 
   // Tests that does not have valid input for us to be compatible with jack/dx running.
   private static List<String> noInputJar = ImmutableList.of(
-      "064-field-access", // Missing classes2 dir (has src2)
       "097-duplicate-method", // No input class files.
-      "162-method-resolution", // Based on multiple jasmin files
       "630-safecast-array", // No input class files.
       "801-VoidCheckCast", // No input class files.
       "804-class-extends-itself", // No input class files.
@@ -768,12 +754,17 @@
   // Tests to skip on some conditions
   private static final Multimap<String, TestCondition> testToSkip =
       new ImmutableListMultimap.Builder<String, TestCondition>()
+          // When running R8 on dex input (D8, DX or JACK) this test non-deterministically fails
+          // with a compiler exception, due to invoke-virtual on an interface, or it completes but
+          // the output when run on Art is not as expected. b/65233869
+          .put("162-method-resolution",
+              TestCondition.match(
+                  TestCondition.tools(DexTool.DX, DexTool.JACK), TestCondition.R8_COMPILER))
           // Old runtimes used the legacy test directory which does not contain input for tools
           // NONE and DX.
           .put("952-invoke-custom", TestCondition.match(
               TestCondition.tools(DexTool.NONE, DexTool.DX),
-              TestCondition.runtimes(
-                  DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1, DexVm.ART_7_0_0)))
+              LEGACY_RUNTIME))
           // No input dex files for DX
           .put("952-invoke-custom-kinds", TestCondition.match(TestCondition.tools(DexTool.DX)))
           .build();
@@ -955,7 +946,7 @@
   private static Map<SpecificationKey, TestSpecification> getTestsMap(
       CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm dexVm) {
     File artTestDir = new File(ART_TESTS_DIR);
-    if (dexVm != DexVm.ART_DEFAULT) {
+    if (LEGACY_RUNTIME.set.contains(dexVm)) {
       artTestDir = new File(ART_LEGACY_TESTS_DIR);
     }
     if (!artTestDir.exists()) {
@@ -1446,25 +1437,20 @@
 
     File[] inputFiles;
     if (toolchain == DexTool.NONE) {
-      File classes = new File(specification.directory, "classes");
-      inputFiles =
-          com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes).filter(
-              (File f) -> !f.isDirectory()).toArray(File.class);
+      inputFiles = addFileTree(new File[0], new File(specification.directory, "classes"));
+      inputFiles = addFileTree(inputFiles, new File(specification.directory, "jasmin_classes"));
       File smali = new File(specification.directory, "smali");
       if (smali.exists()) {
         File smaliDex = new File(smali, "out.dex");
         assert smaliDex.exists();
         inputFiles = ObjectArrays.concat(inputFiles, smaliDex);
       }
-      File classes2 = new File(specification.directory, "classes2");
-      if (classes2.exists()) {
-        inputFiles = ObjectArrays.concat(inputFiles,
-            com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes2).filter(
-                (File f) -> !f.isDirectory()).toArray(File.class), File.class);
-      }
+      inputFiles = addFileTree(inputFiles, new File(specification.directory, "classes2"));
+      inputFiles = addFileTree(inputFiles, new File(specification.directory, "jasmin_classes2"));
     } else {
       inputFiles =
-          specification.directory.listFiles((File file) -> file.getName().endsWith(".dex"));
+          specification.directory.listFiles((File file) ->
+              file.getName().endsWith(".dex") && !file.getName().startsWith("jasmin"));
     }
     List<String> fileNames = new ArrayList<>();
     for (File file : inputFiles) {
@@ -1503,6 +1489,18 @@
     }
   }
 
+  private File[] addFileTree(File[] files, File directory) {
+    if (!directory.exists()) {
+      return files;
+    }
+    return ObjectArrays.concat(
+        files,
+        com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(directory)
+            .filter(f -> !f.isDirectory())
+            .toArray(File.class),
+        File.class);
+  }
+
   private void runArtTestDoRunOnArt(
       DexVm version,
       CompilerUnderTest compilerUnderTest,
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 66853cb..61a049e 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -28,7 +28,7 @@
   private static final Path SMALI_DIR = Paths.get(ToolHelper.SMALI_BUILD_DIR);
 
   @Test
-  public void UnreachableCode() throws IOException, ExecutionException {
+  public void UnreachableCode() throws IOException, ExecutionException, CompilationException {
     String name = "unreachable-code-1";
     AndroidApp input = AndroidApp.fromProgramFiles(SMALI_DIR.resolve(name).resolve(name + ".dex"));
     ExecutorService executorService = Executors.newSingleThreadExecutor();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 937f55c..0ca84a1 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -106,9 +106,6 @@
     }
 
     void run() throws Throwable {
-      if (compilationErrorExpected(testName)) {
-        thrown.expect(CompilationError.class);
-      }
       if (minSdkErrorExpected(testName)) {
         thrown.expect(ApiLevelException.class);
       }
@@ -154,11 +151,9 @@
     abstract void build(Path inputFile, Path out) throws Throwable;
   }
 
-  private static List<String> compilationErrorExpected =
-      ImmutableList.of("invokepolymorphic-error-due-to-min-sdk");
-
   private static List<String> minSdkErrorExpected =
-      ImmutableList.of("invokecustom-error-due-to-min-sdk");
+      ImmutableList.of(
+          "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
 
   private static Map<DexVm, List<String>> failsOn =
       ImmutableMap.of(
@@ -216,10 +211,6 @@
     return failsOn(failsOn, name);
   }
 
-  boolean compilationErrorExpected(String testName) {
-    return compilationErrorExpected.contains(testName);
-  }
-
   boolean minSdkErrorExpected(String testName) {
     return minSdkErrorExpected.contains(testName);
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 663d6bd..9952f70 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -93,6 +93,10 @@
       return compareTo(other) > 0;
     }
 
+    public boolean isOlderThanOrEqual(DexVm other) {
+      return compareTo(other) <= 0;
+    }
+
     private DexVm(String shortName) {
       this.shortName = shortName;
     }
@@ -448,7 +452,9 @@
       return ProguardConfiguration.defaultConfiguration(factory);
     }
     ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
-    parser.parse(configPaths);
+    for (Path configPath : configPaths) {
+      parser.parse(configPath);
+    }
     return parser.getConfig();
   }
 
@@ -523,7 +529,7 @@
       DexApplication application,
       AppInfoWithSubtyping appInfo,
       InternalOptions options)
-      throws ProguardRuleParserException, ExecutionException, IOException {
+      throws CompilationException, ExecutionException, IOException {
     return R8.optimize(application, appInfo, options);
   }
 
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/debug/ClassInitializationTest.java b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
new file mode 100644
index 0000000..e90bca7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
@@ -0,0 +1,110 @@
+// 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.debug;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Test;
+
+public class ClassInitializationTest extends DebugTestBase {
+
+  @Test
+  public void testStaticAssingmentInitialization() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerAssignmentInitialization.java";
+    final String CLASS = "ClassInitializerAssignmentInitialization";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 7),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 10),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+        breakpoint(CLASS, "main"),
+        run(),
+        checkStaticField(CLASS, "x", null, Value.createInt(1)),
+        checkStaticField(CLASS, "y", null, Value.createInt(0)),
+        checkStaticField(CLASS, "z", null, Value.createInt(2)),
+        run());
+  }
+
+  @Test
+  public void testBreakpointInEmptyClassInitializer() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerEmpty.java";
+    final String CLASS = "ClassInitializerEmpty";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 8),
+        run());
+  }
+
+  @Test
+  public void testStaticBlockInitialization() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerStaticBlockInitialization.java";
+    final String CLASS = "ClassInitializerStaticBlockInitialization";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 12),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 13),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 14),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 17),
+        stepOver(),
+        checkLine(SOURCE_FILE, 19),
+        breakpoint(CLASS, "main"),
+        run(),
+        checkLine(SOURCE_FILE, 23),
+        checkStaticField(CLASS, "x", null, Value.createInt(3)),
+        run());
+  }
+
+  @Test
+  public void testStaticMixedInitialization() throws Throwable {
+    final String SOURCE_FILE = "ClassInitializerMixedInitialization.java";
+    final String CLASS = "ClassInitializerMixedInitialization";
+
+    runDebugTest(CLASS,
+        breakpoint(CLASS, "<clinit>"),
+        run(),
+        checkLine(SOURCE_FILE, 8),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 12),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 13),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 16),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 18),
+        checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+        checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(2)),
+        breakpoint(CLASS, "main"),
+        run(),
+        checkLine(SOURCE_FILE, 22),
+        checkStaticField(CLASS, "x", null, Value.createInt(3)),
+        checkStaticField(CLASS, "y", null, Value.createInt(2)),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 6340728..fa78fd0 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -10,9 +10,12 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongList;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -62,8 +65,10 @@
 import org.junit.rules.TestName;
 
 /**
+ * Base class for debugging tests.
  *
- * Base class for debugging tests
+ * The protocol messages are described here:
+ * https://docs.oracle.com/javase/8/docs/platform/jpda/jdwp/jdwp-protocol.html
  */
 public abstract class DebugTestBase {
 
@@ -347,6 +352,27 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkStaticFieldClinitSafe(
+      String className, String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(t -> {
+      // TODO(65148874): The current Art from AOSP master hangs when requesting static fields
+      // when breaking in <clinit>. Last known good version is 7.0.0.
+      Assume.assumeTrue(
+          "Skipping test " + testName.getMethodName() + " because ART version is not supported",
+          isRunningJava() || ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_7_0_0));
+      checkStaticField(className, fieldName, fieldSignature, expectedValue);
+    });
+  }
+
+  protected final JUnit3Wrapper.Command checkStaticField(
+      String className, String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(t -> {
+      Value value = t.getStaticField(className, fieldName, fieldSignature);
+      Assert.assertEquals("Incorrect value for static '" + className + "." + fieldName + "'",
+          expectedValue, value);
+    });
+  }
+
   protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
     return t -> inspector.accept(t.debuggeeState);
   }
@@ -561,6 +587,7 @@
     public static class DebuggeeState implements FrameInspector {
 
       private class DebuggeeFrame implements FrameInspector {
+
         private final long frameId;
         private final Location location;
 
@@ -819,6 +846,77 @@
       public String getMethodSignature() {
         return getTopFrame().getMethodSignature();
       }
+
+      public Value getStaticField(String className, String fieldName, String fieldSignature) {
+        String classSignature = DescriptorUtils.javaTypeToDescriptor(className);
+        byte typeTag = TypeTag.CLASS;
+        long classId = getMirror().getClassID(classSignature);
+        Assert.assertFalse("No class named " + className + " found", classId == -1);
+
+        // The class is available, lookup and read the field.
+        long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+        return getField(getMirror(), classId, fieldId);
+      }
+
+      private long findField(VmMirror mirror, long classId, String fieldName,
+          String fieldSignature) {
+
+        boolean withGenericSignature = true;
+        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+            ReferenceTypeCommandSet.FieldsWithGenericCommand);
+        commandPacket.setNextValueAsReferenceTypeID(classId);
+        ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+        if (replyPacket.getErrorCode() != Error.NONE) {
+          // Retry with older command ReferenceType.Fields.
+          withGenericSignature = false;
+          commandPacket.setCommand(ReferenceTypeCommandSet.FieldsCommand);
+          replyPacket = mirror.performCommand(commandPacket);
+          assert replyPacket.getErrorCode() == Error.NONE;
+        }
+
+        int fieldsCount = replyPacket.getNextValueAsInt();
+        LongList matchingFieldIds = new LongArrayList();
+        for (int i = 0; i < fieldsCount; ++i) {
+          long currentFieldId = replyPacket.getNextValueAsFieldID();
+          String currentFieldName = replyPacket.getNextValueAsString();
+          String currentFieldSignature = replyPacket.getNextValueAsString();
+          if (withGenericSignature) {
+            replyPacket.getNextValueAsString(); // Skip generic signature.
+          }
+          replyPacket.getNextValueAsInt(); // Skip modifiers.
+
+          // Filter fields based on name (and signature if there is).
+          if (fieldName.equals(currentFieldName)) {
+            if (fieldSignature == null || fieldSignature.equals(currentFieldSignature)) {
+              matchingFieldIds.add(currentFieldId);
+            }
+          }
+        }
+        Assert.assertTrue(replyPacket.isAllDataRead());
+
+        Assert.assertFalse("No field named " + fieldName + " found", matchingFieldIds.isEmpty());
+        // There must be only one matching field.
+        Assert.assertEquals("More than 1 field found: please specify a signature", 1,
+            matchingFieldIds.size());
+        return matchingFieldIds.getLong(0);
+      }
+
+      private Value getField(VmMirror mirror, long classId, long fieldId) {
+
+        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+            ReferenceTypeCommandSet.GetValuesCommand);
+        commandPacket.setNextValueAsReferenceTypeID(classId);
+        commandPacket.setNextValueAsInt(1);
+        commandPacket.setNextValueAsFieldID(fieldId);
+        ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+        assert replyPacket.getErrorCode() == Error.NONE;
+
+        int fieldsCount = replyPacket.getNextValueAsInt();
+        assert fieldsCount == 1;
+        Value result = replyPacket.getNextValueAsValue();
+        Assert.assertTrue(replyPacket.isAllDataRead());
+        return result;
+      }
     }
 
     private static boolean inScope(long index, Variable var) {
@@ -886,100 +984,6 @@
       setState(State.WaitForEvent);
     }
 
-    private boolean installBreakpoint(BreakpointInfo breakpointInfo) {
-      String classSignature = getClassSignature(breakpointInfo.className);
-      byte typeTag = TypeTag.CLASS;
-      long classId = getMirror().getClassID(classSignature);
-      if (classId == -1) {
-        // Is it an interface ?
-        classId = getMirror().getInterfaceID(classSignature);
-        typeTag = TypeTag.INTERFACE;
-      }
-      if (classId == -1) {
-        // The class is not ready yet. Request a CLASS_PREPARE to delay the installation of the
-        // breakpoint.
-        ReplyPacket replyPacket = getMirror().setClassPrepared(breakpointInfo.className);
-        int classPrepareRequestId = replyPacket.getNextValueAsInt();
-        assertAllDataRead(replyPacket);
-        events.put(Integer.valueOf(classPrepareRequestId),
-            new ClassPrepareHandler(breakpointInfo, classPrepareRequestId));
-        return false;
-      } else {
-        // Find the method.
-        long breakpointMethodId = findMethod(classId, breakpointInfo.methodName,
-            breakpointInfo.methodSignature);
-        long index = getMethodFirstCodeIndex(classId, breakpointMethodId);
-        Assert.assertTrue("No code in method", index >= 0);
-        // Install the breakpoint.
-        ReplyPacket replyPacket = getMirror()
-            .setBreakpoint(new Location(typeTag, classId, breakpointMethodId, index),
-                SuspendPolicy.ALL);
-        checkReplyPacket(replyPacket, "Breakpoint");
-        int breakpointId = replyPacket.getNextValueAsInt();
-        // Nothing to do on breakpoint
-        events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
-        return true;
-      }
-    }
-
-    private long findMethod(long classId, String methodName, String methodSignature) {
-      class MethodInfo {
-
-        final long methodId;
-        final String methodName;
-        final String methodSignature;
-
-        MethodInfo(long methodId, String methodName, String methodSignature) {
-          this.methodId = methodId;
-          this.methodName = methodName;
-          this.methodSignature = methodSignature;
-        }
-      }
-
-      boolean withGenericSignature = true;
-      CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
-          ReferenceTypeCommandSet.MethodsWithGenericCommand);
-      commandPacket.setNextValueAsReferenceTypeID(classId);
-      ReplyPacket replyPacket = getMirror().performCommand(commandPacket);
-      if (replyPacket.getErrorCode() != Error.NONE) {
-        // Retry with older command ReferenceType.Methods
-        withGenericSignature = false;
-        commandPacket.setCommand(ReferenceTypeCommandSet.MethodsCommand);
-        replyPacket = getMirror().performCommand(commandPacket);
-        assert replyPacket.getErrorCode() == Error.NONE;
-      }
-
-      int methodsCount = replyPacket.getNextValueAsInt();
-      List<MethodInfo> methodInfos = new ArrayList<>(methodsCount);
-      for (int i = 0; i < methodsCount; ++i) {
-        long currentMethodId = replyPacket.getNextValueAsMethodID();
-        String currentMethodName = replyPacket.getNextValueAsString();
-        String currentMethodSignature = replyPacket.getNextValueAsString();
-        if (withGenericSignature) {
-          replyPacket.getNextValueAsString(); // skip generic signature
-        }
-        replyPacket.getNextValueAsInt(); // skip modifiers
-        methodInfos
-            .add(new MethodInfo(currentMethodId, currentMethodName, currentMethodSignature));
-      }
-      Assert.assertTrue(replyPacket.isAllDataRead());
-
-      // Only keep methods with the expected name.
-      methodInfos = methodInfos.stream()
-          .filter(m -> m.methodName.equals(methodName)).collect(
-              Collectors.toList());
-      if (methodSignature != null) {
-        methodInfos = methodInfos.stream()
-            .filter(m -> methodSignature.equals(m.methodSignature)).collect(
-                Collectors.toList());
-      }
-      Assert.assertFalse("No method found", methodInfos.isEmpty());
-      // There must be only one matching method
-      Assert.assertEquals("More than 1 method found: please specify a signature", 1,
-          methodInfos.size());
-      return methodInfos.get(0).methodId;
-    }
-
     private long getMethodFirstCodeIndex(long classId, long breakpointMethodId) {
       ReplyPacket replyPacket = getMirror().getLineTable(classId, breakpointMethodId);
       checkReplyPacket(replyPacket, "Failed to get method line table");
@@ -1019,6 +1023,7 @@
         private final String className;
         private final String methodName;
         private final String methodSignature;
+        private boolean requestedClassPrepare = false;
 
         public BreakpointCommand(String className, String methodName,
             String methodSignature) {
@@ -1031,7 +1036,91 @@
 
         @Override
         public void perform(JUnit3Wrapper testBase) {
-          testBase.installBreakpoint(new BreakpointInfo(className, methodName, methodSignature));
+          VmMirror mirror = testBase.getMirror();
+          String classSignature = getClassSignature(className);
+          byte typeTag = TypeTag.CLASS;
+          long classId = mirror.getClassID(classSignature);
+          if (classId == -1) {
+            // Is it an interface ?
+            classId = mirror.getInterfaceID(classSignature);
+            typeTag = TypeTag.INTERFACE;
+          }
+          if (classId == -1) {
+            // The class is not ready yet. Request a CLASS_PREPARE to delay the installation of the
+            // breakpoint.
+            assert requestedClassPrepare == false : "Already requested class prepare";
+            requestedClassPrepare = true;
+            ReplyPacket replyPacket = mirror.setClassPrepared(className);
+            final int classPrepareRequestId = replyPacket.getNextValueAsInt();
+            testBase.events.put(Integer.valueOf(classPrepareRequestId), wrapper -> {
+              // Remove the CLASS_PREPARE
+              wrapper.events.remove(Integer.valueOf(classPrepareRequestId));
+              wrapper.getMirror().clearEvent(JDWPConstants.EventKind.CLASS_PREPARE,
+                  classPrepareRequestId);
+
+              // Breakpoint then resume. Note: we add them at the beginning of the queue (to be the
+              // next commands to be processed), thus they need to be pushed in reverse order.
+              wrapper.commandsQueue.addFirst(new JUnit3Wrapper.Command.RunCommand());
+              wrapper.commandsQueue.addFirst(BreakpointCommand.this);
+
+              // Set wrapper ready to process next command.
+              wrapper.setState(State.ProcessCommand);
+            });
+          } else {
+            // The class is available: lookup the method then set the breakpoint.
+            long breakpointMethodId = findMethod(mirror, classId, methodName, methodSignature);
+            long index = testBase.getMethodFirstCodeIndex(classId, breakpointMethodId);
+            Assert.assertTrue("No code in method", index >= 0);
+            ReplyPacket replyPacket = testBase.getMirror().setBreakpoint(
+                new Location(typeTag, classId, breakpointMethodId, index), SuspendPolicy.ALL);
+            assert replyPacket.getErrorCode() == Error.NONE;
+            int breakpointId = replyPacket.getNextValueAsInt();
+            testBase.events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
+          }
+        }
+
+        private static long findMethod(VmMirror mirror, long classId, String methodName,
+            String methodSignature) {
+
+          boolean withGenericSignature = true;
+          CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+              ReferenceTypeCommandSet.MethodsWithGenericCommand);
+          commandPacket.setNextValueAsReferenceTypeID(classId);
+          ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+          if (replyPacket.getErrorCode() != Error.NONE) {
+            // Retry with older command ReferenceType.Methods
+            withGenericSignature = false;
+            commandPacket.setCommand(ReferenceTypeCommandSet.MethodsCommand);
+            replyPacket = mirror.performCommand(commandPacket);
+            assert replyPacket.getErrorCode() == Error.NONE;
+          }
+
+          int methodsCount = replyPacket.getNextValueAsInt();
+          List<Long> matchingMethodIds = new ArrayList<>();
+          for (int i = 0; i < methodsCount; ++i) {
+            long currentMethodId = replyPacket.getNextValueAsMethodID();
+            String currentMethodName = replyPacket.getNextValueAsString();
+            String currentMethodSignature = replyPacket.getNextValueAsString();
+            if (withGenericSignature) {
+              replyPacket.getNextValueAsString(); // skip generic signature
+            }
+            replyPacket.getNextValueAsInt(); // skip modifiers
+
+            // Filter methods based on name (and signature if there is).
+            if (methodName.equals(currentMethodName)) {
+              if (methodSignature == null || methodSignature.equals(currentMethodSignature)) {
+                matchingMethodIds.add(Long.valueOf(currentMethodId));
+              }
+            }
+          }
+          Assert.assertTrue(replyPacket.isAllDataRead());
+
+          Assert
+              .assertFalse("No method named " + methodName + " found", matchingMethodIds.isEmpty());
+          // There must be only one matching method
+          Assert.assertEquals("More than 1 method found: please specify a signature", 1,
+              matchingMethodIds.size());
+          return matchingMethodIds.get(0);
         }
 
         @Override
@@ -1178,47 +1267,6 @@
       }
     }
 
-    private static class BreakpointInfo {
-
-      private final String className;
-      private final String methodName;
-      private final String methodSignature;
-
-      private BreakpointInfo(String className, String methodName, String methodSignature) {
-        this.className = className;
-        this.methodName = methodName;
-        this.methodSignature = methodSignature;
-      }
-    }
-
-    /**
-     * CLASS_PREPARE signals us that we can install a breakpoint
-     */
-    private static class ClassPrepareHandler implements EventHandler {
-
-      private final BreakpointInfo breakpointInfo;
-      private final int classPrepareRequestId;
-
-      private ClassPrepareHandler(BreakpointInfo breakpointInfo, int classPrepareRequestId) {
-        this.breakpointInfo = breakpointInfo;
-        this.classPrepareRequestId = classPrepareRequestId;
-      }
-
-      @Override
-      public void handle(JUnit3Wrapper testBase) {
-        // Remove the CLASS_PREPARE
-        testBase.events.remove(Integer.valueOf(classPrepareRequestId));
-        testBase.getMirror().clearEvent(JDWPConstants.EventKind.CLASS_PREPARE,
-            classPrepareRequestId);
-
-        // Install breakpoint now.
-        boolean success = testBase.installBreakpoint(breakpointInfo);
-        Assert.assertTrue("Failed to insert breakpoint after class has been prepared", success);
-
-        // Resume now
-        testBase.resume();
-      }
-    }
   }
 
   //
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
new file mode 100644
index 0000000..e62a17b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -0,0 +1,172 @@
+// 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.debug;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class KotlinInlineTest extends DebugTestBase {
+
+  @Ignore("Requires kotlin-specific stepping behavior")
+  @Test
+  public void testStepOverInline() throws Throwable {
+    String methodName = "singleInline";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", methodName),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(41, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        // TODO(shertz) stepping over must take kotlin inline range into account.
+        stepOver(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(42, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepOver(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(43, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        run());
+  }
+
+  @Ignore("Requires kotlin-specific stepping behavior")
+  @Test
+  public void testStepIntoInline() throws Throwable {
+    String methodName = "singleInline";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", methodName),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(41, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        // TODO(shertz) stepping over must take kotlin inline range into account.
+        stepInto(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          // The actual line number (the one encoded in debug information) is different than the
+          // source file one.
+          // TODO(shertz) extract original line number from JSR-45's SMAP (only supported on
+          // Android O+).
+          assertTrue(42 != s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        run());
+  }
+
+  @Ignore("Requires kotlin-specific stepping behavior")
+  @Test
+  public void testStepOutInline() throws Throwable {
+    String methodName = "singleInline";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", methodName),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(41, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        // TODO(shertz) stepping out must take kotlin inline range into account.
+        stepInto(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+        }),
+        stepOut(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(43, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        run());
+  }
+
+  @Test
+  public void testKotlinInline() throws Throwable {
+    final String inliningMethodName = "invokeInlinedFunctions";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", inliningMethodName),
+        run(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(16, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          // We must have stepped into the code of the inlined method but the actual current method
+          // did not change.
+          assertEquals(inliningMethodName, s.getMethodName());
+          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(17, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(18, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("inA", Value.createInt(1));
+          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
+          s.checkLocal("$i$f$inlinedA");
+          s.checkLocal("$i$a$1$inlinedA");
+        }),
+        stepInto(),
+        inspect(s -> {
+          // We must have stepped into the code of the second inlined method but the actual current
+          // method did not change.
+          assertEquals(inliningMethodName, s.getMethodName());
+          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(19, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(20, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("inB", Value.createInt(2));
+          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
+          s.checkLocal("$i$f$inlinedB");
+          s.checkLocal("$i$a$1$inlinedB");
+        }),
+        run());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinTest.java b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
index 8caa3c1..a6b57b8 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
@@ -3,97 +3,160 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import static org.junit.Assert.assertEquals;
+
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
-import org.junit.Assert;
 import org.junit.Test;
 
 public class KotlinTest extends DebugTestBase {
 
+  // TODO(shertz) simplify test
+  // TODO(shertz) add more variables ?
   @Test
-  public void testKotlinApp() throws Throwable {
-    final String inliningMethodName = "invokeInlinedFunctions";
+  public void testStepOver() throws Throwable {
     runDebugTestKotlin("KotlinApp",
         breakpoint("KotlinApp$Companion", "main"),
         run(),
         inspect(s -> {
-          Assert.assertEquals("KotlinApp.kt", s.getSourceFile());
-          Assert.assertEquals(8, s.getLineNumber());
+          assertEquals("KotlinApp$Companion", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(24, s.getLineNumber());
           s.checkLocal("this");
           s.checkLocal("args");
+          checkNoLocal("instance");
         }),
         stepOver(),
         inspect(s -> {
-          Assert.assertEquals(9, s.getLineNumber());
-          s.checkLocal("this");
-          s.checkLocal("args");
-        }),
-        stepOver(),
-        inspect(s -> {
-          Assert.assertEquals(10, s.getLineNumber());
+          assertEquals(25, s.getLineNumber());
           s.checkLocal("this");
           s.checkLocal("args");
           s.checkLocal("instance");
         }),
         stepOver(),
         inspect(s -> {
-          Assert.assertEquals(11, s.getLineNumber());
+          assertEquals(26, s.getLineNumber());
           s.checkLocal("this");
           s.checkLocal("args");
           s.checkLocal("instance");
         }),
+        run());
+  }
+
+  @Test
+  public void testStepIntoAndOut() throws Throwable {
+    runDebugTestKotlin("KotlinApp",
+        breakpoint("KotlinApp$Companion", "main"),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinApp$Companion", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(24, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          checkNoLocal("instance");
+        }),
+        stepOver(),
+        inspect(s -> {
+          assertEquals(25, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          s.checkLocal("instance");
+        }),
+        // Step into 1st invoke of ifElse
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(24, s.getLineNumber());
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(8, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(true));
+          checkNoLocal("a");
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          // We must have stepped into the code of the inlined method but the actual current method
-          // did not change.
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(9, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(true));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(25, s.getLineNumber());
+          // We should be into the 'then' statement.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(10, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(true));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(26, s.getLineNumber());
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(11, s.getLineNumber());
           s.checkLocal("this");
-          s.checkLocal("inA", Value.createInt(1));
-          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedA");
-          s.checkLocal("$i$a$1$inlinedA");
+          s.checkLocal("cond", Value.createBoolean(true));
+          s.checkLocal("a", Value.createInt(10));
+          s.checkLocal("b", Value.createInt(20));
+          checkNoLocal("c");
+        }),
+        // Go back to the main method
+        stepOut(),
+        inspect(s -> {
+          assertEquals("KotlinApp$Companion", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(26, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          checkNoLocal("instance");
+        }),
+        // Step into 2nd invoke of ifElse
+        stepInto(),
+        inspect(s -> {
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(8, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(false));
+          checkNoLocal("a");
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          // We must have stepped into the code of the second inlined method but the actual current
-          // method did not change.
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(9, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(false));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(27, s.getLineNumber());
+          // We should be into the 'else' statement this time.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(13, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(false));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(28, s.getLineNumber());
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(14, s.getLineNumber());
           s.checkLocal("this");
-          s.checkLocal("inB", Value.createInt(2));
-          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedB");
-          s.checkLocal("$i$a$1$inlinedB");
+          s.checkLocal("cond", Value.createBoolean(false));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          s.checkLocal("c", Value.createInt(5));
         }),
         run());
   }
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 02fb694..12fd23d 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -470,4 +470,99 @@
         checkNoLocal("t"),
         run());
   }
+
+  @Test
+  public void argumentLiveAtReturn() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "argumentLiveAtReturn"),
+        run(),
+        checkLine(SOURCE_FILE, 248),
+        stepOver(),
+        checkLine(SOURCE_FILE, 262),
+        checkLocal("x", Value.createInt(-1)),
+        checkNoLocal("t"),
+        run());
+  }
+
+  @Test
+  public void switchRewriteToIfs() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "switchRewriteToIfs"),
+        run(),
+        checkLine(SOURCE_FILE, 267),
+        stepOver(),
+        checkLine(SOURCE_FILE, 268),
+        checkLocal("x", Value.createInt(1)),
+        checkLocal("t", Value.createInt(2)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 269),
+        checkLocal("x", Value.createInt(2)),
+        checkLocal("t", Value.createInt(2)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 271),
+        checkLocal("x", Value.createInt(4)),
+        checkNoLocal("t"),
+        run());
+  }
+
+  @Test
+  public void switchRewriteToSwitches() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "switchRewriteToSwitches"),
+        run(),
+        checkLine(SOURCE_FILE, 282),
+        stepOver(),
+        checkLine(SOURCE_FILE, 283),
+        checkLocal("x", Value.createInt(1)),
+        checkLocal("t", Value.createInt(2)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 284),
+        checkLocal("x", Value.createInt(2)),
+        checkLocal("t", Value.createInt(2)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 286),
+        checkLocal("x", Value.createInt(4)),
+        checkNoLocal("t"),
+        run());
+  }
+
+  @Test
+  public void regression65039701() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "regression65039701"),
+        run(),
+        checkLine(SOURCE_FILE, 304),
+        checkLocal("createIntNotLong", Value.createBoolean(true)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 305),
+        checkLocal("a"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 306),
+        stepOver(),
+        checkLine(SOURCE_FILE, 308),
+        run());
+  }
+
+  @Test
+  public void regression65066975() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "regression65066975"),
+        run(),
+        checkLine(SOURCE_FILE, 312),
+        checkLocal("bit", Value.createBoolean(false)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 313),
+        stepOver(),
+        checkLine(SOURCE_FILE, 316),
+        stepOver(),
+        checkLine(SOURCE_FILE, 318),
+        stepOver(),
+        checkLine(SOURCE_FILE, 319),
+        run());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index ad6f925..4eae243 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -11,14 +11,18 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexDebugEntry;
 import com.android.tools.r8.graph.DexDebugEntryBuilder;
+import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -37,8 +41,12 @@
   public DebugInfoInspector(DexEncodedMethod method, DexItemFactory factory) {
     this.method = method;
     info = method.getCode().asDexCode().getDebugInfo();
-    entries = new DexDebugEntryBuilder(method, factory).build();
-    checkConsistentEntries();
+    if (info != null) {
+      entries = new DexDebugEntryBuilder(method, factory).build();
+      checkConsistentEntries();
+    } else {
+      entries = Collections.emptyList();
+    }
   }
 
   public DebugInfoInspector(DexInspector inspector, String clazz, MethodSignature method) {
@@ -50,6 +58,24 @@
     this(new DexInspector(app), clazz, method);
   }
 
+  public boolean hasLocalsInfo() {
+    DexDebugInfo dexInfo = method.getCode().asDexCode().getDebugInfo();
+    if (info == null || dexInfo == null) {
+      return false;
+    }
+    for (DexString parameter : dexInfo.parameters) {
+      if (parameter != null) {
+        return true;
+      }
+    }
+    for (DexDebugEvent event : dexInfo.events) {
+      if (event instanceof DexDebugEvent.StartLocal) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public void checkStartLine(int i) {
     assertEquals(i, info.startLine);
   }
@@ -102,7 +128,8 @@
     DexDebugEntry previousEntry = null;
     for (DexDebugEntry entry : entries) {
       if (previousEntry != null) {
-        assertTrue("More than one entry defined for PC " + entry.address,
+        assertTrue(
+            "More than one entry defined for PC " + StringUtils.hexString(entry.address, 2),
             entry.address > previousEntry.address);
       }
       previousEntry = entry;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
index be0a122..74992e9 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
@@ -19,8 +19,43 @@
     return -Math.abs(x);
   }
 
+  private static synchronized int throwing(int cond) {
+    int x = 42;
+    if (cond < 0) {
+      throw new IllegalStateException();
+    }
+    return 2;
+  }
+
+  private static synchronized int monitorExitRegression(int cond) {
+    int x = 42;
+    switch (cond) {
+      case 1:
+        return 1;
+      case 2:
+        throw new IllegalStateException();
+      case 3:
+        throw new RuntimeException();
+      case 4:
+        return 2;
+      case 5:
+        x = 7;
+      case 6:
+        return 3;
+      default:
+    }
+    if (cond > 0) {
+      x = cond + cond;
+    } else {
+      throw new ArithmeticException();
+    }
+    return 2;
+  }
+
   public static void main(String[] args) {
     System.out.println(syncStatic(1234));
     System.out.println(new SynchronizedMethodTest().syncInstance(1234));
+    System.out.println(throwing(1234));
+    System.out.println(monitorExitRegression(1234));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
index 7cee4a3..ba0849b 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
@@ -5,8 +5,9 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
 import org.junit.Test;
 
 public class SynchronizedMethodTestRunner extends DebugInfoTestBase {
@@ -18,7 +19,7 @@
     AndroidApp d8App = compileWithD8(clazz);
     AndroidApp dxApp = getDxCompiledSources();
 
-    String expected = "42" + ToolHelper.LINE_SEPARATOR + "42" + ToolHelper.LINE_SEPARATOR;
+    String expected = StringUtils.lines("42", "42", "2", "2");
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
     assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
@@ -28,6 +29,14 @@
 
     checkSyncInstance(inspectMethod(d8App, clazz, "int", "syncInstance", "int"));
     checkSyncInstance(inspectMethod(dxApp, clazz, "int", "syncInstance", "int"));
+
+    checkThrowing(inspectMethod(d8App, clazz, "int", "throwing", "int"), false);
+    checkThrowing(inspectMethod(dxApp, clazz, "int", "throwing", "int"), true);
+
+    checkMonitorExitRegression(
+        inspectMethod(d8App, clazz, "int", "monitorExitRegression", "int"), false);
+    checkMonitorExitRegression(
+        inspectMethod(dxApp, clazz, "int", "monitorExitRegression", "int"), true);
   }
 
   private void checkSyncStatic(DebugInfoInspector info) {
@@ -48,4 +57,25 @@
     info.checkLineHasExactLocals(19, locals);
     info.checkNoLine(20);
   }
+
+  private void checkThrowing(DebugInfoInspector info, boolean dx) {
+    info.checkStartLine(23);
+    if (!dx) {
+      info.checkLineHasExactLocals(23, "cond", "int");
+    }
+    info.checkLineHasExactLocals(24, "cond", "int", "x", "int");
+    info.checkLineHasExactLocals(25, "cond", "int", "x", "int");
+    info.checkNoLine(26);
+    info.checkLineHasExactLocals(27, "cond", "int", "x", "int");
+  }
+
+  private void checkMonitorExitRegression(DebugInfoInspector info, boolean dx) {
+    info.checkStartLine(31);
+    for (int line : Arrays.asList(32, 34, 36, 38, 40, 42, 44, 48, 50, 52)) {
+      if (dx && line == 40) {
+        continue;
+      }
+      info.checkLineHasExactLocals(line, "cond", "int", "x", "int");
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index f731e39..e7718e1 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -16,7 +16,7 @@
 public class TargetLookupTest extends SmaliTestBase {
 
   @Test
-  public void lookupDirect() {
+  public void lookupDirect() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addDefaultConstructor();
@@ -71,7 +71,7 @@
   }
 
   @Test
-  public void lookupDirectSuper() {
+  public void lookupDirectSuper() throws Exception {
     SmaliBuilder builder = new SmaliBuilder("TestSuper");
 
     builder.addDefaultConstructor();
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/D8FrameworkDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java
new file mode 100644
index 0000000..2cbd60a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java
@@ -0,0 +1,50 @@
+// 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.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8FrameworkDeterministicTest extends CompilationTestBase {
+  private static final int MIN_SDK = 24;
+  private static final String JAR = "third_party/framework/framework_160115954.jar";
+
+  private AndroidApp doRun(D8Command command) throws IOException, CompilationException {
+    return ToolHelper.runD8(command);
+  }
+
+  @Test
+  public void verifyDebugBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    D8Command command = D8Command.builder()
+        .addProgramFiles(Paths.get(JAR))
+        .setMode(CompilationMode.DEBUG)
+        .setMinApiLevel(MIN_SDK)
+        .build();
+    AndroidApp app1 = doRun(command);
+    AndroidApp app2 = doRun(command);
+    assertIdenticalApplications(app1, app2);
+  }
+
+  @Test
+  public void verifyReleaseBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    D8Command command = D8Command.builder()
+        .addProgramFiles(Paths.get(JAR))
+        .setMode(CompilationMode.RELEASE)
+        .setMinApiLevel(MIN_SDK)
+        .build();
+    AndroidApp app1 = doRun(command);
+    AndroidApp app2 = doRun(command);
+    assertIdenticalApplications(app1, app2);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index bc3d758..75a0275 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -11,19 +11,23 @@
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.OutputMode;
+import com.beust.jcommander.internal.Lists;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
 
-  public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods) {
-    Collections.shuffle(methods);
-    return methods;
+  public Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
+    List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
+    Collections.shuffle(toShuffle);
+    return new LinkedHashSet<>(toShuffle);
   }
 
   private AndroidApp doRun()
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index 50cee1b..cf191de 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir;
 
+import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -31,7 +32,7 @@
    * Third block: Return instruction
    *
    */
-  IRCode simpleCode() {
+  IRCode simpleCode() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "int";
@@ -61,7 +62,7 @@
   }
 
   @Test
-  public void removeBeforeNext() {
+  public void removeBeforeNext() throws Exception {
     IRCode code = simpleCode();
 
     ListIterator<BasicBlock> blocks = code.listIterator();
@@ -70,7 +71,7 @@
   }
 
   @Test
-  public void removeTwice() {
+  public void removeTwice() throws Exception {
     IRCode code = simpleCode();
 
     ListIterator<BasicBlock> blocks = code.listIterator();
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index f4206ea..6fb5db1 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -24,7 +24,7 @@
 
 public class InlineTest extends SmaliTestBase {
 
-  TestApplication codeForMethodReplaceTest(int a, int b) {
+  TestApplication codeForMethodReplaceTest(int a, int b) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature = builder.addStaticMethod(
@@ -88,7 +88,7 @@
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
   }
 
-  public void runInlineTest(int a, int b, int expectedA, int expectedB) {
+  public void runInlineTest(int a, int b, int expectedA, int expectedB) throws Exception {
     // Run code without inlining.
     TestApplication test = codeForMethodReplaceTest(a, b);
     String result = test.run();
@@ -116,12 +116,12 @@
   }
 
   @Test
-  public void inline() {
+  public void inline() throws Exception {
     runInlineTest(1, 1, 2, 0);
     runInlineTest(1, 2, 3, 1);
   }
 
-  TestApplication codeForMethodReplaceReturnVoidTest(int a, int b) {
+  TestApplication codeForMethodReplaceReturnVoidTest(int a, int b) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature = builder.addStaticMethod(
@@ -169,7 +169,7 @@
   }
 
   @Test
-  public void inlineReturnVoid() {
+  public void inlineReturnVoid() throws Exception {
     // Run code without inlining.
     TestApplication test = codeForMethodReplaceReturnVoidTest(1, 2);
     String result = test.run();
@@ -187,7 +187,7 @@
     assertEquals(Integer.toString(1), result);
   }
 
-  TestApplication codeForMultipleMethodReplaceTest(int a, int b) {
+  TestApplication codeForMultipleMethodReplaceTest(int a, int b) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature = builder.addStaticMethod(
@@ -259,7 +259,7 @@
         additionalCode, valueNumberGenerator, options);
   }
 
-  public void runInlineMultipleTest(int a, int b, int expectedA, int expectedB) {
+  public void runInlineMultipleTest(int a, int b, int expectedA, int expectedB) throws Exception {
     // Run code without inlining.
     TestApplication test = codeForMultipleMethodReplaceTest(a, b);
     String result = test.run();
@@ -304,12 +304,13 @@
   }
 
   @Test
-  public void inlineMultiple() {
+  public void inlineMultiple() throws Exception {
     runInlineMultipleTest(1, 1, 4, 1);
     runInlineMultipleTest(1, 2, 7, 8);
   }
 
-  TestApplication codeForMethodReplaceTestWithCatchHandler(int a, int b, boolean twoGuards) {
+  TestApplication codeForMethodReplaceTestWithCatchHandler(int a, int b, boolean twoGuards)
+      throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String secondGuard = twoGuards ?
@@ -385,7 +386,7 @@
   }
 
   public void runInlineCallerHasCatchHandlersTest(
-      int a, int b, boolean twoGuards, int expectedA, int expectedB) {
+      int a, int b, boolean twoGuards, int expectedA, int expectedB) throws Exception {
     // Run code without inlining.
     TestApplication test = codeForMethodReplaceTestWithCatchHandler(a, b, twoGuards);
     String result = test.run();
@@ -413,14 +414,14 @@
   }
 
   @Test
-  public void inlineCallerHasCatchHandlers() {
+  public void inlineCallerHasCatchHandlers() throws Exception {
     runInlineCallerHasCatchHandlersTest(1, 1, false, 2, 0);
     runInlineCallerHasCatchHandlersTest(1, 2, false, 3, 1);
     runInlineCallerHasCatchHandlersTest(1, 1, true, 2, 0);
     runInlineCallerHasCatchHandlersTest(1, 2, true, 3, 1);
   }
 
-  TestApplication codeForInlineCanThrow(int a, int b, boolean twoGuards) {
+  TestApplication codeForInlineCanThrow(int a, int b, boolean twoGuards) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String secondGuard = twoGuards ?
@@ -499,7 +500,7 @@
   }
 
   public void runInlineCanThrow(
-      int a, int b, boolean twoGuards, int expectedA, int expectedB) {
+      int a, int b, boolean twoGuards, int expectedA, int expectedB) throws Exception {
     // Run code without inlining.
     TestApplication test = codeForInlineCanThrow(a, b, twoGuards);
     String result = test.run();
@@ -527,14 +528,14 @@
   }
 
   @Test
-  public void inlineCanThrow() {
+  public void inlineCanThrow() throws Exception {
     runInlineCanThrow(2, 2, false, 1, 1);
     runInlineCanThrow(2, 0, false, -2, -1);
     runInlineCanThrow(2, 2, true, 1, 1);
     runInlineCanThrow(2, 0, true, -2, -1);
   }
 
-  private TestApplication codeForInlineAlwaysThrows(boolean twoGuards) {
+  private TestApplication codeForInlineAlwaysThrows(boolean twoGuards) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String secondGuard = twoGuards ?
@@ -611,7 +612,8 @@
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
   }
 
-  private void runInlineAlwaysThrows(boolean twoGuards, int expectedA, int expectedB) {
+  private void runInlineAlwaysThrows(boolean twoGuards, int expectedA, int expectedB)
+      throws Exception {
     // Run code without inlining.
     TestApplication test = codeForInlineAlwaysThrows(twoGuards);
     String result = test.run();
@@ -640,12 +642,12 @@
   }
 
   @Test
-  public void inlineAlwaysThrows() {
+  public void inlineAlwaysThrows() throws Exception {
     runInlineAlwaysThrows(false, -2, -2);
     runInlineAlwaysThrows(true, -2, -1);
   }
 
-  private TestApplication codeForInlineAlwaysThrowsMultiple(boolean twoGuards) {
+  private TestApplication codeForInlineAlwaysThrowsMultiple(boolean twoGuards) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String secondGuard = twoGuards ?
@@ -732,7 +734,8 @@
         application, method, code, additionalCode, valueNumberGenerator, options);
   }
 
-  private void runInlineAlwaysThrowsMultiple(boolean twoGuards, int expectedA, int expectedB) {
+  private void runInlineAlwaysThrowsMultiple(boolean twoGuards, int expectedA, int expectedB)
+      throws Exception {
     // Run code without inlining.
     TestApplication test = codeForInlineAlwaysThrows(twoGuards);
     String result = test.run();
@@ -785,13 +788,13 @@
   }
 
   @Test
-  public void inlineAlwaysThrowsMultiple() {
+  public void inlineAlwaysThrowsMultiple() throws Exception {
     runInlineAlwaysThrowsMultiple(false, -2, -2);
     runInlineAlwaysThrowsMultiple(true, -2, -1);
   }
 
   private TestApplication codeForInlineAlwaysThrowsMultipleWithControlFlow(
-      int a, boolean twoGuards) {
+      int a, boolean twoGuards) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String secondGuard = twoGuards ?
@@ -886,7 +889,7 @@
   }
 
   private void runInlineAlwaysThrowsMultipleWithControlFlow(
-      int a, boolean twoGuards, int expectedA, int expectedB) {
+      int a, boolean twoGuards, int expectedA, int expectedB) throws Exception {
     // Run code without inlining.
     TestApplication test = codeForInlineAlwaysThrows(twoGuards);
     String result = test.run();
@@ -939,7 +942,7 @@
   }
 
   @Test
-  public void inlineAlwaysThrowsMultipleWithControlFlow() {
+  public void inlineAlwaysThrowsMultipleWithControlFlow() throws Exception {
     runInlineAlwaysThrowsMultipleWithControlFlow(0, false, -2, -2);
     runInlineAlwaysThrowsMultipleWithControlFlow(0, true, -2, -1);
     runInlineAlwaysThrowsMultipleWithControlFlow(1, false, -2, -2);
@@ -948,8 +951,9 @@
     runInlineAlwaysThrowsMultipleWithControlFlow(2, true, -2, -1);
   }
 
-  private TestApplication codeForInlineWithHandlersCanThrow(int a, int b, int c,
-      boolean twoGuards, boolean callerHasCatchAll, boolean inlineeHasCatchAll) {
+  private TestApplication codeForInlineWithHandlersCanThrow(
+      int a, int b, int c, boolean twoGuards, boolean callerHasCatchAll, boolean inlineeHasCatchAll)
+      throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String secondGuard = "";
@@ -1122,7 +1126,7 @@
 
   private void runInlineWithHandlersCanThrow(int a, int b, int c,
       boolean twoGuards, boolean callerHasCatchAll, boolean inlineeHasCatchAll,
-      int expectedA, int expectedB) {
+      int expectedA, int expectedB) throws Exception {
     // Run code without inlining.
     TestApplication test = codeForInlineWithHandlersCanThrow(
         a, b, c, twoGuards, callerHasCatchAll, inlineeHasCatchAll);
@@ -1153,7 +1157,7 @@
   }
 
   @Test
-  public void inlineCanWithHandlersThrow() {
+  public void inlineCanWithHandlersThrow() throws Exception {
     // The base generated code will be:
     //
     //  int method(int a, int b, int c) {
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
index 1e35475..b9eef9c 100644
--- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -32,7 +32,7 @@
    * Third block: Return instruction
    *
    */
-  IRCode simpleCode() {
+  IRCode simpleCode() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "int";
@@ -62,7 +62,7 @@
   }
 
   @Test
-  public void removeBeforeNext() {
+  public void removeBeforeNext() throws Exception {
     IRCode code = simpleCode();
 
     ListIterator<BasicBlock> blocks = code.listIterator();
@@ -72,7 +72,7 @@
   }
 
   @Test
-  public void removeTwice() {
+  public void removeTwice() throws Exception {
     IRCode code = simpleCode();
 
     ListIterator<BasicBlock> blocks = code.listIterator();
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 90e2089..04a55b5 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -29,7 +29,7 @@
 
 public class SplitBlockTest extends SmaliTestBase {
 
-  TestApplication codeWithoutCatchHandlers() {
+  TestApplication codeWithoutCatchHandlers() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "int";
@@ -68,7 +68,7 @@
   }
 
   @Test
-  public void noCatchHandlers() {
+  public void noCatchHandlers() throws Exception {
     final int initialBlockCount = 1;
     final int argumentInstructions = 2;
     final int firstBlockInstructions = 6;
@@ -102,7 +102,7 @@
   }
 
   @Test
-  public void noCatchHandlersSplitThree() {
+  public void noCatchHandlersSplitThree() throws Exception {
     final int initialBlockCount = 1;
     final int argumentInstructions = 2;
     final int firstBlockInstructions = 6;
@@ -136,7 +136,7 @@
     }
   }
 
-  TestApplication codeWithCatchHandlers(boolean shouldThrow, boolean twoGuards) {
+  TestApplication codeWithCatchHandlers(boolean shouldThrow, boolean twoGuards) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String secondGuard = twoGuards ?
@@ -192,7 +192,7 @@
     assertEquals(throwing, block.hasCatchHandlers());
   }
 
-  public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) {
+  public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception {
     final int secondBlockInstructions = 4;
     final int initialBlockCount = 5;
     // Try split between all instructions in second block.
@@ -223,14 +223,15 @@
   }
 
   @Test
-  public void catchHandlers() {
+  public void catchHandlers() throws Exception {
     runCatchHandlerTest(false, false);
     runCatchHandlerTest(true, false);
     runCatchHandlerTest(false, true);
     runCatchHandlerTest(true, true);
   }
 
-  public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards) {
+  public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards)
+      throws Exception {
     final int secondBlockInstructions = 4;
     final int initialBlockCount = 5;
     // Try split out all instructions in second block.
@@ -262,14 +263,14 @@
   }
 
   @Test
-  public void catchHandlersSplitThree() {
+  public void catchHandlersSplitThree() throws Exception {
     runCatchHandlerSplitThreeTest(false, false);
     runCatchHandlerSplitThreeTest(true, false);
     runCatchHandlerSplitThreeTest(false, true);
     runCatchHandlerSplitThreeTest(true, true);
   }
 
-  TestApplication codeWithIf(boolean hitTrueBranch) {
+  TestApplication codeWithIf(boolean hitTrueBranch) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "int";
@@ -309,7 +310,7 @@
     return new TestApplication(application, method, code, valueNumberGenerator, options);
   }
 
-  public void runWithIfTest(boolean hitTrueBranch) {
+  public void runWithIfTest(boolean hitTrueBranch) throws Exception {
     final int initialBlockCount = 4;
     final int argumentInstructions = 2;
     final int firstBlockInstructions = 3;
@@ -343,12 +344,12 @@
   }
 
   @Test
-  public void withIf() {
+  public void withIf() throws Exception {
     runWithIfTest(false);
     runWithIfTest(true);
   }
 
-  public void splitBeforeReturn(boolean hitTrueBranch) {
+  public void splitBeforeReturn(boolean hitTrueBranch) throws Exception {
     TestApplication test = codeWithIf(hitTrueBranch);
     IRCode code = test.code;
     // Locate the exit block and split before the return (the first instruction in the block).
@@ -375,12 +376,12 @@
   }
 
   @Test
-  public void splitBeforeReturn() {
+  public void splitBeforeReturn() throws Exception {
     splitBeforeReturn(false);
     splitBeforeReturn(true);
   }
 
-  TestApplication codeWithSwitch(boolean hitCase) {
+  TestApplication codeWithSwitch(boolean hitCase) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "int";
@@ -428,7 +429,7 @@
     return new TestApplication(application, method, code, valueNumberGenerator, options);
   }
 
-  public void runWithSwitchTest(boolean hitCase) {
+  public void runWithSwitchTest(boolean hitCase) throws Exception {
     final int initialBlockCount = 5;
     final int argumentInstructions = 1;
     final int firstBlockInstructions = 2;
@@ -462,7 +463,7 @@
   }
 
   @Test
-  public void withSwitch() {
+  public void withSwitch() throws Exception {
     runWithSwitchTest(false);
     runWithSwitchTest(true);
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 2782c91..531831b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -336,4 +336,329 @@
     info.checkLineHasExactLocals(8, "param", "int", "x", "int");
     info.checkLineHasExactLocals(9, "param", "int");
   }
+
+  @Test
+  public void argumentLiveAtReturn() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    /*
+     This is the original Java source code.
+
+     public static int argumentLiveAtReturn(int x) {  // Line 1
+       switch (x) {
+         case 0:
+           return 0;
+         case 1:
+           return 0;
+         case 2:
+           return 0;
+         case 100:
+           return 1;
+         case 101:
+           return 1;
+         case 102:
+           return 1;
+       }
+       return -1;
+     }
+   */
+    MethodSignature foo = clazz.addStaticMethod("argumentLiveAtReturn", ImmutableList.of("I"), "I",
+        ".limit stack 2",
+        ".limit locals 1",
+        ".var 0 is x I from L0 to L8",
+        "L0:",
+        ".line 2",
+        "  iload 0",
+        "lookupswitch",
+        "  0: L1",
+        "  1: L2",
+        "  2: L3",
+        "  100: L4",
+        "  101: L5",
+        "  102: L6",
+        "  default: L7",
+        "L1:",
+        ".line 4",
+        "  iconst_0",
+        "  ireturn",
+        "L2:",
+        ".line 6",
+        "  iconst_0",
+        "  ireturn",
+        "L3:",
+        ".line 8",
+        "  iconst_0",
+        "  ireturn",
+        "L4:",
+        ".line 10",
+        "  iconst_1",
+        "  ireturn",
+        "L5:",
+        ".line 12",
+        "  iconst_1",
+        "  ireturn",
+        "L6:",
+        ".line 14",
+        "  iconst_1",
+        "  ireturn",
+        "L7:",
+        ".line 16",
+        "  iconst_m1",
+        "  ireturn",
+        "L8:"
+    );
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc -1",
+        "  invokestatic Test/argumentLiveAtReturn(I)I",
+        "  invokevirtual java/io/PrintStream/print(I)V",
+        "  return");
+
+    String expected = "-1";
+    String javaResult = runOnJava(builder, clazz.name);
+    assertEquals(expected, javaResult);
+
+    AndroidApp jasminApp = builder.build();
+    AndroidApp d8App = ToolHelper.runD8(jasminApp);
+    String artResult = runOnArt(d8App, clazz.name);
+    assertEquals(expected, artResult);
+    DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+    info.checkStartLine(2);
+    info.checkLineHasExactLocals(2, "x", "int");
+    info.checkLineHasExactLocals(4, "x", "int");
+    info.checkLineHasExactLocals(6, "x", "int");
+    info.checkLineHasExactLocals(8, "x", "int");
+    info.checkLineHasExactLocals(10, "x", "int");
+    info.checkLineHasExactLocals(12, "x", "int");
+    info.checkLineHasExactLocals(14, "x", "int");
+    info.checkLineHasExactLocals(16, "x", "int");
+  }
+
+  @Test
+  public void testLocalSwitchRewriteToIfs() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    /*
+      This is the original Java source code. The code generated by javac have been
+      slightly modified below to end local t on the lookupswitch instruction.
+
+      public static int switchRewrite(int x) {  // Line 1
+        {
+          int t = x + 1;
+          x = t;
+          x = x + x;
+        }
+        switch (x) {
+          case 1:
+            return 7;
+          case 100:
+            return 8;
+        }
+        return -1;
+      }
+    */
+    MethodSignature foo = clazz.addStaticMethod("switchRewrite", ImmutableList.of("I"), "I",
+        ".limit stack 2",
+        ".limit locals 2",
+        ".var 0 is x I from L0 to L7",
+        ".var 1 is t I from L1 to L3",
+
+        "L0:",
+        ".line 3",
+        "  iload 0",
+        "  iconst_1",
+        "  iadd",
+        "  istore 1",
+        "L1:",
+        ".line 4",
+        "  iload 1",
+        "  istore 0",
+        "L2:",
+        ".line 5",
+        "  iload 0",
+        "  iload 0",
+        "  iadd",
+        "  istore 0",
+        "L3_ORIGINAL:",  // This is where javac normally ends t.
+        ".line 7",
+        "  iload 0",
+        "L3:",           // Moved L3 here to end t on the switch instruction.
+        "lookupswitch",
+        "  0: L4",
+        "  100: L5",
+        "  default: L6",
+        "L4:",
+        ".line 9",
+        "  iconst_0",
+        "  ireturn",
+        "L5:",
+        ".line 11",
+        "  iconst_1",
+        "  ireturn",
+        "L6:",
+        ".line 13",
+        "  iconst_m1",
+        "  ireturn",
+        "L7:"
+    );
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc 1",
+        "  invokestatic Test/switchRewrite(I)I",
+        "  invokevirtual java/io/PrintStream/print(I)V",
+        "  return");
+
+    String expected = "-1";
+    String javaResult = runOnJava(builder, clazz.name);
+    assertEquals(expected, javaResult);
+
+    AndroidApp jasminApp = builder.build();
+    AndroidApp d8App = ToolHelper.runD8(jasminApp);
+    String artResult = runOnArt(d8App, clazz.name);
+    assertEquals(expected, artResult);
+
+    DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+    info.checkStartLine(3);
+    info.checkLineHasExactLocals(3, "x", "int");
+    info.checkLineHasExactLocals(4, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(5, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(7, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(9, "x", "int");
+    info.checkLineHasExactLocals(11, "x", "int");
+    info.checkLineHasExactLocals(13, "x", "int");
+  }
+
+  @Test
+  public void testLocalSwitchRewriteToSwitches() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    /*
+      This is the original Java source code. The code generated by javac have been
+      slightly modified below to end local t on the lookupswitch instruction.
+
+      public static int switchRewrite(int x) {  // Line 1
+        {
+          int t = x + 1;
+          x = t;
+          x = x + x;
+        }
+        switch (x) {
+          case 0:
+            return 0;
+          case 1:
+            return 0;
+          case 2:
+            return 0;
+          case 100:
+            return 1;
+          case 101:
+            return 1;
+          case 102:
+            return 1;
+        }
+        return -1;
+      }
+    */
+    MethodSignature foo = clazz.addStaticMethod("switchRewrite", ImmutableList.of("I"), "I",
+        ".limit stack 2",
+        ".limit locals 2",
+        ".var 0 is x I from L0 to L11",
+        ".var 1 is t I from L1 to L3",
+
+        "L0:",
+        ".line 3",
+        "  iload 0",
+        "  iconst_1",
+        "  iadd",
+        "  istore 1",
+        "L1:",
+        ".line 4",
+        "  iload 1",
+        "  istore 0",
+        "L2:",
+        ".line 5",
+        "  iload 0",
+        "  iload 0",
+        "  iadd",
+        "  istore 0",
+        "L3_ORIGINAL:",  // This is where javac normally ends t.
+        ".line 7",
+        "  iload 0",
+        "L3:",           // Moved L3 here to end t on the switch instruction.
+        "lookupswitch",
+        "  0: L4",
+        "  1: L5",
+        "  2: L6",
+        "  100: L7",
+        "  101: L8",
+        "  102: L9",
+        "  default: L10",
+        "L4:",
+        ".line 9",
+        "  iconst_0",
+        "  ireturn",
+        "L5:",
+        ".line 11",
+        "  iconst_0",
+        "  ireturn",
+        "L6:",
+        ".line 13",
+        "  iconst_0",
+        "  ireturn",
+        "L7:",
+        ".line 15",
+        "  iconst_1",
+        "  ireturn",
+        "L8:",
+        ".line 17",
+        "  iconst_1",
+        "  ireturn",
+        "L9:",
+        ".line 19",
+        "  iconst_1",
+        "  ireturn",
+        "L10:",
+        ".line 21",
+        "  iconst_m1",
+        "  ireturn",
+        "L11:"
+    );
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc 1",
+        "  invokestatic Test/switchRewrite(I)I",
+        "  invokevirtual java/io/PrintStream/print(I)V",
+        "  return");
+
+    String expected = "-1";
+    String javaResult = runOnJava(builder, clazz.name);
+    assertEquals(expected, javaResult);
+
+    AndroidApp jasminApp = builder.build();
+    AndroidApp d8App = ToolHelper.runD8(jasminApp);
+    String artResult = runOnArt(d8App, clazz.name);
+    assertEquals(expected, artResult);
+
+    DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+    info.checkStartLine(3);
+    info.checkLineHasExactLocals(3, "x", "int");
+    info.checkLineHasExactLocals(4, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(5, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(7, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(9, "x", "int");
+    info.checkLineHasExactLocals(11, "x", "int");
+    info.checkLineHasExactLocals(13, "x", "int");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
index 81d299e..2551941 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
@@ -4,8 +4,13 @@
 package com.android.tools.r8.jasmin;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
@@ -128,7 +133,7 @@
   public void invalidInfoBug63412730_onWrite() throws Throwable {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-    clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+    MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
         ".limit stack 3",
         ".limit locals 2",
         ".var 1 is i I from LI to End",
@@ -136,9 +141,11 @@
         "Init:",
         "  ldc 42",
         "  istore 0",
+        ".line 1",
         "LI:",
         "  ldc 7.5",
         "  fstore 1",
+        ".line 2",
         "LF:",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  dup",
@@ -146,6 +153,7 @@
         "  invokevirtual java/io/PrintStream/println(I)V",
         "  fload 1",
         "  invokevirtual java/io/PrintStream/println(F)V",
+        ".line 3",
         "  return",
         "End:");
 
@@ -158,15 +166,18 @@
     String expected = StringUtils.lines("42", "7.5");
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
-    String artResult = runOnArtD8(builder, clazz.name);
+    AndroidApp app = compileWithD8(builder);
+    String artResult = runOnArt(app, clazz.name);
     assertEquals(expected, artResult);
+    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+    assertFalse(info.hasLocalsInfo());
   }
 
   @Test
   public void invalidInfoBug63412730_onRead() throws Throwable {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-    clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+    MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
         ".limit stack 3",
         ".limit locals 2",
         ".var 1 is i I from Locals to End",
@@ -174,8 +185,10 @@
         "Init:",
         "  ldc 42",
         "  istore 0",
+        ".line 1",
         "  ldc 7.5",
         "  fstore 1",
+        ".line 2",
         "Locals:",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  dup",
@@ -183,6 +196,7 @@
         "  invokevirtual java/io/PrintStream/println(I)V",
         "  fload 1",
         "  invokevirtual java/io/PrintStream/println(F)V",
+        ".line 3",
         "  return",
         "End:");
 
@@ -195,15 +209,18 @@
     String expected = StringUtils.lines("42", "7.5");
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
-    String artResult = runOnArtD8(builder, clazz.name);
+    AndroidApp app = compileWithD8(builder);
+    String artResult = runOnArt(app, clazz.name);
     assertEquals(expected, artResult);
+    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+    assertFalse(info.hasLocalsInfo());
   }
 
   @Test
   public void invalidInfoBug63412730_onMove() throws Throwable {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-    clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+    MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
         ".limit stack 3",
         ".limit locals 2",
         ".var 1 is i I from LI to End",
@@ -211,21 +228,25 @@
         "Init:",
         "  ldc 42",
         "  istore 0",
+        ".line 1",
         "LI:",
         "  ldc 0",
         "  ldc 0",
         "  ifeq LZ",
         "  ldc 75",
         "  istore 1",
+        ".line 2",
         "LJ:",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  iload 1",
         "  invokevirtual java/io/PrintStream/println(I)V",
+        ".line 3",
         "  return",
         "LZ:",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  iload 0",
         "  invokevirtual java/io/PrintStream/println(I)V",
+        ".line 4",
         "  return",
         "End:");
 
@@ -238,15 +259,18 @@
     String expected = StringUtils.lines("42");
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
-    String artResult = runOnArtD8(builder, clazz.name);
+    AndroidApp app = compileWithD8(builder);
+    String artResult = runOnArt(app, clazz.name);
     assertEquals(expected, artResult);
+    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+    assertFalse(info.hasLocalsInfo());
   }
 
   @Test
   public void invalidInfoBug63412730_onPop() throws Throwable {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-    clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+    MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
         ".limit stack 3",
         ".limit locals 2",
         ".var 1 is a [Ljava/lang/Object; from Locals to End",
@@ -255,21 +279,25 @@
         "  ldc 1",
         "  anewarray java/lang/Object",
         "  astore 0",
+        ".line 1",
         "  new java/lang/Integer",
         "  dup",
         "  ldc 42",
         "  invokespecial java/lang/Integer/<init>(I)V",
         "  astore 1",
+        ".line 2",
         "Locals:",
         "  aload 0",
         "  ldc 0",
         "  aload 1",
         "  aastore",
+        ".line 3",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  aload 0",
         "  ldc 0",
         "  aaload",
         "  invokevirtual java/io/PrintStream/println(Ljava/lang/Object;)V",
+        ".line 4",
         "  return",
         "End:");
 
@@ -282,7 +310,12 @@
     String expected = StringUtils.lines("42");
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
-    String artResult = runOnArtD8(builder, clazz.name);
+    AndroidApp app = compileWithD8(builder);
+    String artResult = runOnArt(app, clazz.name);
     assertEquals(expected, artResult);
+    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+    // Note: This code is actually invalid debug info, but we do not reject it because both types
+    // are reference types. If we ever change that we should update this test.
+    assertTrue(info.hasLocalsInfo());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 04d84e4..1573b34 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -131,7 +132,7 @@
   }
 
   protected static DexApplication process(DexApplication app, InternalOptions options)
-      throws IOException, ProguardRuleParserException, ExecutionException {
+      throws IOException, CompilationException, ExecutionException {
     return ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), options);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
new file mode 100644
index 0000000..1eb0586
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class Regress65007724 extends JasminTestBase {
+  @Test
+  public void testThat16BitsIndexAreAllowed() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    for (int i = 0; i < 35000; i++) {
+      builder.addClass("C" + i);
+    }
+
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addStaticField("f", "LC34000;", null);
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"Hello World!\"",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "return");
+
+    String expected = runOnJava(builder, clazz.name);
+    String artResult = runOnArtD8(builder, clazz.name);
+    assertEquals(expected, artResult);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index d5f9c6a..601ca23 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -60,7 +60,7 @@
   }
 
   void compileWithR8(Path inputPath, Path outputPath, Path keepRulesPath)
-      throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
+      throws IOException, CompilationException, ProguardRuleParserException {
     AndroidApp androidApp =
         R8.run(
             R8Command.builder()
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index dfc20f9..1039475 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -38,7 +38,7 @@
     Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
     R8Command command =
         ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
-            .addMainDexRules(mainDexRules)
+            .addMainDexRulesFiles(mainDexRules)
             .setMainDexListOutputPath(mainDexListOutput)
             .build();
     ToolHelper.runR8(command);
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 a296a3e..4122416 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.Code;
@@ -36,6 +36,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
@@ -161,7 +162,7 @@
     try {
       verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
       fail("Expect to fail, for there are too many classes for the main-dex list.");
-    } catch (CompilationError e) {
+    } catch (DexOverflowException e) {
       // Make sure {@link MonoDexDistributor} was _not_ used.
       assertFalse(e.getMessage().contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
@@ -202,7 +203,7 @@
     try {
       verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
       fail("Expect to fail, for there are too many classes for the main-dex list.");
-    } catch (CompilationError e) {
+    } catch (DexOverflowException e) {
       // Make sure {@link MonoDexDistributor} was _not_ used.
       assertFalse(e.getMessage().contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
@@ -302,7 +303,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;
@@ -402,7 +403,7 @@
       generateApplication(
           MANY_CLASSES, Constants.ANDROID_K_API, false, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
       fail("Expect to fail, for there are many classes while multidex is not enabled.");
-    } catch (MainDexError e) {
+    } catch (DexOverflowException e) {
       // Make sure {@link MonoDexDistributor} was used.
       assertTrue(e.getMessage().contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
@@ -538,13 +539,13 @@
   }
 
   public static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
-      throws IOException, ExecutionException {
+      throws IOException, ExecutionException, CompilationException {
     return generateApplication(classes, minApi, false, methodCount);
   }
 
   private static AndroidApp generateApplication(
       List<String> classes, int minApi, boolean intermediate, int methodCount)
-      throws IOException, ExecutionException {
+      throws IOException, ExecutionException, CompilationException {
     Timing timing = new Timing("MainDexListTests");
     InternalOptions options = new InternalOptions();
     options.minApiLevel = minApi;
@@ -689,6 +690,16 @@
     }
 
     @Override
+    public int getMoveExceptionRegister() {
+      throw new Unreachable();
+    }
+
+    @Override
+    public DebugPosition getDebugPositionAtOffset(int offset) {
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean verifyRegister(int register) {
       throw new Unreachable();
     }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 57c8cf6..c7e0c18 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -133,7 +133,7 @@
           .addLibraryFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION),
               Paths.get(ToolHelper.getAndroidJar(minSdk)))
           .setOutputPath(out)
-          .addMainDexRules(mainDexRules)
+          .addMainDexRulesFiles(mainDexRules)
           .build();
       CompilationResult result = ToolHelper.runR8WithFullResult(command, optionsConsumer);
       List<String> resultMainDexList =
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 195c3aa..b8d8e58 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -7,7 +7,21 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Sput;
+import com.android.tools.r8.code.SputObject;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -17,7 +31,7 @@
 public class StaticValuesTest extends SmaliTestBase {
 
   @Test
-  public void testAllTypes() {
+  public void testAllTypes() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticField("booleanField", "Z");
@@ -80,7 +94,55 @@
     DexApplication processedApplication = processApplication(originalApplication, options);
 
     DexInspector inspector = new DexInspector(processedApplication);
-    assertFalse(inspector.clazz("Test").clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+    DexValue value;
+    assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasStaticValue());
+    value = inspector.clazz("Test").field("boolean", "booleanField").getStaticValue();
+    assertTrue(value instanceof DexValueBoolean);
+    assertEquals(true, ((DexValueBoolean) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("byte", "byteField").hasStaticValue());
+    value = inspector.clazz("Test").field("byte", "byteField").getStaticValue();
+    assertTrue(value instanceof DexValueByte);
+    assertEquals(1, ((DexValueByte) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("short", "shortField").hasStaticValue());
+    value = inspector.clazz("Test").field("short", "shortField").getStaticValue();
+    assertTrue(value instanceof DexValueShort);
+    assertEquals(2, ((DexValueShort) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+    value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+    assertTrue(value instanceof DexValueInt);
+    assertEquals(3, ((DexValueInt) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("long", "longField").hasStaticValue());
+    value = inspector.clazz("Test").field("long", "longField").getStaticValue();
+    assertTrue(value instanceof DexValueLong);
+    assertEquals(4, ((DexValueLong) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("float", "floatField").hasStaticValue());
+    value = inspector.clazz("Test").field("float", "floatField").getStaticValue();
+    assertTrue(value instanceof DexValueFloat);
+    assertEquals(5.0f, ((DexValueFloat) value).getValue(), 0.0);
+
+    assertTrue(inspector.clazz("Test").field("double", "doubleField").hasStaticValue());
+    value = inspector.clazz("Test").field("double", "doubleField").getStaticValue();
+    assertTrue(value instanceof DexValueDouble);
+    assertEquals(6.0f, ((DexValueDouble) value).getValue(), 0.0);
+
+    assertTrue(inspector.clazz("Test").field("char", "charField").hasStaticValue());
+    value = inspector.clazz("Test").field("char", "charField").getStaticValue();
+    assertTrue(value instanceof DexValueChar);
+    assertEquals(0x30 + 7, ((DexValueChar) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+    value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+    assertTrue(value instanceof DexValueString);
+    assertEquals(("8"), ((DexValueString) value).getValue().toString());
 
     String result = runArt(processedApplication, options);
 
@@ -88,7 +150,7 @@
   }
 
   @Test
-  public void getBeforePut() {
+  public void getBeforePut() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticField("field1", "I", "1");
@@ -127,7 +189,7 @@
   }
 
   @Test
-  public void testNull() {
+  public void testNull() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
@@ -159,7 +221,9 @@
     DexApplication processedApplication = processApplication(originalApplication, options);
 
     DexInspector inspector = new DexInspector(processedApplication);
-    assertFalse(inspector.clazz("Test").clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
     String result = runArt(processedApplication, options);
 
@@ -167,7 +231,7 @@
   }
 
   @Test
-  public void testString() {
+  public void testString() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticField("stringField1", "Ljava/lang/String;", "Hello");
@@ -200,7 +264,9 @@
     DexApplication processedApplication = processApplication(originalApplication, options);
 
     DexInspector inspector = new DexInspector(processedApplication);
-    assertFalse(inspector.clazz("Test").clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
     String result = runArt(processedApplication, options);
 
@@ -208,7 +274,153 @@
   }
 
   @Test
-  public void testInitializationToOwnClassName() {
+  public void testMultiplePuts() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 0",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"4\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 1",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"5\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 2",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"6\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"7\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+    DexValue value;
+    assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+    value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+    assertTrue(value instanceof DexValueInt);
+    assertEquals(3, ((DexValueInt) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+    value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+    assertTrue(value instanceof DexValueString);
+    assertEquals(("7"), ((DexValueString) value).getValue().toString());
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("3\n7\n", result);
+  }
+
+
+  @Test
+  public void testMultiplePutsWithControlFlow() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("booleanField", "Z");
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("intField2", "I");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 0",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"4\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 1",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"5\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "sget-boolean        v0, LTest;->booleanField:Z",
+        "if-eqz              v0, :label_1",
+        "const               v0, 8",
+        "sput                v0, LTest;->intField2:I",
+        ":label_1",
+        "const               v0, 9",
+        "sput                v0, LTest;->intField2:I",
+        "goto                :label_2",
+        ":label_2",
+        "const               v0, 2",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"6\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "const-string        v0, \"7\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    assertTrue(inspector.clazz("Test").clinit().isPresent());
+
+    DexValue value;
+    assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+    value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+    assertTrue(value instanceof DexValueInt);
+    assertEquals(3, ((DexValueInt) value).getValue());
+
+    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+    value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+    assertTrue(value instanceof DexValueString);
+    assertEquals(("7"), ((DexValueString) value).getValue().toString());
+
+    DexCode code = inspector.clazz("Test").clinit().getMethod().getCode().asDexCode();
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof Sput) {
+        Sput put = (Sput) instruction;
+        // Only int put ot intField2.
+        assertEquals(put.getField().name.toString(), "intField2");
+      } else {
+        // No Object (String) puts.
+        assertFalse(instruction instanceof SputObject);
+      }
+    }
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("3\n7\n", result);
+  }
+
+  @Test
+  public void testInitializationToOwnClassName() throws Exception {
     String className = "org.example.Test";
     SmaliBuilder builder = new SmaliBuilder(className);
 
@@ -266,7 +478,9 @@
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
-    assertFalse(inspector.clazz(className).clinit().isPresent());
+    // Test is running without tree-shaking, so the empty <clinit> is not removed.
+    assertTrue(
+        inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
     String result = runArt(processedApplication, options, className);
 
@@ -275,7 +489,7 @@
   }
 
   @Test
-  public void testInitializationToOtherClassName() {
+  public void testInitializationToOtherClassName() throws Exception {
     String className = "org.example.Test";
     SmaliBuilder builder = new SmaliBuilder(className);
 
@@ -322,7 +536,7 @@
   }
 
   @Test
-  public void fieldOnOtherClass() {
+  public void fieldOnOtherClass() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticInitializer(
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 03df65c..4cb6736 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -126,7 +126,9 @@
 
   private static void inspectShaking1(PrintUsageInspector inspector) {
     assertTrue(inspector.clazz("shaking1.Unused").isPresent());
-    assertFalse(inspector.clazz("shaking1.Used").isPresent());
+    assertTrue(inspector.clazz("shaking1.Used").isPresent());
+    ClassSubject used = inspector.clazz("shaking1.Used").get();
+    assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
   }
 
   private static void inspectShaking2(PrintUsageInspector inspector) {
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 ac0178c..0f2e456 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexAccessFlags;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -102,11 +103,22 @@
 
   @Test
   public void parse() throws IOException, ProguardRuleParserException {
-    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    ProguardConfigurationParser parser;
+
+    // Parse from file.
+    parser = new ProguardConfigurationParser(new DexItemFactory());
     parser.parse(Paths.get(PROGUARD_SPEC_FILE));
     List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
     assertEquals(24, rules.size());
     assertEquals(1, rules.get(0).getMemberRules().size());
+
+    // Parse from strings.
+    parser = new ProguardConfigurationParser(new DexItemFactory());
+    List<String> lines = FileUtils.readTextFile(Paths.get(PROGUARD_SPEC_FILE));
+    parser.parse(new ProguardConfigurationSourceStrings(lines));
+    rules = parser.getConfig().getRules();
+    assertEquals(24, rules.size());
+    assertEquals(1, rules.get(0).getMemberRules().size());
   }
 
   @Test
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 ab6d6e2..66082fc 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -150,6 +150,9 @@
         used.method("java.lang.String", "aMethodThatIsNotUsedButKept", Collections.emptyList())
             .isPresent());
     Assert.assertTrue(used.field("int", "aStaticFieldThatIsNotUsedButKept").isPresent());
+    // Rewriting of <clinit> moves the initialization of aStaticFieldThatIsNotUsedButKept
+    // from <clinit> code into statics value section of the dex file.
+    Assert.assertFalse(used.clinit().isPresent());
   }
 
   public static void shaking1IsCorrectlyRepackaged(DexInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index a02a186..5ad5787 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -13,15 +13,15 @@
 import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
 import org.junit.Test;
 
 public class IncludeDescriptorClassesTest extends TestBase {
@@ -51,14 +51,14 @@
 
   private Set<String> readJarClasses(Path jar) throws IOException {
     Set<String> result = new HashSet<>();
-    try (ZipInputStream in = new ZipInputStream(new FileInputStream(jar.toFile()))) {
-      ZipEntry entry = in.getNextEntry();
-      while (entry != null) {
+    try (ZipFile zipFile = new ZipFile(jar.toFile())) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
         String name = entry.getName();
         if (name.endsWith(".class")) {
           result.add(name.substring(0, name.length() - ".class".length()).replace('/', '.'));
         }
-        entry = in.getNextEntry();
       }
     }
     return result;
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index ca30f76..909f574 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -26,7 +26,7 @@
 public class CatchSuccessorFallthroughTest extends SmaliTestBase {
 
   @Test
-  public void catchSuccessorFallthroughTest() {
+  public void catchSuccessorFallthroughTest() throws Exception {
 
     SmaliBuilder builder = new SmaliBuilder("Test");
 
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 1d256e0..a52c68b 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -15,7 +15,7 @@
 public class JumboStringTest extends SmaliTestBase {
 
   @Test
-  public void test() {
+  public void test() throws Exception {
     StringBuilder builder = new StringBuilder();
     StringBuilder expectedBuilder = new StringBuilder();
     builder.append("    new-instance         v0, Ljava/lang/StringBuilder;\n");
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index c726aad..c64e39c 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.code.ConstWide;
 import com.android.tools.r8.code.ConstWideHigh16;
 import com.android.tools.r8.code.DivInt;
+import com.android.tools.r8.code.DivInt2Addr;
 import com.android.tools.r8.code.InvokeStatic;
 import com.android.tools.r8.code.InvokeVirtual;
 import com.android.tools.r8.code.MoveResult;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.code.ReturnObject;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.ReturnWide;
+import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -88,7 +90,7 @@
   }
 
   @Test
-  public void a() {
+  public void a() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "java.lang.String";
@@ -150,7 +152,7 @@
   }
 
   @Test
-  public void b() {
+  public void b() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "java.lang.String";
@@ -220,7 +222,7 @@
   }
 
   @Test
-  public void c() {
+  public void c() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     // Method with const instructions after the outline.
@@ -277,7 +279,7 @@
   }
 
   @Test
-  public void d() {
+  public void d() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     // Method with mixed use of arguments and locals.
@@ -416,7 +418,7 @@
   }
 
   @Test
-  public void doubleArguments() {
+  public void doubleArguments() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "java.lang.String";
@@ -490,7 +492,7 @@
   }
 
   @Test
-  public void invokeStatic() {
+  public void invokeStatic() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "void";
@@ -563,7 +565,7 @@
   }
 
   @Test
-  public void constructor() throws IOException, RecognitionException {
+  public void constructor() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature1 = builder.addStaticMethod(
@@ -648,7 +650,7 @@
   }
 
   @Test
-  public void constructorDontSplitNewInstanceAndInit() {
+  public void constructorDontSplitNewInstanceAndInit() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature = builder.addStaticMethod(
@@ -718,7 +720,7 @@
   }
 
   @Test
-  public void outlineWithoutArguments() {
+  public void outlineWithoutArguments() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature1 = builder.addStaticMethod(
@@ -763,7 +765,7 @@
   }
 
   @Test
-  public void outlineDifferentReturnType() {
+  public void outlineDifferentReturnType() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
@@ -850,7 +852,7 @@
   }
 
   @Test
-  public void outlineMultipleTimes() {
+  public void outlineMultipleTimes() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     String returnType = "java.lang.String";
@@ -923,7 +925,7 @@
   }
 
   @Test
-  public void outlineReturnLong() {
+  public void outlineReturnLong() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature = builder.addStaticMethod(
@@ -976,7 +978,7 @@
   }
 
   @Test
-  public void outlineArrayType() {
+  public void outlineArrayType() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticMethod(
@@ -1034,7 +1036,7 @@
   }
 
   @Test
-  public void outlineArithmeticBinop() {
+  public void outlineArithmeticBinop() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticMethod(
@@ -1115,7 +1117,7 @@
   }
 
   @Test
-  public void outlineWithHandler() {
+  public void outlineWithHandler() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticMethod(
@@ -1175,7 +1177,7 @@
     assertTrue(code.instructions[0] instanceof DivInt);
     assertTrue(code.instructions[1] instanceof InvokeStatic);
     assertTrue(code.instructions[2] instanceof MoveResult);
-    assertTrue(code.instructions[3] instanceof DivInt);
+    assertTrue(code.instructions[3] instanceof DivInt2Addr);
     assertTrue(code.instructions[4] instanceof Return);
     assertTrue(code.instructions[5] instanceof Const4);
     assertTrue(code.instructions[6] instanceof Return);
@@ -1188,7 +1190,7 @@
   }
 
   @Test
-  public void outlineUnusedOutValue() {
+  public void outlineUnusedOutValue() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     // The result from neither the div-int is never used.
@@ -1235,7 +1237,7 @@
   }
 
   @Test
-  public void outlineUnusedNewInstanceOutValue() {
+  public void outlineUnusedNewInstanceOutValue() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     // The result from the new-instance instructions are never used (<init> is not even called).
@@ -1280,7 +1282,7 @@
   }
 
   @Test
-  public void regress33733666() {
+  public void regress33733666() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addStaticMethod(
diff --git a/src/test/java/com/android/tools/r8/smali/Regress38014736.java b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
index 91d8023..c97d412 100644
--- a/src/test/java/com/android/tools/r8/smali/Regress38014736.java
+++ b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
@@ -12,7 +12,7 @@
 public class Regress38014736 extends SmaliTestBase {
 
   @Test
-  public void handlerRangeStartingOnMoveResult() {
+  public void handlerRangeStartingOnMoveResult() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     builder.addMainMethod(
diff --git a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
index 45cd8cd..e1dc5c4 100644
--- a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
@@ -21,7 +21,7 @@
 public class RunArtSmokeTest extends SmaliTestBase {
 
   @Test
-  public void test() {
+  public void test() throws Exception {
     // Build simple "Hello, world!" application.
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
     MethodSignature mainSignature = builder.addMainMethod(
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 0322407..e2006d0 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -6,10 +6,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+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.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
@@ -386,7 +388,7 @@
       return iterator;
     }
 
-    public String run() {
+    public String run() throws DexOverflowException {
       AppInfo appInfo = new AppInfo(application);
       IRConverter converter = new IRConverter(application, appInfo, options);
       converter.replaceCodeForTesting(method, code);
@@ -431,7 +433,7 @@
   protected DexApplication processApplication(DexApplication application, InternalOptions options) {
     try {
       return ToolHelper.optimizeWithR8(application, new AppInfoWithSubtyping(application), options);
-    } catch (IOException | ProguardRuleParserException | ExecutionException e) {
+    } catch (IOException | CompilationException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
@@ -513,11 +515,13 @@
         processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters);
   }
 
-  public String runArt(DexApplication application, InternalOptions options) {
+  public String runArt(DexApplication application, InternalOptions options)
+      throws DexOverflowException {
     return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
   }
 
-  public String runArt(DexApplication application, InternalOptions options, String mainClass) {
+  public String runArt(DexApplication application, InternalOptions options, String mainClass)
+      throws DexOverflowException {
     try {
       AndroidApp app = writeDex(application, options);
       Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
@@ -529,7 +533,8 @@
     }
   }
 
-  public void runDex2Oat(DexApplication application, InternalOptions options) {
+  public void runDex2Oat(DexApplication application, InternalOptions options)
+      throws DexOverflowException {
     try {
       AndroidApp app = writeDex(application, options);
       Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip");
@@ -541,7 +546,8 @@
     }
   }
 
-  public AndroidApp writeDex(DexApplication application, InternalOptions options) {
+  public AndroidApp writeDex(DexApplication application, InternalOptions options)
+      throws DexOverflowException {
     AppInfo appInfo = new AppInfo(application);
     try {
       return R8.writeApplication(
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 c256920..3750a86 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -34,7 +34,8 @@
 public class SwitchRewritingTest extends SmaliTestBase {
 
   private boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
-    return Math.abs((long) key1 - (long) key2) <= 2;
+    assert key1 != key2;
+    return Math.abs((long) key1 - (long) key2) == 1;
   }
 
   private boolean some16BitConst(Instruction instruction) {
@@ -114,7 +115,7 @@
     }
   }
 
-  private void runTwoCaseSparseToPackedDexTest(int key1, int key2) {
+  private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
 
     MethodSignature signature = builder.addStaticMethod(
@@ -156,24 +157,29 @@
     if (twoCaseWillUsePackedSwitch(key1, key2)) {
       assertTrue(code.instructions[0] instanceof PackedSwitch);
     } else {
-      assertTrue(code.instructions[0] instanceof SparseSwitch);
+      if (key1 == 0) {
+        assertTrue(code.instructions[0] instanceof IfEqz);
+      } else {
+        // Const instruction before if.
+        assertTrue(code.instructions[1] instanceof IfEq);
+      }
     }
   }
 
   @Test
-  public void twoCaseSparseToPackedDex() {
+  public void twoCaseSparseToPackedOrIfsDex() {
     for (int delta = 1; delta <= 3; delta++) {
-      runTwoCaseSparseToPackedDexTest(0, delta);
-      runTwoCaseSparseToPackedDexTest(-delta, 0);
-      runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
-      runTwoCaseSparseToPackedDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+      runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
+      runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
+      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
     }
-    runTwoCaseSparseToPackedDexTest(-1, 1);
-    runTwoCaseSparseToPackedDexTest(-2, 1);
-    runTwoCaseSparseToPackedDexTest(-1, 2);
-    runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+    runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
+    runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
+    runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
   }
 
   private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
@@ -356,7 +362,12 @@
     if (twoCaseWillUsePackedSwitch(key1, key2)) {
       assertTrue(code.instructions[3] instanceof PackedSwitch);
     } else {
-      assertTrue(code.instructions[3] instanceof SparseSwitch);
+      if (key1 == 0) {
+        assertTrue(code.instructions[3] instanceof IfEqz);
+      } else {
+        // Const instruction before if.
+        assertTrue(code.instructions[4] instanceof IfEq);
+      }
     }
   }
 
@@ -462,8 +473,8 @@
     runLargerSwitchJarTest(0, 1, 5503, null);
   }
 
-  private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs)
-      throws Exception {
+  private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
+      int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
 
@@ -514,8 +525,8 @@
       }
     }
 
-    assertEquals(1, packedSwitches);
-    assertEquals(0, sparseSwitches);
+    assertEquals(expectedPackedSwitches, packedSwitches);
+    assertEquals(expectedSparceSwitches, sparseSwitches);
     assertEquals(expectedIfs, ifs);
 
     // Run the code
@@ -526,12 +537,34 @@
 
   @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);
+    // Switches that are completely converted to ifs.
+    runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
+
+    // Switches that are completely converted to ifs and one switch.
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
     runConvertCasesToIf(ImmutableList.of(
-        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2);
+        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
+
+    // Switches that are completely converted to ifs and two switches.
+    runConvertCasesToIf(ImmutableList.of(
+        0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
+
+    // Switches that are completely converted two switches (one sparse and one packed).
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, -900, -800, -700, -600, -500, -400, -300,
+        1000, 1001, 1002, 1003, 1004,
+        2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
   }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 0ad240c..26de96c 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -56,6 +56,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
 import com.android.tools.r8.naming.MemberNaming;
@@ -691,8 +692,11 @@
   }
 
   public abstract class FieldSubject extends MemberSubject {
+    public abstract boolean hasStaticValue();
 
     public abstract DexEncodedField getField();
+
+    public abstract DexValue getStaticValue();
   }
 
   public class AbsentFieldSubject extends FieldSubject {
@@ -733,6 +737,16 @@
     }
 
     @Override
+    public boolean hasStaticValue() {
+      return false;
+    }
+
+    @Override
+    public DexValue getStaticValue() {
+      return null;
+    }
+
+    @Override
     public DexEncodedField getField() {
       return null;
     }
@@ -791,6 +805,16 @@
     }
 
     @Override
+    public boolean hasStaticValue() {
+      return dexField.staticValue != null;
+    }
+
+    @Override
+    public DexValue getStaticValue() {
+      return dexField.staticValue;
+    }
+
+    @Override
     public DexEncodedField getField() {
       return dexField;
     }
diff --git a/tools/archive.py b/tools/archive.py
new file mode 100755
index 0000000..ee1053d
--- /dev/null
+++ b/tools/archive.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# 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.
+
+import d8
+import os
+import r8
+import sys
+import utils
+
+ARCHIVE_BUCKET = 'r8-releases'
+
+def GetVersion():
+  r8_version = r8.run(['--version'], build = False).strip()
+  d8_version = d8.run(['--version'], build = False).strip()
+  # The version printed is "D8 vVERSION_NUMBER" and "R8 vVERSION_NUMBER"
+  # Sanity check that versions match.
+  if d8_version.split()[1] != r8_version.split()[1]:
+    raise Exception(
+        'Version mismatch: \n%s\n%s' % (d8_version, r8_version))
+  return d8_version.split()[1]
+
+def GetStorageDestination(storage_prefix, version, file_name):
+  return '%s%s/raw/%s/%s' % (storage_prefix, ARCHIVE_BUCKET, version, file_name)
+
+def GetUploadDestination(version, file_name):
+  return GetStorageDestination('gs://', version, file_name)
+
+def GetUrl(version, file_name):
+  return GetStorageDestination('http://storage.googleapis.com/',
+                               version,
+                               file_name)
+
+def Main():
+  if not 'BUILDBOT_BUILDERNAME' in os.environ:
+    raise Exception('You are not a bot, don\'t archive builds')
+  version = GetVersion()
+  for jar in [utils.D8_JAR, utils.R8_JAR]:
+    file_name = os.path.basename(jar)
+    destination = GetUploadDestination(version, file_name)
+    print('Uploading %s to %s' % (jar, destination))
+    utils.upload_file_to_cloud_storage(jar, destination)
+    print('File available at: %s' % GetUrl(version, file_name))
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/d8.py b/tools/d8.py
index d214cf5..5478897 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -9,9 +9,8 @@
 import sys
 import utils
 
-D8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'd8.jar')
-
-def run(args, build = True, debug = True, profile = False, track_memory_file=None):
+def run(args, build = True, debug = True, profile = False,
+        track_memory_file=None):
   if build:
     gradle.RunGradle(['D8'])
   cmd = []
@@ -22,10 +21,12 @@
     cmd.append('-ea')
   if profile:
     cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', D8_JAR])
+  cmd.extend(['-jar', utils.D8_JAR])
   cmd.extend(args)
   utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  result = subprocess.check_output(cmd)
+  print(result)
+  return result
 
 def main():
   build = True
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/r8.py b/tools/r8.py
index b453227e..7de2c94 100755
--- a/tools/r8.py
+++ b/tools/r8.py
@@ -9,9 +9,8 @@
 import sys
 import utils
 
-R8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'r8.jar')
-
-def run(args, build = True, debug = True, profile = False, track_memory_file=None):
+def run(args, build = True, debug = True, profile = False,
+        track_memory_file=None):
   if build:
     gradle.RunGradle(['r8'])
   cmd = []
@@ -22,10 +21,12 @@
     cmd.append('-ea')
   if profile:
     cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', R8_JAR])
+  cmd.extend(['-jar', utils.R8_JAR])
   cmd.extend(args)
   utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  result = subprocess.check_output(cmd)
+  print(result)
+  return result
 
 def main():
   build = True
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(' '))
 
diff --git a/tools/test.py b/tools/test.py
index b0255cb..ab4b0d6 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -76,7 +76,7 @@
   upload_dir = os.path.join(utils.REPO_ROOT, 'build', 'reports', 'tests')
   u_dir = uuid.uuid4()
   destination = 'gs://%s/%s' % (BUCKET, u_dir)
-  utils.upload_html_to_cloud_storage(upload_dir, destination)
+  utils.upload_dir_to_cloud_storage(upload_dir, destination)
   url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
   print 'Test results available at: %s' % url
   print '@@@STEP_LINK@Test failures@%s@@@' % url
diff --git a/tools/test_android_cts.py b/tools/test_android_cts.py
index 3f66302..887e3ae 100755
--- a/tools/test_android_cts.py
+++ b/tools/test_android_cts.py
@@ -39,7 +39,6 @@
   'aosp_manifest.xml')
 AOSP_HELPER_SH = join(utils.REPO_ROOT, 'scripts', 'aosp_helper.sh')
 
-D8_JAR = join(utils.REPO_ROOT, 'build/libs/d8.jar')
 D8LOGGER_JAR = join(utils.REPO_ROOT, 'build/libs/d8logger.jar')
 
 AOSP_ROOT = join(utils.REPO_ROOT, 'build/aosp')
@@ -133,7 +132,7 @@
   counter = 0
   if tool_is_d8 or clean_dex:
     if not clean_dex:
-      d8jar_mtime = os.path.getmtime(D8_JAR)
+      d8jar_mtime = os.path.getmtime(utils.D8_JAR)
     dex_files = (chain.from_iterable(glob(join(x[0], '*.dex'))
       for x in os.walk(OUT_IMG)))
     for f in dex_files:
diff --git a/tools/test_framework.py b/tools/test_framework.py
index 9e5f63f..51c870a 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -30,7 +30,6 @@
 
 DX_JAR = os.path.join(utils.REPO_ROOT, 'tools', 'linux', 'dx', 'framework',
     'dx.jar')
-D8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'd8.jar')
 GOYT_EXE = os.path.join('third_party', 'goyt',
     'goyt_164843480')
 FRAMEWORK_JAR = os.path.join('third_party', 'framework',
@@ -57,16 +56,22 @@
              ' peak resident set size (VmHWM) in bytes.',
       default = False,
       action = 'store_true')
+  parser.add_argument('--output',
+                      help = 'Output directory to keep the generated files')
   return parser.parse_args()
 
 def Main():
   utils.check_java_version()
   args = parse_arguments()
+  output_dir = args.output
 
   with utils.TempDir() as temp_dir:
 
+    if not output_dir:
+      output_dir = temp_dir
+
     if args.tool in ['dx', 'goyt', 'goyt-release']:
-      tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
+      tool_args = ['--dex', '--output=' + output_dir, '--multi-dex',
           '--min-sdk-version=' + MIN_SDK_VERSION]
 
     xmx = None
@@ -79,8 +84,8 @@
       tool_file = DX_JAR
       xmx = '-Xmx1600m'
     else:
-      tool_file = D8_JAR
-      tool_args = ['--output', temp_dir, '--min-api', MIN_SDK_VERSION]
+      tool_file = utils.D8_JAR
+      tool_args = ['--output', output_dir, '--min-api', MIN_SDK_VERSION]
       if args.tool == 'd8-release':
         tool_args.append('--release')
       xmx = '-Xmx600m'
@@ -89,7 +94,7 @@
 
     track_memory_file = None
     if args.print_memoryuse:
-      track_memory_file = os.path.join(temp_dir, utils.MEMORY_USE_TMP_FILE)
+      track_memory_file = os.path.join(output_dir, utils.MEMORY_USE_TMP_FILE)
       cmd.extend(['tools/track_memory.sh', track_memory_file])
 
     if tool_file.endswith('.jar'):
@@ -108,7 +113,7 @@
       print('{}-Total(MemoryUse): {}'
           .format(args.name, utils.grep_memoryuse(track_memory_file)))
 
-    dex_files = [f for f in glob(os.path.join(temp_dir, '*.dex'))]
+    dex_files = [f for f in glob(os.path.join(output_dir, '*.dex'))]
     code_size = 0
     for dex_file in dex_files:
       code_size += os.path.getsize(dex_file)
diff --git a/tools/utils.py b/tools/utils.py
index e281ccd..44e608d 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -19,6 +19,8 @@
     'dexsegments.jar')
 DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
 COMPATDX_JAR = os.path.join(REPO_ROOT, 'build', 'libs', 'compatdx.jar')
+D8_JAR = os.path.join(REPO_ROOT, 'build', 'libs', 'd8.jar')
+R8_JAR = os.path.join(REPO_ROOT, 'build', 'libs', 'r8.jar')
 
 def PrintCmd(s):
   if type(s) is list:
@@ -55,13 +57,18 @@
     if not os.path.isdir(path):
         raise
 
-def upload_html_to_cloud_storage(directory, destination):
+def upload_dir_to_cloud_storage(directory, destination):
   # Upload and make the content encoding right for viewing directly
   cmd = ['gsutil.py', 'cp', '-z', 'html', '-a',
          'public-read', '-R', directory, destination]
   PrintCmd(cmd)
   subprocess.check_call(cmd)
 
+def upload_file_to_cloud_storage(source, destination):
+  cmd = ['gsutil.py', 'cp', '-a', 'public-read', source, destination]
+  PrintCmd(cmd)
+  subprocess.check_call(cmd)
+
 class TempDir(object):
  def __init__(self, prefix=''):
    self._temp_dir = None