Merge "Fix issue where const instructions for uninitialized locals can interfere with arguments."
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 0c0ac08..b992f65 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -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);
@@ -210,7 +210,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 b7afda7..a85f397 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -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,12 +220,12 @@
       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()));
     }
@@ -445,7 +445,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);
@@ -516,7 +516,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;
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..0c33273 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -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);
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..0d59d98 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -4,9 +4,6 @@
 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.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -243,7 +240,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..f4fa365 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -49,6 +49,7 @@
 import com.android.tools.r8.naming.NamingLens;
 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;
@@ -365,21 +366,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 +445,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 +456,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 +694,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
   }
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..e2519e3 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -226,7 +226,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);
@@ -322,7 +323,8 @@
       this.fillStrategy = FillStrategy.FILL_MAX;
     }
 
-    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+    @Override
+    public Map<Integer, VirtualFile> run() throws IOException {
       // First fill required classes into the main dex file.
       fillForMainDexList(classes);
       if (classes.isEmpty()) {
@@ -381,6 +383,7 @@
       this.executorService = executorService;
     }
 
+    @Override
     public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
       // Strategy for distributing classes for write out:
       // 1. Place all files in the package distribution file in the proposed files (if any).
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..1a8602c 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;
@@ -147,7 +148,8 @@
   }
 
   @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 +158,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/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index e66b984..8c071c8 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);
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/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/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e99dbf1..52fff08 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
@@ -74,8 +74,9 @@
     if (debugValues == null) {
       debugValues = new HashSet<>();
     }
-    debugValues.add(value);
-    value.addDebugUser(this);
+    if (debugValues.add(value)) {
+      value.addDebugUser(this);
+    }
   }
 
   public static void clearUserInfo(Instruction instruction) {
@@ -94,19 +95,17 @@
 
   public abstract void buildDex(DexBuilder builder);
 
-  public void replaceValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
+  public void replaceValue(Value oldValue, Value newValue) {
     for (int i = 0; i < inValues.size(); i++) {
       if (oldValue == inValues.get(i)) {
         inValues.set(i, newValue);
         newValue.addUser(this);
-        toRemove.add(this);
       }
     }
   }
 
-  public void replaceDebugValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
+  public void replaceDebugValue(Value oldValue, Value newValue) {
     if (debugValues.remove(oldValue)) {
-      toRemove.add(this);
       if (newValue.getLocalInfo() != null) {
         // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
         addDebugValue(newValue);
@@ -115,6 +114,16 @@
     }
   }
 
+  public void moveDebugValues(Instruction target) {
+    if (debugValues == null) {
+      return;
+    }
+    for (Value value : debugValues) {
+      value.replaceDebugUser(this, target);
+    }
+    debugValues.clear();
+  }
+
   /**
    * Returns the basic block containing this instruction.
    */
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 96069bf..20dc3c7 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
@@ -155,22 +155,20 @@
     current.removePhiUser(this);
   }
 
-  void replaceTrivialPhi(Value current, Value newValue, List<Phi> toRemove) {
+  void replaceOperand(Value current, Value newValue) {
     for (int i = 0; i < operands.size(); i++) {
       if (operands.get(i) == current) {
         operands.set(i, newValue);
         newValue.addPhiUser(this);
-        toRemove.add(this);
       }
     }
   }
 
-  void replaceTrivialDebugPhi(Value current, Value newValue, List<Phi> toRemove) {
+  void replaceDebugValue(Value current, Value newValue) {
     assert current.getLocalInfo() != null;
     assert current.getLocalInfo() == newValue.getLocalInfo();
     if (debugValues.remove(current)) {
       addDebugValue(newValue);
-      toRemove.add(this);
     }
   }
 
@@ -228,7 +226,7 @@
     {
       Set<Phi> phiUsersToSimplify = uniquePhiUsers();
       // Replace this phi with the unique value in all users.
-      replaceInUsers(same);
+      replaceUsers(same);
       // Try to simplify phi users that might now have become trivial.
       for (Phi user : phiUsersToSimplify) {
         user.removeTrivialPhi();
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 dfa8548..62a1152 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;
@@ -79,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) {
@@ -152,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() {
@@ -244,7 +308,7 @@
     if (isUninitializedLocal()) {
       return;
     }
-    debugData.users.add(user);
+    debugData.users.putIfAbsent(user, DebugUse.LIVE);
   }
 
   public void addDebugPhiUser(Phi user) {
@@ -263,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() {
@@ -290,68 +362,27 @@
       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()) {
-        if (user.getDebugValues().remove(this)) {
-          user.addDebugValue(newValue);
-        }
+        user.replaceDebugValue(this, newValue);
       }
       for (Phi user : debugPhiUsers()) {
-        if (user.getDebugValues().remove(this)) {
-          user.addDebugValue(newValue);
-        }
+        user.replaceDebugValue(this, newValue);
       }
     }
     clearUsers();
   }
 
-  public void replaceInUsers(Value newValue) {
-    if (!uniqueUsers().isEmpty()) {
-      List<Instruction> toRemove = new ArrayList<>(uniqueUsers().size());
-      for (Instruction user : uniqueUsers()) {
-        user.replaceValue(this, newValue, toRemove);
-      }
-      toRemove.forEach(this::removeUser);
-    }
-    if (!uniquePhiUsers().isEmpty()) {
-      List<Phi> toRemove = new ArrayList<>(uniquePhiUsers().size());
-      for (Phi user : uniquePhiUsers()) {
-        user.replaceTrivialPhi(this, newValue, toRemove);
-      }
-      toRemove.forEach(this::removePhiUser);
-    }
-    if (debugData != null) {
-      if (!debugUsers().isEmpty()) {
-        List<Instruction> toRemove = new ArrayList<>(debugUsers().size());
-        for (Instruction user : debugUsers()) {
-          user.replaceDebugValue(this, newValue, toRemove);
-        }
-        toRemove.forEach(this::removeDebugUser);
-      }
-      if (!debugPhiUsers().isEmpty()) {
-        List<Phi> toRemove = new ArrayList<>(debugPhiUsers().size());
-        for (Phi user : debugPhiUsers()) {
-          user.replaceTrivialDebugPhi(this, newValue, toRemove);
-        }
-        toRemove.forEach(this::removeDebugPhiUser);
-      }
+  public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
+    DebugUse use = debugData.users.remove(oldUser);
+    if (use != null) {
+      newUser.addDebugValue(this);
+      debugData.users.put(newUser, use);
     }
   }
 
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..bc821d3 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;
@@ -148,7 +149,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];
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 7f7b726..5eeda96 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;
@@ -311,7 +312,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 +442,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;
@@ -917,12 +918,14 @@
     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(
+          Constants.ANDROID_O_API,
+          "Android O",
+          "MethodHandle.invoke and MethodHandle.invokeExact",
+          null /* sourceString */);
     }
     add(Invoke.create(type, item, callSiteProto, null, arguments));
   }
@@ -932,7 +935,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++) {
@@ -1000,7 +1004,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);
@@ -1027,7 +1032,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;
@@ -1051,7 +1057,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);
@@ -1078,7 +1085,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;
@@ -1792,12 +1800,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);
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..ea1ef57 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,34 @@
       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 +268,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,13 +279,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();
