Merge "minify_tool: Fix benchmark output and rename parameter"
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE
index c19c9c3..12674e3 100644
--- a/LIBRARY-LICENSE
+++ b/LIBRARY-LICENSE
@@ -3,11 +3,11 @@
   copyrightHolder: The Guava Authors
   license: The Apache Software License, Version 2.0
   licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
-- artifact: com.googlecode.json-simple:json-simple:+
-  name: JSON.Simple
+- artifact: com.google.code.gson:gson:+
+  name: Gson
   license: The Apache Software License, Version 2.0
   licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
-  url: http://code.google.com/p/json-simple/
+  url: https://github.com/google/gson
 - artifact: it.unimi.dsi:fastutil:+
   name: fastutil
   license: Apache License, Version 2.0
diff --git a/build.gradle b/build.gradle
index 0f782b2..8ee6b41 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,7 +34,7 @@
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
     joptSimpleVersion = '4.6'
-    jsonSimpleVersion = '1.1'
+    gsonVersion = '2.7'
     junitVersion = '4.12'
     kotlinVersion = '1.2.30'
     protobufVersion = '3.0.0'
@@ -205,7 +205,7 @@
 
 dependencies {
     compile "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
-    compile "com.googlecode.json-simple:json-simple:$jsonSimpleVersion"
+    compile "com.google.code.gson:gson:$gsonVersion"
     // Include all of guava when compiling the code, but exclude annotations that we don't
     // need from the packaging.
     compileOnly("com.google.guava:guava:$guavaVersion")
@@ -485,11 +485,11 @@
 
 static configureRelocations(ShadowJar task) {
     task.relocate('com.google.common', 'com.android.tools.r8.com.google.common')
+    task.relocate('com.google.gson', 'com.android.tools.r8.com.google.gson')
     task.relocate('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty')
     task.relocate('joptsimple', 'com.android.tools.r8.joptsimple')
     task.relocate('org.apache.commons', 'com.android.tools.r8.org.apache.commons')
     task.relocate('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm')
-    task.relocate('org.json.simple', 'com.android.tools.r8.org.json.simple')
     task.relocate('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil')
 }
 
diff --git a/src/main/java/com/android/tools/r8/ApiLevelException.java b/src/main/java/com/android/tools/r8/ApiLevelException.java
index 14a530c..6d110a4 100644
--- a/src/main/java/com/android/tools/r8/ApiLevelException.java
+++ b/src/main/java/com/android/tools/r8/ApiLevelException.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.utils.AndroidApiLevel;
 
 /**
  * Exception to signal features that are not supported until a given API level.
  */
-public class ApiLevelException extends CompilationException {
+public class ApiLevelException extends CompilationError {
 
   public ApiLevelException(
       AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2d52c64..1db187e 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -196,7 +196,7 @@
       InternalOptions options,
       Timing timing,
       ExecutorService executor)
-      throws IOException, ExecutionException, ApiLevelException {
+      throws IOException, ExecutionException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
     IRConverter converter = new IRConverter(appInfo, options, timing, printer);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 0a98cb3..3122404 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
@@ -48,8 +47,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
-      throws ApiLevelException {
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     builder.addConstMethodHandle(
         state.push(builder.getFactory().methodHandleType).register, handle);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index e933c11..ebbaf21 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
@@ -49,8 +48,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
-      throws ApiLevelException {
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     builder.addConstMethodType(state.push(builder.getFactory().methodTypeType).register, type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index eaa7b2a..6d2a3d5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
@@ -49,8 +48,7 @@
     return false;
   }
 
-  public abstract void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
-      throws ApiLevelException;
+  public abstract void buildIR(IRBuilder builder, CfState state, CfSourceCode code);
 
   /** Return true if this instruction directly emits IR instructions. */
   public boolean emitsIR() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 32c7022..5dfef0c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -91,8 +90,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
-      throws ApiLevelException {
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     Invoke.Type type;
     DexMethod canonicalMethod;
     DexProto callSiteProto = null;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 2dfc8fa..015b8a3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
@@ -52,8 +51,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
-      throws ApiLevelException {
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     int[] dimensions = state.popReverse(this.dimensions);
     builder.addMultiNewArray(type, state.push(type).register, dimensions);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 733f2d9..a30c6e6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -216,9 +216,7 @@
           // Current stack: ..., value1, value2, value1copy
           state.pop();
           // Output stack: ..., value1, value2
-          throw new Unimplemented(
-              "Building IR for CfStackInstruction " + opcode + " not supported");
-          // break;
+          break;
         }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
index a346020..bad4e47 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -3,7 +3,6 @@
 // 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.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
@@ -71,7 +70,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addConstMethodHandle(AA, (DexMethodHandle) BBBB);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
index 6a1bef5..84c090a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -3,7 +3,6 @@
 // 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.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
@@ -71,7 +70,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addConstMethodType(AA, (DexProto) BBBB);
   }
 
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 8aaf5b9..4f2ebb6 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
@@ -3,7 +3,6 @@
 // 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;
@@ -42,7 +41,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 2d39d50..0ee63b0 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
@@ -3,7 +3,6 @@
 // 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;
@@ -42,7 +41,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 09d5671b..c8b5154 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -3,7 +3,6 @@
 // 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;
@@ -184,7 +183,7 @@
     return NO_TARGETS;
   }
 
-  public abstract void buildIR(IRBuilder builder) throws ApiLevelException;
+  public abstract void buildIR(IRBuilder builder);
 
   public DexCallSite getCallSite() {
     return 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 06b919b..08d6826 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
@@ -3,7 +3,6 @@
 // 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;
@@ -51,7 +50,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 e2b129d..8f72a2b 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
@@ -3,7 +3,6 @@
 // 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;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 c0d912a..b98af41 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
@@ -3,7 +3,6 @@
 // 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;
@@ -51,7 +50,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 c63c41b..e4d63b4 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
@@ -3,7 +3,6 @@
 // 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;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 891dcca..d5ecd11 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
@@ -3,7 +3,6 @@
 // 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;
@@ -30,7 +29,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 3969a8c..45eb6e8 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
@@ -3,7 +3,6 @@
 // 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;
@@ -53,7 +52,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 6c0a724..73cb887 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
@@ -3,7 +3,6 @@
 // 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;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 912cd4b..6fd3f42 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
@@ -3,7 +3,6 @@
 // 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;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 10ed497..9575ed9 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
@@ -3,7 +3,6 @@
 // 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;
@@ -51,7 +50,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 8933527..63c6318 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
@@ -3,7 +3,6 @@
 // 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;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 132c564..003debf 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -3,7 +3,6 @@
 // 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;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     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 00cb861..64e9b6d 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -3,7 +3,6 @@
 // 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;
@@ -50,7 +49,7 @@
   }
 
   @Override
-  public void buildIR(IRBuilder builder) throws ApiLevelException {
+  public void buildIR(IRBuilder builder) {
     builder.addInvokeRange(Type.VIRTUAL, getMethod(), getProto(), AA, CCCC);
   }
 
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 382bc66..127ec43 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
@@ -429,8 +428,7 @@
     }
   }
 
-  private byte[] writeDexFile(ObjectToOffsetMapping mapping)
-      throws ApiLevelException {
+  private byte[] writeDexFile(ObjectToOffsetMapping mapping) {
     FileWriter fileWriter = new FileWriter(mapping, application, options, namingLens);
     // Collect the non-fixed sections.
     fileWriter.collect();
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 4767c64..b73f8a4 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -51,7 +51,6 @@
 import com.android.tools.r8.utils.DexVersion;
 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;
@@ -135,7 +134,7 @@
     return this;
   }
 
-  public byte[] generate() throws ApiLevelException {
+  public byte[] generate() {
     // Check restrictions on interface methods.
     checkInterfaceMethods();
 
@@ -203,7 +202,7 @@
     return Arrays.copyOf(dest.asArray(), layout.getEndOfFile());
   }
 
-  private void checkInterfaceMethods() throws ApiLevelException {
+  private void checkInterfaceMethods() {
     for (DexProgramClass clazz : mapping.getClasses()) {
       if (clazz.isInterface()) {
         for (DexEncodedMethod method : clazz.directMethods()) {
@@ -222,7 +221,7 @@
   //  -- starting with N interfaces may also have public or private
   //     static methods, as well as public non-abstract (default)
   //     and private instance methods.
-  private void checkInterfaceMethod(DexEncodedMethod method) throws ApiLevelException {
+  private void checkInterfaceMethod(DexEncodedMethod method) {
     if (application.dexItemFactory.isClassConstructor(method.method)) {
       return; // Class constructor is always OK.
     }
@@ -295,16 +294,16 @@
     }
   }
 
-  private <T extends IndexedDexItem> void writeFixedSectionItems(Collection<T> items, int offset,
-      ThrowingConsumer<T, ApiLevelException> writer) throws ApiLevelException {
+  private <T extends IndexedDexItem> void writeFixedSectionItems(
+      Collection<T> items, int offset, Consumer<T> writer) {
     assert dest.position() == offset;
     for (T item : items) {
       writer.accept(item);
     }
   }
 
-  private void writeFixedSectionItems(DexProgramClass[] items, int offset,
-      ThrowingConsumer<DexProgramClass, ApiLevelException> writer) throws ApiLevelException {
+  private void writeFixedSectionItems(
+      DexProgramClass[] items, int offset, Consumer<DexProgramClass> writer) {
     assert dest.position() == offset;
     for (DexProgramClass item : items) {
       writer.accept(item);
@@ -610,7 +609,7 @@
     }
   }
 
-  private void writeMethodHandle(DexMethodHandle methodHandle) throws ApiLevelException {
+  private void writeMethodHandle(DexMethodHandle methodHandle) {
     checkThatInvokeCustomIsAllowed();
     MethodHandleType methodHandleDexType;
     switch (methodHandle.type) {
@@ -636,7 +635,7 @@
     dest.putShort((short) 0); // unused
   }
 
-  private void writeCallSite(DexCallSite callSite) throws ApiLevelException {
+  private void writeCallSite(DexCallSite callSite) {
     checkThatInvokeCustomIsAllowed();
     assert dest.isAligned(4);
     dest.putInt(mixedSectionOffsets.getOffsetFor(callSite.getEncodedArray()));
@@ -1296,7 +1295,7 @@
     }
   }
 
-  private void checkThatInvokeCustomIsAllowed() throws ApiLevelException {
+  private void checkThatInvokeCustomIsAllowed() {
     if (!options.canUseInvokeCustom()) {
       throw new ApiLevelException(
           AndroidApiLevel.O,
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 31628ad..b36f30c 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -4,11 +4,12 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.graph.DexString;
-import java.util.Map;
-import java.util.TreeMap;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import java.util.Comparator;
+import java.util.Map.Entry;
 
 /**
  * Abstraction for hidden dex marker intended for the main dex file.
@@ -26,22 +27,17 @@
   private static final String D8_PREFIX = PREFIX + Tool.D8 + "{";
   private static final String R8_PREFIX = PREFIX + Tool.R8 + "{";
 
-  private final TreeMap<String, Object> content;
+  private final JsonObject jsonObject;
   private final Tool tool;
 
   public Marker(Tool tool) {
     this.tool = tool;
-    this.content = new TreeMap<>();
+    jsonObject = new JsonObject();
   }
 
-  private Marker(Tool tool, JSONObject object) {
+  private Marker(Tool tool, JsonObject jsonObject) {
     this.tool = tool;
-    content = new TreeMap<>();
-    // This loop is necessary to make the type checker to shut up.
-    for (Object e : object.entrySet()) {
-      Map.Entry<?,?> entry = (Map.Entry<?,?>) e;
-      content.put(String.valueOf(entry.getKey()), entry.getValue());
-    }
+    this.jsonObject = jsonObject;
   }
 
   public Tool getTool() {
@@ -57,70 +53,58 @@
   }
 
   public String getVersion() {
-    return (String) content.get(VERSION);
+    return jsonObject.get(VERSION).getAsString();
   }
 
   public Marker setVersion(String version) {
-    internalPut(VERSION, version);
+    assert !jsonObject.has(VERSION);
+    jsonObject.addProperty(VERSION, version);
     return this;
   }
 
   public Long getMinApi() {
-    return (Long) content.get(MIN_API);
+    return jsonObject.get(MIN_API).getAsLong();
   }
 
   public Marker setMinApi(long minApi) {
-    internalPut(MIN_API, minApi);
+    assert !jsonObject.has(MIN_API);
+    jsonObject.addProperty(MIN_API, minApi);
     return this;
   }
 
   public String getSha1() {
-    return (String) content.get(SHA1);
+    return jsonObject.get(SHA1).getAsString();
   }
 
   public Marker setSha1(String sha1) {
-    internalPut(SHA1, sha1);
-    return this;
-  }
-
-  private Marker internalPut(String key, Object value) {
-    assert (key != null) && (value != null);
-    assert !content.containsKey(key);
-    content.put(key, value);
+    assert !jsonObject.has(SHA1);
+    jsonObject.addProperty(SHA1, sha1);
     return this;
   }
 
   @Override
   public String toString() {
-    // The JSONObject does not support a predictable sorted serialization of the object.
-    // Therefore, a TreeMap is used and iteration is over the keySet.
-    StringBuffer sb = new StringBuffer(PREFIX + tool);
-    boolean first = true;
-    sb.append('{');
-    for (String key : content.keySet()) {
-      if (first) {
-        first = false;
-      } else {
-        sb.append(',');
-      }
-      sb.append(JSONObject.toString(key, content.get(key)));
-    }
-    sb.append('}');
-    return sb.toString();
+    // In order to make printing of markers deterministic we sort the entries by key.
+    final JsonObject sortedJson = new JsonObject();
+    jsonObject.entrySet()
+        .stream()
+        .sorted(Comparator.comparing(Entry::getKey))
+        .forEach(entry -> sortedJson.add(entry.getKey(), entry.getValue()));
+    return PREFIX + tool + sortedJson;
   }
 
   @Override
   public boolean equals(Object obj) {
     if (obj instanceof Marker) {
       Marker other = (Marker) obj;
-      return (tool == other.tool) && content.equals(other.content);
+      return (tool == other.tool) && jsonObject.equals(other.jsonObject);
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return tool.hashCode() + 3 * content.hashCode();
+    return tool.hashCode() + 3 * jsonObject.hashCode();
   }
 
   // Try to parse str as a marker.
@@ -142,11 +126,11 @@
 
   private static Marker internalParse(Tool tool, String str) {
     try {
-      Object result =  new JSONParser().parse(str);
-      if (result instanceof JSONObject) {
-        return new Marker(tool, (JSONObject) result);
+      JsonElement result =  new JsonParser().parse(str);
+      if (result.isJsonObject()) {
+        return new Marker(tool, result.getAsJsonObject());
       }
-    } catch (ParseException e) {
+    } catch (JsonSyntaxException e) {
       // Fall through.
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 6e93a90..1fd4a73 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -3,7 +3,6 @@
 // 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.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -201,11 +200,7 @@
 
   @Override
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod,
-      AppInfo appInfo,
-      InternalOptions options,
-      Origin origin)
-      throws ApiLevelException {
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
     return internalBuild(encodedMethod, appInfo, options, null, null, origin);
   }
 
@@ -216,8 +211,7 @@
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin)
-      throws ApiLevelException {
+      Origin origin) {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
     return internalBuild(
@@ -230,8 +224,7 @@
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
-      Origin origin)
-      throws ApiLevelException {
+      Origin origin) {
     assert !options.isGeneratingDex() || !encodedMethod.accessFlags.isSynchronized()
         : "Converting CfCode to IR not supported for DEX output of synchronized methods.";
     CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
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 6f86412..60a5ef5 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -3,7 +3,6 @@
 // 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;
@@ -18,11 +17,7 @@
 public abstract class Code extends CachedHashValueDexItem {
 
   public abstract IRCode buildIR(
-      DexEncodedMethod encodedMethod,
-      AppInfo appInfo,
-      InternalOptions options,
-      Origin origin)
-      throws ApiLevelException;
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin);
 
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
@@ -30,8 +25,7 @@
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin)
-      throws ApiLevelException {
+      Origin origin) {
     throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
         + getClass().getCanonicalName());
   }
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 685f32e..b1b5f8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,7 +3,6 @@
 // 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;
@@ -164,9 +163,8 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
-      InternalOptions options, Origin origin)
-      throws ApiLevelException {
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
     DexSourceCode source =
         new DexSourceCode(
             this, encodedMethod, null, options.lineNumberOptimization == LineNumberOptimization.ON);
@@ -177,11 +175,11 @@
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
-      AppInfo appInfo, InternalOptions options,
+      AppInfo appInfo,
+      InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin)
-      throws ApiLevelException {
+      Origin origin) {
     DexSourceCode source =
         new DexSourceCode(
             this,
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 ee29ff3..0914e7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -9,7 +9,6 @@
 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.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfThrow;
@@ -231,23 +230,21 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(
-      AppInfo appInfo, InternalOptions options, Origin origin) throws ApiLevelException {
+  public IRCode buildIR(AppInfo appInfo, InternalOptions options, Origin origin) {
     return code == null ? null : code.buildIR(this, appInfo, options, origin);
   }
 
   public IRCode buildInliningIRForTesting(
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator)
-      throws ApiLevelException {
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
     return buildInliningIR(null, options, valueNumberGenerator, null, Origin.unknown());
   }
 
   public IRCode buildInliningIR(
-      AppInfo appInfo, InternalOptions options,
+      AppInfo appInfo,
+      InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin)
-      throws ApiLevelException {
+      Origin origin) {
     return code.buildInliningIR(
         this, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
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 a992371..640172f 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -3,7 +3,6 @@
 // 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.graph.JarClassFileReader.ReparseContext;
 import com.android.tools.r8.ir.code.IRCode;
@@ -104,9 +103,8 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
-      InternalOptions options, Origin origin)
-      throws ApiLevelException {
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
     triggerDelayedParsingIfNeccessary();
     return options.debug
         ? internalBuildWithLocals(encodedMethod, appInfo, options, null, null)
@@ -116,11 +114,11 @@
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
-      AppInfo appInfo, InternalOptions options,
+      AppInfo appInfo,
+      InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
-      Origin origin)
-      throws ApiLevelException {
+      Origin origin) {
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
@@ -130,10 +128,10 @@
 
   private IRCode internalBuildWithLocals(
       DexEncodedMethod encodedMethod,
-      AppInfo appInfo, InternalOptions options,
+      AppInfo appInfo,
+      InternalOptions options,
       ValueNumberGenerator generator,
-      Position callerPosition)
-      throws ApiLevelException {
+      Position callerPosition) {
     try {
       return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
     } catch (InvalidDebugInfoException e) {
@@ -145,10 +143,10 @@
 
   private IRCode internalBuild(
       DexEncodedMethod encodedMethod,
-      AppInfo appInfo, InternalOptions options,
+      AppInfo appInfo,
+      InternalOptions options,
       ValueNumberGenerator generator,
-      Position callerPosition)
-      throws ApiLevelException {
+      Position callerPosition) {
     if (!options.debug) {
       node.localVariables.clear();
     }
@@ -187,15 +185,44 @@
   }
 
   private void triggerDelayedParsingIfNeccessary() {
-    if (context != null) {
-      // The SecondVistor is in charge of setting the context to null.
-      DexProgramClass owner = context.owner;
-      new ClassReader(context.classCache).accept(new SecondVisitor(context, application),
-          ClassReader.SKIP_FRAMES);
-      assert verifyNoReparseContext(owner);
+    if (this.context != null) {
+      // The SecondVisitor is in charge of setting this.context to null.
+      ReparseContext context = this.context;
+      parseCode(context, false);
+      if (hasJsr(context)) {
+        System.out.println("JarCode: JSR encountered; reparse using JSRInlinerAdapter");
+        parseCode(context, true);
+        assert !hasJsr(context);
+      }
+      assert verifyNoReparseContext(context.owner);
     }
   }
 
+  private void parseCode(ReparseContext context, boolean useJsrInliner) {
+    SecondVisitor classVisitor = new SecondVisitor(context, application, useJsrInliner);
+    new ClassReader(context.classCache).accept(classVisitor, ClassReader.SKIP_FRAMES);
+  }
+
+  private boolean hasJsr(ReparseContext context) {
+    for (Code code : context.codeList) {
+      if (hasJsr(code.asJarCode().node)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasJsr(MethodNode node) {
+    Iterator<AbstractInsnNode> it = node.instructions.iterator();
+    while (it.hasNext()) {
+      int opcode = it.next().getOpcode();
+      if (opcode == Opcodes.JSR || opcode == Opcodes.RET) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Fills the MethodNodes of all the methods in the class and removes the ReparseContext.
    */
@@ -203,18 +230,24 @@
 
     private final ReparseContext context;
     private final JarApplicationReader application;
+    private final boolean useJsrInliner;
     private int methodIndex = 0;
 
-    public SecondVisitor(ReparseContext context, JarApplicationReader application) {
+    public SecondVisitor(
+        ReparseContext context, JarApplicationReader application, boolean useJsrInliner) {
       super(Opcodes.ASM6);
       this.context = context;
       this.application = application;
+      this.useJsrInliner = useJsrInliner;
     }
 
     @Override
     public MethodVisitor visitMethod(int access, String name, String desc, String signature,
         String[] exceptions) {
-      MethodNode node = new JSRInlinerAdapter(null, access, name, desc, signature, exceptions);
+      MethodNode node =
+          useJsrInliner
+              ? new JSRInlinerAdapter(null, access, name, desc, signature, exceptions)
+              : new MethodNode(Opcodes.ASM6, access, name, desc, signature, exceptions);
       JarCode code = null;
       MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
       if (!flags.isAbstract() && !flags.isNative()) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 57932cc..8ecc1ab 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -3,7 +3,6 @@
 // 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.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfArrayLength;
 import com.android.tools.r8.cf.code.CfArrayLoad;
@@ -64,6 +63,7 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -78,6 +78,12 @@
 
 public class LazyCfCode extends Code {
 
+  private static class JsrEncountered extends RuntimeException {
+    public JsrEncountered(String s) {
+      super(s);
+    }
+  }
+
   public LazyCfCode(
       DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
 
@@ -107,17 +113,34 @@
   @Override
   public CfCode asCfCode() {
     if (code == null) {
+      ReparseContext context = this.context;
       assert context != null;
-      // The SecondVistor is in charge of setting the context to null.
-      DexProgramClass owner = context.owner;
-      ClassReader classReader = new ClassReader(context.classCache);
-      classReader.accept(new ClassCodeVisitor(context, application), ClassReader.EXPAND_FRAMES);
-      assert verifyNoReparseContext(owner);
+      // The ClassCodeVisitor is in charge of setting this.context to null.
+      try {
+        parseCode(context, false);
+      } catch (JsrEncountered e) {
+        System.out.println("LazyCfCode: JSR encountered; reparse using JSRInlinerAdapter");
+        for (Code code : context.codeList) {
+          code.asLazyCfCode().code = null;
+          code.asLazyCfCode().context = context;
+        }
+        try {
+          parseCode(context, true);
+        } catch (JsrEncountered e1) {
+          throw new Unreachable(e1);
+        }
+      }
+      assert verifyNoReparseContext(context.owner);
     }
     assert code != null;
     return code;
   }
 
+  public void parseCode(ReparseContext context, boolean useJsrInliner) {
+    ClassCodeVisitor classVisitor = new ClassCodeVisitor(context, application, useJsrInliner);
+    new ClassReader(context.classCache).accept(classVisitor, ClassReader.EXPAND_FRAMES);
+  }
+
   private void setCode(CfCode code) {
     assert this.code == null;
     assert this.context != null;
@@ -151,9 +174,8 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, AppInfo appInfo,
-      InternalOptions options, Origin origin)
-      throws ApiLevelException {
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
     return asCfCode().buildIR(encodedMethod, appInfo, options, origin);
   }
 
@@ -164,8 +186,7 @@
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
-      Origin origin)
-      throws ApiLevelException {
+      Origin origin) {
     return asCfCode().buildInliningIR(
         encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
   }
@@ -190,11 +211,14 @@
     private final ReparseContext context;
     private final JarApplicationReader application;
     private int methodIndex = 0;
+    private boolean usrJsrInliner;
 
-    ClassCodeVisitor(ReparseContext context, JarApplicationReader application) {
+    ClassCodeVisitor(
+        ReparseContext context, JarApplicationReader application, boolean useJsrInliner) {
       super(Opcodes.ASM6);
       this.context = context;
       this.application = application;
+      this.usrJsrInliner = useJsrInliner;
     }
 
     @Override
@@ -206,6 +230,9 @@
         DexMethod method = application.getMethod(context.owner.type, name, desc);
         assert code.method == method;
         MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, code);
+        if (!usrJsrInliner) {
+          return methodVisitor;
+        }
         return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
       }
       return null;
@@ -220,6 +247,7 @@
     private List<CfInstruction> instructions;
     private List<CfTryCatch> tryCatchRanges;
     private List<LocalVariableInfo> localVariables;
+    private final Map<DebugLocalInfo, DebugLocalInfo> canonicalDebugLocalInfo = new HashMap<>();
     private Map<Label, CfLabel> labelMap;
     private final LazyCfCode code;
     private DexMethod method;
@@ -581,7 +609,7 @@
           type = ValueType.OBJECT;
           break;
         case Opcodes.RET:
-          throw new Unreachable("RET should be handled by the ASM jsr inliner");
+          throw new JsrEncountered("RET should be handled by the ASM jsr inliner");
         default:
           throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
       }
@@ -670,7 +698,7 @@
             instructions.add(new CfIf(type, ValueType.OBJECT, target));
             break;
           case Opcodes.JSR:
-            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
+            throw new JsrEncountered("JSR should be handled by the ASM jsr inliner");
           default:
             throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
         }
@@ -782,14 +810,19 @@
     public void visitLocalVariable(
         String name, String desc, String signature, Label start, Label end, int index) {
       DebugLocalInfo debugLocalInfo =
-          new DebugLocalInfo(
-              factory.createString(name),
-              factory.createType(desc),
-              signature == null ? null : factory.createString(signature));
+          canonicalize(
+              new DebugLocalInfo(
+                  factory.createString(name),
+                  factory.createType(desc),
+                  signature == null ? null : factory.createString(signature)));
       localVariables.add(
           new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
     }
 
+    private DebugLocalInfo canonicalize(DebugLocalInfo debugLocalInfo) {
+      return canonicalDebugLocalInfo.computeIfAbsent(debugLocalInfo, o -> debugLocalInfo);
+    }
+
     @Override
     public void visitLineNumber(int line, Label start) {
       instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 382a0a9..e68e3d5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -5,7 +5,6 @@
 
 import static it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps.emptyMap;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
@@ -359,8 +358,7 @@
 
   @Override
   public void buildInstruction(
-      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException {
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     CfInstruction instruction = code.getInstructions().get(instructionIndex);
     currentInstructionIndex = instructionIndex;
     if (firstBlockInstruction) {
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 0d87723..31ee706 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,7 +4,6 @@
 
 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;
@@ -173,8 +172,7 @@
 
   @Override
   public void buildInstruction(
-      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException {
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     updateCurrentCatchHandlers(instructionIndex);
     updateDebugPosition(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 d2ba632..702218e 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
@@ -357,7 +357,7 @@
    *
    * @return The list of basic blocks. First block is the main entry.
    */
-  public IRCode build() throws ApiLevelException {
+  public IRCode build() {
     assert source != null;
     source.setUp();
 
@@ -516,7 +516,7 @@
     return true;
   }
 
-  private void processWorklist() throws ApiLevelException {
+  private void processWorklist() {
     for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) {
       if (item.block.isFilled()) {
         continue;
@@ -835,8 +835,7 @@
     add(instruction);
   }
 
-  public void addConstMethodHandle(int dest, DexMethodHandle methodHandle)
-      throws ApiLevelException {
+  public void addConstMethodHandle(int dest, DexMethodHandle methodHandle) {
     if (!options.canUseConstantMethodHandle()) {
       throw new ApiLevelException(
           AndroidApiLevel.P,
@@ -848,8 +847,7 @@
     add(instruction);
   }
 
-  public void addConstMethodType(int dest, DexProto methodType)
-      throws ApiLevelException {
+  public void addConstMethodType(int dest, DexProto methodType) {
     if (!options.canUseConstantMethodType()) {
       throw new ApiLevelException(
           AndroidApiLevel.P,
@@ -1040,8 +1038,7 @@
   }
 
   public void addInvoke(
-      Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf)
-      throws ApiLevelException {
+      Type type, DexItem item, DexProto callSiteProto, List<Value> arguments, boolean itf) {
     if (type == Type.POLYMORPHIC) {
       assert item instanceof DexMethod;
       if (!options.canUseInvokePolymorphic()) {
@@ -1062,16 +1059,18 @@
       // not apply (see jvm spec on method resolution 5.4.3.3 and overriding 5.4.5) and
       // therefore we use an invoke-direct instead. We need to do this as the Android Runtime
       // will not allow invoke-virtual of a private method.
-      DexMethod method = (DexMethod) item;
-      if (appInfo.lookupDirectTarget(method) != null) {
-        type = Type.DIRECT;
+      DexMethod invocationMethod = (DexMethod) item;
+      if (invocationMethod.holder == method.method.holder) {
+        DexEncodedMethod directTarget = appInfo.lookupDirectTarget(invocationMethod);
+        if (directTarget != null && invocationMethod.holder == directTarget.method.holder) {
+          type = Type.DIRECT;
+        }
       }
     }
     add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
   }
 
-  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments)
-      throws ApiLevelException {
+  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments) {
     addInvoke(type, item, callSiteProto, arguments, false);
   }
 
@@ -1080,8 +1079,7 @@
       DexItem item,
       DexProto callSiteProto,
       List<ValueType> types,
-      List<Integer> registers)
-      throws ApiLevelException {
+      List<Integer> registers) {
     addInvoke(type, item, callSiteProto, types, registers, false);
   }
 
@@ -1091,8 +1089,7 @@
       DexProto callSiteProto,
       List<ValueType> types,
       List<Integer> registers,
-      boolean itf)
-      throws ApiLevelException {
+      boolean itf) {
     assert types.size() == registers.size();
     List<Value> arguments = new ArrayList<>(types.size());
     for (int i = 0; i < types.size(); i++) {
@@ -1160,8 +1157,7 @@
       DexMethod method,
       DexProto callSiteProto,
       int argumentRegisterCount,
-      int[] argumentRegisters)
-      throws ApiLevelException {
+      int[] argumentRegisters) {
     // 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);
@@ -1188,8 +1184,7 @@
     addInvoke(type, method, callSiteProto, arguments);
   }
 
-  public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters)
-      throws ApiLevelException {
+  public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters) {
     String descriptor = type.descriptor.toString();
     assert descriptor.charAt(0) == '[';
     assert descriptor.length() >= 2;
@@ -1208,7 +1203,7 @@
     addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments);
   }
 
-  public void addMultiNewArray(DexType type, int dest, int[] dimensions) throws ApiLevelException {
+  public void addMultiNewArray(DexType type, int dest, int[] dimensions) {
     assert isGeneratingClassFiles();
     List<Value> arguments = new ArrayList<>(dimensions.length);
     for (int dimension : dimensions) {
@@ -1223,8 +1218,7 @@
       DexMethod method,
       DexProto callSiteProto,
       int argumentCount,
-      int firstArgumentRegister)
-      throws ApiLevelException {
+      int firstArgumentRegister) {
     // 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);
@@ -1251,8 +1245,7 @@
     addInvoke(type, method, callSiteProto, arguments);
   }
 
-  public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister)
-      throws ApiLevelException {
+  public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister) {
     String descriptor = type.descriptor.toString();
     assert descriptor.charAt(0) == '[';
     assert descriptor.length() >= 2;
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 8477d9b..fc5e95b 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,7 +6,6 @@
 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;
@@ -233,7 +232,7 @@
     }
   }
 
-  private void synthesizeLambdaClasses(Builder<?> builder) throws ApiLevelException {
+  private void synthesizeLambdaClasses(Builder<?> builder) {
     if (lambdaRewriter != null) {
       lambdaRewriter.adjustAccessibility();
       lambdaRewriter.synthesizeLambdaClasses(builder);
@@ -241,15 +240,14 @@
   }
 
   private void desugarInterfaceMethods(
-      Builder<?> builder, InterfaceMethodRewriter.Flavor includeAllResources)
-      throws ApiLevelException {
+      Builder<?> builder, InterfaceMethodRewriter.Flavor includeAllResources) {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(builder, includeAllResources);
     }
   }
 
   public DexApplication convertToDex(DexApplication application, ExecutorService executor)
-      throws ExecutionException, ApiLevelException {
+      throws ExecutionException {
     removeLambdaDeserializationMethods();
 
     timing.begin("IR conversion");
@@ -347,7 +345,7 @@
     ThreadUtils.awaitFutures(futures);
   }
 
-  void convertMethodToDex(DexEncodedMethod method) throws ApiLevelException {
+  void convertMethodToDex(DexEncodedMethod method) {
     assert options.isGeneratingDex();
     if (method.getCode() != null) {
       boolean matchesMethodFilter = options.methodMatchesFilter(method);
@@ -362,8 +360,7 @@
     }
   }
 
-  public DexApplication optimize(DexApplication application)
-      throws ExecutionException, ApiLevelException {
+  public DexApplication optimize(DexApplication application) throws ExecutionException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
       return optimize(application, executor);
@@ -372,9 +369,8 @@
     }
   }
 
-  public DexApplication optimize(DexApplication application,
-      ExecutorService executorService)
-      throws ExecutionException, ApiLevelException {
+  public DexApplication optimize(DexApplication application, ExecutorService executorService)
+      throws ExecutionException {
     removeLambdaDeserializationMethods();
     collectLambdaMergingCandidates(application);
 
@@ -392,10 +388,14 @@
           .build(application, appInfo.withLiveness(), 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);
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
+          outliner == null ? Outliner::noProcessing : outliner.identifyCandidateMethods();
+      callGraph.forEachMethod(
+          (method, isProcessedConcurrently) -> {
+            processMethod(
+                method, directFeedback, isProcessedConcurrently, callGraph, outlineHandler);
+          },
+          executorService);
       timing.end();
     }
 
@@ -418,25 +418,23 @@
 
     if (outliner != null) {
       timing.begin("IR conversion phase 2");
-      // Compile all classes flagged for outlining and
-      // add the outline support class IF needed.
-      DexProgramClass outlineClass = prepareOutlining();
-      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
-            .build(application, appInfo.withLiveness(), GraphLense.getIdentityLense(), options);
-        Set<DexEncodedMethod> outlineMethods = outliner.getMethodsSelectedForOutlining();
-        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, isProcessedConcurrently, callGraph,
-              outliner::applyOutliningCandidate);
-          assert method.isProcessed();
-        }, executorService);
+      if (outliner.selectMethodsForOutlining()) {
+        forEachSelectedOutliningMethod(
+            executorService,
+            (code, method) -> {
+              printMethod(code, "IR before outlining (SSA)");
+              outliner.identifyOutlineSites(code, method);
+            });
+        DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
+        optimizeSynthesizedClass(outlineClass);
+        forEachSelectedOutliningMethod(
+            executorService,
+            (code, method) -> {
+              outliner.applyOutliningCandidate(code, method);
+              printMethod(code, "IR after outlining (SSA)");
+              finalizeIR(method, code, ignoreOptimizationFeedback);
+            });
+        assert outliner.checkAllOutlineSitesFoundAgain();
         builder.addSynthesizedClass(outlineClass, true);
         clearDexMethodCompilationState(outlineClass);
       }
@@ -451,15 +449,45 @@
     return builder.build();
   }
 
+  private void forEachSelectedOutliningMethod(
+      ExecutorService executorService, BiConsumer<IRCode, DexEncodedMethod> consumer)
+      throws ExecutionException {
+    assert !options.skipIR;
+    Set<DexEncodedMethod> methods = outliner.getMethodsSelectedForOutlining();
+    List<Future<?>> futures = new ArrayList<>();
+    for (DexEncodedMethod method : methods) {
+      futures.add(
+          executorService.submit(
+              () -> {
+                IRCode code =
+                    method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
+                assert code != null;
+                assert !method.getCode().isOutlineCode();
+                // Instead of repeating all the optimizations of rewriteCode(), only run the
+                // optimizations needed for outlining: rewriteMoveResult() to remove out-values on
+                // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
+                // unused out-values.
+                codeRewriter.rewriteMoveResult(code);
+                DeadCodeRemover.removeDeadCode(code, codeRewriter, options);
+                consumer.accept(code, method);
+                return null;
+              }));
+    }
+    ThreadUtils.awaitFutures(futures);
+  }
+
   private void collectLambdaMergingCandidates(DexApplication application) {
     if (lambdaMerger != null) {
       lambdaMerger.collectGroupCandidates(application, appInfo.withLiveness(), options);
     }
   }
 
-  private void finalizeLambdaMerging(DexApplication application,
-      OptimizationFeedback directFeedback, Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException, ApiLevelException {
+  private void finalizeLambdaMerging(
+      DexApplication application,
+      OptimizationFeedback directFeedback,
+      Builder<?> builder,
+      ExecutorService executorService)
+      throws ExecutionException {
     if (lambdaMerger != null) {
       lambdaMerger.applyLambdaClassMapping(
           application, this, directFeedback, builder, executorService);
@@ -512,16 +540,7 @@
     return result;
   }
 
-  private DexProgramClass prepareOutlining() throws ApiLevelException {
-    if (!outliner.selectMethodsForOutlining()) {
-      return null;
-    }
-    DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
-    optimizeSynthesizedClass(outlineClass);
-    return outlineClass;
-  }
-
-  public void optimizeSynthesizedClass(DexProgramClass clazz) throws ApiLevelException {
+  public void optimizeSynthesizedClass(DexProgramClass clazz) {
     try {
       codeRewriter.enterCachedClass(clazz);
       // Process the generated class, but don't apply any outlining.
@@ -531,7 +550,7 @@
     }
   }
 
-  public void optimizeSynthesizedMethod(DexEncodedMethod method) throws ApiLevelException {
+  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
     // Process the generated method, but don't apply any outlining.
     processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
         Outliner::noProcessing);
@@ -541,12 +560,12 @@
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
 
-  public void processMethod(DexEncodedMethod method,
+  public void processMethod(
+      DexEncodedMethod method,
       OptimizationFeedback feedback,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
-      throws ApiLevelException {
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
     Code code = method.getCode();
     boolean matchesMethodFilter = options.methodMatchesFilter(method);
     if (code != null && matchesMethodFilter) {
@@ -565,12 +584,12 @@
     }
   }
 
-  private void rewriteCode(DexEncodedMethod method,
+  private void rewriteCode(
+      DexEncodedMethod method,
       OptimizationFeedback feedback,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation,
-      BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
-      throws ApiLevelException {
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
     if (options.verbose) {
       options.reporter.info(
           new StringDiagnostic("Processing: " + method.toSourceString()));
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 46cc8e9..b5f0a05 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,7 +3,6 @@
 // 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;
@@ -29,7 +28,6 @@
 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;
@@ -44,6 +42,7 @@
 import java.util.List;
 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;
@@ -478,8 +477,7 @@
 
   @Override
   public void buildInstruction(
-      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException {
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
       buildExceptionalPostlude(builder);
       return;
@@ -1816,7 +1814,7 @@
 
   // IR instruction building procedures.
 
-  private void build(AbstractInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(AbstractInsnNode insn, IRBuilder builder) {
     switch (insn.getType()) {
       case AbstractInsnNode.INSN:
         build((InsnNode) insn, builder);
@@ -2540,7 +2538,7 @@
     }
   }
 
-  private void build(MethodInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(MethodInsnNode insn, IRBuilder builder) {
     // Resolve the target method of the invoke.
     DexMethod method = application.getMethod(insn.owner, insn.name, insn.desc);
 
@@ -2619,8 +2617,7 @@
       Type methodOwner,
       boolean addImplicitReceiver,
       IRBuilder builder,
-      ThrowingBiConsumer<List<ValueType>, List<Integer>, ApiLevelException> creator)
-      throws ApiLevelException {
+      BiConsumer<List<ValueType>, List<Integer>> creator) {
 
     // 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.
@@ -2657,7 +2654,7 @@
     registers.add(slot.register);
   }
 
-  private void build(InvokeDynamicInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(InvokeDynamicInsnNode insn, IRBuilder builder) {
     DexCallSite callSite = DexCallSite.fromAsmInvokeDynamic(insn, application, clazz);
 
     buildInvoke(insn.desc, null /* Not needed */,
@@ -2716,7 +2713,7 @@
     // Intentionally empty.
   }
 
-  private void build(LdcInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(LdcInsnNode insn, IRBuilder builder) {
     if (insn.cst instanceof Type) {
       Type type = (Type) insn.cst;
       if (type.getSort() == Type.METHOD) {
@@ -2781,7 +2778,7 @@
     builder.addSwitch(index, keys, fallthroughOffset, labelOffsets);
   }
 
-  private void build(MultiANewArrayInsnNode insn, IRBuilder builder) throws ApiLevelException {
+  private void build(MultiANewArrayInsnNode insn, IRBuilder builder) {
     // Type of the full array.
     Type arrayType = application.getAsmObjectType(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 cd8efa7..e95790a 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,7 +4,6 @@
 
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
@@ -48,8 +47,8 @@
   // Delegates for IR building.
   void buildPrelude(IRBuilder builder);
 
-  void buildInstruction(IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException;
+  void buildInstruction(IRBuilder builder, int instructionIndex, boolean firstBlockInstruction);
+
   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 8add960..73c5118 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,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.DexApplication.Builder;
@@ -325,7 +324,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) throws ApiLevelException {
+  public void desugarInterfaceMethods(Builder<?> builder, Flavor flavour) {
     // 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 d9bd4bd..81407bd 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,7 +4,6 @@
 
 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;
@@ -425,7 +424,7 @@
     }
 
     // Ensure access of the referenced symbol(s).
-    abstract boolean ensureAccessibility() throws ApiLevelException;
+    abstract boolean ensureAccessibility();
 
     DexClass definitionFor(DexType type) {
       return rewriter.converter.appInfo.app.definitionFor(type);
@@ -520,7 +519,7 @@
     }
 
     @Override
-    boolean ensureAccessibility() throws ApiLevelException {
+    boolean ensureAccessibility() {
       // 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/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 11ff65b..d6b99a7 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,7 +4,6 @@
 
 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;
@@ -162,7 +161,7 @@
    * Adjust accessibility of referenced application symbols or
    * creates necessary accessors.
    */
-  public void adjustAccessibility() throws ApiLevelException {
+  public void adjustAccessibility() {
     // 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.
@@ -172,7 +171,7 @@
   }
 
   /** Generates lambda classes and adds them to the builder. */
-  public void synthesizeLambdaClasses(Builder<?> builder) throws ApiLevelException {
+  public void synthesizeLambdaClasses(Builder<?> builder) {
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
       DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
       converter.optimizeSynthesizedClass(synthesizedClass);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 90081d1..845e17b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -3,7 +3,6 @@
 // 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.Code;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -305,8 +304,7 @@
   }
 
   @Override
-  public void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee) throws ApiLevelException {
+  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
     if (!target.isProcessed()) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
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 59ddc36..1b68f7d 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,7 +3,6 @@
 // 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;
@@ -40,7 +39,7 @@
     this.options = options;
   }
 
-  public AppInfoWithLiveness run() throws ApiLevelException {
+  public AppInfoWithLiveness run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       processClasses(clazz);
     }
@@ -50,7 +49,7 @@
     return appInfo;
   }
 
-  private void processClasses(DexProgramClass clazz) throws ApiLevelException {
+  private void processClasses(DexProgramClass clazz) {
     // 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 860cc21..d4cb8a8 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,7 +3,6 @@
 // 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.AccessFlags;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexClass;
@@ -151,8 +150,8 @@
     return target;
   }
 
-  public synchronized void processDoubleInlineCallers(IRConverter converter,
-      OptimizationFeedback feedback) throws ApiLevelException {
+  public synchronized void processDoubleInlineCallers(
+      IRConverter converter, OptimizationFeedback feedback) {
     if (doubleInlineCallers.size() > 0) {
       applyDoubleInlining = true;
       List<DexEncodedMethod> methods = doubleInlineCallers
@@ -267,8 +266,7 @@
         AppInfoWithSubtyping appInfo,
         GraphLense graphLense,
         InternalOptions options,
-        Position callerPosition)
-        throws ApiLevelException {
+        Position callerPosition) {
       // Build the IR for a yet not processed method, and perform minimal IR processing.
       Origin origin = appInfo.originFor(target.method.holder);
       IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
@@ -375,8 +373,10 @@
     }
   }
 
-  public void performForcedInlining(DexEncodedMethod method, IRCode code,
-      Map<InvokeMethodWithReceiver, InliningInfo> invokesToInline) throws ApiLevelException {
+  public void performForcedInlining(
+      DexEncodedMethod method,
+      IRCode code,
+      Map<InvokeMethodWithReceiver, InliningInfo> invokesToInline) {
 
     ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline);
     performInliningImpl(oracle, oracle, method, code);
@@ -387,8 +387,7 @@
       IRCode code,
       TypeEnvironment typeEnvironment,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
-      CallSiteInformation callSiteInformation)
-      throws ApiLevelException {
+      CallSiteInformation callSiteInformation) {
 
     DefaultInliningOracle oracle =
         new DefaultInliningOracle(
@@ -405,11 +404,7 @@
   }
 
   private void performInliningImpl(
-      InliningStrategy strategy,
-      InliningOracle oracle,
-      DexEncodedMethod method,
-      IRCode code)
-      throws ApiLevelException {
+      InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod method, IRCode code) {
     if (strategy.exceededAllowance()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 2b0c7e5..7b88b38 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -4,7 +4,6 @@
 
 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.DexType;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -17,8 +16,7 @@
 
   void markInlined(IRCode inlinee);
 
-  void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee) throws ApiLevelException;
+  void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee);
 
   ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
       ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor);
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 9462354..2e5b703 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,7 +4,6 @@
 
 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;
@@ -64,13 +63,44 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
+/**
+ * Support class for implementing outlining (i.e. extracting common code patterns as methods).
+ *
+ * <p>Outlining happens in three steps.
+ *
+ * <ul>
+ *   <li>First, all methods are converted to IR and passed to {@link
+ *       Outliner#identifyCandidateMethods()} to identify outlining candidates and the methods
+ *       containing each candidate. IR is converted to the output format (DEX or CF) and thrown away
+ *       along with the outlining candidates; only a list of lists of methods is kept, where each
+ *       list of methods corresponds to methods containing an outlining candidate.
+ *   <li>Second, {@link Outliner#selectMethodsForOutlining()} is called to retain the lists of
+ *       methods found in the first step that are large enough (see {@link InternalOptions#outline}
+ *       {@link OutlineOptions#threshold}), and the methods to be further analyzed for outlining is
+ *       returned by {@link Outliner#getMethodsSelectedForOutlining}. Each selected method is then
+ *       converted back to IR and passed to {@link Outliner#identifyOutlineSites(IRCode,
+ *       DexEncodedMethod)}, which then stores concrete outlining candidates in {@link
+ *       Outliner#outlineSites}.
+ *   <li>Third, {@link Outliner#buildOutlinerClass(DexType)} is called to construct the <em>outline
+ *       support class</em> containing a static helper method for each outline candidate that occurs
+ *       frequently enough. Each selected method is then converted to IR, passed to {@link
+ *       Outliner#applyOutliningCandidate(IRCode, DexEncodedMethod)} to perform the outlining, and
+ *       converted back to the output format (DEX or CF).
+ * </ul>
+ */
 public class Outliner {
 
   private final InternalOptions options;
-  private final Map<Outline, List<DexEncodedMethod>> candidates = new HashMap<>();
-  private final Map<Outline, DexMethod> generatedOutlines = new HashMap<>();
+  /** Result of first step (see {@link Outliner#identifyCandidateMethods()}. */
+  private final List<List<DexEncodedMethod>> candidateMethodLists = new ArrayList<>();
+  /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
   private final Set<DexEncodedMethod> methodsSelectedForOutlining = Sets.newIdentityHashSet();
+  /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
+  private final Map<Outline, List<DexEncodedMethod>> outlineSites = new HashMap<>();
+  /** Result of third step (see {@link Outliner#buildOutlinerClass(DexType)}. */
+  private final Map<Outline, DexMethod> generatedOutlines = new HashMap<>();
 
   static final int MAX_IN_SIZE = 5;  // Avoid using ranged calls for outlined code.
 
@@ -652,16 +682,42 @@
 
   // Collect outlining candidates with the methods that can use them.
   // TODO(sgjesse): This does not take several usages in the same method into account.
-  private class OutlineIdentifier extends OutlineSpotter {
+  private class OutlineMethodIdentifier extends OutlineSpotter {
 
-    OutlineIdentifier(DexEncodedMethod method, BasicBlock block) {
+    private final Map<Outline, List<DexEncodedMethod>> candidateMap;
+
+    OutlineMethodIdentifier(
+        DexEncodedMethod method,
+        BasicBlock block,
+        Map<Outline, List<DexEncodedMethod>> candidateMap) {
+      super(method, block);
+      this.candidateMap = candidateMap;
+    }
+
+    @Override
+    protected void handle(int start, int end, Outline outline) {
+      synchronized (candidateMap) {
+        candidateMap.computeIfAbsent(outline, this::addOutlineMethodList).add(method);
+      }
+    }
+
+    private List<DexEncodedMethod> addOutlineMethodList(Outline outline) {
+      List<DexEncodedMethod> result = new ArrayList<>();
+      candidateMethodLists.add(result);
+      return result;
+    }
+  }
+
+  private class OutlineSiteIdentifier extends OutlineSpotter {
+
+    OutlineSiteIdentifier(DexEncodedMethod method, BasicBlock block) {
       super(method, block);
     }
 
     @Override
     protected void handle(int start, int end, Outline outline) {
-      synchronized (candidates) {
-        candidates.computeIfAbsent(outline, k -> new ArrayList<>()).add(method);
+      synchronized (outlineSites) {
+        outlineSites.computeIfAbsent(outline, k -> new ArrayList<>()).add(method);
       }
     }
   }
@@ -685,9 +741,9 @@
 
     @Override
     protected void handle(int start, int end, Outline outline) {
-      if (candidates.containsKey(outline)) {
-        DexMethod m = generatedOutlines.get(outline);
-        assert m != null;
+      DexMethod m = generatedOutlines.get(outline);
+      if (m != null) {
+        assert removeMethodFromOutlineList(outline);
         List<Value> in = new ArrayList<>();
         Value returnValue = null;
         argumentsMapIndex = 0;
@@ -748,6 +804,14 @@
         }
       }
     }
+
+    /** When assertions are enabled, remove method from the outline's list. */
+    private boolean removeMethodFromOutlineList(Outline outline) {
+      synchronized (outlineSites) {
+        assert outlineSites.get(outline).remove(method);
+      }
+      return true;
+    }
   }
 
   public Outliner(AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -756,26 +820,37 @@
     this.options = options;
   }
 
-  public void identifyCandidates(IRCode code, DexEncodedMethod method) {
+  public BiConsumer<IRCode, DexEncodedMethod> identifyCandidateMethods() {
+    // Since optimizations may change the map identity of Outline objects (e.g. by setting the
+    // out-value of invokes to null), this map must not be used except for identifying methods
+    // potentially relevant to outlining. OutlineMethodIdentifier will add method lists to
+    // candidateMethodLists whenever it adds an entry to candidateMap.
+    Map<Outline, List<DexEncodedMethod>> candidateMap = new HashMap<>();
+    assert candidateMethodLists.isEmpty();
+    return (code, method) -> {
+      assert !(method.getCode() instanceof OutlineCode);
+      for (BasicBlock block : code.blocks) {
+        new OutlineMethodIdentifier(method, block, candidateMap).process();
+      }
+    };
+  }
+
+  public void identifyOutlineSites(IRCode code, DexEncodedMethod method) {
     assert !(method.getCode() instanceof OutlineCode);
     for (BasicBlock block : code.blocks) {
-      new OutlineIdentifier(method, block).process();
+      new OutlineSiteIdentifier(method, block).process();
     }
   }
 
   public boolean selectMethodsForOutlining() {
     assert methodsSelectedForOutlining.size() == 0;
-    List<Outline> toRemove = new ArrayList<>();
-    for (Entry<Outline, List<DexEncodedMethod>> entry : candidates.entrySet()) {
-      if (entry.getValue().size() < options.outline.threshold) {
-        toRemove.add(entry.getKey());
-      } else {
-        methodsSelectedForOutlining.addAll(entry.getValue());
+    assert outlineSites.size() == 0;
+    for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
+      if (outlineMethods.size() >= options.outline.threshold) {
+        methodsSelectedForOutlining.addAll(outlineMethods);
       }
     }
-    for (Outline outline : toRemove) {
-      candidates.remove(outline);
-    }
+    candidateMethodLists.clear();
     return methodsSelectedForOutlining.size() > 0;
   }
 
@@ -783,6 +858,95 @@
     return methodsSelectedForOutlining;
   }
 
+  public DexProgramClass buildOutlinerClass(DexType type) {
+    // Build the outlined methods.
+    // By now the candidates are the actual selected outlines. Name the generated methods in a
+    // consistent order, to provide deterministic output.
+    List<Outline> outlines = selectOutlines();
+    outlines.sort(Comparator.naturalOrder());
+    DexEncodedMethod[] direct = new DexEncodedMethod[outlines.size()];
+    int count = 0;
+    for (Outline outline : outlines) {
+      MethodAccessFlags methodAccess =
+          MethodAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
+      DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
+      DexMethod method = outline.buildMethod(type, methodName);
+      direct[count] =
+          new DexEncodedMethod(
+              method,
+              methodAccess,
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              new OutlineCode(outline));
+      generatedOutlines.put(outline, method);
+      count++;
+    }
+    // No need to sort the direct methods as they are generated in sorted order.
+
+    // Build the outliner class.
+    DexType superType = dexItemFactory.createType("Ljava/lang/Object;");
+    DexTypeList interfaces = DexTypeList.empty();
+    DexString sourceFile = dexItemFactory.createString("outline");
+    ClassAccessFlags accessFlags = ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC);
+    DexProgramClass clazz =
+        new DexProgramClass(
+            type,
+            null,
+            new SynthesizedOrigin("outlining", getClass()),
+            accessFlags,
+            superType,
+            interfaces,
+            sourceFile,
+            null,
+            Collections.emptyList(),
+            // TODO: Build dex annotations structure.
+            DexAnnotationSet.empty(),
+            DexEncodedField.EMPTY_ARRAY, // Static fields.
+            DexEncodedField.EMPTY_ARRAY, // Instance fields.
+            direct,
+            DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
+            options.itemFactory.getSkipNameValidationForTesting());
+    if (options.isGeneratingClassFiles()) {
+      // Don't set class file version below 50.0 (JDK release 1.6).
+      clazz.setClassFileVersion(Math.max(50, getClassFileVersion(outlines)));
+    }
+
+    return clazz;
+  }
+
+  private List<Outline> selectOutlines() {
+    assert outlineSites.size() > 0;
+    assert candidateMethodLists.isEmpty();
+    List<Outline> result = new ArrayList<>();
+    for (Entry<Outline, List<DexEncodedMethod>> entry : outlineSites.entrySet()) {
+      if (entry.getValue().size() >= options.outline.threshold) {
+        result.add(entry.getKey());
+      }
+    }
+    return result;
+  }
+
+  private int getClassFileVersion(List<Outline> outlines) {
+    assert options.isGeneratingClassFiles();
+    int classFileVersion = -1;
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    for (Outline outline : outlines) {
+      List<DexEncodedMethod> methods = outlineSites.get(outline);
+      for (DexEncodedMethod method : methods) {
+        DexType holder = method.method.holder;
+        if (seen.add(holder)) {
+          DexProgramClass programClass = appInfo.definitionFor(holder).asProgramClass();
+          assert programClass != null : "Attempt to outline from library class";
+          assert programClass.originatesFromClassResource()
+              : "Attempt to outline from non-classfile input to classfile output";
+          classFileVersion = Math.max(classFileVersion, programClass.getClassFileVersion());
+        }
+      }
+    }
+    return classFileVersion;
+  }
+
   public void applyOutliningCandidate(IRCode code, DexEncodedMethod method) {
     assert !(method.getCode() instanceof OutlineCode);
     ListIterator<BasicBlock> blocksIterator = code.blocks.listIterator();
@@ -794,6 +958,13 @@
     }
   }
 
+  public boolean checkAllOutlineSitesFoundAgain() {
+    for (Outline outline : generatedOutlines.keySet()) {
+      assert outlineSites.get(outline).isEmpty() : outlineSites.get(outline);
+    }
+    return true;
+  }
+
   static public void noProcessing(IRCode code, DexEncodedMethod method) {
     // No operation.
   }
@@ -1004,9 +1175,8 @@
     }
 
     @Override
-    public IRCode buildIR(DexEncodedMethod encodedMethod,
-        AppInfo appInfo, InternalOptions options, Origin origin)
-        throws ApiLevelException {
+    public IRCode buildIR(
+        DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
       OutlineSourceCode source = new OutlineSourceCode(outline);
       IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
       return builder.build();
@@ -1037,81 +1207,4 @@
       return null;
     }
   }
-
-
-  public DexProgramClass buildOutlinerClass(DexType type) {
-    if (candidates.size() == 0) {
-      return null;
-    }
-
-    // Build the outlined methods.
-    DexEncodedMethod[] direct = new DexEncodedMethod[candidates.size()];
-    int count = 0;
-
-    // By now the candidates are the actual selected outlines. Name the generated methods in a
-    // consistent order, to provide deterministic output.
-    List<Outline> outlines = new ArrayList<>(candidates.keySet());
-    outlines.sort(Comparator.naturalOrder());
-    for (Outline outline : outlines) {
-      MethodAccessFlags methodAccess =
-          MethodAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
-      DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
-      DexMethod method = outline.buildMethod(type, methodName);
-      direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(),
-          ParameterAnnotationsList.empty(), new OutlineCode(outline));
-      generatedOutlines.put(outline, method);
-      count++;
-    }
-    // No need to sort the direct methods as they are generated in sorted order.
-
-    // Build the outliner class.
-    DexType superType = dexItemFactory.createType("Ljava/lang/Object;");
-    DexTypeList interfaces = DexTypeList.empty();
-    DexString sourceFile = dexItemFactory.createString("outline");
-    ClassAccessFlags accessFlags = ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC);
-    DexProgramClass clazz =
-        new DexProgramClass(
-            type,
-            null,
-            new SynthesizedOrigin("outlining", getClass()),
-            accessFlags,
-            superType,
-            interfaces,
-            sourceFile,
-            null,
-            Collections.emptyList(),
-            // TODO: Build dex annotations structure.
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY, // Static fields.
-            DexEncodedField.EMPTY_ARRAY, // Instance fields.
-            direct,
-            DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
-            options.itemFactory.getSkipNameValidationForTesting());
-    if (options.isGeneratingClassFiles()) {
-      // Don't set class file version below 50.0 (JDK release 1.6).
-      clazz.setClassFileVersion(Math.max(50, getClassFileVersion()));
-    }
-
-    return clazz;
-  }
-
-  private int getClassFileVersion() {
-    assert options.isGeneratingClassFiles();
-    int classFileVersion = -1;
-    Set<DexType> seen = Sets.newIdentityHashSet();
-    for (List<DexEncodedMethod> methods : candidates.values()) {
-      for (DexEncodedMethod method : methods) {
-        DexType holder = method.method.holder;
-        if (seen.add(holder)) {
-          DexProgramClass programClass = appInfo.definitionFor(holder).asProgramClass();
-          assert programClass != null : "Attempt to outline from library class";
-          assert programClass.originatesFromClassResource()
-              : "Attempt to outline from non-classfile input to classfile output";
-          classFileVersion = Math.max(classFileVersion, programClass.getClassFileVersion());
-        }
-      }
-    }
-    return classFileVersion;
-  }
 }
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 1284671..a586355 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,7 +3,6 @@
 // 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,7 +74,7 @@
     intArrayType = appInfo.dexItemFactory.createType("[I");
   }
 
-  public AppInfoWithLiveness run() throws ApiLevelException {
+  public AppInfoWithLiveness run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       processClasses(clazz);
     }
@@ -85,7 +84,7 @@
     return appInfo;
   }
 
-  private void processClasses(DexProgramClass clazz) throws ApiLevelException {
+  private void processClasses(DexProgramClass clazz) {
     // 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/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 7d13b1a..9ff44eb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
-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;
@@ -40,7 +39,7 @@
   private static final Map<DexField, Integer> NO_MAPPING = new IdentityHashMap<>();
 
   public interface InlinerAction {
-    void inline(Map<InvokeMethodWithReceiver, InliningInfo> methods) throws ApiLevelException;
+    void inline(Map<InvokeMethodWithReceiver, InliningInfo> methods);
   }
 
   public ClassInliner(DexItemFactory factory) {
@@ -110,7 +109,7 @@
       DexEncodedMethod method,
       IRCode code,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
-      InlinerAction inliner) throws ApiLevelException {
+      InlinerAction inliner) {
 
     // Collect all the new-instance instructions in the code before inlining.
     List<NewInstance> newInstances = Streams.stream(code.instructionIterator())
@@ -246,8 +245,8 @@
     }
   }
 
-  private void inlineAllCalls(InlinerAction inliner,
-      Map<InvokeMethodWithReceiver, InliningInfo> methodCalls) throws ApiLevelException {
+  private void inlineAllCalls(
+      InlinerAction inliner, Map<InvokeMethodWithReceiver, InliningInfo> methodCalls) {
     if (!methodCalls.isEmpty()) {
       inliner.inline(methodCalls);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 466117f..d1b56f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.lambda;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -187,9 +186,13 @@
     }
   }
 
-  public final void applyLambdaClassMapping(DexApplication app,
-      IRConverter converter, OptimizationFeedback feedback, Builder<?> builder,
-      ExecutorService executorService) throws ExecutionException, ApiLevelException {
+  public final void applyLambdaClassMapping(
+      DexApplication app,
+      IRConverter converter,
+      OptimizationFeedback feedback,
+      Builder<?> builder,
+      ExecutorService executorService)
+      throws ExecutionException {
     if (lambdas.isEmpty()) {
       return;
     }
@@ -294,8 +297,7 @@
     }
   }
 
-  private void rewriteLambdaReferences(
-      IRConverter converter, OptimizationFeedback feedback) throws ApiLevelException {
+  private void rewriteLambdaReferences(IRConverter converter, OptimizationFeedback feedback) {
     List<DexEncodedMethod> methods =
         methodsToReprocess
             .stream()
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 2f055ce..bb27c08 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
@@ -390,14 +390,10 @@
         // Compute the final change in locals and insert it before nextInstruction.
         boolean localsChanged = !ending.isEmpty() || !starting.isEmpty();
         if (localsChanged) {
-          boolean skipChange =
-              nextInstruction == nextInstruction.getBlock().exit() && nextInstruction.isGoto();
-          if (!skipChange) {
-            DebugLocalsChange change = createLocalsChange(ending, starting);
-            if (change != null) {
-              // Insert the DebugLocalsChange instruction before nextInstruction.
-              instructionIterator.add(change);
-            }
+          DebugLocalsChange change = createLocalsChange(ending, starting);
+          if (change != null) {
+            // Insert the DebugLocalsChange instruction before nextInstruction.
+            instructionIterator.add(change);
           }
           // Create new maps for the next DebugLocalsChange instruction.
           ending = new Int2ReferenceOpenHashMap<>();
@@ -636,13 +632,21 @@
       LiveIntervals intervals = current.getLiveIntervals();
       assert intervals.getRegisterLimit() == Constants.U16BIT_MAX;
       boolean canUseArgumentRegister = true;
+      boolean couldUseArgumentRegister = true;
       for (LiveIntervals child : intervals.getSplitChildren()) {
-        if (child.getRegisterLimit() < highestUsedRegister()) {
-          canUseArgumentRegister = false;
-          break;
+        int registerConstraint = child.getRegisterLimit();
+        if (registerConstraint < Constants.U16BIT_MAX) {
+          couldUseArgumentRegister = false;
+
+          if (registerConstraint < highestUsedRegister()) {
+            canUseArgumentRegister = false;
+            break;
+          }
         }
       }
-      if (canUseArgumentRegister) {
+      if (canUseArgumentRegister && !couldUseArgumentRegister) {
+        // Only return true if there is a constrained use where it turns out that we can use the
+        // original argument register. This way we will not unnecessarily redo move insertion.
         argumentRegisterUnsplit = true;
         for (LiveIntervals child : intervals.getSplitChildren()) {
           child.clearRegisterAssignment();
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 f8134f4..a5807c3 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,7 +4,6 @@
 
 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.AppInfo;
 import com.android.tools.r8.graph.Code;
@@ -40,9 +39,7 @@
 
   @Override
   public final IRCode buildIR(
-      DexEncodedMethod encodedMethod, AppInfo appInfo,
-      InternalOptions options, Origin origin)
-      throws ApiLevelException {
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
     return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 03206b5..80a439a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -6,7 +6,6 @@
 
 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;
@@ -18,9 +17,9 @@
 import com.android.tools.r8.ir.code.ValueType;
 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;
 import java.util.function.Predicate;
 
 public abstract class SyntheticSourceCode implements SourceCode {
@@ -43,7 +42,7 @@
   private Value[] paramValues;
 
   // Instruction constructors
-  private List<ThrowingConsumer<IRBuilder, ApiLevelException>> constructors = new ArrayList<>();
+  private List<Consumer<IRBuilder>> constructors = new ArrayList<>();
   private List<Predicate<IRBuilder>> traceEvents = new ArrayList<>();
 
   protected SyntheticSourceCode(DexType receiver, DexProto proto) {
@@ -63,12 +62,11 @@
     }
   }
 
-  protected final void add(ThrowingConsumer<IRBuilder, ApiLevelException> constructor) {
+  protected final void add(Consumer<IRBuilder> constructor) {
     add(constructor, doesNotEndBlock);
   }
 
-  protected final void add(
-      ThrowingConsumer<IRBuilder, ApiLevelException> constructor, Predicate<IRBuilder> traceEvent) {
+  protected final void add(Consumer<IRBuilder> constructor, Predicate<IRBuilder> traceEvent) {
     constructors.add(constructor);
     traceEvents.add(traceEvent);
   }
@@ -189,8 +187,7 @@
 
   @Override
   public final void buildInstruction(
-      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
-      throws ApiLevelException {
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
     constructors.get(instructionIndex).accept(builder);
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index a87de4b..27b2da7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
@@ -834,7 +833,7 @@
       return;
     }
     if (Log.ENABLED) {
-      Log.verbose(getClass(), "Register new instatiation of `%s`.", clazz);
+      Log.verbose(getClass(), "Register new instantiation of `%s`.", clazz);
     }
     workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(method)));
   }
@@ -1343,13 +1342,8 @@
   }
 
   private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
-    try {
-      IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
-      code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
-    } catch (ApiLevelException e) {
-      // Ignore this exception here. It will be hit again further in the pipeline when
-      // generating code.
-    }
+    IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
+    code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
   }
 
   private void handleProguardReflectiveBehavior(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index b007f83..568ad3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -83,6 +83,10 @@
     return nameList == null ? Collections::emptyIterator : nameList.getWildcards();
   }
 
+  protected ProguardClassNameList materialize() {
+    return this;
+  }
+
   public abstract void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer);
 
   private static class EmptyClassNameList extends ProguardClassNameList {
@@ -149,6 +153,11 @@
     }
 
     @Override
+    protected SingleClassNameList materialize() {
+      return new SingleClassNameList(className.materialize());
+    }
+
+    @Override
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
       consumer.accept(className);
     }
@@ -202,6 +211,12 @@
     }
 
     @Override
+    protected PositiveClassNameList materialize() {
+      return new PositiveClassNameList(
+          classNames.stream().map(ProguardTypeMatcher::materialize).collect(Collectors.toList()));
+    }
+
+    @Override
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
       classNames.forEach(consumer);
     }
@@ -260,6 +275,13 @@
     }
 
     @Override
+    protected ProguardClassNameList materialize() {
+      Builder builder = builder();
+      classNames.forEach((m, negated) -> builder.addClassName(negated, m.materialize()));
+      return builder.build();
+    }
+
+    @Override
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
       classNames.object2BooleanEntrySet().forEach(entry -> consumer.accept(entry.getKey()));
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8eb8e9c..4a25e42 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -717,6 +717,11 @@
       }
     }
 
+    private StringDiagnostic parseClassTypeUnexpected(Origin origin, TextPosition start) {
+      return new StringDiagnostic(
+          "Expected [!]interface|@interface|class|enum", origin, getPosition(start));
+    }
+
     private void parseClassType(
         ProguardClassSpecification.Builder builder) throws ProguardRuleParserException {
       skipWhitespace();
@@ -724,17 +729,21 @@
       if (acceptChar('!')) {
         builder.setClassTypeNegated(true);
       }
-      if (acceptString("interface")) {
+      if (acceptChar('@')) {
+        skipWhitespace();
+        if (acceptString("interface")) {
+          builder.setClassType(ProguardClassType.ANNOTATION_INTERFACE);
+        } else {
+          throw reporter.fatalError(parseClassTypeUnexpected(origin, start));
+        }
+      } else if (acceptString("interface")) {
         builder.setClassType(ProguardClassType.INTERFACE);
-      } else if (acceptString("@interface")) {
-        builder.setClassType(ProguardClassType.ANNOTATION_INTERFACE);
       } else if (acceptString("class")) {
         builder.setClassType(ProguardClassType.CLASS);
       } else if (acceptString("enum")) {
         builder.setClassType(ProguardClassType.ENUM);
       } else {
-        throw reporter.fatalError(new StringDiagnostic(
-            "Expected [!]interface|@interface|class|enum", origin, getPosition(start)));
+        throw reporter.fatalError(parseClassTypeUnexpected(origin, start));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 2e8cccc..47f26ed 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,6 +5,7 @@
 
 import com.google.common.collect.Iterables;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class ProguardIfRule extends ProguardKeepRule {
 
@@ -51,6 +52,24 @@
   }
 
   @Override
+  protected ProguardIfRule materialize() {
+    return new ProguardIfRule(
+        getClassAnnotation(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        getMemberRules() == null ? null :
+            getMemberRules().stream()
+                .map(ProguardMemberRule::materialize).collect(Collectors.toList()),
+        subsequentRule.materialize());
+  }
+
+  @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardIfRule)) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 8756f1a..6651dae 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -5,6 +5,7 @@
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 public class ProguardKeepRule extends ProguardConfigurationRule {
 
@@ -68,6 +69,24 @@
     return modifiers;
   }
 
+  protected ProguardKeepRule materialize() {
+    return new ProguardKeepRule(
+        getClassAnnotation(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames() == null ? null : getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        getMemberRules() == null ? null :
+            getMemberRules().stream()
+                .map(ProguardMemberRule::materialize).collect(Collectors.toList()),
+        getType(),
+        getModifiers());
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardKeepRule)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index c3454a7..39737ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -12,6 +12,7 @@
 import com.google.common.collect.Iterables;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
 public class ProguardMemberRule {
@@ -278,6 +279,20 @@
     );
   }
 
+  ProguardMemberRule materialize() {
+    return new ProguardMemberRule(
+        getAnnotation() == null ? null : getAnnotation().materialize(),
+        getAccessFlags(),
+        getNegatedAccessFlags(),
+        getRuleType(),
+        getType() == null ? null : getType().materialize(),
+        getName() == null ? null : getName().materialize(),
+        getArguments() == null ? null :
+            getArguments().stream()
+                .map(ProguardTypeMatcher::materialize).collect(Collectors.toList()),
+        getReturnValue());
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardMemberRule)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
index 4adb948..c217d45 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
@@ -9,6 +9,7 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public abstract class ProguardNameMatcher {
 
@@ -96,11 +97,19 @@
     return nameMatcher == null ? Collections::emptyIterator : nameMatcher.getWildcards();
   }
 
+  protected ProguardNameMatcher materialize() {
+    return this;
+  }
+
   private static class MatchAllNames extends ProguardNameMatcher {
     private final ProguardWildcard wildcard;
 
     MatchAllNames() {
-      this.wildcard = new Pattern("*");
+      this(new Pattern("*"));
+    }
+
+    private MatchAllNames(ProguardWildcard wildcard) {
+      this.wildcard = wildcard;
     }
 
     @Override
@@ -115,6 +124,11 @@
     }
 
     @Override
+    protected MatchAllNames materialize() {
+      return new MatchAllNames(wildcard.materialize());
+    }
+
+    @Override
     public String toString() {
       return "*";
     }
@@ -145,6 +159,15 @@
     }
 
     @Override
+    protected MatchNamePattern materialize() {
+      List<ProguardWildcard> materializedWildcards =
+          wildcards.stream().map(ProguardWildcard::materialize).collect(Collectors.toList());
+      IdentifierPatternWithWildcards identifierPatternWithMaterializedWildcards =
+          new IdentifierPatternWithWildcards(pattern, materializedWildcards);
+      return new MatchNamePattern(identifierPatternWithMaterializedWildcards);
+    }
+
+    @Override
     public String toString() {
       return pattern;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index 0d5016b..34ee554 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -13,6 +13,7 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public abstract class ProguardTypeMatcher {
 
@@ -40,6 +41,10 @@
     return typeMatcher == null ? Collections::emptyIterator : typeMatcher.getWildcards();
   }
 
+  protected ProguardTypeMatcher materialize() {
+    return this;
+  }
+
   @Override
   public abstract String toString();
 
@@ -99,7 +104,11 @@
     private final ProguardWildcard wildcard;
 
     MatchAllTypes() {
-      this.wildcard = new Pattern(MATCH_ALL_PATTERN);
+      this(new Pattern(MATCH_ALL_PATTERN));
+    }
+
+    private MatchAllTypes(ProguardWildcard wildcard) {
+      this.wildcard = wildcard;
     }
 
     @Override
@@ -114,6 +123,11 @@
     }
 
     @Override
+    protected MatchAllTypes materialize() {
+      return new MatchAllTypes(wildcard.materialize());
+    }
+
+    @Override
     public String toString() {
       return MATCH_ALL_PATTERN;
     }
@@ -170,9 +184,13 @@
     private final ProguardWildcard wildcard;
 
     private MatchClassTypes(String pattern) {
+      this(pattern, new Pattern(pattern));
+    }
+
+    private MatchClassTypes(String pattern, ProguardWildcard wildcard) {
       assert pattern.equals(LEGACY_MATCH_CLASS_PATTERN) || pattern.equals(MATCH_CLASS_PATTERN);
       this.pattern = pattern;
-      this.wildcard = new Pattern(pattern);
+      this.wildcard = wildcard;
     }
 
     @Override
@@ -190,6 +208,11 @@
     }
 
     @Override
+    protected MatchClassTypes materialize() {
+      return new MatchClassTypes(pattern, wildcard.materialize());
+    }
+
+    @Override
     public String toString() {
       return pattern;
     }
@@ -212,7 +235,11 @@
     private final ProguardWildcard wildcard;
 
     MatchBasicTypes() {
-      this.wildcard = new Pattern(MATCH_BASIC_PATTERN);
+      this(new Pattern(MATCH_BASIC_PATTERN));
+    }
+
+    private MatchBasicTypes(ProguardWildcard wildcard) {
+      this.wildcard = wildcard;
     }
 
     @Override
@@ -230,6 +257,11 @@
     }
 
     @Override
+    protected MatchBasicTypes materialize() {
+      return new MatchBasicTypes(wildcard.materialize());
+    }
+
+    @Override
     public String toString() {
       return MATCH_BASIC_PATTERN;
     }
@@ -311,6 +343,15 @@
       return wildcards;
     }
 
+    @Override
+    protected MatchTypePattern materialize() {
+      List<ProguardWildcard> materializedWildcards =
+          wildcards.stream().map(ProguardWildcard::materialize).collect(Collectors.toList());
+      IdentifierPatternWithWildcards identifierPatternWithMaterializedWildcards =
+          new IdentifierPatternWithWildcards(pattern, materializedWildcards);
+      return new MatchTypePattern(identifierPatternWithMaterializedWildcards, kind);
+    }
+
     private static boolean matchClassOrTypeNameImpl(
         String pattern, int patternIndex,
         String name, int nameIndex,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java b/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
index 84151e8..b6cb8e5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
@@ -10,6 +10,7 @@
   abstract void setCaptured(String captured);
   abstract void clearCaptured();
   abstract String getCaptured();
+  abstract ProguardWildcard materialize();
 
   boolean isPattern() {
     return false;
@@ -29,7 +30,6 @@
 
   static class Pattern extends ProguardWildcard {
     final String pattern;
-    // TODO(b/79486261): Having captured part here makes back-reference handling not thread-safe.
     private String captured = null;
 
     Pattern(String pattern) {
@@ -37,21 +37,31 @@
     }
 
     @Override
-    void setCaptured(String captured) {
+    synchronized void setCaptured(String captured) {
       this.captured = captured;
     }
 
     @Override
-    void clearCaptured() {
+    synchronized void clearCaptured() {
       captured = null;
     }
 
     @Override
-    String getCaptured() {
+    synchronized String getCaptured() {
       return captured;
     }
 
     @Override
+    Pattern materialize() {
+      if (captured == null) {
+        return this;
+      }
+      Pattern copy = new Pattern(pattern);
+      copy.setCaptured(captured);
+      return copy;
+    }
+
+    @Override
     boolean isPattern() {
       return true;
     }
@@ -96,6 +106,16 @@
     }
 
     @Override
+    BackReference materialize() {
+      if (reference == null || reference.getCaptured() == null) {
+        return this;
+      }
+      BackReference copy = new BackReference(referenceIndex);
+      copy.setReference(reference.materialize());
+      return copy;
+    }
+
+    @Override
     boolean isBackReference() {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 6a8adfa..700e146 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -348,10 +348,9 @@
             if (ifRule.getClassNames().matches(currentLiveType)) {
               Collection<ProguardMemberRule> memberKeepRules = ifRule.getMemberRules();
               if (memberKeepRules.isEmpty()) {
-                runPerRule(executorService, futures, ifRule.subsequentRule, ifRule);
-                // TODO(b/79486261): remove this barrier per rule.
-                ThreadUtils.awaitFutures(futures);
-                futures.clear();
+                ProguardIfRule materializedRule = ifRule.materialize();
+                runPerRule(
+                    executorService, futures, materializedRule.subsequentRule, materializedRule);
                 // No member rule to satisfy. Move on to the next live type.
                 continue;
               }
@@ -391,17 +390,15 @@
                   }
                 }
                 if (satisfied) {
-                  runPerRule(executorService, futures, ifRule.subsequentRule, ifRule);
-                  // TODO(b/79486261): remove this barrier per rule.
-                  ThreadUtils.awaitFutures(futures);
-                  futures.clear();
+                  ProguardIfRule materializedRule = ifRule.materialize();
+                  runPerRule(
+                      executorService, futures, materializedRule.subsequentRule, materializedRule);
                 }
               }
             }
           }
         }
-        // TODO(b/79486261): This is the best place to fully utilize available threads.
-        // ThreadUtils.awaitFutures(futures);
+        ThreadUtils.awaitFutures(futures);
       }
     } finally {
       application.timing.end();
diff --git a/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt b/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt
new file mode 100644
index 0000000..4a94d41
--- /dev/null
+++ b/src/test/examples/multidex/main-dex-rules-whyareyoukeeping.txt
@@ -0,0 +1,30 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class * extends **.Instrumentation {
+  <init>();
+}
+-keep public class * extends **.Application {
+  <init>();
+  void attachBaseContext(*.Context);
+}
+-keep public class * extends **.Activity {
+  <init>();
+}
+-keep public class * extends **.Service {
+  <init>();
+}
+-keep public class * extends **.ContentProvider {
+ <init>();
+}
+-keep public class * extends **.BroadcastReceiver {
+ <init>();
+}
+-keep public class * extends **.BackupAgent {
+ <init>();
+}
+
+-whyareyoukeeping class **
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 5ec1bce..cc8633e4 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -432,7 +432,7 @@
     assertEquals(0, javaResult.exitCode);
 
     AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
-        // Disable inlining to avoid the (short) tested method from being inlined then removed.
+        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
         internalOptions -> internalOptions.enableInlining = false);
 
     // Run processed (output) program on ART
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 35a7613..60df7d2 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -109,7 +109,7 @@
     assertEquals(0, javaResult.exitCode);
 
     AndroidApp optimizedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
-        // Disable inlining to avoid the (short) tested method from being inlined then removed.
+        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
         internalOptions -> internalOptions.enableInlining = false);
 
     // Run optimized (output) program on ART
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
index ad6a72b..d6e0542 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -14,8 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import java.nio.file.Path;
@@ -36,18 +33,9 @@
     ProcessResult run1 = ToolHelper.runJava(out1, CLASS.getCanonicalName());
     assertEquals(runInput.toString(), run1.toString());
     Path out2 = temp.getRoot().toPath().resolve("out2.zip");
-    boolean invalidDebugInfo = false;
-    try {
-      build(builder -> builder.addProgramFiles(out1), new ClassFileConsumer.ArchiveConsumer(out2));
-    } catch (CompilationError e) {
-      invalidDebugInfo = e.getCause() instanceof InvalidDebugInfoException;
-    }
-    // TODO(b/77522100): Change to assertFalse when fixed.
-    assertTrue(invalidDebugInfo);
-    if (!invalidDebugInfo) {
-      ProcessResult run2 = ToolHelper.runJava(out2, CLASS.getCanonicalName());
-      assertEquals(runInput.toString(), run2.toString());
-    }
+    build(builder -> builder.addProgramFiles(out1), new ClassFileConsumer.ArchiveConsumer(out2));
+    ProcessResult run2 = ToolHelper.runJava(out2, CLASS.getCanonicalName());
+    assertEquals(runInput.toString(), run2.toString());
   }
 
   private void build(ThrowingConsumer<Builder, Exception> input, ProgramConsumer consumer)
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 967b33e..aefe665 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -7,9 +7,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
@@ -124,15 +122,11 @@
             .method(
                 new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = subtract.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
-      analysis.forEach((v, l) -> {
-        assertEither(l, PRIMITIVE, NULL, TOP);
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = subtract.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
+    analysis.forEach((v, l) -> {
+      assertEither(l, PRIMITIVE, NULL, TOP);
+    });
   }
 
   // A couple branches, along with some recursive calls.
@@ -142,15 +136,11 @@
         inspector.clazz("Test")
             .method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
             .getMethod();
-    try {
-      IRCode irCode = fib.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
-      analysis.forEach((v, l) -> {
-        assertEither(l, PRIMITIVE, NULL);
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = fib.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
+    analysis.forEach((v, l) -> {
+      assertEither(l, PRIMITIVE, NULL);
+    });
   }
 
   // fill-array-data
@@ -160,32 +150,28 @@
         inspector.clazz("Test")
             .method(new MethodSignature("test1", "int[]", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = test1.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
-      Value array = null;
-      InstructionIterator iterator = irCode.instructionIterator();
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        if (instruction instanceof NewArrayEmpty) {
-          array = instruction.outValue();
-          break;
-        }
+    IRCode irCode = test1.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
+    Value array = null;
+    InstructionIterator iterator = irCode.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (instruction instanceof NewArrayEmpty) {
+        array = instruction.outValue();
+        break;
       }
-      assertNotNull(array);
-      final Value finalArray = array;
-      analysis.forEach((v, l) -> {
-        if (v == finalArray) {
-          assertTrue(l.isArrayTypeLatticeElement());
-          ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-          assertTrue(lattice.getArrayType().isPrimitiveArrayType());
-          assertEquals(1, lattice.getNesting());
-          assertFalse(lattice.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
     }
+    assertNotNull(array);
+    final Value finalArray = array;
+    analysis.forEach((v, l) -> {
+      if (v == finalArray) {
+        assertTrue(l.isArrayTypeLatticeElement());
+        ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+        assertTrue(lattice.getArrayType().isPrimitiveArrayType());
+        assertEquals(1, lattice.getNesting());
+        assertFalse(lattice.isNullable());
+      }
+    });
   }
 
   // filled-new-array
@@ -195,32 +181,28 @@
         inspector.clazz("Test")
             .method(new MethodSignature("test4", "int[]", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = test4.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
-      Value array = null;
-      InstructionIterator iterator = irCode.instructionIterator();
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        if (instruction instanceof InvokeNewArray) {
-          array = instruction.outValue();
-          break;
-        }
+    IRCode irCode = test4.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
+    Value array = null;
+    InstructionIterator iterator = irCode.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (instruction instanceof InvokeNewArray) {
+        array = instruction.outValue();
+        break;
       }
-      assertNotNull(array);
-      final Value finalArray = array;
-      analysis.forEach((v, l) -> {
-        if (v == finalArray) {
-          assertTrue(l.isArrayTypeLatticeElement());
-          ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-          assertTrue(lattice.getArrayType().isPrimitiveArrayType());
-          assertEquals(1, lattice.getNesting());
-          assertFalse(lattice.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
     }
+    assertNotNull(array);
+    final Value finalArray = array;
+    analysis.forEach((v, l) -> {
+      if (v == finalArray) {
+        assertTrue(l.isArrayTypeLatticeElement());
+        ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+        assertTrue(lattice.getArrayType().isPrimitiveArrayType());
+        assertEquals(1, lattice.getNesting());
+        assertFalse(lattice.isNullable());
+      }
+    });
   }
 
   // Make sure the analysis does not hang.
@@ -230,20 +212,16 @@
         inspector.clazz("Test")
             .method(new MethodSignature("loop2", "void", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = loop2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
-      analysis.forEach((v, l) -> {
-        if (l.isClassTypeLatticeElement()) {
-          ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
-          assertEquals("Ljava/io/PrintStream;", lattice.getClassType().toDescriptorString());
-          // TODO(b/70795205): Can be refined by using control-flow info.
-          assertTrue(l.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = loop2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
+    analysis.forEach((v, l) -> {
+      if (l.isClassTypeLatticeElement()) {
+        ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
+        assertEquals("Ljava/io/PrintStream;", lattice.getClassType().toDescriptorString());
+        // TODO(b/70795205): Can be refined by using control-flow info.
+        assertTrue(l.isNullable());
+      }
+    });
   }
 
   // move-exception
@@ -253,19 +231,15 @@
         inspector.clazz("Test")
             .method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
             .getMethod();
-    try {
-      IRCode irCode = test2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
-      analysis.forEach((v, l) -> {
-        if (l.isClassTypeLatticeElement()) {
-          ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
-          assertEquals("Ljava/lang/Throwable;", lattice.getClassType().toDescriptorString());
-          assertFalse(l.isNullable());
-        }
-      });
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = test2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
+    analysis.forEach((v, l) -> {
+      if (l.isClassTypeLatticeElement()) {
+        ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
+        assertEquals("Ljava/lang/Throwable;", lattice.getClassType().toDescriptorString());
+        assertFalse(l.isNullable());
+      }
+    });
   }
 
   // One very complicated example.
@@ -283,13 +257,9 @@
         ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
         CheckCast.class, new ClassTypeLatticeElement(test, true),
         NewInstance.class, new ClassTypeLatticeElement(test, false));
-    try {
-      IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
-      analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
+    analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
   }
 
   // One more complicated example.
@@ -305,13 +275,9 @@
       ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
       InstanceOf.class, PRIMITIVE,
       StaticGet.class, new ClassTypeLatticeElement(test, true));
-    try {
-      IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
-      TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
-      analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
-    } catch (ApiLevelException e) {
-      fail(e.getMessage());
-    }
+    IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
+    analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
   }
 
   private static void assertEither(TypeLatticeElement actual, TypeLatticeElement... expected) {
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index b50718d..eb6e943 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -28,6 +28,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -86,6 +87,7 @@
     private final List<String> fields = new ArrayList<>();
     private boolean makeInit = false;
     private boolean hasInit = false;
+    private final List<String> clinit = new ArrayList<>();
     private boolean isInterface = false;
     private String access = "public";
 
@@ -217,6 +219,10 @@
       return new MethodSignature(name, returnJavaType, argumentJavaTypes);
     }
 
+    public void addClassInitializer(String... lines) {
+      clinit.addAll(Arrays.asList(lines));
+    }
+
     public FieldSignature addField(String flags, String name, String type, String value) {
       fields.add(
           ".field " + flags + " " + name + " " + type + (value != null ? (" = " + value) : ""));
@@ -262,6 +268,11 @@
       for (String method : methods) {
         builder.append(method).append("\n");
       }
+      if (!clinit.isEmpty()) {
+        builder.append(".method public static <clinit>()V\n");
+        clinit.forEach(line -> builder.append(line).append('\n'));
+        builder.append(".end method\n");
+      }
       return builder.toString();
     }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index 5e41a25..ec18fac 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -8,11 +8,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import org.junit.Test;
 
 public class JumpSubroutineTests extends JasminTestBase {
@@ -22,10 +26,28 @@
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, main);
     assertEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, main);
+    assertEquals(expected, cfFrontendResult);
     String dxArtResult = runOnArtDx(builder, main);
     assertEquals(expected, dxArtResult);
   }
 
+  private String runOnJavaR8CfFrontend(JasminBuilder builder, String main) throws Exception {
+    Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+    builder.writeJar(inputJar, null);
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addProgramFiles(inputJar)
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .build(),
+        options -> options.enableCfFrontend = true);
+    ProcessResult processResult = ToolHelper.runJava(outputJar, main);
+    assertEquals(0, processResult.exitCode);
+    return processResult.stdout;
+  }
+
   private void expectDxFailure(JasminBuilder builder) throws Exception {
     // This expects this dx failure:
     // Uncaught translation error: com.android.dex.util.ExceptionWithContext: returning from
@@ -387,6 +409,8 @@
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, clazz.name);
     assertEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1344,6 +1368,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1392,6 +1418,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
@@ -1441,6 +1469,8 @@
     String artResult = runOnArtD8(builder, clazz.name);
     // The ASM jsr inliner does not get the control-flow dependent ret right in his case.
     assertNotEquals(expected, artResult);
+    String cfFrontendResult = runOnJavaR8CfFrontend(builder, clazz.name);
+    assertNotEquals(expected, cfFrontendResult);
     // This fails with dx.
     expectDxFailure(builder);
   }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index bb2628a..43b4c0f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -15,8 +15,12 @@
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -40,6 +44,23 @@
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   @Test
+  public void traceMainDexList001_whyareyoukeeping() throws Throwable {
+    PrintStream stdout = System.out;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    System.setOut(new PrintStream(baos));
+    doTest(
+        "traceMainDexList001_1",
+        "multidex001",
+        EXAMPLE_BUILD_DIR,
+        Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules-whyareyoukeeping.txt"),
+        Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
+        AndroidApiLevel.I);
+    String output = new String(baos.toByteArray(), Charset.defaultCharset());
+    Assert.assertTrue(output.contains("is live because referenced in keep rule:"));
+    System.setOut(stdout);
+  }
+
+  @Test
   public void traceMainDexList001_1() throws Throwable {
     doTest(
         "traceMainDexList001_1",
@@ -271,10 +292,9 @@
   }
 
   private String mainDexStringToDescriptor(String mainDexString) {
-    final String CLASS_EXTENSION = ".class";
-    Assert.assertTrue(mainDexString.endsWith(CLASS_EXTENSION));
+    Assert.assertTrue(mainDexString.endsWith(FileUtils.CLASS_EXTENSION));
     return DescriptorUtils.getDescriptorFromClassBinaryName(
-        mainDexString.substring(0, mainDexString.length() - CLASS_EXTENSION.length()));
+        mainDexString.substring(0, mainDexString.length() - FileUtils.CLASS_EXTENSION.length()));
   }
 
   private void checkSameMainDexEntry(String reference, String computed) {
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/AnotherClass.java b/src/test/java/com/android/tools/r8/naming/b80083341/AnotherClass.java
new file mode 100644
index 0000000..e612fec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/AnotherClass.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2018, 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.naming.b80083341;
+
+public class AnotherClass {
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
new file mode 100644
index 0000000..1a1ae50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, 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.naming.b80083341;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B80083341 extends TestBase {
+
+  @Ignore("b/80083341")
+  @Test
+  public void test() throws Exception {
+    Class mainClass = TestMain.class;
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        "-keepattributes EnclosingMethod,InnerClasses,Signature",
+        "-repackageclasses",
+        "-keepclassmembers class " + mainClass.getCanonicalName() + " {",
+        "  public void main(...);",
+        "}",
+        "-keep,allowobfuscation class " + mainClass.getCanonicalName() + " {",
+        "}"
+    );
+    AndroidApp app = readClassesAndAndriodJar(ImmutableList.of(
+        mainClass, TestClass.class, AnotherClass.class,
+        PackagePrivateClass.class, PackagePrivateClass.Itf.class, PackagePrivateClass.Impl.class
+    ));
+    AndroidApp processedApp = compileWithR8(app, String.join(System.lineSeparator(), config));
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject mainSubject = inspector.clazz(mainClass);
+    assertThat(mainSubject, isPresent());
+
+    ProcessResult artResult =
+        runOnArtRaw(processedApp, getClassNameFromDescriptor(mainSubject.getFinalDescriptor()));
+    assertEquals(0, artResult.exitCode);
+    assertEquals(-1, artResult.stderr.indexOf("IllegalAccessError"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/PackagePrivateClass.java b/src/test/java/com/android/tools/r8/naming/b80083341/PackagePrivateClass.java
new file mode 100644
index 0000000..62a4181
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/PackagePrivateClass.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2018, 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.naming.b80083341;
+
+final class PackagePrivateClass {
+  private static final boolean flag = false;
+
+  private PackagePrivateClass() {}
+
+  interface Itf<T> {
+    boolean foo();
+  }
+
+  static class Impl<T> implements Itf<T> {
+    private final Object[] objs;
+
+    Impl(int size) {
+      objs = new Object[size];
+    }
+
+    @Override
+    public boolean foo() {
+      return flag;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/TestClass.java b/src/test/java/com/android/tools/r8/naming/b80083341/TestClass.java
new file mode 100644
index 0000000..a5dc174
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/TestClass.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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.naming.b80083341;
+
+public class TestClass {
+  PackagePrivateClass.Impl<AnotherClass> anotherClasses = new PackagePrivateClass.Impl<>(8);
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/TestMain.java b/src/test/java/com/android/tools/r8/naming/b80083341/TestMain.java
new file mode 100644
index 0000000..6be514c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/TestMain.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, 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.naming.b80083341;
+
+public class TestMain {
+  public static void main(String[] args) {
+    new TestClass().anotherClasses.foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
new file mode 100644
index 0000000..9e0429b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class FieldTypeTest extends TestBase {
+
+  @Ignore("b/78788577")
+  @Test
+  public void test_brokenTypeHierarchy() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    // interface Itf
+    ClassBuilder itf = jasminBuilder.addInterface("Itf");
+    MethodSignature foo = itf.addAbstractMethod("foo", ImmutableList.of(), "V");
+    // class Impl /* implements Itf */
+    ClassBuilder impl = jasminBuilder.addClass("Impl");
+    impl.addDefaultConstructor();
+    impl.addVirtualMethod("foo", ImmutableList.of(), "V",
+        ".limit locals 2",
+        ".limit stack 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"" + "foo" + "\"",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "return");
+    impl.addVirtualMethod("toString", ImmutableList.of(), "Ljava/lang/String;",
+        ".limit locals 1",
+        ".limit stack 2",
+        "ldc \"" + impl.name + "\"",
+        "areturn");
+    ClassBuilder client = jasminBuilder.addClass("Client");
+    FieldSignature obj = client.addStaticFinalField("obj", itf.getDescriptor(), null);
+    client.addClassInitializer(
+        ".limit locals 1",
+        ".limit stack 2",
+        "new " + impl.name,
+        "dup",
+        "invokespecial " + impl.name + "/<init>()V",
+        "putstatic " + client.name + "/" + obj.name + " " + itf.getDescriptor(),
+        "return"
+    );
+
+    ClassBuilder mainClass = jasminBuilder.addClass("Main");
+    mainClass.addMainMethod(
+        ".limit locals 2",
+        ".limit stack 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "getstatic " + client.name + "/" + obj.name + " " + itf.getDescriptor(),
+        /*
+        "astore_0",
+        "aload_0",
+        // java.lang.IncompatibleClassChangeError:
+        //     Class Impl does not implement the requested interface Itf
+        "invokeinterface " + itf.name + "/" + foo.name + "()V 1",
+        "aload_0",
+        */
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+        "return"
+    );
+
+    final String mainClassName = mainClass.name;
+    String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+
+    // Run input program on java.
+    Path outputDirectory = temp.newFolder().toPath();
+    jasminBuilder.writeClassFiles(outputDirectory);
+    ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+    assertEquals(0, javaResult.exitCode);
+    assertThat(javaResult.stdout, containsString(impl.name));
+
+    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+        // Disable inlining to avoid the (short) tested method from being inlined and then removed.
+        internalOptions -> internalOptions.enableInlining = false);
+
+    // Run processed (output) program on ART
+    ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+    assertEquals(0, artResult.exitCode);
+    assertThat(artResult.stdout, containsString(impl.name));
+    assertEquals(-1, artResult.stderr.indexOf("DoFieldPut"));
+
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject itfSubject = inspector.clazz(itf.name);
+    assertThat(itfSubject, isPresent());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index dd7465f..f558ee7 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -139,6 +139,8 @@
   private Reporter reporter;
   private KeepingDiagnosticHandler handler;
   private ProguardConfigurationParser parser;
+  private List<String> whiteSpace = ImmutableList.of("", " ", "   ", "\t", " \t", " \t", " \t ", " \t\t \t ");
+
 
   @Before
   public void reset() {
@@ -789,12 +791,10 @@
 
   @Test
   public void parseKeepModifiers() {
-    List<String> ws = ImmutableList.of("", " ", "   ", "\t", " \t", " \t", " \t ", " \t\t \t ");
-
-    for (String before : ws) {
-      for (String after : ws) {
+    for (String before : whiteSpace) {
+      for (String after : whiteSpace) {
         reset();
-        ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
+        parseAndVerifyParserEndsCleanly(ImmutableList.of(
             "-keep"
                 + before + "," + after + "includedescriptorclasses"
                 + before + "," + after + "allowshrinking"
@@ -807,6 +807,16 @@
   }
 
   @Test
+  public void parseKeepAnnotation() {
+    for (String space : whiteSpace) {
+      reset();
+      parseAndVerifyParserEndsCleanly(ImmutableList.of(
+          "-keep @" + space + "interface A"
+      ));
+    }
+  }
+
+  @Test
   public void regress78442725() {
     parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-keep, includedescriptorclasses class in.uncod.android.bypass.Document { *; }",
@@ -1717,6 +1727,24 @@
     verifyWithProguard(proguardConfig);
   }
 
+  @Test
+  public void parse_regress79925760() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-keep public @ interface test.MyAnnotation"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(1, config.getRules().size());
+    ProguardKeepRule rule = (ProguardKeepRule) config.getRules().get(0);
+    assertEquals(ProguardClassType.ANNOTATION_INTERFACE, rule.getClassType());
+
+    verifyWithProguard(proguardConfig);
+  }
+
   private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) {
     parser.parse(createConfigurationForTesting(config));
     verifyParserEndsCleanly();
diff --git a/tools/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index b1bdc6e..4a64693 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -76,7 +76,7 @@
         build=not options.no_build,
         debug=not options.no_debug,
         profile=options.profile,
-        track_memory_to_file=options.track_memory_to_file)
+        track_memory_file=options.track_memory_to_file)
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 571d96d..41340c7 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -203,7 +203,7 @@
       toolhelper.run(options.compiler, args, build=not options.no_build,
                      debug=not options.no_debug,
                      profile=options.profile,
-                     track_memory_to_file=options.track_memory_to_file)
+                     track_memory_file=options.track_memory_to_file)
       if options.print_memoryuse:
         print('{}(MemoryUse): {}'
             .format(options.print_memoryuse,