+    {
+      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();
+    }
 
     // Get rid of <clinit> methods with no code.
     removeEmptyClassInitializers();
@@ -304,16 +316,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);
@@ -380,7 +393,7 @@
     return result;
   }
 
-  private DexProgramClass prepareOutlining() {
+  private DexProgramClass prepareOutlining() throws ApiLevelException {
     if (!outliner.selectMethodsForOutlining()) {
       return null;
     }
@@ -389,14 +402,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 +419,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 +435,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 +482,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);
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 e22b2f3..fdc4743 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;
@@ -32,6 +33,7 @@
 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;
@@ -47,7 +49,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;
@@ -477,7 +478,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;
@@ -1793,7 +1794,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);
@@ -2521,7 +2522,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);
 
@@ -2544,8 +2545,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.
@@ -2586,7 +2591,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 &&
@@ -2786,7 +2791,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);
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..cf1e669 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,6 +4,7 @@
 
 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;
 
@@ -46,7 +47,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).
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 d44ecd9..a758223 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
@@ -1101,18 +1101,18 @@
             // 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();
-            List<Instruction> toRemove = new ArrayList<>(constantValue.uniqueUsers().size());
+            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);
               iterator.previous();
               iterator.add(newCstNum);
-              user.replaceValue(constantValue, newCstNum.outValue(), toRemove);
+              user.replaceValue(constantValue, newCstNum.outValue());
             }
-            toRemove.forEach(constantValue::removeUser);
+            constantValue.clearUsers();
           }
         }
       } else {
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..f6d9c26 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;
@@ -969,7 +970,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/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..f4c51ce 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
@@ -197,7 +197,6 @@
       }
     }
 
-    clearUserInfo();
     assert code.isConsistentGraph();
     if (Log.ENABLED) {
       Log.debug(this.getClass(), toString());
@@ -206,6 +205,7 @@
     if (debug) {
       computeDebugInfo(blocks);
     }
+    clearUserInfo();
     clearState();
   }
 
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..5fa247e 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,6 +6,7 @@
 
 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;
@@ -16,9 +17,9 @@
 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 +39,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 +58,7 @@
     }
   }
 
-  protected final void add(Consumer<IRBuilder> constructor) {
+  protected final void add(ThrowingConsumer<IRBuilder, ApiLevelException> constructor) {
     constructors.add(constructor);
   }
 
@@ -174,7 +175,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);
   }
 
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/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/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 153f604..3decec1 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -39,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(
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 9f3488c..9a87320 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 {
@@ -197,7 +198,7 @@
 
   public static class TestingOptions {
 
-    public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering =
+    public Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> irOrdering =
         Function.identity();
   }
 
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/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..cb8e307 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -523,7 +523,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/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/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/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index d7d30d4..619f808 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -538,13 +538,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;
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/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 0322407..f35dd21 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -6,6 +6,7 @@
 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;
@@ -431,7 +432,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);
     }
   }