Merge "Sanitize quasi-trivial phis in re-processing IR code."
diff --git a/.gitignore b/.gitignore
index 51e0c02..cb3eaab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,8 @@
 tools/*/art-6.0.1.tar.gz
 tools/*/art-7.0.0
 tools/*/art-7.0.0.tar.gz
+tools/*/art-8.1.0
+tools/*/art-8.1.0.tar.gz
 tools/*/dalvik
 tools/*/dalvik.tar.gz
 tools/*/dalvik-4.0.4
diff --git a/build.gradle b/build.gradle
index 59e6b9e..5ec2f08 100644
--- a/build.gradle
+++ b/build.gradle
@@ -296,6 +296,7 @@
                 "linux/art-5.1.1",
                 "linux/art-6.0.1",
                 "linux/art-7.0.0",
+                "linux/art-8.1.0",
                 "linux/dalvik",
                 "linux/dalvik-4.0.4",
                 "${osString}/dx",
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 34a3a93..584b4de 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -25,6 +25,7 @@
 
   private final CompilationMode mode;
   private final ProgramConsumer programConsumer;
+  private final StringConsumer mainDexListConsumer;
   private final int minApiLevel;
   private final Reporter reporter;
   private final boolean enableDesugaring;
@@ -33,6 +34,7 @@
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
     programConsumer = null;
+    mainDexListConsumer = null;
     mode = null;
     minApiLevel = 0;
     reporter = new Reporter();
@@ -44,6 +46,7 @@
       AndroidApp app,
       CompilationMode mode,
       ProgramConsumer programConsumer,
+      StringConsumer mainDexListConsumer,
       int minApiLevel,
       Reporter reporter,
       boolean enableDesugaring,
@@ -53,6 +56,7 @@
     assert mode != null;
     this.mode = mode;
     this.programConsumer = programConsumer;
+    this.mainDexListConsumer = mainDexListConsumer;
     this.minApiLevel = minApiLevel;
     this.reporter = reporter;
     this.enableDesugaring = enableDesugaring;
@@ -81,6 +85,13 @@
     return programConsumer;
   }
 
+  /**
+   * Get the main dex list consumer that will receive the final complete main dex list.
+   */
+  public StringConsumer getMainDexListConsumer() {
+    return mainDexListConsumer;
+  }
+
   /** Get the use-desugaring state. True if enabled, false otherwise. */
   public boolean getEnableDesugaring() {
     return enableDesugaring;
@@ -109,6 +120,7 @@
       extends BaseCommand.Builder<C, B> {
 
     private ProgramConsumer programConsumer = null;
+    private StringConsumer mainDexListConsumer = null;
     private Path outputPath = null;
     // TODO(b/70656566): Remove default output mode when deprecated API is removed.
     private OutputMode outputMode = OutputMode.DexIndexed;
@@ -189,6 +201,13 @@
     }
 
     /**
+     * Get the main dex list consumer that will receive the final complete main dex list.
+     */
+    public StringConsumer getMainDexListConsumer() {
+      return mainDexListConsumer;
+    }
+
+    /**
      * If set to true, legacy multidex partitioning will be optimized to reduce LinearAlloc usage
      * during Dalvik DexOpt. Has no effect when compiling for a target with native multidex support
      * or without main dex list specification.
@@ -224,6 +243,33 @@
     }
 
     /**
+     * Set an output destination to which main-dex-list content should be written.
+     *
+     * <p>This is a short-hand for setting a {@link StringConsumer.FileConsumer} using {@link
+     * #setMainDexListConsumer}. Note that any subsequent call to this method or {@link
+     * #setMainDexListConsumer} will override the previous setting.
+     *
+     * @param mainDexListOutputPath File-system path to write output at.
+     */
+    public B setMainDexListOutputPath(Path mainDexListOutputPath) {
+      mainDexListConsumer = new StringConsumer.FileConsumer(mainDexListOutputPath);
+      return self();
+    }
+
+    /**
+     * Set a consumer for receiving the main-dex-list content.
+     *
+     * <p>Note that any subsequent call to this method or {@link #setMainDexListOutputPath} will
+     * override the previous setting.
+     *
+     * @param mainDexListConsumer Consumer to receive the content once produced.
+     */
+    public B setMainDexListConsumer(StringConsumer mainDexListConsumer) {
+      this.mainDexListConsumer = mainDexListConsumer;
+      return self();
+    }
+
+    /**
      * Set the output path-and-mode.
      *
      * <p>Setting the output path-and-mode will override any previous set consumer or any previous
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index e0f330d..f0217b9 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -137,6 +137,8 @@
         if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
           reporter.error("Option --main-dex-list cannot be used with --file-per-class");
         }
+      } else if (getMainDexListConsumer() != null) {
+        reporter.error("Option --main-dex-list-output require --main-dex-list");
       }
       super.validate();
     }
@@ -153,6 +155,7 @@
           getAppBuilder().build(),
           getMode(),
           getProgramConsumer(),
+          getMainDexListConsumer(),
           getMinApiLevel(),
           getReporter(),
           !getDisableDesugaring(),
@@ -209,6 +212,7 @@
       AndroidApp inputApp,
       CompilationMode mode,
       ProgramConsumer programConsumer,
+      StringConsumer mainDexListConsumer,
       int minApiLevel,
       Reporter diagnosticsHandler,
       boolean enableDesugaring,
@@ -218,6 +222,7 @@
         inputApp,
         mode,
         programConsumer,
+        mainDexListConsumer,
         minApiLevel,
         diagnosticsHandler,
         enableDesugaring,
@@ -235,6 +240,7 @@
     assert !internal.debug;
     internal.debug = getMode() == CompilationMode.DEBUG;
     internal.programConsumer = getProgramConsumer();
+    internal.mainDexListConsumer = getMainDexListConsumer();
     internal.minimalMainDex = internal.debug;
     internal.minApiLevel = getMinApiLevel();
     internal.intermediate = intermediate;
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index a933b8f..b84b398 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -104,6 +104,7 @@
               "  --file-per-class        # Produce a separate dex file per input class",
               "  --no-desugaring         # Force disable desugaring.",
               "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
+              "  --main-dex-list-output <file> # Output resulting main dex list in <file>.",
               "  --version               # Print the version of d8.",
               "  --help                  # Print this message."));
 
@@ -198,6 +199,8 @@
           }
         } else if (arg.equals("--main-dex-list")) {
           builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+        } else if (arg.equals("--main-dex-list-output")) {
+          builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
           builder.setOptimizeMultidexForLinearAlloc(true);
         } else if (arg.equals("--min-api")) {
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 118df2e..e235e67 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -160,7 +160,7 @@
     public void accept(
         int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       super.accept(fileIndex, data, descriptors, handler);
-      outputBuilder.addFile(getDexFileName(fileIndex), data, handler);
+      outputBuilder.addIndexedClassFile(fileIndex, getDexFileName(fileIndex), data, handler);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3ed662f..6d00ebb 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -237,12 +237,6 @@
     if (options.quiet) {
       System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
     }
-    // TODO(b/65390962): Remove this warning once the CF backend is complete.
-    if (options.isGeneratingClassFiles() && !options.testing.suppressExperimentalCfBackendWarning) {
-      options.reporter.warning(new StringDiagnostic(
-          "R8 support for generating Java classfiles is incomplete and experimental. "
-              + "Even if R8 appears to succeed, the generated output is likely incorrect."));
-    }
     try {
       AndroidApiLevel oLevel = AndroidApiLevel.O;
       if (options.minApiLevel >= oLevel.getLevel()
@@ -310,7 +304,10 @@
                   .prunedCopyFrom(application, pruner.getRemovedClasses()));
           new AbstractMethodRemover(appView.appInfo()).run();
         }
-        new AnnotationRemover(appView.appInfo().withLiveness(), compatibility, options).run();
+
+        new AnnotationRemover(appView.appInfo().withLiveness(), options)
+            .ensureValid(compatibility)
+            .run();
 
         // TODO(69445518): This is still work in progress, and this file writing is currently used
         // for testing.
@@ -484,6 +481,8 @@
             // Print reasons on the application after pruning, so that we reflect the actual result.
             ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
             reasonPrinter.run(application);
+            // Remove annotations that refer to types that no longer exist.
+            new AnnotationRemover(appView.appInfo().withLiveness(), options).run();
           }
         } finally {
           timing.end();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a1858a9..338fb4f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -90,8 +90,6 @@
     private boolean allowPartiallyImplementedProguardOptions = false;
     private boolean allowTestProguardOptions = false;
 
-    private StringConsumer mainDexListConsumer = null;
-
     // TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
     Builder() {
       this(new DefaultR8DiagnosticsHandler());
@@ -178,33 +176,6 @@
       return self();
     }
 
-    /**
-     * Set an output destination to which main-dex-list content should be written.
-     *
-     * <p>This is a short-hand for setting a {@link StringConsumer.FileConsumer} using {@link
-     * #setMainDexListConsumer}. Note that any subsequent call to this method or {@link
-     * #setMainDexListConsumer} will override the previous setting.
-     *
-     * @param mainDexListOutputPath File-system path to write output at.
-     */
-    public Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
-      mainDexListConsumer = new StringConsumer.FileConsumer(mainDexListOutputPath);
-      return self();
-    }
-
-    /**
-     * Set a consumer for receiving the main-dex-list content.
-     *
-     * <p>Note that any subsequent call to this method or {@link #setMainDexListOutputPath} will
-     * override the previous setting.
-     *
-     * @param mainDexListConsumer Consumer to receive the content once produced.
-     */
-    public Builder setMainDexListConsumer(StringConsumer mainDexListConsumer) {
-      this.mainDexListConsumer = mainDexListConsumer;
-      return self();
-    }
-
     /** Add proguard configuration-file resources. */
     public Builder addProguardConfigurationFiles(Path... paths) {
       guard(() -> {
@@ -321,7 +292,7 @@
       if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
         reporter.error("R8 does not support compiling to a single DEX file per Java class file");
       }
-      if (mainDexListConsumer != null
+      if (getMainDexListConsumer() != null
           && mainDexRules.isEmpty()
           && !getAppBuilder().hasMainDexList()) {
         reporter.error(
@@ -433,7 +404,7 @@
               getAppBuilder().build(),
               getProgramConsumer(),
               mainDexKeepRules,
-              mainDexListConsumer,
+              getMainDexListConsumer(),
               configuration,
               getMode(),
               getMinApiLevel(),
@@ -503,7 +474,6 @@
   static final String USAGE_MESSAGE = R8CommandParser.USAGE_MESSAGE;
 
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
-  private final StringConsumer mainDexListConsumer;
   private final ProguardConfiguration proguardConfiguration;
   private final boolean enableTreeShaking;
   private final boolean enableMinification;
@@ -576,12 +546,11 @@
       StringConsumer proguardMapConsumer,
       Path proguardCompatibilityRulesOutput,
       boolean optimizeMultidexForLinearAlloc) {
-    super(inputApp, mode, programConsumer, minApiLevel, reporter, enableDesugaring,
-        optimizeMultidexForLinearAlloc);
+    super(inputApp, mode, programConsumer, mainDexListConsumer, minApiLevel, reporter,
+        enableDesugaring, optimizeMultidexForLinearAlloc);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
-    this.mainDexListConsumer = mainDexListConsumer;
     this.proguardConfiguration = proguardConfiguration;
     this.enableTreeShaking = enableTreeShaking;
     this.enableMinification = enableMinification;
@@ -594,7 +563,6 @@
   private R8Command(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
     mainDexKeepRules = ImmutableList.of();
-    mainDexListConsumer = null;
     proguardConfiguration = null;
     enableTreeShaking = false;
     enableMinification = false;
@@ -641,7 +609,7 @@
     assert !internal.verbose;
     internal.mainDexKeepRules = mainDexKeepRules;
     internal.minimalMainDex = getMode() == CompilationMode.DEBUG;
-    internal.mainDexListConsumer = mainDexListConsumer;
+    internal.mainDexListConsumer = getMainDexListConsumer();
     internal.lineNumberOptimization =
         !internal.debug && (proguardConfiguration.isOptimizing() || internal.enableMinification)
             ? LineNumberOptimization.ON
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 3798c3f..bdea79c 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.10-dev";
+  public static final String LABEL = "1.4.13-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index d5221b2..6914bc1 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -145,15 +145,14 @@
   }
 
   @Override
-  public void allocateRegisters(boolean debug) {
-    assert options.debug == debug;
-    allocateRegisters();
-  }
-
   public void allocateRegisters() {
     computeNeedsRegister();
     ImmutableList<BasicBlock> blocks = computeLivenessInformation();
     performLinearScan();
+    // Even if the method is reachability sensitive, we do not compute debug information after
+    // register allocation. We just treat the method as being in debug mode in order to keep
+    // locals alive for their entire live range. In release mode the liveness is all that matters
+    // and we do not actually want locals information in the output.
     if (options.debug) {
       LinearScanRegisterAllocator.computeDebugInfo(blocks, liveIntervals, this, liveAtEntrySets);
     }
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index 64395de..16a03e2 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -10,17 +10,21 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StackValue;
 import com.android.tools.r8.ir.code.Value;
 import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -221,6 +225,7 @@
   public Map<Value, TypeInfo> computeVerificationTypes() {
     computingVerificationTypes = true;
     types = new HashMap<>();
+    List<ConstNumber> nullsUsedInPhis = new ArrayList<>();
     Set<Value> worklist = new HashSet<>();
     {
       InstructionIterator it = code.instructionIterator();
@@ -261,6 +266,12 @@
           } else if (instruction.outType().isObject()) {
             Value outValue = instruction.outValue();
             if (instruction.hasInvariantOutType()) {
+              if (instruction.isConstNumber()) {
+                assert instruction.asConstNumber().isZero();
+                if (outValue.numberOfAllUsers() == outValue.numberOfPhiUsers()) {
+                  nullsUsedInPhis.add(instruction.asConstNumber());
+                }
+              }
               DexType type = instruction.computeVerificationType(this);
               types.put(outValue, createInitializedType(type));
               addUsers(outValue, worklist);
@@ -284,6 +295,20 @@
       }
     }
     computingVerificationTypes = false;
+    for (ConstNumber instruction : nullsUsedInPhis) {
+      TypeInfo refinedType = null;
+      for (Phi phi : instruction.outValue().uniquePhiUsers()) {
+        if (refinedType == null) {
+          refinedType = types.get(phi);
+        } else if (refinedType.getDexType() != types.get(phi).getDexType()) {
+          refinedType = null;
+          break;
+        }
+      }
+      if (refinedType != null) {
+        types.put(instruction.outValue(), refinedType);
+      }
+    }
     return types;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 3bd4a00..f71588b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -109,11 +109,11 @@
     }
   }
 
-  private static boolean isNegativeZeroDouble(double value) {
+  public static boolean isNegativeZeroDouble(double value) {
     return Double.doubleToLongBits(value) == Double.doubleToLongBits(-0.0);
   }
 
-  private static boolean isNegativeZeroFloat(float value) {
+  public static boolean isNegativeZeroFloat(float value) {
     return Float.floatToIntBits(value) == Float.floatToIntBits(-0.0f);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 627bbe6..f0c9636 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -6,24 +6,34 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfDexItemBasedConstString extends CfInstruction {
 
-  private DexReference item;
+  private final DexReference item;
+  private final ClassNameComputationInfo classNameComputationInfo;
 
-  public CfDexItemBasedConstString(DexReference item) {
+  public CfDexItemBasedConstString(
+      DexReference item, ClassNameComputationInfo classNameComputationInfo) {
     this.item = item;
+    this.classNameComputationInfo = classNameComputationInfo;
   }
 
   public DexReference getItem() {
     return item;
   }
 
+  public ClassNameComputationInfo getClassNameComputationInfo() {
+    return classNameComputationInfo;
+  }
+
   @Override
   public CfDexItemBasedConstString asDexItemBasedConstString() {
     return this;
@@ -52,6 +62,13 @@
   }
 
   @Override
+  public void registerUse(UseRegistry registry, DexType clazz) {
+    if (item.isDexType() && classNameComputationInfo.needsToRegisterTypeReference()) {
+      registry.registerTypeReference(item.asDexType());
+    }
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     builder.addDexItemBasedConstString(state.push(builder.getFactory().stringType).register, item);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index e0ed773..3b8a64b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -61,7 +61,7 @@
         break;
       case TABLE: {
         int min = keys[0];
-        int max = min + labels.length - 1;
+        int max = min + targets.size() - 1;
         visitor.visitTableSwitchInsn(min, max, defaultTarget.getLabel(), labels);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
index 1871c41..a55ddfa 100644
--- a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
@@ -6,7 +6,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -15,14 +17,22 @@
   public static final String NAME = "DexItemBasedConstString";
   public static final String SMALI_NAME = "const-string*";
 
-  public DexItemBasedConstString(int register, DexReference string) {
+  private final ClassNameComputationInfo classNameComputationInfo;
+
+  public DexItemBasedConstString(
+      int register, DexReference string, ClassNameComputationInfo classNameComputationInfo) {
     super(register, string);
+    this.classNameComputationInfo = classNameComputationInfo;
   }
 
   public DexReference getItem() {
     return (DexReference) BBBB;
   }
 
+  public ClassNameComputationInfo getClassNameComputationInfo() {
+    return classNameComputationInfo;
+  }
+
   @Override
   public String getName() {
     return NAME;
@@ -68,6 +78,13 @@
   }
 
   @Override
+  public void registerUse(UseRegistry registry) {
+    if (getItem().isDexType() && classNameComputationInfo.needsToRegisterTypeReference()) {
+      registry.registerTypeReference(getItem().asDexType());
+    }
+  }
+
+  @Override
   public void buildIR(IRBuilder builder) {
     builder.addDexItemBasedConstString(AA, (DexReference) BBBB);
   }
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 b85fe37..d458d6e 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -408,7 +408,7 @@
             if (enclosingMethod == null
                 && (innerClass.getOuter() == null || innerClass.isAnonymous())) {
               options.warningMissingEnclosingMember(
-                  clazz.type, clazz.origin, clazz.getClassFileVersion());
+                  clazz.type, clazz.origin, clazz.getInitialClassFileVersion());
             } else {
               annotations.add(
                   DexAnnotation.createInnerClassAnnotation(
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 150e516..fd04436 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -3,16 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -243,32 +242,19 @@
    * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
    *
    * @param callSite Call site to resolve.
-   * @param reporter Reporter used when an unknown metafactory is encountered.
    * @return Methods implemented by the lambda expression that created the {@code callSite}.
    */
-  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(
-      DexCallSite callSite, Reporter reporter) {
+  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(DexCallSite callSite) {
     List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
-    if (callSiteInterfaces == null) {
-      if (!isStringConcat(callSite.bootstrapMethod)) {
-        if (reporter != null) {
-          Diagnostic message =
-              new StringDiagnostic("Unknown bootstrap method " + callSite.bootstrapMethod);
-          reporter.warning(message);
-        }
-      }
+    if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
       return Collections.emptySet();
     }
     Set<DexEncodedMethod> result = new HashSet<>();
-    for (DexType iface : callSiteInterfaces) {
+    Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
+    Set<DexType> visited = Sets.newIdentityHashSet();
+    while (!worklist.isEmpty()) {
+      DexType iface = worklist.removeFirst();
       if (iface.isUnknown()) {
-        if (reporter != null) {
-          StringDiagnostic message =
-              new StringDiagnostic(
-                  "Lambda expression implements missing library interface "
-                      + iface.toSourceString());
-          reporter.warning(message);
-        }
         // Skip this interface. If the lambda only implements missing library interfaces and not any
         // program interfaces, then minification and tree shaking are not interested in this
         // DexCallSite anyway, so skipping this interface is harmless. On the other hand, if
@@ -279,21 +265,26 @@
         // anyway.
         continue;
       }
+      if (!visited.add(iface)) {
+        // Already visited previously. May happen due to "diamond shapes" in the interface
+        // hierarchy.
+        continue;
+      }
       assert iface.isInterface();
       DexClass clazz = definitionFor(iface);
       if (clazz != null) {
-        clazz.forEachMethod(
-            method -> {
-              if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
-                result.add(method);
-              }
-            });
+        for (DexEncodedMethod method : clazz.virtualMethods()) {
+          if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
+            result.add(method);
+          }
+        }
+        Collections.addAll(worklist, clazz.interfaces.values);
       }
     }
     return result;
   }
 
-  private boolean isStringConcat(DexMethodHandle bootstrapMethod) {
+  public boolean isStringConcat(DexMethodHandle bootstrapMethod) {
     return bootstrapMethod.type.isInvokeStatic()
         && (bootstrapMethod.asMethod() == dexItemFactory.stringConcatWithConstantsMethod
             || bootstrapMethod.asMethod() == dexItemFactory.stringConcatMethod);
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 790cc5d..aefed39 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfPosition;
@@ -150,8 +151,15 @@
     return this;
   }
 
-  public void write(MethodVisitor visitor, NamingLens namingLens) {
+  public void write(
+      MethodVisitor visitor, NamingLens namingLens, InternalOptions options, int classFileVersion) {
     for (CfInstruction instruction : instructions) {
+      if (instruction instanceof CfFrame
+          && (classFileVersion <= 49
+              || (classFileVersion == 50
+                  && !options.proguardConfiguration.getKeepAttributes().stackMapTable))) {
+        continue;
+      }
       instruction.write(visitor, namingLens);
     }
     visitor.visitEnd();
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 ec9b186..46738e2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -847,6 +847,11 @@
     }
 
     @Override
+    public boolean isReachabilitySensitive() {
+      return false;
+    }
+
+    @Override
     public boolean returnsArgument() {
       return false;
     }
@@ -956,6 +961,7 @@
     // TODO(b/71500340): We call this *hint* because it does not 100% guarantee that a parameter is
     // not null when the method returns normally. Maybe nonNullParamOnNormalExit in the future.
     private BitSet nonNullParamHints = null;
+    private boolean reachabilitySensitive = false;
 
     private OptimizationInfoImpl() {
       // Intentionally left empty, just use the default values.
@@ -977,6 +983,7 @@
       initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
       parametersUsages = template.parametersUsages;
       nonNullParamHints = template.nonNullParamHints;
+      reachabilitySensitive = template.reachabilitySensitive;
     }
 
     @Override
@@ -995,6 +1002,11 @@
     }
 
     @Override
+    public boolean isReachabilitySensitive() {
+      return reachabilitySensitive;
+    }
+
+    @Override
     public boolean returnsArgument() {
       return returnedArgument != -1;
     }
@@ -1077,6 +1089,11 @@
     }
 
     @Override
+    public void setReachabilitySensitive(boolean reachabilitySensitive) {
+      this.reachabilitySensitive = reachabilitySensitive;
+    }
+
+    @Override
     public void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
       this.classInlinerEligibility = eligibility;
     }
@@ -1186,14 +1203,14 @@
     optimizationInfo = info;
   }
 
-  public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
+  public void copyMetadata(DexEncodedMethod from) {
     checkIfObsolete();
     // Record that the current method uses identifier name string if the inlinee did so.
-    if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
+    if (from.getOptimizationInfo().useIdentifierNameString()) {
       getMutableOptimizationInfo().markUseIdentifierNameString();
     }
-    if (inlinee.classFileVersion > classFileVersion) {
-      upgradeClassFileVersion(inlinee.getClassFileVersion());
+    if (from.classFileVersion > classFileVersion) {
+      upgradeClassFileVersion(from.getClassFileVersion());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 6503f11..0b07644 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -128,6 +128,9 @@
   public final DexString containsMethodName = createString("contains");
   public final DexString startsWithMethodName = createString("startsWith");
   public final DexString endsWithMethodName = createString("endsWith");
+  public final DexString equalsMethodName = createString("equals");
+  public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
+  public final DexString contentEqualsMethodName = createString("contentEquals");
 
   public final DexString valueOfMethodName = createString("valueOf");
   public final DexString toStringMethodName = createString("toString");
@@ -272,6 +275,8 @@
       createType("Ldalvik/annotation/codegen/CovariantReturnType;");
   public final DexType annotationCovariantReturnTypes =
       createType("Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;");
+  public final DexType annotationReachabilitySensitive =
+      createType("Ldalvik/annotation/optimization/ReachabilitySensitive;");
 
   private static final String METAFACTORY_METHOD_NAME = "metafactory";
   private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
@@ -578,6 +583,9 @@
     public final DexMethod contains;
     public final DexMethod startsWith;
     public final DexMethod endsWith;
+    public final DexMethod equals;
+    public final DexMethod equalsIgnoreCase;
+    public final DexMethod contentEqualsCharSequence;
 
     public final DexMethod valueOf;
     public final DexMethod toString;
@@ -588,17 +596,25 @@
       length = createMethod(
           stringDescriptor, lengthMethodName, intDescriptor, DexString.EMPTY_ARRAY);
 
-      DexString[] needsOneCharSequence = new DexString[]{charSequenceDescriptor};
+      DexString[] needsOneCharSequence = { charSequenceDescriptor };
+      DexString[] needsOneString = { stringDescriptor };
+      DexString[] needsOneObject = { objectDescriptor };
+
       contains = createMethod(
           stringDescriptor, containsMethodName, booleanDescriptor, needsOneCharSequence);
-      DexString[] needsOneString = new DexString[]{stringDescriptor};
       startsWith = createMethod(
           stringDescriptor, startsWithMethodName, booleanDescriptor, needsOneString);
       endsWith = createMethod(
           stringDescriptor, endsWithMethodName, booleanDescriptor, needsOneString);
+      equals = createMethod(
+          stringDescriptor, equalsMethodName, booleanDescriptor, needsOneObject);
+      equalsIgnoreCase = createMethod(
+          stringDescriptor, equalsIgnoreCaseMethodName, booleanDescriptor, needsOneString);
+      contentEqualsCharSequence = createMethod(
+          stringDescriptor, contentEqualsMethodName, booleanDescriptor, needsOneCharSequence);
 
       valueOf = createMethod(
-          stringDescriptor, valueOfMethodName, stringDescriptor, new DexString[]{objectDescriptor});
+          stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
       toString = createMethod(
           stringDescriptor, toStringMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
     }
@@ -973,14 +989,13 @@
 
   public ReferenceTypeLatticeElement createReferenceTypeLatticeElement(
       DexType type, boolean isNullable, AppInfo appInfo) {
-    ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
-    if (typeLattice != null) {
-      return isNullable == typeLattice.isNullable() ? typeLattice
-          : typeLattice.getOrCreateDualLattice();
-    }
     synchronized (type) {
-      typeLattice = referenceTypeLatticeElements.get(type);
-      if (typeLattice == null) {
+      ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
+      if (typeLattice != null) {
+        typeLattice = isNullable == typeLattice.isNullable() ? typeLattice
+            : typeLattice.getOrCreateDualLattice();
+        assert typeLattice.isNullable() == isNullable;
+      } else {
         if (type.isClassType()) {
           if (!type.isUnknown() && type.isInterface()) {
             typeLattice = new ClassTypeLatticeElement(
@@ -998,8 +1013,9 @@
         }
         referenceTypeLatticeElements.put(type, typeLattice);
       }
+      assert typeLattice.isNullable() == isNullable;
+      return typeLattice;
     }
-    return typeLattice;
   }
 
   private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items,
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1516398..dafb07c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -26,7 +26,7 @@
   private final ProgramResource.Kind originKind;
   private DexEncodedArray staticValues = SENTINEL_NOT_YET_COMPUTED;
   private final Collection<DexProgramClass> synthesizedFrom;
-  private int classFileVersion = -1;
+  private int initialClassFileVersion = -1;
   private KotlinInfo kotlinInfo = null;
 
   public DexProgramClass(
@@ -369,17 +369,57 @@
     return this;
   }
 
-  public void setClassFileVersion(int classFileVersion) {
-    assert classFileVersion >= 0;
-    this.classFileVersion = classFileVersion;
+  public void setInitialClassFileVersion(int initialClassFileVersion) {
+    assert this.initialClassFileVersion == -1 && initialClassFileVersion > 0;
+    this.initialClassFileVersion = initialClassFileVersion;
   }
 
   public boolean hasClassFileVersion() {
-    return classFileVersion >= 0;
+    return initialClassFileVersion > -1;
   }
 
-  public int getClassFileVersion() {
-    assert classFileVersion != -1;
-    return classFileVersion;
+  public int getInitialClassFileVersion() {
+    assert initialClassFileVersion > -1;
+    return initialClassFileVersion;
+  }
+
+  /**
+   * Is the class reachability sensitive.
+   *
+   * <p>A class is reachability sensitive if the
+   * dalvik.annotation.optimization.ReachabilitySensitive annotation is on any field or method. When
+   * that is the case, dead reference elimination is disabled and locals are kept alive for their
+   * entire scope.
+   */
+  public boolean hasReachabilitySensitiveAnnotation(DexItemFactory factory) {
+    for (DexEncodedMethod directMethod : directMethods) {
+      for (DexAnnotation annotation : directMethod.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    for (DexEncodedMethod virtualMethod : virtualMethods) {
+      for (DexAnnotation annotation : virtualMethod.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    for (DexEncodedField staticField : staticFields) {
+      for (DexAnnotation annotation : staticField.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    for (DexEncodedField instanceField : instanceFields) {
+      for (DexAnnotation annotation : instanceField.annotations.annotations) {
+        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 6eb69b8..c0413e0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.utils.EncodedValueUtils;
 import java.util.Arrays;
 import org.objectweb.asm.Handle;
@@ -719,9 +720,20 @@
   }
 
   public static class DexItemBasedValueString extends NestedDexValue<DexReference> {
+    private final ClassNameComputationInfo classNameComputationInfo;
 
     public DexItemBasedValueString(DexReference value) {
+      this(value, ClassNameComputationInfo.none());
+    }
+
+    public DexItemBasedValueString(
+        DexReference value, ClassNameComputationInfo classNameComputationInfo) {
       super(value);
+      this.classNameComputationInfo = classNameComputationInfo;
+    }
+
+    public ClassNameComputationInfo getClassNameComputationInfo() {
+      return classNameComputationInfo;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 0f0b5e5..a25bae0 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -167,6 +167,7 @@
     private final List<DexEncodedMethod> directMethods = new ArrayList<>();
     private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
     private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
+    private boolean hasReachabilitySensitiveMethod = false;
 
     public CreateDexClassVisitor(
         Origin origin,
@@ -301,6 +302,7 @@
         addAnnotation(DexAnnotation.createAnnotationDefaultAnnotation(
             type, defaultAnnotations, application.getFactory()));
       }
+      checkReachabilitySensitivity();
       DexClass clazz =
           classKind.create(
               type,
@@ -320,11 +322,50 @@
               application.getFactory().getSkipNameValidationForTesting());
       if (clazz.isProgramClass()) {
         context.owner = clazz.asProgramClass();
-        clazz.asProgramClass().setClassFileVersion(version);
+        clazz.asProgramClass().setInitialClassFileVersion(version);
       }
       classConsumer.accept(clazz);
     }
 
+    // If anything is marked reachability sensitive, all methods need to be parsed including
+    // locals information. This propagates the reachability sensitivity bit so that if any field
+    // or method is annotated, all methods get parsed with locals information.
+    private void checkReachabilitySensitivity() {
+      if (hasReachabilitySensitiveMethod || hasReachabilitySensitiveField()) {
+        for (DexEncodedMethod method : directMethods) {
+          Code code = method.getCode();
+          if (code != null && code.isJarCode()) {
+            code.asJarCode().markReachabilitySensitive();
+          }
+        }
+        for (DexEncodedMethod method : virtualMethods) {
+          Code code = method.getCode();
+          if (code != null && code.isJarCode()) {
+            code.asJarCode().markReachabilitySensitive();
+          }
+        }
+      }
+    }
+
+    private boolean hasReachabilitySensitiveField() {
+      DexType reachabilitySensitive = application.getFactory().annotationReachabilitySensitive;
+      for (DexEncodedField field : instanceFields) {
+        for (DexAnnotation annotation : field.annotations.annotations) {
+          if (annotation.annotation.type == reachabilitySensitive) {
+            return true;
+          }
+        }
+      }
+      for (DexEncodedField field : staticFields) {
+        for (DexAnnotation annotation : field.annotations.annotations) {
+          if (annotation.annotation.type == reachabilitySensitive) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
     private void addDefaultAnnotation(String name, DexValue value) {
       if (defaultAnnotations == null) {
         defaultAnnotations = new ArrayList<>();
@@ -626,6 +667,7 @@
               parent.version);
       Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
       if (parent.methodSignatures.add(signature)) {
+        parent.hasReachabilitySensitiveMethod |= isReachabilitySensitive();
         if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
           parent.directMethods.add(dexMethod);
         } else {
@@ -644,6 +686,17 @@
       }
     }
 
+    private boolean isReachabilitySensitive() {
+      DexType reachabilitySensitive =
+          parent.application.getFactory().annotationReachabilitySensitive;
+      for (DexAnnotation annotation : getAnnotations()) {
+        if (annotation.annotation.type == reachabilitySensitive) {
+          return true;
+        }
+      }
+      return false;
+    }
+
     private List<DexAnnotation> getAnnotations() {
       if (annotations == null) {
         annotations = new ArrayList<>();
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 99e544a..57a4c78 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -52,8 +52,8 @@
   private final Origin origin;
   private MethodNode node;
   protected ReparseContext context;
-
   protected final JarApplicationReader application;
+  private boolean reachabilitySensitive = false;
 
   public JarCode(
       DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
@@ -64,6 +64,13 @@
     context.codeList.add(this);
   }
 
+  public void markReachabilitySensitive() {
+    // We need to mark before we have reparsed so that the method code is reparsed
+    // including debug information.
+    assert context != null;
+    this.reachabilitySensitive = true;
+  }
+
   public MethodNode getNode() {
     triggerDelayedParsingIfNeccessary();
     return node;
@@ -122,11 +129,10 @@
       Origin origin) {
     assert getOwner() == encodedMethod;
     triggerDelayedParsingIfNeccessary();
-    return options.debug
+    return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
         ? internalBuildWithLocals(
             encodedMethod, encodedMethod, appInfo, graphLense, options, null, null)
-        : internalBuild(
-            encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
+        : internalBuild(encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
   }
 
   @Override
@@ -142,7 +148,7 @@
     assert getOwner() == encodedMethod;
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
-    return options.debug
+    return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
         ? internalBuildWithLocals(
             context, encodedMethod, appInfo, graphLense, options, generator, callerPosition)
         : internalBuild(
@@ -176,7 +182,9 @@
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition) {
-    if (!options.debug || !options.proguardConfiguration.getKeepAttributes().localVariableTable) {
+    if (!(encodedMethod.getOptimizationInfo().isReachabilitySensitive()
+        || (options.debug
+            && options.proguardConfiguration.getKeepAttributes().localVariableTable))) {
       node.localVariables.clear();
     }
 
@@ -270,9 +278,16 @@
   private void parseCode(ReparseContext context, boolean useJsrInliner) {
     // If the keep attributes do not specify keeping LocalVariableTable, LocalVariableTypeTable or
     // LineNumberTable, then we can skip parsing all the debug related attributes during code read.
+    // If the method is reachability sensitive we have to include debug information in order
+    // to get locals information which we need to extend the live ranges of locals for their
+    // entire scope.
     int parsingOptions = ClassReader.SKIP_FRAMES;
     ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
-    if (!keep.localVariableTable && !keep.localVariableTypeTable && !keep.lineNumberTable) {
+
+    if (!keep.localVariableTable
+        && !keep.localVariableTypeTable
+        && !keep.lineNumberTable
+        && !reachabilitySensitive) {
       parsingOptions |= ClassReader.SKIP_DEBUG;
     }
     SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
diff --git a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
index c693ea4..4fc81d0 100644
--- a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
@@ -21,6 +21,8 @@
 
   BitSet getNonNullParamHints();
 
+  boolean isReachabilitySensitive();
+
   boolean returnsArgument();
 
   int getReturnedArgument();
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
index d7ae7ae..cb93ee0 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
@@ -31,6 +31,8 @@
 
   void setNonNullParamHints(BitSet hints);
 
+  void setReachabilitySensitive(boolean reachabilitySensitive);
+
   void markUseIdentifierNameString();
 
   void markForceInline();
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 33b04cb..7a6a9a4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.NumberUtils;
 import java.util.function.Function;
 
@@ -153,8 +154,46 @@
     }
   }
 
-  // Estimated size of the resulting dex instruction in code units.
-  public static int estimatedDexSize(ValueType type, long value) {
+  // Estimated size of the resulting instructions in code units (bytes in CF, 16-bit in Dex).
+  public static int estimatedSize(InternalOutputMode mode, ValueType type, long value) {
+    return mode.isGeneratingDex() ? estimatedDexSize(type, value) : estimatedCfSize(type, value);
+  }
+
+  private static int estimatedCfSize(ValueType type, long value) {
+    switch (type) {
+      case INT:
+        if (-1 <= value && value <= 5) {
+          return 1;
+        } else if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
+          return 2;
+        } else {
+          return 3;
+        }
+      case LONG:
+        if (value == 0 || value == 1) {
+          return 1;
+        } else {
+          return 3;
+        }
+      case FLOAT:
+        if (value == 0 || value == 1 || value == 2) {
+          return CfConstNumber.isNegativeZeroFloat((float) value) ? 2 : 1;
+        } else {
+          return 3;
+        }
+      case DOUBLE:
+        if (value == 0 || value == 1) {
+          return CfConstNumber.isNegativeZeroDouble((double) value) ? 2 : 1;
+        } else {
+          return 3;
+        }
+      case OBJECT:
+        return 1;
+    }
+    throw new UnsupportedOperationException("Not a constant number");
+  }
+
+  private static int estimatedDexSize(ValueType type, long value) {
     if (type.isSingle()) {
       assert NumberUtils.is32Bit(value);
       if (NumberUtils.is4Bit(value)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 3ec7971..2b54a4b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -13,25 +13,38 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 
 public class DexItemBasedConstString extends ConstInstruction {
 
   private final DexReference item;
+  private final ClassNameComputationInfo classNameComputationInfo;
 
   public DexItemBasedConstString(Value dest, DexReference item) {
+    this(dest, item, ClassNameComputationInfo.none());
+  }
+
+  public DexItemBasedConstString(
+      Value dest, DexReference item, ClassNameComputationInfo classNameComputationInfo) {
     super(dest);
     dest.markNeverNull();
     this.item = item;
+    this.classNameComputationInfo = classNameComputationInfo;
   }
 
   public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
-    return new DexItemBasedConstString(newValue, original.getItem());
+    return new DexItemBasedConstString(
+        newValue, original.getItem(), original.classNameComputationInfo);
   }
 
   public DexReference getItem() {
     return item;
   }
 
+  public ClassNameComputationInfo getClassNameComputationInfo() {
+    return classNameComputationInfo;
+  }
+
   @Override
   public boolean isDexItemBasedConstString() {
     return true;
@@ -45,7 +58,10 @@
   @Override
   public void buildDex(DexBuilder builder) {
     int dest = builder.allocatedRegister(outValue(), getNumber());
-    builder.add(this, new com.android.tools.r8.code.DexItemBasedConstString(dest, item));
+    builder.add(
+        this,
+        new com.android.tools.r8.code.DexItemBasedConstString(
+            dest, item, classNameComputationInfo));
   }
 
   @Override
@@ -99,7 +115,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfDexItemBasedConstString(item));
+    builder.add(new CfDexItemBasedConstString(item, classNameComputationInfo));
   }
 
   @Override
@@ -109,6 +125,6 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
+    return TypeLatticeElement.stringClassType(appInfo);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index e348d10..e11f1bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -635,7 +635,9 @@
   private boolean consistentBlockInstructions() {
     boolean argumentsAllowed = true;
     for (BasicBlock block : blocks) {
-      block.consistentBlockInstructions(argumentsAllowed, options.debug);
+      block.consistentBlockInstructions(
+          argumentsAllowed,
+          options.debug || method.getOptimizationInfo().isReachabilitySensitive());
       argumentsAllowed = false;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index ca2fb4a..421872f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.InternalOutputMode;
 import java.util.List;
 
 public class If extends JumpInstruction {
@@ -126,9 +127,14 @@
     builder.addIf(this);
   }
 
-  // Estimated size of the resulting dex instruction in code units.
-  public static int estimatedDexSize() {
-    return 2;
+  // Estimated size of the resulting instructions in code units (bytes in CF, 16-bit in Dex).
+  public static int estimatedSize(InternalOutputMode mode) {
+    if (mode.isGeneratingClassFiles()) {
+      // op + branch1 + branch2
+      return 3;
+    } else {
+      return 2;
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 190ab3a..7cdcd76 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -63,7 +63,8 @@
 
   @Override
   public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
-    return !code.options.debug && code.options.isGeneratingDex();
+    return !(code.options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+        && code.options.isGeneratingDex();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index fb45d97..9493ff5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
-import com.google.common.primitives.Ints;
+import com.android.tools.r8.utils.InternalOutputMode;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.ArrayList;
@@ -62,75 +62,109 @@
     return ((long) keys[keys.length - 1]) - ((long) keys[0]) + 1;
   }
 
-  public static boolean canBePacked(int keys[]) {
+  public static boolean canBePacked(InternalOutputMode mode, int keys[]) {
     // The size of a switch payload is stored in an ushort in the Dex file.
-    return numberOfTargetsIfPacked(keys) <= Constants.U16BIT_MAX;
+    return canBePacked(mode, numberOfTargetsIfPacked(keys));
   }
 
-  // Number of targets if this switch is emitted as a packed switch.
-  public static int numberOfTargetsForPacked(int keys[]) {
-    assert canBePacked(keys);
-    return (int) numberOfTargetsIfPacked(keys);
+  public static boolean canBePacked(InternalOutputMode mode, long numberOfTargets) {
+    // The size of a switch payload is stored in an ushort in the Dex file.
+    return numberOfTargets
+        <= (mode.isGeneratingClassFiles() ? Constants.U32BIT_MAX : Constants.U16BIT_MAX);
   }
 
-  // Size of the switch payload if emitted as packed (in code units).
-  // This size can not exceed Constants.U16BIT_MAX * 2 + 4 and can be contained in an integer.
-  private static int packedPayloadSize(int keys[]) {
-    return (numberOfTargetsForPacked(keys) * 2) + 4;
+  // Estimated size of the resulting dex instruction in code units (bytes in CF, 16-bit in Dex).
+  public static long estimatedSize(InternalOutputMode mode, int[] keys) {
+    long sparseSize = sparsePayloadSize(mode, keys) + baseSparseSize(mode);
+    long packedSize = Long.MAX_VALUE;
+    if (canBePacked(mode, keys)) {
+      long packedPayloadSize = packedPayloadSize(mode, keys);
+      packedSize = packedPayloadSize + basePackedSize(mode);
+      if (packedSize < packedPayloadSize) {
+        packedSize = Integer.MAX_VALUE;
+      }
+    }
+    return Math.min(sparseSize, packedSize);
   }
 
-  // Size of the switch payload if emitted as sparse (in code units).
-  // This size can not exceed Constants.U16BIT_MAX * 4 + 2 and can be contained in an integer.
-  private static int sparsePayloadSize(int keys[]) {
-    return (keys.length * 4) + 2;
+  public static long estimatedSparseSize(InternalOutputMode mode, long keys) {
+    return sparsePayloadSize(mode, keys) + baseSparseSize(mode);
   }
 
-  /**
-   * Size of the switch payload instruction for the given keys. This will be the payload
-   * size for the smallest encoding of the provided keys.
-   *
-   * @param keys the switch keys
-   * @return Size of the switch payload instruction in code units
-   */
-  public static int payloadSize(List<Integer> keys) {
-    return payloadSize(Ints.toArray(keys));
-  }
-
-  /**
-   * Size of the switch payload instruction for the given keys.
-   *
-   * @see #payloadSize(List)
-   */
-  public static int payloadSize(int keys[]) {
-    int sparse = sparsePayloadSize(keys);
-    if (canBePacked(keys)) {
-      return Math.min(sparse, packedPayloadSize(keys));
+  // Estimated size of the packed/table instructions in code units excluding the payload (bytes in
+  // CF, 16-bit in Dex).
+  public static int basePackedSize(InternalOutputMode mode) {
+    if (mode.isGeneratingClassFiles()) {
+      // Table switch: opcode + padding + default + high + low.
+      return 1 + 3 + 4 + 4 + 4;
     } else {
-      return sparse;
+      return 3;
     }
   }
 
-  private boolean canBePacked() {
-    return canBePacked(keys);
+  // Estimated size of the sparse/lookup instructions in code units excluding the payload (bytes in
+  // CF, 16-bit in Dex).
+  public static int baseSparseSize(InternalOutputMode mode) {
+    if (mode.isGeneratingClassFiles()) {
+      // Lookup switch: opcode + padding + default + number of keys.
+      return 1 + 3 + 4 + 4;
+    } else {
+      return 3;
+    }
   }
 
-  // Number of targets if this switch is emitted as a packed switch.
-  private int numberOfTargetsForPacked() {
-    return numberOfTargetsForPacked(keys);
+  // Size of the switch payload if emitted as packed in code units (bytes in CF, 16-bit in Dex).
+  public static long packedPayloadSize(InternalOutputMode mode, long numberOfTargets) {
+    if (mode.isGeneratingClassFiles()) {
+      assert numberOfTargets <= Constants.U32BIT_MAX;
+      return numberOfTargets * 4;
+    } else {
+      // This size can not exceed Constants.U16BIT_MAX * 2 + 4 and can be contained in an 32-bit
+      // integer.
+      return (numberOfTargets * 2) + 4;
+    }
   }
 
-  // Size of the switch payload if emitted as packed (in code units).
-  private int packedPayloadSize() {
-    return packedPayloadSize(keys);
+  // Size of the switch payload if emitted as packed in code units (bytes in CF, 16-bit in Dex).
+  public static long packedPayloadSize(InternalOutputMode mode, int keys[]) {
+    assert canBePacked(mode, keys);
+    long numberOfTargets = numberOfTargetsIfPacked(keys);
+    return packedPayloadSize(mode, numberOfTargets);
   }
 
-  // Size of the switch payload if emitted as sparse (in code units).
-  private int sparsePayloadSize() {
-    return sparsePayloadSize(keys);
+  // Size of the switch payload if emitted as sparse in code units (bytes in CF, 16-bit in Dex).
+  public static long sparsePayloadSize(InternalOutputMode mode, int[] keys) {
+    return sparsePayloadSize(mode, keys.length);
   }
 
-  private boolean emitPacked() {
-    return canBePacked() && packedPayloadSize() <= sparsePayloadSize();
+  // Size of the switch payload if emitted as sparse in code units (bytes in CF, 16-bit in Dex).
+  public static long sparsePayloadSize(InternalOutputMode mode, long keys) {
+    if (mode.isGeneratingClassFiles()) {
+      // Lookup-switch has a 32-bit int key and 32-bit int value for each offset, 8 bytes per entry.
+      return keys * 8;
+    } else {
+      // This size can not exceed Constants.U16BIT_MAX * 4 and can be contained in a
+      // 32-bit integer.
+      return keys * 4 + 2;
+    }
+  }
+
+  private boolean canBePacked(InternalOutputMode mode) {
+    return canBePacked(mode, keys);
+  }
+
+  // Size of the switch payload if emitted as packed in code units (bytes in CF, 16-bit in Dex).
+  private long packedPayloadSize(InternalOutputMode mode) {
+    return packedPayloadSize(mode, keys);
+  }
+
+  // Size of the switch payload if emitted as sparse in code units (bytes in CF, 16-bit in Dex).
+  private long sparsePayloadSize(InternalOutputMode mode) {
+    return sparsePayloadSize(mode, keys);
+  }
+
+  private boolean emitPacked(InternalOutputMode mode) {
+    return canBePacked(mode) && packedPayloadSize(mode) <= sparsePayloadSize(mode);
   }
 
   public int getFirstKey() {
@@ -155,17 +189,13 @@
   @Override
   public void buildDex(DexBuilder builder) {
     int value = builder.allocatedRegister(value(), getNumber());
-    if (emitPacked()) {
+    if (emitPacked(InternalOutputMode.DexIndexed)) {
       builder.addSwitch(this, new PackedSwitch(value));
     } else {
       builder.addSwitch(this, new SparseSwitch(value));
     }
   }
 
-  // Estimated size of the resulting dex instruction in code units (excluding the payload).
-  public static int estimatedDexSize() {
-    return 3;
-  }
 
   public int numberOfKeys() {
     return keys.length;
@@ -213,10 +243,11 @@
     getBlock().getSuccessors().set(fallthroughBlockIndex, block);
   }
 
-  public Nop buildPayload(int[] targets, int fallthroughTarget) {
+  public Nop buildPayload(int[] targets, int fallthroughTarget, InternalOutputMode mode) {
     assert keys.length == targets.length;
-    if (emitPacked()) {
-      int targetsCount = numberOfTargetsForPacked();
+    assert mode.isGeneratingDex();
+    if (emitPacked(mode)) {
+      int targetsCount = (int) numberOfTargetsIfPacked(keys);
       if (targets.length == targetsCount) {
         // All targets are already present.
         return new PackedSwitchPayload(getFirstKey(), targets);
@@ -284,11 +315,28 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
+    CfLabel fallthroughLabel = builder.getLabel(fallthroughBlock());
     List<CfLabel> labels = new ArrayList<>(numberOfKeys());
     List<BasicBlock> successors = getBlock().getSuccessors();
-    for (int index : targetBlockIndices) {
-      labels.add(builder.getLabel(successors.get(index)));
+    if (emitPacked(InternalOutputMode.ClassFile)) {
+      int min = keys[0];
+      int max = keys[keys.length - 1];
+      int index = 0;
+      for (long i = min; i <= max; i++) {
+        if (i == keys[index]) {
+          labels.add(builder.getLabel(successors.get(targetBlockIndices[index])));
+          index++;
+        } else {
+          labels.add(fallthroughLabel);
+        }
+      }
+      assert index == targetBlockIndices.length;
+      builder.add(new CfSwitch(Kind.TABLE, fallthroughLabel, new int[] { min }, labels));
+    } else {
+      for (int index : targetBlockIndices) {
+        labels.add(builder.getLabel(successors.get(index)));
+      }
+      builder.add(new CfSwitch(Kind.LOOKUP, fallthroughLabel, this.keys, labels));
     }
-    builder.add(new CfSwitch(Kind.LOOKUP, builder.getLabel(fallthroughBlock()), keys, labels));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 644dfa3..b4ad228 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -726,6 +726,12 @@
     return isConstant() && getConstInstruction().isDexItemBasedConstString();
   }
 
+  public boolean isDexItemBasedConstStringThatNeedsToComputeClassName() {
+    return isDexItemBasedConstString()
+        && getConstInstruction().asDexItemBasedConstString()
+            .getClassNameComputationInfo().needsToComputeClassName();
+  }
+
   public boolean isConstClass() {
     return isConstant() && getConstInstruction().isConstClass();
   }
@@ -762,7 +768,9 @@
    * Returns whether this value is known to never be <code>null</code>.
    */
   public boolean isNeverNull() {
-    return neverNull || (definition != null && definition.isNonNull());
+    return neverNull
+        || (definition != null && definition.isNonNull())
+        || (typeLattice.isReference() && typeLattice.nullElement().isDefinitelyNotNull());
   }
 
   public boolean canBeNull() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index f3b58db..8f32081 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -45,7 +44,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StackValue;
 import com.android.tools.r8.ir.code.StackValues;
-import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -346,10 +344,12 @@
       previousFallthrough = fallthrough;
     } while (block != null);
     // TODO(mkroghj) Move computation of stack-height to CF instructions.
-    CfLabel endLabel = ensureLabel();
-    for (LocalVariableInfo info : openLocalVariables.values()) {
-      info.setEnd(endLabel);
-      localVariablesTable.add(info);
+    if (!openLocalVariables.isEmpty()) {
+      CfLabel endLabel = ensureLabel();
+      for (LocalVariableInfo info : openLocalVariables.values()) {
+        info.setEnd(endLabel);
+        localVariablesTable.add(info);
+      }
     }
     return new CfCode(
         method.method,
@@ -383,75 +383,59 @@
 
   private void rewriteIincPatterns() {
     for (BasicBlock block : code.blocks) {
-      if (block.getInstructions().size() < IINC_PATTERN_SIZE) {
-        continue;
-      }
-      int earliestAddInstrIdx = 2;
-      int latestAddInstrIdx = block.getInstructions().size() - 3;
       ListIterator<Instruction> it = block.getInstructions().listIterator();
-      int instrIdx = -1;
-      while (it.hasNext()) {
-        ++instrIdx;
-        Instruction current = it.next();
-        if (instrIdx < earliestAddInstrIdx || latestAddInstrIdx < instrIdx || !current.isAdd()) {
-          continue;
-        }
-        it.previous();
-        it.previous();
-        it.previous();
-        Instruction instruction0 = it.next();
-        Instruction instruction1 = it.next();
-        Add instruction2 = it.next().asAdd();
-        Store instruction3 = it.next().asStore();
-
-        // Set bail-out position after Add.
-        it.previous();
-
-        if (instruction3 == null) {
+      // Test that we have enough instructions for iinc.
+      while (IINC_PATTERN_SIZE <= block.getInstructions().size() - it.nextIndex()) {
+        Instruction loadOrConst1 = it.next();
+        if (!loadOrConst1.isLoad() && !loadOrConst1.isConstNumber()) {
           continue;
         }
         Load load;
         ConstNumber constNumber;
-
-        if (instruction0.isLoad()) {
-          load = instruction0.asLoad();
-          constNumber = instruction1.asConstNumber();
+        if (loadOrConst1.isLoad()) {
+          load = loadOrConst1.asLoad();
+          constNumber = it.next().asConstNumber();
         } else {
-          constNumber = instruction0.asConstNumber();
-          load = instruction1.asLoad();
+          load = it.next().asLoad();
+          constNumber = loadOrConst1.asConstNumber();
         }
-
+        Instruction add = it.next().asAdd();
+        Instruction store = it.next().asStore();
+        // Reset pointer to load.
+        it.previous();
+        it.previous();
+        it.previous();
+        it.previous();
         if (load == null
             || constNumber == null
+            || add == null
+            || store == null
             || constNumber.outValue().getTypeLattice() != TypeLatticeElement.INT) {
+          it.next();
           continue;
         }
-
         int increment = constNumber.getIntValue();
         if (increment < Byte.MIN_VALUE || Byte.MAX_VALUE < increment) {
+          it.next();
           continue;
         }
-
-        int register = getLocalRegister(load.inValues().get(0));
-        if (register != getLocalRegister(instruction3.outValue())) {
+        if (getLocalRegister(load.src()) != getLocalRegister(store.outValue())) {
+          it.next();
           continue;
         }
-        Position position = instruction2.getPosition();
-        if (position != instruction0.getPosition()
-            || position != instruction1.getPosition()
-            || position != instruction3.getPosition()) {
+        Position position = add.getPosition();
+        if (position != load.getPosition()
+            || position != constNumber.getPosition()
+            || position != store.getPosition()) {
           continue;
         }
-        it.previous();
-        it.previous();
-        it.previous();
         it.remove();
         it.next();
         it.remove();
         it.next();
         it.remove();
         it.next();
-        Inc inc = new Inc(instruction3.outValue(), load.inValues().get(0), increment);
+        Inc inc = new Inc(store.outValue(), load.inValues().get(0), increment);
         inc.setPosition(position);
         inc.setBlock(block);
         it.set(inc);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 7089293..fcd28b2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -61,6 +61,7 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Lists;
@@ -626,7 +627,7 @@
     int fallthroughTarget =
         getInfo(fallthroughTargetInstruction).getOffset() - getInfo(ir).getOffset();
 
-    return ir.buildPayload(targets, fallthroughTarget);
+    return ir.buildPayload(targets, fallthroughTarget, InternalOutputMode.DexIndexed);
   }
 
   // Helpers for computing the try items and handlers.
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 eda63f0..953a7f2 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
@@ -458,7 +458,7 @@
   }
 
   public boolean isDebugMode() {
-    return options.debug;
+    return options.debug || method.getOptimizationInfo().isReachabilitySensitive();
   }
 
   public Int2ReferenceSortedMap<BlockInfo> getCFG() {
@@ -630,7 +630,7 @@
 
   private boolean insertDebugPositions() {
     boolean hasDebugPositions = false;
-    if (!options.debug) {
+    if (!isDebugMode()) {
       return hasDebugPositions;
     }
     for (BasicBlock block : blocks) {
@@ -847,7 +847,7 @@
 
   public void addDebugLocalStart(int register, DebugLocalInfo local) {
     assert local != null;
-    if (!options.debug) {
+    if (!isDebugMode()) {
       return;
     }
     // If the local was not introduced by the previous instruction, start it here.
@@ -867,7 +867,7 @@
 
   public void addDebugLocalEnd(int register, DebugLocalInfo local) {
     assert local != null;
-    if (!options.debug) {
+    if (!isDebugMode()) {
       return;
     }
     Value value = readRegisterForDebugLocal(register, local);
@@ -877,7 +877,7 @@
   }
 
   public void addDebugPosition(Position position) {
-    if (options.debug) {
+    if (isDebugMode()) {
       assert previousLocalValue == null;
       assert source.getCurrentPosition().equals(position);
       if (!debugLocalEnds.isEmpty()) {
@@ -1096,7 +1096,7 @@
 
   public void addMove(ValueTypeConstraint constraint, int dest, int src) {
     Value in = readRegister(src, constraint);
-    if (options.debug) {
+    if (isDebugMode()) {
       // If the move is writing to a different local we must construct a new value.
       DebugLocalInfo destLocal = getOutgoingLocal(dest);
       if (destLocal != null && destLocal != in.getLocalInfo()) {
@@ -1876,7 +1876,7 @@
   }
 
   private Value readRegisterForDebugLocal(int register, DebugLocalInfo local) {
-    assert options.debug;
+    assert isDebugMode();
     ValueTypeConstraint type = ValueTypeConstraint.fromDexType(local.type);
     return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, RegisterReadType.DEBUG);
   }
@@ -1959,7 +1959,7 @@
   }
 
   private DebugLocalInfo getIncomingLocalAtBlock(int register, BasicBlock block) {
-    if (options.debug) {
+    if (isDebugMode()) {
       int blockOffset = offsets.getInt(block);
       return source.getIncomingLocalAtBlock(register, blockOffset);
     }
@@ -2055,11 +2055,11 @@
   }
 
   private DebugLocalInfo getIncomingLocal(int register) {
-    return options.debug ? source.getIncomingLocal(register) : null;
+    return isDebugMode() ? source.getIncomingLocal(register) : null;
   }
 
   private DebugLocalInfo getOutgoingLocal(int register) {
-    return options.debug ? source.getOutgoingLocal(register) : null;
+    return isDebugMode() ? source.getOutgoingLocal(register) : null;
   }
 
   private void checkRegister(int register) {
@@ -2161,7 +2161,7 @@
   }
 
   private void attachLocalValues(Instruction ir) {
-    if (!options.debug) {
+    if (!isDebugMode()) {
       assert previousLocalValue == null;
       assert debugLocalEnds.isEmpty();
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 61c69f8..38842da 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
@@ -36,6 +36,8 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
@@ -74,6 +76,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -93,6 +96,7 @@
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class IRConverter {
@@ -175,7 +179,8 @@
       assert appInfo.hasLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
       AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      this.nonNullTracker = new NonNullTracker(appInfo);
+      this.nonNullTracker =
+          new NonNullTracker(appInfo, libraryMethodsReturningNonNull(appInfo.dexItemFactory));
       this.inliner = new Inliner(appViewWithLiveness, this, options);
       this.outliner = new Outliner(appInfoWithLiveness, options, this);
       this.memberValuePropagation =
@@ -288,6 +293,16 @@
     return methods;
   }
 
+  // Library methods listed here are based on their original implementations. That is, we assume
+  // these cannot be overridden.
+  public static Set<DexMethod> libraryMethodsReturningNonNull(DexItemFactory factory) {
+    return ImmutableSet.of(
+        factory.stringMethods.valueOf,
+        factory.classMethods.getName,
+        factory.classMethods.getSimpleName
+    );
+  }
+
   private void removeLambdaDeserializationMethods() {
     if (lambdaRewriter != null) {
       lambdaRewriter.removeLambdaDeserializationMethods(appInfo.classes());
@@ -438,18 +453,22 @@
   }
 
   private void convertMethodsToDex(DexProgramClass clazz) {
+    boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
     // When converting all methods on a class always convert <clinit> first.
     for (DexEncodedMethod method : clazz.directMethods()) {
       if (method.isClassInitializer()) {
+        method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
         convertMethodToDex(method);
         break;
       }
     }
-    clazz.forEachMethod(method -> {
-      if (!method.isClassInitializer()) {
-        convertMethodToDex(method);
-      }
-    });
+    clazz.forEachMethod(
+        method -> {
+          if (!method.isClassInitializer()) {
+            method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
+            convertMethodToDex(method);
+          }
+        });
   }
 
   private void convertMethodToDex(DexEncodedMethod method) {
@@ -478,6 +497,7 @@
 
   public DexApplication optimize(DexApplication application, ExecutorService executorService)
       throws ExecutionException {
+    computeReachabilitySensitivity(application);
     removeLambdaDeserializationMethods();
     collectLambdaMergingCandidates(application);
     collectStaticizerCandidates(application);
@@ -566,6 +586,14 @@
     return builder.build();
   }
 
+  private void computeReachabilitySensitivity(DexApplication application) {
+    application.classes().forEach(c -> {
+      if (c.hasReachabilitySensitiveAnnotation(options.itemFactory)) {
+        c.methods().forEach(m -> m.getMutableOptimizationInfo().setReachabilitySensitive(true));
+      }
+    });
+  }
+
   private void forEachSelectedOutliningMethod(
       ExecutorService executorService, BiConsumer<IRCode, DexEncodedMethod> consumer)
       throws ExecutionException {
@@ -812,7 +840,9 @@
       CodeRewriter.ensureDirectStringNewToInit(code);
     }
 
-    if (options.debug) {
+    boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+
+    if (isDebugMode) {
       codeRewriter.simplifyDebugLocals(code);
     }
 
@@ -867,24 +897,17 @@
       nonNullTracker.addNonNull(code);
       assert code.isConsistentSSA();
     }
-    if (options.enableInlining && inliner != null) {
+    if (!isDebugMode && options.enableInlining && inliner != null) {
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
-      assert !options.debug;
       inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
     }
 
-    // Either marked by IdentifierNameStringMarker or propagated from inlinee.
-    // Then, make it visible to IdentifierMinifier.
-    if (method.getOptimizationInfo().useIdentifierNameString()) {
-      feedback.markUseIdentifierNameString(method);
-    }
-
     if (appInfo.hasLiveness()) {
       // Reflection optimization 1. getClass() -> const-class
       codeRewriter.rewriteGetClass(code);
     }
 
-    if (!options.debug) {
+    if (!isDebugMode) {
       // TODO(jsjeon): Consider merging these into one single optimize().
       stringOptimizer.computeTrivialOperationsOnConstString(code, appInfo.dexItemFactory);
       // Reflection optimization 2. get*Name() with const-class -> const-string
@@ -894,6 +917,12 @@
       assert code.isConsistentSSA();
     }
 
+    // Either marked by IdentifierNameStringMarker or name reflection, or propagated from inlinee.
+    // Then, make it visible to IdentifierMinifier.
+    if (method.getOptimizationInfo().useIdentifierNameString()) {
+      feedback.markUseIdentifierNameString(method);
+    }
+
     if (devirtualizer != null) {
       assert code.verifyTypes(appInfo, appView, graphLense());
       devirtualizer.devirtualizeInvokeInterface(code, method.method.getHolder());
@@ -926,7 +955,7 @@
       assert code.isConsistentSSA();
     }
 
-    if (!options.debug) {
+    if (!isDebugMode) {
       codeRewriter.collectClassInitializerDefaults(method, code);
     }
     if (Log.ENABLED) {
@@ -1147,9 +1176,11 @@
   }
 
   private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    // After all the optimizations have take place, we compute whether method should be inlinedex.
+    // After all the optimizations have take place, we compute whether method should be inlined.
     ConstraintWithTarget state;
-    if (!options.enableInlining || inliner == null) {
+    if (!options.enableInlining
+        || inliner == null
+        || method.getOptimizationInfo().isReachabilitySensitive()) {
       state = ConstraintWithTarget.NEVER;
     } else {
       state = inliner.computeInliningConstraint(code, method);
@@ -1175,7 +1206,7 @@
     workaroundForwardingInitializerBug(code);
     LinearScanRegisterAllocator registerAllocator =
         new LinearScanRegisterAllocator(appInfo, code, options);
-    registerAllocator.allocateRegisters(options.debug);
+    registerAllocator.allocateRegisters();
     if (options.canHaveExceptionTargetingLoopHeaderBug()) {
       codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
     }
@@ -1227,20 +1258,116 @@
     }
   }
 
+  /**
+   * For each block, we look to see if the header matches:
+   *
+   * <pre>
+   *   pseudo-instructions*
+   *   v2 <- long-{mul,div} v0 v1
+   *   pseudo-instructions*
+   *   v5 <- long-{add,sub} v3 v4
+   * </pre>
+   *
+   * where v2 ~=~ v3 or v2 ~=~ v4 (with ~=~ being equal or an alias of) and the block is not a
+   * fallthrough target.
+   */
   private void materializeInstructionBeforeLongOperationsWorkaround(IRCode code) {
     if (!options.canHaveDex2OatLinkedListBug()) {
       return;
     }
+    DexItemFactory factory = options.itemFactory;
+    final Supplier<DexMethod> javaLangLangSignum =
+        Suppliers.memoize(
+            () ->
+                factory.createMethod(
+                    factory.createString("Ljava/lang/Long;"),
+                    factory.createString("signum"),
+                    factory.intDescriptor,
+                    new DexString[] {factory.longDescriptor}));
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator();
-      Instruction firstMaterializing =
-          it.nextUntil(IRConverter::isMaterializingInstructionOnArtArmVersionM);
-      if (needsInstructionBeforeLongOperation(firstMaterializing)) {
-        ensureInstructionBefore(code, firstMaterializing, it);
+      Instruction firstMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
+      if (!isLongMul(firstMaterializing)) {
+        continue;
+      }
+      Instruction secondMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
+      if (!isLongAddOrSub(secondMaterializing)) {
+        continue;
+      }
+      if (isFallthoughTarget(block)) {
+        continue;
+      }
+      Value outOfMul = firstMaterializing.outValue();
+      for (Value inOfAddOrSub : secondMaterializing.inValues()) {
+        if (isAliasOf(inOfAddOrSub, outOfMul)) {
+          it = block.listIterator();
+          it.nextUntil(i -> i == firstMaterializing);
+          Value longValue = firstMaterializing.inValues().get(0);
+          InvokeStatic invokeLongSignum =
+              new InvokeStatic(
+                  javaLangLangSignum.get(), null, Collections.singletonList(longValue));
+          ensureThrowingInstructionBefore(code, firstMaterializing, it, invokeLongSignum);
+          return;
+        }
       }
     }
   }
 
+  private static boolean isAliasOf(Value usedValue, Value definingValue) {
+    while (true) {
+      if (usedValue == definingValue) {
+        return true;
+      }
+      Instruction definition = usedValue.definition;
+      if (definition == null || !definition.isMove()) {
+        return false;
+      }
+      usedValue = definition.asMove().src();
+    }
+  }
+
+  private static boolean isNotPseudoInstruction(Instruction instruction) {
+    return !(instruction.isDebugInstruction() || instruction.isMove());
+  }
+
+  private static boolean isLongMul(Instruction instruction) {
+    return instruction != null
+        && instruction.isMul()
+        && instruction.asBinop().getNumericType() == NumericType.LONG
+        && instruction.outValue() != null;
+  }
+
+  private static boolean isLongAddOrSub(Instruction instruction) {
+    return instruction != null
+        && (instruction.isAdd() || instruction.isSub())
+        && instruction.asBinop().getNumericType() == NumericType.LONG;
+  }
+
+  private static boolean isFallthoughTarget(BasicBlock block) {
+    for (BasicBlock pred : block.getPredecessors()) {
+      if (pred.exit().fallthroughBlock() == block) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void ensureThrowingInstructionBefore(
+      IRCode code, Instruction addBefore, InstructionListIterator it, Instruction instruction) {
+    Instruction check = it.previous();
+    assert addBefore == check;
+    BasicBlock block = check.getBlock();
+    if (block.hasCatchHandlers()) {
+      // Split so the existing instructions retain their handlers and the new instruction has none.
+      BasicBlock split = it.split(code);
+      assert split.hasCatchHandlers();
+      assert !block.hasCatchHandlers();
+      it = block.listIterator(block.getInstructions().size() - 1);
+    }
+    instruction.setPosition(addBefore.getPosition());
+    it.add(instruction);
+  }
+
   private static void ensureInstructionBefore(
       IRCode code, Instruction addBefore, InstructionListIterator it) {
     // Force materialize a constant-zero before the long operation.
@@ -1259,33 +1386,6 @@
     it.add(fixitUser);
   }
 
-  private static boolean needsInstructionBeforeLongOperation(Instruction instruction) {
-    // The cortex fixup will only trigger on long sub and long add instructions.
-    if (!((instruction.isAdd() || instruction.isSub()) && instruction.outType().isWide())) {
-      return false;
-    }
-    // If the block with the instruction is a fallthrough block, then it can't end up being
-    // preceded by the incorrectly linked prologue/epilogue..
-    BasicBlock block = instruction.getBlock();
-    for (BasicBlock pred : block.getPredecessors()) {
-      if (pred.exit().fallthroughBlock() == block) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private static boolean isMaterializingInstructionOnArtArmVersionM(Instruction instruction) {
-    return !instruction.isDebugInstruction()
-        && !instruction.isMove()
-        && !isPossiblyNonMaterializingLongOperationOnArtArmVersionM(instruction);
-  }
-
-  private static boolean isPossiblyNonMaterializingLongOperationOnArtArmVersionM(
-      Instruction instruction) {
-    return (instruction.isMul() || instruction.isDiv()) && instruction.outType().isWide();
-  }
-
   private void printC1VisualizerHeader(DexEncodedMethod method) {
     if (printer != null) {
       printer.begin("compilation");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
index 3ef25f5..5dbdbe1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
@@ -59,7 +59,7 @@
   }
 
   @Override
-  public void markUseIdentifierNameString(DexEncodedMethod method) {
+  public synchronized void markUseIdentifierNameString(DexEncodedMethod method) {
     getOptimizationInfoForUpdating(method).markUseIdentifierNameString();
   }
 
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 01555da..7a9ec00 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
@@ -531,6 +531,7 @@
                   encodedMethod.annotations,
                   encodedMethod.parameterAnnotationsList,
                   encodedMethod.getCode());
+          newMethod.copyMetadata(encodedMethod);
           rewriter.methodMapping.put(encodedMethod.method, callTarget);
           // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
           DexCode dexCode = newMethod.getCode().asDexCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index d976c6f..98ee72c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -81,6 +81,7 @@
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
@@ -88,6 +89,7 @@
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence;
@@ -108,13 +110,13 @@
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
-import it.unimi.dsi.fastutil.ints.IntLists;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
@@ -124,6 +126,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Function;
@@ -148,8 +151,10 @@
   private final Set<DexMethod> libraryMethodsReturningReceiver;
   private final InternalOptions options;
 
-  public CodeRewriter(IRConverter converter,
-      Set<DexMethod> libraryMethodsReturningReceiver, InternalOptions options) {
+  public CodeRewriter(
+      IRConverter converter,
+      Set<DexMethod> libraryMethodsReturningReceiver,
+      InternalOptions options) {
     this.converter = converter;
     this.appInfo = converter.appInfo;
     this.options = options;
@@ -526,6 +531,198 @@
     newBlocks.forEach(blocksIterator::add);
   }
 
+  private static class Interval {
+
+    private final IntList keys = new IntArrayList();
+
+    public Interval(IntList... allKeys) {
+      assert allKeys.length > 0;
+      for (IntList keys : allKeys) {
+        assert keys.size() > 0;
+        this.keys.addAll(keys);
+      }
+    }
+
+    public int getMin() {
+      return keys.getInt(0);
+    }
+
+    public int getMax() {
+      return keys.getInt(keys.size() - 1);
+    }
+
+    public void addInterval(Interval other) {
+      assert getMax() < other.getMin();
+      keys.addAll(other.keys);
+    }
+
+    public long packedSavings(InternalOutputMode mode) {
+      long packedTargets = (long) getMax() - (long) getMin() + 1;
+      if (!Switch.canBePacked(mode, packedTargets)) {
+        return Long.MIN_VALUE + 1;
+      }
+      long sparseCost = Switch.baseSparseSize(mode) + Switch.sparsePayloadSize(mode, keys.size());
+      long packedCost = Switch.basePackedSize(mode) + Switch.packedPayloadSize(mode, packedTargets);
+      return sparseCost - packedCost;
+    }
+
+    public long estimatedSize(InternalOutputMode mode) {
+      return Switch.estimatedSize(mode, keys.toIntArray());
+    }
+  }
+
+  private Interval combineOrAddInterval(
+      List<Interval> intervals, Interval previous, Interval current) {
+    // As a first iteration, we only combine intervals if their packed size is less than their
+    // sparse counterpart. In CF we will have to add a load and a jump which add to the
+    // stack map table (1 is the size of a same entry).
+    InternalOutputMode mode = options.getInternalOutputMode();
+    int penalty = mode.isGeneratingClassFiles() ? 3 + 1 : 0;
+    if (previous == null) {
+      intervals.add(current);
+      return current;
+    }
+    Interval combined = new Interval(previous.keys, current.keys);
+    long packedSavings = combined.packedSavings(mode);
+    if (packedSavings <= 0
+        || packedSavings < previous.estimatedSize(mode) + current.estimatedSize(mode) - penalty) {
+      intervals.add(current);
+      return current;
+    } else {
+      intervals.set(intervals.size() - 1, combined);
+      return combined;
+    }
+  }
+
+  private void tryAddToBiggestSavings(
+      Set<Interval> biggestPackedSet,
+      PriorityQueue<Interval> intervals,
+      Interval toAdd,
+      int maximumNumberOfSwitches) {
+    assert !biggestPackedSet.contains(toAdd);
+    long savings = toAdd.packedSavings(options.getInternalOutputMode());
+    if (savings <= 0) {
+      return;
+    }
+    if (intervals.size() < maximumNumberOfSwitches) {
+      intervals.add(toAdd);
+      biggestPackedSet.add(toAdd);
+    } else if (savings > intervals.peek().packedSavings(options.getInternalOutputMode())) {
+      intervals.add(toAdd);
+      biggestPackedSet.add(toAdd);
+      biggestPackedSet.remove(intervals.poll());
+    }
+  }
+
+  private int sizeForKeysWrittenAsIfs(ValueType type, Collection<Integer> keys) {
+    int ifsSize = If.estimatedSize(options.getInternalOutputMode()) * keys.size();
+    // In Cf we also require a load as well (and a stack map entry)
+    if (options.getInternalOutputMode().isGeneratingClassFiles()) {
+      ifsSize += keys.size() * (3 + 1);
+    }
+    for (int k : keys) {
+      if (k != 0) {
+        ifsSize += ConstNumber.estimatedSize(options.getInternalOutputMode(), type, k);
+      }
+    }
+    return ifsSize;
+  }
+
+  private int codeUnitMargin() {
+    return options.getInternalOutputMode().isGeneratingClassFiles() ? 3 : 1;
+  }
+
+  private int findIfsForCandidates(List<Interval> newSwitches, Switch theSwitch, IntList outliers) {
+    Set<Interval> switchesToRemove = new HashSet<>();
+    InternalOutputMode mode = options.getInternalOutputMode();
+    int outliersAsIfSize = 0;
+    // The candidateForIfs is either an index to a switch that can be eliminated totally or a sparse
+    // where removing a key may produce a greater saving. It is only if keys are small in the packed
+    // switch that removing the keys makes sense (size wise).
+    for (Interval candidate : newSwitches) {
+      int maxIfBudget = 10;
+      long switchSize = candidate.estimatedSize(mode);
+      int sizeOfAllKeysAsIf = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), candidate.keys);
+      if (candidate.keys.size() <= maxIfBudget
+          && sizeOfAllKeysAsIf < switchSize - codeUnitMargin()) {
+        outliersAsIfSize += sizeOfAllKeysAsIf;
+        switchesToRemove.add(candidate);
+        outliers.addAll(candidate.keys);
+        continue;
+      }
+      // One could do something clever here, but we use a simple algorithm that use the fact that
+      // all keys are sorted in ascending order and that the smallest absolute value will give the
+      // best saving.
+      IntList candidateKeys = candidate.keys;
+      int smallestPosition = -1;
+      long smallest = Long.MAX_VALUE;
+      for (int i = 0; i < candidateKeys.size(); i++) {
+        long current = Math.abs((long) candidateKeys.getInt(i));
+        if (current < smallest) {
+          smallestPosition = i;
+          smallest = current;
+        }
+      }
+      // Add as many keys forward and backward as we have budget and we decrease in size.
+      IntList ifKeys = new IntArrayList();
+      ifKeys.add(candidateKeys.getInt(smallestPosition));
+      long previousSavings = 0;
+      long currentSavings =
+          switchSize
+              - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
+              - Switch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+      int minIndex = smallestPosition - 1;
+      int maxIndex = smallestPosition + 1;
+      while (ifKeys.size() < maxIfBudget && currentSavings > previousSavings) {
+        if (minIndex >= 0 && maxIndex < candidateKeys.size()) {
+          long valMin = Math.abs((long) candidateKeys.getInt(minIndex));
+          int valMax = Math.abs(candidateKeys.getInt(maxIndex));
+          if (valMax <= valMin) {
+            ifKeys.add(candidateKeys.getInt(maxIndex++));
+          } else {
+            ifKeys.add(candidateKeys.getInt(minIndex--));
+          }
+        } else if (minIndex >= 0) {
+          ifKeys.add(candidateKeys.getInt(minIndex--));
+        } else if (maxIndex < candidateKeys.size()) {
+          ifKeys.add(candidateKeys.getInt(maxIndex++));
+        } else {
+          // No more elements to add as if's.
+          break;
+        }
+        previousSavings = currentSavings;
+        currentSavings =
+            switchSize
+                - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
+                - Switch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+      }
+      if (previousSavings >= currentSavings) {
+        // Remove the last added key since it did not contribute to savings.
+        int lastKey = ifKeys.getInt(ifKeys.size() - 1);
+        ifKeys.removeInt(ifKeys.size() - 1);
+        if (lastKey == candidateKeys.getInt(minIndex + 1)) {
+          minIndex++;
+        } else {
+          maxIndex--;
+        }
+      }
+      // Adjust pointers into the candidate keys.
+      minIndex++;
+      maxIndex--;
+      if (ifKeys.size() > 0) {
+        int ifsSize = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys);
+        long newSwitchSize = Switch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+        if (newSwitchSize + ifsSize + codeUnitMargin() < switchSize) {
+          candidateKeys.removeElements(minIndex, maxIndex);
+          outliers.addAll(ifKeys);
+          outliersAsIfSize += ifsSize;
+        }
+      }
+    }
+    newSwitches.removeAll(switchesToRemove);
+    return outliersAsIfSize;
+  }
+
   public void rewriteSwitch(IRCode code) {
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
     while (blocksIterator.hasNext()) {
@@ -555,75 +752,91 @@
               iterator.replaceCurrentInstruction(theIf);
             }
           } else {
-            // Split keys into outliers and sequences.
-            List<IntList> sequences = new ArrayList<>();
-            IntList outliers = new IntArrayList();
+            // If there are more than 1 key, we use the following algorithm to find keys to combine.
+            // First, scan through the keys forward and combine each packed interval with the
+            // previous interval if it gives a net saving.
+            // Secondly, go through all created intervals and combine the ones without a saving into
+            // a single interval and keep a max number of packed switches.
+            // Finally, go through all intervals and check if the switch or part of the switch
+            // should be transformed to ifs.
 
-            IntList current = new IntArrayList();
+            // Phase 1: Combine packed intervals.
+            InternalOutputMode mode = options.getInternalOutputMode();
             int[] keys = theSwitch.getKeys();
+            int maxNumberOfIfsOrSwitches = 10;
+            PriorityQueue<Interval> biggestPackedSavings =
+                new PriorityQueue<>(
+                    (x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
+            Set<Interval> biggestPackedSet = new HashSet<>();
+            List<Interval> intervals = new ArrayList<>();
             int previousKey = keys[0];
-            current.add(previousKey);
+            IntList currentKeys = new IntArrayList();
+            currentKeys.add(previousKey);
+            Interval previousInterval = null;
             for (int i = 1; i < keys.length; i++) {
-              assert current.size() > 0;
-              assert current.getInt(current.size() - 1) == previousKey;
               int key = keys[i];
               if (((long) key - (long) previousKey) > 1) {
-                if (current.size() == 1) {
-                  outliers.add(previousKey);
-                } else {
-                  sequences.add(current);
+                Interval current = new Interval(currentKeys);
+                Interval added = combineOrAddInterval(intervals, previousInterval, current);
+                if (added != current && biggestPackedSet.contains(previousInterval)) {
+                  biggestPackedSet.remove(previousInterval);
+                  biggestPackedSavings.remove(previousInterval);
                 }
-                current = new IntArrayList();
+                tryAddToBiggestSavings(
+                    biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+                previousInterval = added;
+                currentKeys = new IntArrayList();
               }
-              current.add(key);
+              currentKeys.add(key);
               previousKey = key;
             }
-            if (current.size() == 1) {
-              outliers.add(previousKey);
-            } else {
-              sequences.add(current);
+            Interval current = new Interval(currentKeys);
+            Interval added = combineOrAddInterval(intervals, previousInterval, current);
+            if (added != current && biggestPackedSet.contains(previousInterval)) {
+              biggestPackedSet.remove(previousInterval);
+              biggestPackedSavings.remove(previousInterval);
+            }
+            tryAddToBiggestSavings(
+                biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+
+            // Phase 2: combine sparse intervals into a single bin.
+            // Check if we should save a space for a sparse switch, if so, remove the switch with
+            // the smallest savings.
+            if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
+                && maxNumberOfIfsOrSwitches < intervals.size()) {
+              biggestPackedSet.remove(biggestPackedSavings.poll());
+            }
+            Interval sparse = null;
+            List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
+            for (int i = 0; i < intervals.size(); i++) {
+              Interval interval = intervals.get(i);
+              if (biggestPackedSet.contains(interval)) {
+                newSwitches.add(interval);
+              } else if (sparse == null) {
+                sparse = interval;
+                newSwitches.add(sparse);
+              } else {
+                sparse.addInterval(interval);
+              }
             }
 
-            // Get the existing dex size for the payload and switch.
-            int currentSize = Switch.payloadSize(keys) + Switch.estimatedDexSize();
+            // Phase 3: at this point we are guaranteed to have the biggest saving switches
+            // in newIntervals, potentially with a switch combining the remaining intervals.
+            // Now we check to see if we can create any if's to reduce size.
+            IntList outliers = new IntArrayList();
+            int outliersAsIfSize = findIfsForCandidates(newSwitches, theSwitch, outliers);
 
-            // Never replace with more than 10 if/switch instructions.
-            if (outliers.size() + sequences.size() <= 10) {
-              // Calculate estimated size for splitting into ifs and switches.
-              long rewrittenSize = 0;
-              for (Integer outlier : outliers) {
-                if (outlier != 0) {
-                  rewrittenSize += ConstNumber.estimatedDexSize(
-                      theSwitch.value().outType(), outlier);
-                }
-                rewrittenSize += If.estimatedDexSize();
-              }
-              for (List<Integer> sequence : sequences) {
-                rewrittenSize += Switch.payloadSize(sequence);
-              }
-              rewrittenSize += Switch.estimatedDexSize() * sequences.size();
+            long newSwitchesSize = 0;
+            List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
+            for (Interval interval : newSwitches) {
+              newSwitchesSize += interval.estimatedSize(mode);
+              newSwitchSequences.add(interval.keys);
+            }
 
-              if (rewrittenSize < currentSize) {
-                convertSwitchToSwitchAndIfs(
-                    code, blocksIterator, block, iterator, theSwitch, sequences, outliers);
-              }
-            } else if (outliers.size() > 1) {
-              // Calculate estimated size for splitting into switches (packed for the sequences
-              // and sparse for the outliers).
-              long rewrittenSize = 0;
-              for (List<Integer> sequence : sequences) {
-                rewrittenSize += Switch.payloadSize(sequence);
-              }
-              rewrittenSize += Switch.payloadSize(outliers);
-              rewrittenSize += Switch.estimatedDexSize() * (sequences.size() + 1);
-
-              if (rewrittenSize < currentSize) {
-                // Create a copy to not modify sequences.
-                List<IntList> seqs = new ArrayList<>(sequences);
-                seqs.add(outliers);
-                convertSwitchToSwitchAndIfs(
-                    code, blocksIterator, block, iterator, theSwitch, seqs, IntLists.EMPTY_LIST);
-              }
+            long currentSize = Switch.estimatedSize(mode, theSwitch.getKeys());
+            if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
+              convertSwitchToSwitchAndIfs(
+                  code, blocksIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
             }
           }
         }
@@ -1320,15 +1533,15 @@
       return;
     }
     AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
+    Set<Value> needToWidenValues = Sets.newIdentityHashSet();
+    Set<Value> needToNarrowValues = Sets.newIdentityHashSet();
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         if (invoke.outValue() != null && !invoke.outValue().hasLocalInfo()) {
-          boolean isLibraryMethodReturningReceiver =
-              libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod());
-          if (isLibraryMethodReturningReceiver) {
+          if (libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod())) {
             if (checkArgumentType(invoke, invoke.getInvokedMethod(), 0)) {
               invoke.outValue().replaceUsers(invoke.arguments().get(0));
               invoke.setOutValue(null);
@@ -1343,11 +1556,18 @@
               if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
                 int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
                 // Replace the out value of the invoke with the argument and ignore the out value.
-                if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
-                    argumentIndex)) {
+                if (argumentIndex != -1
+                    && checkArgumentType(invoke, target.method, argumentIndex)) {
                   Value argument = invoke.arguments().get(argumentIndex);
-                  assert invoke.outValue().verifyCompatible(argument.outType());
-                  invoke.outValue().replaceUsers(argument);
+                  Value outValue = invoke.outValue();
+                  assert outValue.verifyCompatible(argument.outType());
+                  if (argument.getTypeLattice().lessThanOrEqual(
+                      outValue.getTypeLattice(), appInfo)) {
+                    needToNarrowValues.addAll(outValue.affectedValues());
+                  } else {
+                    needToWidenValues.addAll(outValue.affectedValues());
+                  }
+                  outValue.replaceUsers(argument);
                   invoke.setOutValue(null);
                 }
               }
@@ -1356,6 +1576,17 @@
         }
       }
     }
+    if (!needToWidenValues.isEmpty() || !needToNarrowValues.isEmpty()) {
+      TypeAnalysis analysis = new TypeAnalysis(appInfo, code.method);
+      // If out value of invoke < argument (e.g., losing non-null info), widen users type.
+      if (!needToWidenValues.isEmpty()) {
+        analysis.widening(needToWidenValues);
+      }
+      // Otherwise, i.e., argument has more precise types, narrow users type.
+      if (!needToNarrowValues.isEmpty()) {
+        analysis.narrowing(needToNarrowValues);
+      }
+    }
     assert code.isConsistentGraph();
   }
 
@@ -1515,7 +1746,7 @@
   }
 
   // Check if the static put is a constant derived from the class holding the method.
-  // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
+  // This checks for java.lang.Class.get*Name.
   private boolean isClassNameConstantOf(DexClass clazz, StaticPut put) {
     if (put.getField().type != dexItemFactory.stringType) {
       return false;
@@ -1529,8 +1760,7 @@
   private boolean isClassNameConstantOf(DexClass clazz, Instruction instruction) {
     if (instruction.isInvokeVirtual()) {
       InvokeVirtual invoke = instruction.asInvokeVirtual();
-      if ((invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName
-          || invoke.getInvokedMethod() == dexItemFactory.classMethods.getName)
+      if (dexItemFactory.classMethods.isReflectiveNameLookup(invoke.getInvokedMethod())
           && !invoke.inValues().get(0).isPhi()
           && invoke.inValues().get(0).definition.isConstClass()
           && invoke.inValues().get(0).definition.asConstClass().getValue() == clazz.type) {
@@ -1574,7 +1804,9 @@
             } else if (inValue.isDexItemBasedConstString()) {
               DexItemBasedConstString cnst =
                   inValue.getConstInstruction().asDexItemBasedConstString();
-              encodedField.setStaticValue(new DexItemBasedValueString(cnst.getItem()));
+              assert !cnst.getClassNameComputationInfo().needsToComputeClassName();
+              encodedField.setStaticValue(
+                  new DexItemBasedValueString(cnst.getItem(), cnst.getClassNameComputationInfo()));
             } else {
               assert false;
             }
@@ -1675,6 +1907,9 @@
               }
               DexField field = put.getField();
               if (clazz.definesStaticField(field)) {
+                if (put.inValue().isDexItemBasedConstStringThatNeedsToComputeClassName()) {
+                  continue;
+                }
                 if (put.inValue().isConstant()) {
                   if ((field.type.isClassType() || field.type.isArrayType())
                       && put.inValue().isZero()) {
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 bad5456..be94302 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
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -427,7 +428,8 @@
       assert IteratorUtils.peekNext(blockIterator) == block;
 
       // Kick off the tracker to add non-null IRs only to the inlinee blocks.
-      new NonNullTracker(appView.appInfo())
+      new NonNullTracker(appView.appInfo(),
+          IRConverter.libraryMethodsReturningNonNull(appView.dexItemFactory()))
           .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
       assert !blockIterator.hasNext();
 
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 63d0242..3c27a60 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
@@ -647,7 +647,7 @@
                 context.accessFlags.unsetBridge();
               }
 
-              context.copyMetadataFromInlinee(target);
+              context.copyMetadata(target);
               code.copyMetadataFromInlinee(inlinee.code);
             }
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 29c7979..b82c743 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -178,7 +178,12 @@
           DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
           if (target != null) {
             if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
-              invoke.outValue().markNeverNull();
+              Value knownToBeNonNullValue = invoke.outValue();
+              knownToBeNonNullValue.markNeverNull();
+              TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+              assert typeLattice.isNullable() && typeLattice.isReference();
+              knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+              affectedValues.addAll(knownToBeNonNullValue.affectedValues());
             }
             if (target.getOptimizationInfo().returnsConstant()) {
               long constant = target.getOptimizationInfo().getReturnedConstant();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 77339e0..1e62c36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -34,9 +35,11 @@
 public class NonNullTracker {
 
   private final AppInfo appInfo;
+  private final Set<DexMethod> libraryMethodsReturningNonNull;
 
-  public NonNullTracker(AppInfo appInfo) {
+  public NonNullTracker(AppInfo appInfo, Set<DexMethod> libraryMethodsReturningNonNull) {
     this.appInfo = appInfo;
+    this.libraryMethodsReturningNonNull = libraryMethodsReturningNonNull;
   }
 
   @VisibleForTesting
@@ -76,102 +79,48 @@
   public void addNonNullInPart(
       IRCode code, ListIterator<BasicBlock> blockIterator, Predicate<BasicBlock> blockTester) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
+    Set<Value> knownToBeNonNullValues = Sets.newIdentityHashSet();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (!blockTester.test(block)) {
         continue;
       }
-      // Add non-null after instructions that implicitly indicate receiver/array is not null.
+      // Add non-null after
+      // 1) invocations that call non-overridable library methods that are known to return non null.
+      // 2) instructions that implicitly indicate receiver/array is not null.
+      // TODO(b/71500340): We can add non-null IRs for known-to-be-non-null parameters here.
       InstructionListIterator iterator = block.listIterator();
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
-        if (!throwsOnNullInput(current)) {
-          continue;
-        }
-        Value knownToBeNonNullValue = getNonNullInput(current);
-        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-        // Avoid adding redundant non-null instruction.
-        if (knownToBeNonNullValue.isNeverNull() || !isNonNullCandidate(typeLattice)) {
+        if (current.isInvokeMethod()
+            && libraryMethodsReturningNonNull.contains(
+                current.asInvokeMethod().getInvokedMethod())) {
+          Value knownToBeNonNullValue = current.outValue();
+          // Avoid adding redundant non-null instruction.
           // Otherwise, we will have something like:
           // non_null_rcv <- non-null(rcv)
           // ...
           // another_rcv <- non-null(non_null_rcv)
-          continue;
-        }
-        // First, if the current block has catch handler, split into two blocks, e.g.,
-        //
-        // ...x
-        // invoke(rcv, ...)
-        // ...y
-        //
-        //   ~>
-        //
-        // ...x
-        // invoke(rcv, ...)
-        // goto A
-        //
-        // A: ...y // blockWithNonNullInstruction
-        boolean split = block.hasCatchHandlers();
-        BasicBlock blockWithNonNullInstruction =
-            split ? iterator.split(code, blockIterator) : block;
-
-        // Find all users of the original value that are dominated by either the current block
-        // or the new split-off block. Since NPE can be explicitly caught, nullness should be
-        // propagated through dominance.
-        Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
-        Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
-        Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
-        DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-        Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
-        for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
-          dominatedBlocks.add(dominatee);
-          InstructionListIterator dominateeIterator = dominatee.listIterator();
-          if (dominatee == blockWithNonNullInstruction && !split) {
-            // In the block where the non null instruction will be inserted, skip instructions up
-            // to and including the insertion point.
-            dominateeIterator.nextUntil(instruction -> instruction == current);
-          }
-          while (dominateeIterator.hasNext()) {
-            Instruction potentialUser = dominateeIterator.next();
-            if (users.contains(potentialUser)) {
-              dominatedUsers.add(potentialUser);
-            }
+          if (knownToBeNonNullValue != null && isNonNullCandidate(knownToBeNonNullValue)) {
+            knownToBeNonNullValues.add(knownToBeNonNullValue);
           }
         }
-        for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
-          IntList dominatedPredecessorIndexes =
-              findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
-          if (!dominatedPredecessorIndexes.isEmpty()) {
-            dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
+        if (throwsOnNullInput(current)) {
+          Value knownToBeNonNullValue = getNonNullInput(current);
+          if (isNonNullCandidate(knownToBeNonNullValue)) {
+            knownToBeNonNullValues.add(knownToBeNonNullValue);
           }
         }
-
-        // Only insert non-null instruction if it is ever used.
-        if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
-          // Add non-null fake IR, e.g.,
-          // ...x
-          // invoke(rcv, ...)
-          // goto A
-          // ...
-          // A: non_null_rcv <- non-null(rcv)
-          // ...y
-          Value nonNullValue = code.createValue(
-              typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
-          affectedValues.addAll(knownToBeNonNullValue.affectedValues());
-          NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
-          nonNull.setPosition(current.getPosition());
-          if (blockWithNonNullInstruction != block) {
-            // If we split, add non-null IR on top of the new split block.
-            blockWithNonNullInstruction.listIterator().add(nonNull);
-          } else {
-            // Otherwise, just add it to the current block at the position of the iterator.
-            iterator.add(nonNull);
-          }
-
-          // Replace all users of the original value that are dominated by either the current
-          // block or the new split-off block.
-          knownToBeNonNullValue.replaceSelectiveUsers(
-              nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+        if (!knownToBeNonNullValues.isEmpty()) {
+          addNonNullForValues(
+              code,
+              blockIterator,
+              block,
+              iterator,
+              current,
+              knownToBeNonNullValues,
+              affectedValues);
+          knownToBeNonNullValues.clear();
         }
       }
 
@@ -200,9 +149,8 @@
         // ...
         If theIf = block.exit().asIf();
         Value knownToBeNonNullValue = theIf.inValues().get(0);
-        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-        // Avoid adding redundant non-null instruction (or non-null of non-object types).
-        if (!knownToBeNonNullValue.isNeverNull() && isNonNullCandidate(typeLattice)) {
+        // Avoid adding redundant non-null instruction.
+        if (isNonNullCandidate(knownToBeNonNullValue)) {
           BasicBlock target = theIf.targetFromNonNullObject();
           // Ignore uncommon empty blocks.
           if (!target.isEmpty()) {
@@ -228,6 +176,7 @@
               }
               // Avoid adding a non-null for the value without meaningful users.
               if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+                TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
                 Value nonNullValue = code.createValue(
                     typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
                 affectedValues.addAll(knownToBeNonNullValue.affectedValues());
@@ -249,6 +198,93 @@
     }
   }
 
+  private void addNonNullForValues(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      BasicBlock block,
+      InstructionListIterator iterator,
+      Instruction current,
+      Set<Value> knownToBeNonNullValues,
+      Set<Value> affectedValues) {
+    // First, if the current block has catch handler, split into two blocks, e.g.,
+    //
+    // ...x
+    // invoke(rcv, ...)
+    // ...y
+    //
+    //   ~>
+    //
+    // ...x
+    // invoke(rcv, ...)
+    // goto A
+    //
+    // A: ...y // blockWithNonNullInstruction
+    boolean split = block.hasCatchHandlers();
+    BasicBlock blockWithNonNullInstruction = split ? iterator.split(code, blockIterator) : block;
+    DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+
+    for (Value knownToBeNonNullValue : knownToBeNonNullValues) {
+      // Find all users of the original value that are dominated by either the current block
+      // or the new split-off block. Since NPE can be explicitly caught, nullness should be
+      // propagated through dominance.
+      Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
+      Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+      Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
+      Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
+      for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
+        dominatedBlocks.add(dominatee);
+        InstructionListIterator dominateeIterator = dominatee.listIterator();
+        if (dominatee == blockWithNonNullInstruction && !split) {
+          // In the block where the non null instruction will be inserted, skip instructions up to
+          // and including the insertion point.
+          dominateeIterator.nextUntil(instruction -> instruction == current);
+        }
+        while (dominateeIterator.hasNext()) {
+          Instruction potentialUser = dominateeIterator.next();
+          if (users.contains(potentialUser)) {
+            dominatedUsers.add(potentialUser);
+          }
+        }
+      }
+      for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
+        IntList dominatedPredecessorIndexes =
+            findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
+        if (!dominatedPredecessorIndexes.isEmpty()) {
+          dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
+        }
+      }
+
+      // Only insert non-null instruction if it is ever used.
+      if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+        // Add non-null fake IR, e.g.,
+        // ...x
+        // invoke(rcv, ...)
+        // goto A
+        // ...
+        // A: non_null_rcv <- non-null(rcv)
+        // ...y
+        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+        Value nonNullValue =
+            code.createValue(typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
+        affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+        NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
+        nonNull.setPosition(current.getPosition());
+        if (blockWithNonNullInstruction != block) {
+          // If we split, add non-null IR on top of the new split block.
+          blockWithNonNullInstruction.listIterator().add(nonNull);
+        } else {
+          // Otherwise, just add it to the current block at the position of the iterator.
+          iterator.add(nonNull);
+        }
+
+        // Replace all users of the original value that are dominated by either the current block
+        // or the new split-off block.
+        knownToBeNonNullValue.replaceSelectiveUsers(
+            nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+      }
+    }
+  }
+
   private IntList findDominatedPredecessorIndexesInPhi(
       Phi user, Value knownToBeNonNullValue, Set<BasicBlock> dominatedBlocks) {
     assert user.getOperands().contains(knownToBeNonNullValue);
@@ -274,10 +310,13 @@
     return predecessorIndexes;
   }
 
-  private boolean isNonNullCandidate(TypeLatticeElement typeLattice) {
+  private boolean isNonNullCandidate(Value knownToBeNonNullValue) {
+    TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
     return
+        // redundant
+        !knownToBeNonNullValue.isNeverNull()
         // v <- non-null NULL ?!
-        !typeLattice.isNull()
+        && !typeLattice.isConstantNull()
         // v <- non-null known-to-be-non-null // redundant
         && typeLattice.isNullable()
         // e.g., v <- non-null INT ?!
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 c245c25..f76769f 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
@@ -71,7 +71,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.BiConsumer;
-import org.objectweb.asm.Opcodes;
 
 /**
  * Support class for implementing outlining (i.e. extracting common code patterns as methods).
@@ -1301,10 +1300,6 @@
             direct,
             DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
             options.itemFactory.getSkipNameValidationForTesting());
-    if (options.isGeneratingClassFiles()) {
-      // All program classes must have a class-file version. Use Java 6.
-      clazz.setClassFileVersion(Opcodes.V1_6);
-    }
     return clazz;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
new file mode 100644
index 0000000..ecd3a7a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -0,0 +1,117 @@
+// 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.ir.optimize;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getCanonicalNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption;
+import com.google.common.base.Strings;
+
+public class ReflectionOptimizer {
+
+  public static class ClassNameComputationInfo {
+    public enum ClassNameComputationOption {
+      NONE,
+      NAME,           // getName()
+      TYPE_NAME,      // getTypeName()
+      CANONICAL_NAME, // getCanonicalName()
+      SIMPLE_NAME;    // getSimpleName()
+
+      boolean needsToComputeClassName() {
+        return this != NONE;
+      }
+
+      boolean needsToRegisterTypeReference() {
+        return this == SIMPLE_NAME;
+      }
+    }
+
+    private static final ClassNameComputationInfo DEFAULT_INSTANCE =
+        new ClassNameComputationInfo(ClassNameComputationOption.NONE, 0);
+
+    final ClassNameComputationOption classNameComputationOption;
+    final int arrayDepth;
+
+    public ClassNameComputationInfo(
+        ClassNameComputationOption classNameComputationOption, int arrayDepth) {
+      this.classNameComputationOption = classNameComputationOption;
+      this.arrayDepth = arrayDepth;
+    }
+
+    public static ClassNameComputationInfo none() {
+      return DEFAULT_INSTANCE;
+    }
+
+    public boolean needsToComputeClassName() {
+      return classNameComputationOption.needsToComputeClassName();
+    }
+
+    public boolean needsToRegisterTypeReference() {
+      return classNameComputationOption.needsToRegisterTypeReference();
+    }
+  }
+
+  public static String computeClassName(
+      DexString descriptor, DexClass holder, ClassNameComputationInfo classNameComputationInfo) {
+    return computeClassName(
+        descriptor.toString(),
+        holder,
+        classNameComputationInfo.classNameComputationOption,
+        classNameComputationInfo.arrayDepth);
+  }
+
+  public static String computeClassName(
+      String descriptor,
+      DexClass holder,
+      ClassNameComputationOption classNameComputationOption,
+      int arrayDepth) {
+    String name;
+    switch (classNameComputationOption) {
+      case NAME:
+        name = getClassNameFromDescriptor(descriptor);
+        if (arrayDepth > 0) {
+          name = Strings.repeat("[", arrayDepth) + "L" + name + ";";
+        }
+        break;
+      case TYPE_NAME:
+        // TODO(b/119426668): desugar Type#getTypeName
+        throw new Unreachable("Type#getTypeName not supported yet");
+        // name = getClassNameFromDescriptor(descriptor);
+        // if (arrayDepth > 0) {
+        //   name = name + Strings.repeat("[]", arrayDepth);
+        // }
+        // break;
+      case CANONICAL_NAME:
+        name = getCanonicalNameFromDescriptor(descriptor);
+        if (arrayDepth > 0) {
+          name = name + Strings.repeat("[]", arrayDepth);
+        }
+        break;
+      case SIMPLE_NAME:
+        assert holder != null;
+        boolean renamed = !descriptor.equals(holder.type.toDescriptorString());
+        boolean needsToRetrieveInnerName = holder.isMemberClass() || holder.isLocalClass();
+        if (!renamed && needsToRetrieveInnerName) {
+          name = holder.getInnerClassAttributeForThisClass().getInnerName().toString();
+        } else {
+          name = getSimpleClassNameFromDescriptor(descriptor);
+        }
+        if (arrayDepth > 0) {
+          name = name + Strings.repeat("[]", arrayDepth);
+        }
+        break;
+      default:
+        throw new Unreachable(
+            "Unexpected ClassNameComputationOption: '" + classNameComputationOption + "'");
+    }
+    return name;
+  }
+
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index a55015d..3ba9289 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -249,8 +249,10 @@
     instructionIterator.previous();
 
     // Unlink all blocks that are dominated by successor.
-    DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-    blocksToBeRemoved.addAll(block.unlink(normalSuccessorBlock, dominatorTree));
+    {
+      DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+      blocksToBeRemoved.addAll(block.unlink(normalSuccessorBlock, dominatorTree));
+    }
 
     // Insert constant null before the instruction.
     instructionIterator.previous();
@@ -284,6 +286,9 @@
               return;
             }
             if (!appView.dexItemFactory().npeType.isSubtypeOf(guard, appView.appInfo())) {
+              // TODO(christofferqa): Consider updating previous dominator tree instead of
+              // rebuilding it from scratch.
+              DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
               blocksToBeRemoved.addAll(block.unlink(target, dominatorTree));
             }
           });
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
index a808133..2d4bb17 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/BasicBlockMuncher.java
@@ -25,8 +25,8 @@
         new StoreSequenceLoadPeephole(),
         new StoreLoadPeephole(),
         new LoadLoadDupPeephole(),
-        new DupDupDupPeephole());
-
+        new DupDupDupPeephole(),
+        new StoreLoadToDupStorePeephole());
   }
 
   public static void optimize(IRCode code) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreLoadToDupStorePeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreLoadToDupStorePeephole.java
new file mode 100644
index 0000000..a0f1c8c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreLoadToDupStorePeephole.java
@@ -0,0 +1,112 @@
+// 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.ir.optimize.peepholes;
+
+import com.android.tools.r8.ir.code.Dup;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Load;
+import com.android.tools.r8.ir.code.StackValue;
+import com.android.tools.r8.ir.code.StackValues;
+import com.android.tools.r8.ir.code.Store;
+import com.android.tools.r8.ir.code.Value;
+import java.util.List;
+
+/**
+ * Peephole that looks for the following pattern:
+ *
+ * <pre>
+ * Store                v0 <- sa
+ * Load                 sb <- v0   # where v0 has multiple users
+ * zero or more Dup/Dup2
+ * </pre>
+ *
+ * and replace it with
+ *
+ * <pre>
+ * zero or more Dup/Dup2
+ * Dup            [sb, sc] <- sa
+ * Store                v0 <- sc
+ * </pre>
+ */
+public class StoreLoadToDupStorePeephole implements BasicBlockPeephole {
+
+  private final Point storeExp = new Point(PeepholeHelper.withoutLocalInfo(Instruction::isStore));
+  private final Point loadExp = new Point(Instruction::isLoad);
+  private final Wildcard dupsExp = new Wildcard(i -> i.isDup() || i.isDup2());
+
+  private final PeepholeLayout layout = PeepholeLayout.lookForward(storeExp, loadExp, dupsExp);
+
+  @Override
+  public boolean match(InstructionListIterator it) {
+    Match match = layout.test(it);
+    if (match == null) {
+      return false;
+    }
+    Store oldStore = storeExp.get(match).asStore();
+    Load load = loadExp.get(match).asLoad();
+    if (load.src() != oldStore.outValue() || oldStore.outValue().numberOfAllUsers() <= 1) {
+      return false;
+    }
+    List<Instruction> dups = dupsExp.get(match);
+    Instruction lastDup = dups.isEmpty() ? null : dups.get(dups.size() - 1);
+    StackValue oldStoreSrc = (StackValue) oldStore.src();
+    Value loadOut = load.swapOutValue(null);
+    if (lastDup == null) {
+      // Replace
+      //     Store                     v0 <- oldStoreSrc
+      //     Load                 loadOut <- v0
+      // with
+      //     Dup   [loadOut, newStoreSrc] <- oldStoreSrc
+      //     Store                     v0 <- newStoreSrc
+
+      StackValue newStoreSrc = oldStoreSrc.duplicate(oldStoreSrc.getHeight() + 1);
+      Dup dup = new Dup((StackValue) loadOut, newStoreSrc, oldStoreSrc);
+      dup.setPosition(oldStore.getPosition());
+      oldStore.replaceValue(0, newStoreSrc);
+      it.add(dup);
+      PeepholeHelper.resetPrevious(it, 2);
+      it.removeOrReplaceByDebugLocalRead();
+    } else {
+      // Replace
+      //     Store                         storeOut <- oldStoreSrc
+      //     Load                           loadOut <- storeOut
+      //     Dup                           [sa, sb] <- loadOut
+      //     (Dup/Dup2)*
+      //     Dup/Dup2           [..., topAfterDups] <- [...]           # lastDup
+      // with
+      //     Dup                           [sa, sb] <- oldStoreSrc
+      //     (Dup/Dup2)*
+      //     Dup/Dup2        [..., newTopAfterDups] <- [...]           # lastDup
+      //     Dup        [topAfterDups, newStoreSrc] <- newTopAfterDups
+      //     Store                         storeOut <- newStoreSrc
+      Value storeOut = oldStore.swapOutValue(null);
+      it.removeOrReplaceByDebugLocalRead(); // Remove Store.
+      it.next();
+      it.removeOrReplaceByDebugLocalRead(); // Remove Load.
+      assert dups.get(0).isDup() && dups.get(0).inValues().get(0) == loadOut;
+      dups.get(0).replaceValue(0, oldStoreSrc);
+      StackValues lastDupOut = (StackValues) lastDup.outValue();
+      StackValue topAfterDups = lastDupOut.getStackValues()[lastDupOut.getStackValues().length - 1];
+      StackValue newTopAfterDups = topAfterDups.duplicate(topAfterDups.getHeight());
+      lastDupOut.getStackValues()[lastDupOut.getStackValues().length - 1] = newTopAfterDups;
+      newTopAfterDups.definition = lastDup;
+      StackValue newStoreSrc = oldStoreSrc.duplicate(topAfterDups.getHeight() + 1);
+      Dup newDup = new Dup(topAfterDups, newStoreSrc, newTopAfterDups);
+      newDup.setPosition(lastDup.getPosition());
+      PeepholeHelper.resetPrevious(it, dups.size());
+      it.add(newDup);
+      Store newStore = new Store(storeOut, newStoreSrc);
+      newStore.setPosition(lastDup.getPosition());
+      it.add(newStore);
+    }
+    return true;
+  }
+
+  @Override
+  public boolean resetAfterMatch() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 9fec63a..782951d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -4,9 +4,10 @@
 package com.android.tools.r8.ir.optimize.string;
 
 import static com.android.tools.r8.ir.optimize.CodeRewriter.removeOrReplaceByDebugLocalWrite;
-import static com.android.tools.r8.utils.DescriptorUtils.getCanonicalNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.CANONICAL_NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.SIMPLE_NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.computeClassName;
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -18,20 +19,21 @@
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.base.Strings;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public class StringOptimizer {
 
-  final ThrowingInfo throwingInfo;
+  private final ThrowingInfo throwingInfo;
 
   public StringOptimizer(InternalOptions options) {
     throwingInfo = options.isGeneratingClassFiles()
@@ -43,6 +45,9 @@
   // boolean String#startsWith(str)
   // boolean String#endsWith(str)
   // boolean String#contains(str)
+  // boolean String#equals(str)
+  // boolean String#equalsIgnoreCase(str)
+  // boolean String#contentEquals(str)
   public void computeTrivialOperationsOnConstString(IRCode code, DexItemFactory factory) {
     if (!code.hasConstString) {
       return;
@@ -67,6 +72,12 @@
         operatorWithString = (rcv, arg) -> rcv.startsWith(arg) ? 1 : 0;
       } else if (invokedMethod == factory.stringMethods.endsWith) {
         operatorWithString = (rcv, arg) -> rcv.endsWith(arg) ? 1 : 0;
+      } else if (invokedMethod == factory.stringMethods.equals) {
+        operatorWithString = (rcv, arg) -> rcv.equals(arg) ? 1 : 0;
+      } else if (invokedMethod == factory.stringMethods.equalsIgnoreCase) {
+        operatorWithString = (rcv, arg) -> rcv.equalsIgnoreCase(arg) ? 1 : 0;
+      } else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
+        operatorWithString = (rcv, arg) -> rcv.contentEquals(arg) ? 1 : 0;
       }
       if (operatorWithNoArg == null && operatorWithString == null) {
         continue;
@@ -104,6 +115,7 @@
 
   // Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
   public void rewriteClassGetName(IRCode code, AppInfo appInfo) {
+    boolean markUseIdentifierNameString = false;
     InstructionIterator it = code.instructionIterator();
     while (it.hasNext()) {
       Instruction instr = it.next();
@@ -147,29 +159,18 @@
         continue;
       }
 
+      DexItemBasedConstString deferred = null;
       String name = null;
       if (invokedMethod == appInfo.dexItemFactory.classMethods.getName) {
         if (code.options.enableMinification) {
-          // TODO(b/118536394): Add support minification and pinning.
-          //   May need store array depth on DexItemBasedConstString.
-          //   May need enum on DexItemBasedConstString to distinguish name computation.
-          continue;
-        }
-        name = getClassNameFromDescriptor(baseType.toDescriptorString());
-        if (arrayDepth > 0) {
-          name = Strings.repeat("[", arrayDepth) + "L" + name + ";";
+          deferred = new DexItemBasedConstString(
+              invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
+        } else {
+          name = computeClassName(baseType.toDescriptorString(), holder, NAME, arrayDepth);
         }
       } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getTypeName) {
         // TODO(b/119426668): desugar Type#getTypeName
         continue;
-        // if (code.options.enableMinification) {
-        //   // TODO(b/118536394): Add support minification and pinning.
-        //   continue;
-        // }
-        // name = getClassNameFromDescriptor(baseType.toDescriptorString());
-        // if (arrayDepth > 0) {
-        //   name = name + Strings.repeat("[]", arrayDepth);
-        // }
       } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getCanonicalName) {
         // Always returns null if the target type is local or anonymous class.
         if (holder.isLocalClass() || holder.isAnonymousClass()) {
@@ -177,12 +178,14 @@
           it.replaceCurrentInstruction(constNull);
         } else {
           if (code.options.enableMinification) {
-            // TODO(b/118536394): Add support minification and pinning.
-            continue;
-          }
-          name = getCanonicalNameFromDescriptor(baseType.toDescriptorString());
-          if (arrayDepth > 0) {
-            name = name + Strings.repeat("[]", arrayDepth);
+            deferred =
+                new DexItemBasedConstString(
+                    invoke.outValue(),
+                    baseType,
+                    new ClassNameComputationInfo(CANONICAL_NAME, arrayDepth));
+          } else {
+            name = computeClassName(
+                baseType.toDescriptorString(), holder, CANONICAL_NAME, arrayDepth);
           }
         }
       } else if (invokedMethod == appInfo.dexItemFactory.classMethods.getSimpleName) {
@@ -191,16 +194,10 @@
           name = "";
         } else {
           if (code.options.enableMinification) {
-            // TODO(b/118536394): Add support minification and pinning.
-            continue;
-          }
-          if (holder.isMemberClass() || holder.isLocalClass()) {
-            name = holder.getInnerClassAttributeForThisClass().getInnerName().toString();
+            deferred = new DexItemBasedConstString(
+                invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
           } else {
-            name = getSimpleClassNameFromDescriptor(baseType.toDescriptorString());
-          }
-          if (arrayDepth > 0) {
-            name = name + Strings.repeat("[]", arrayDepth);
+            name = computeClassName(baseType.toDescriptorString(), holder, SIMPLE_NAME, arrayDepth);
           }
         }
       }
@@ -210,8 +207,14 @@
         ConstString constString =
             new ConstString(stringValue, appInfo.dexItemFactory.createString(name), throwingInfo);
         it.replaceCurrentInstruction(constString);
+      } else if (deferred != null) {
+        it.replaceCurrentInstruction(deferred);
+        markUseIdentifierNameString = true;
       }
     }
+    if (markUseIdentifierNameString) {
+      code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
+    }
   }
 
   // String#valueOf(null) -> "null"
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 880a674..c971a14 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
@@ -199,7 +199,7 @@
    * Perform register allocation for the IRCode.
    */
   @Override
-  public void allocateRegisters(boolean debug) {
+  public void allocateRegisters() {
     // There are no linked values prior to register allocation.
     assert noLinkedValues();
     assert code.isConsistentSSA();
@@ -216,7 +216,11 @@
       Log.debug(this.getClass(), toString());
     }
     assert registersUsed() == 0 || unusedRegisters != null;
-    if (debug) {
+    // Even if the method is reachability sensitive, we do not compute debug information after
+    // register allocation. We just treat the method as being in debug mode in order to keep
+    // locals alive for their entire live range. In release mode the liveness is all that matters
+    // and we do not actually want locals information in the output.
+    if (options.debug) {
       computeDebugInfo(blocks);
     }
     clearUserInfo();
@@ -1612,9 +1616,11 @@
     // Set all free positions for possible registers to max integer.
     RegisterPositions freePositions = new RegisterPositions(registerConstraint + 1);
 
-    if (options.debug && !code.method.accessFlags.isStatic()) {
-      // If we are generating debug information, we pin the this value register since the
-      // debugger expects to always be able to find it in the input register.
+    if ((options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+        && !code.method.accessFlags.isStatic()) {
+      // If we are generating debug information or if the method is reachability sensitive,
+      // we pin the this value register. The debugger expects to always be able to find it in
+      // the input register.
       assert numberOfArgumentRegisters > 0;
       assert firstArgumentValue != null && firstArgumentValue.requiredRegisters() == 1;
       freePositions.set(0, 0);
@@ -2620,7 +2626,9 @@
             }
           }
         }
-        if (options.debug) {
+        if (options.debug || code.method.getOptimizationInfo().isReachabilitySensitive()) {
+          // In debug mode, or if the method is reachability sensitive, extend the live range
+          // to cover the full scope of a local variable (encoded as debug values).
           int number = instruction.getNumber();
           for (Value use : instruction.getDebugValues()) {
             assert use.needsRegister();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index ba83787..1314b3f 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -9,7 +9,7 @@
 import java.util.List;
 
 public interface RegisterAllocator {
-  void allocateRegisters(boolean debug);
+  void allocateRegisters();
   int registersUsed();
   int getRegisterForValue(Value value, int instructionNumber);
   int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 65bb387..0505cb2 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -150,10 +150,10 @@
       writeField(field, writer);
     }
     for (DexEncodedMethod method : clazz.directMethods()) {
-      writeMethod(method, writer, defaults);
+      writeMethod(method, writer, defaults, version);
     }
     for (DexEncodedMethod method : clazz.virtualMethods()) {
-      writeMethod(method, writer, defaults);
+      writeMethod(method, writer, defaults, version);
     }
     writer.visitEnd();
 
@@ -172,7 +172,7 @@
   }
 
   private int getClassFileVersion(DexProgramClass clazz) {
-    int version = clazz.getClassFileVersion();
+    int version = clazz.hasClassFileVersion() ? clazz.getInitialClassFileVersion() : 50;
     for (DexEncodedMethod method : clazz.directMethods()) {
       version = Math.max(version, method.getClassFileVersion());
     }
@@ -258,7 +258,10 @@
   }
 
   private void writeMethod(
-      DexEncodedMethod method, ClassWriter writer, ImmutableMap<DexString, DexValue> defaults) {
+      DexEncodedMethod method,
+      ClassWriter writer,
+      ImmutableMap<DexString, DexValue> defaults,
+      int classFileVersion) {
     int access = method.accessFlags.getAsCfAccessFlags();
     String name = namingLens.lookupName(method.method).toString();
     String desc = method.descriptor(namingLens);
@@ -275,7 +278,7 @@
     writeAnnotations(visitor::visitAnnotation, method.annotations.annotations);
     writeParameterAnnotations(visitor, method.parameterAnnotationsList);
     if (!method.shouldNotHaveCode()) {
-      writeCode(method.getCode(), visitor);
+      writeCode(method.getCode(), visitor, options, classFileVersion);
     }
     visitor.visitEnd();
   }
@@ -371,13 +374,14 @@
     }
   }
 
-  private void writeCode(Code code, MethodVisitor visitor) {
+  private void writeCode(
+      Code code, MethodVisitor visitor, InternalOptions options, int classFileVersion) {
     if (code.isJarCode()) {
       assert namingLens.isIdentityLens();
       code.asJarCode().writeTo(visitor);
     } else {
       assert code.isCfCode();
-      code.asCfCode().write(visitor, namingLens);
+      code.asCfCode().write(visitor, namingLens, options, classFileVersion);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 6928d3e..61695f1 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.computeClassName;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 
 import com.android.tools.r8.cf.code.CfConstString;
@@ -23,7 +24,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardClassFilter;
-import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.List;
 import java.util.Map;
 
@@ -36,23 +36,19 @@
   private final AppInfoWithLiveness appInfo;
   private final ProguardClassFilter adaptClassStrings;
   private final NamingLens lens;
-  private final Object2BooleanMap<DexReference> identifierNameStrings;
 
   IdentifierMinifier(
       AppInfoWithLiveness appInfo, ProguardClassFilter adaptClassStrings, NamingLens lens) {
     this.appInfo = appInfo;
     this.adaptClassStrings = adaptClassStrings;
     this.lens = lens;
-    this.identifierNameStrings = appInfo.identifierNameStrings;
   }
 
   void run() {
     if (!adaptClassStrings.isEmpty()) {
       adaptClassStrings();
     }
-    if (!identifierNameStrings.isEmpty()) {
-      replaceIdentifierNameString();
-    }
+    replaceDexItemBasedConstString();
   }
 
   private void adaptClassStrings() {
@@ -120,28 +116,35 @@
     return originalLiteral;
   }
 
-  // TODO(christofferqa): Rename to replaceDexItemBasedConstString.
-  private void replaceIdentifierNameString() {
+  private void replaceDexItemBasedConstString() {
     for (DexProgramClass clazz : appInfo.classes()) {
       // Some const strings could be moved to field's static value (from <clinit>).
       for (DexEncodedField field : clazz.staticFields()) {
-        replaceIdentifierNameStringInStaticField(field);
+        replaceDexItemBasedConstStringInStaticField(field);
       }
-      clazz.forEachMethod(this::replaceIdentifierNameStringInMethod);
+      clazz.forEachMethod(this::replaceDexItemBasedConstStringInMethod);
     }
   }
 
-  private void replaceIdentifierNameStringInStaticField(DexEncodedField encodedField) {
+  private void replaceDexItemBasedConstStringInStaticField(DexEncodedField encodedField) {
     assert encodedField.accessFlags.isStatic();
     DexValue staticValue = encodedField.getStaticValue();
     if (staticValue instanceof DexItemBasedValueString) {
-      DexReference original = ((DexItemBasedValueString) staticValue).getValue();
-      encodedField.setStaticValue(
-          new DexValueString(lens.lookupName(original, appInfo.dexItemFactory)));
+      DexItemBasedValueString dexItemBasedValueString = (DexItemBasedValueString) staticValue;
+      DexReference original = dexItemBasedValueString.getValue();
+      DexString replacement =
+          dexItemBasedValueString.getClassNameComputationInfo().needsToComputeClassName()
+              ? appInfo.dexItemFactory.createString(
+                  computeClassName(
+                      lens.lookupDescriptor(original.asDexType()),
+                      appInfo.definitionFor(original.asDexType()),
+                      dexItemBasedValueString.getClassNameComputationInfo()))
+              : lens.lookupName(original, appInfo.dexItemFactory);
+      encodedField.setStaticValue(new DexValueString(replacement));
     }
   }
 
-  private void replaceIdentifierNameStringInMethod(DexEncodedMethod encodedMethod) {
+  private void replaceDexItemBasedConstStringInMethod(DexEncodedMethod encodedMethod) {
     if (!encodedMethod.getOptimizationInfo().useIdentifierNameString()) {
       return;
     }
@@ -159,8 +162,15 @@
         Instruction instruction = instructions[i];
         if (instruction instanceof DexItemBasedConstString) {
           DexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-          instructions[i] =
-              new ConstString(cnst.AA, lens.lookupName(cnst.getItem(), appInfo.dexItemFactory));
+          DexString replacement =
+              cnst.getClassNameComputationInfo().needsToComputeClassName()
+                  ? appInfo.dexItemFactory.createString(
+                      computeClassName(
+                          lens.lookupDescriptor(cnst.getItem().asDexType()),
+                          appInfo.definitionFor(cnst.getItem().asDexType()),
+                          cnst.getClassNameComputationInfo()))
+                  : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
+          instructions[i] = new ConstString(cnst.AA, replacement);
         }
       }
     } else {
@@ -170,8 +180,15 @@
         CfInstruction instruction = instructions.get(i);
         if (instruction.isDexItemBasedConstString()) {
           CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-          instructions.set(
-              i, new CfConstString(lens.lookupName(cnst.getItem(), appInfo.dexItemFactory)));
+          DexString replacement =
+              cnst.getClassNameComputationInfo().needsToComputeClassName()
+                  ? appInfo.dexItemFactory.createString(
+                      computeClassName(
+                          lens.lookupDescriptor(cnst.getItem().asDexType()),
+                          appInfo.definitionFor(cnst.getItem().asDexType()),
+                          cnst.getClassNameComputationInfo()))
+                  : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
+          instructions.set(i, new CfConstString(replacement));
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 6b109c5..df11ba9 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -264,7 +264,7 @@
           // Don't report errors, as the set of call sites is a conservative estimate, and can
           // refer to interfaces which has been removed.
           Set<DexEncodedMethod> implementedMethods =
-              appInfo.lookupLambdaImplementedMethods(callSite, null);
+              appInfo.lookupLambdaImplementedMethods(callSite);
           if (implementedMethods.isEmpty()) {
             return;
           }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 1f5b0b1..d626407 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -21,13 +21,10 @@
   private final AppInfoWithLiveness appInfo;
   private final ProguardKeepAttributes keep;
   private final InternalOptions options;
-  private final ProguardConfiguration.Builder compatibility;
 
-  public AnnotationRemover(AppInfoWithLiveness appInfo,
-      ProguardConfiguration.Builder compatibility, InternalOptions options) {
+  public AnnotationRemover(AppInfoWithLiveness appInfo, InternalOptions options) {
     this.appInfo = appInfo;
     this.keep = options.proguardConfiguration.getKeepAttributes();
-    this.compatibility = compatibility;
     this.options = options;
   }
 
@@ -128,8 +125,12 @@
     return isAnnotationTypeLive(annotation);
   }
 
-  public void run() {
+  public AnnotationRemover ensureValid(ProguardConfiguration.Builder compatibility) {
     keep.ensureValid(options.forceProguardCompatibility, compatibility);
+    return this;
+  }
+
+  public void run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       stripAttributes(clazz);
       clazz.annotations = clazz.annotations.keepIf(this::filterAnnotations);
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 31771a6..a9f5a71 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -554,9 +554,22 @@
     public void registerCallSite(DexCallSite callSite) {
       callSites.add(callSite);
       super.registerCallSite(callSite);
-      for (DexEncodedMethod method :
-          appInfo.lookupLambdaImplementedMethods(callSite, options.reporter)) {
-        markLambdaInstantiated(method.method.holder, currentMethod);
+
+      List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
+      if (directInterfaces != null) {
+        for (DexType lambdaInstantiatedInterface : directInterfaces) {
+          markLambdaInstantiated(lambdaInstantiatedInterface, currentMethod);
+        }
+      } else {
+        if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
+          if (options.reporter != null) {
+            Diagnostic message =
+                new StringDiagnostic(
+                    "Unknown bootstrap method " + callSite.bootstrapMethod,
+                    appInfo.originFor(currentMethod.method.holder));
+            options.reporter.warning(message);
+          }
+        }
       }
 
       LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo);
@@ -600,7 +613,6 @@
       // and implement all lambda interfaces.
 
       ScopedDexMethodSet seen = new ScopedDexMethodSet();
-      List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
       if (directInterfaces == null) {
         return;
       }
@@ -850,20 +862,24 @@
     }
   }
 
-  private void markMethodAsTargeted(DexEncodedMethod encodedMethod, KeepReason reason) {
-    markTypeAsLive(encodedMethod.method.holder);
-    markParameterAndReturnTypesAsLive(encodedMethod);
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Method `%s` is targeted.", encodedMethod.method);
+  private void markMethodAsTargeted(DexEncodedMethod method, KeepReason reason) {
+    if (!targetedMethods.add(method, reason)) {
+      return;
     }
-    targetedMethods.add(encodedMethod, reason);
+    markTypeAsLive(method.method.holder);
+    markParameterAndReturnTypesAsLive(method);
+    processAnnotations(method.annotations.annotations);
+    method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
+    if (Log.ENABLED) {
+      Log.verbose(getClass(), "Method `%s` is targeted.", method.method);
+    }
     if (forceProguardCompatibility) {
       // Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
       // these methods abstract, whereas Proguard does not (seem to) touch their code.
-      DexClass clazz = appInfo.definitionFor(encodedMethod.method.holder);
-      if (!encodedMethod.accessFlags.isAbstract()
+      DexClass clazz = appInfo.definitionFor(method.method.holder);
+      if (!method.accessFlags.isAbstract()
           && clazz.isInterface() && !clazz.isLibraryClass()) {
-        markMethodAsKeptWithCompatRule(encodedMethod);
+        markMethodAsKeptWithCompatRule(method);
       }
     }
   }
@@ -1059,7 +1075,31 @@
   }
 
   private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
-    instantiatedLambdas.add(itf, KeepReason.instantiatedIn(method));
+    DexClass clazz = appInfo.definitionFor(itf);
+    if (clazz == null) {
+      if (options.reporter != null) {
+        StringDiagnostic message =
+            new StringDiagnostic(
+                "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
+                appInfo.originFor(method.method.holder));
+        options.reporter.warning(message);
+      }
+      return;
+    }
+    if (!clazz.isInterface()) {
+      if (options.reporter != null) {
+        StringDiagnostic message =
+            new StringDiagnostic(
+                "Lambda expression expected to implement an interface, but found "
+                    + "`" + itf.toSourceString() + "`",
+                appInfo.originFor(method.method.holder));
+        options.reporter.warning(message);
+      }
+      return;
+    }
+    if (clazz.isProgramClass()) {
+      instantiatedLambdas.add(itf, KeepReason.instantiatedIn(method));
+    }
   }
 
   private void markDirectStaticOrConstructorMethodAsLive(
@@ -1356,6 +1396,7 @@
           ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
           enqueueRootItems(consequentRootSet.noShrinking);
           rootSet.neverInline.addAll(consequentRootSet.neverInline);
+          rootSet.neverClassInline.addAll(consequentRootSet.neverClassInline);
           rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
           rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
           rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
@@ -2431,6 +2472,9 @@
               : Iterables.concat(
                   ImmutableList.of(refinedReceiverType), subtypes(refinedReceiverType));
       for (DexType type : subTypesToExplore) {
+        if (instantiatedLambdas.contains(type)) {
+          return null;
+        }
         if (pinnedItems.contains(type)) {
           // For kept classes we cannot ensure a single target.
           return null;
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 3011951..8d68d2e 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.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -87,6 +88,24 @@
         subsequentRule.materialize());
   }
 
+  protected ClassInlineRule neverClassInlineRuleForCondition() {
+    return new ClassInlineRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
+        getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+        getClassAccessFlags(),
+        getNegatedClassAccessFlags(),
+        getClassTypeNegated(),
+        getClassType(),
+        getClassNames().materialize(),
+        getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+        getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+        getInheritanceIsExtends(),
+        ImmutableList.of(),
+        ClassInlineRule.Type.NEVER);
+  }
+
   /**
    * Consider the following rule, which requests that class Y should be kept if the method X.m() is
    * in the final output.
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index 49f99dc..79828b3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -30,6 +30,7 @@
   public static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS =
       "RuntimeInvisibleTypeAnnotations";
   public static final String ANNOTATION_DEFAULT = "AnnotationDefault";
+  public static final String STACK_MAP_TABLE = "StackMapTable";
 
   public static final List<String> KEEP_ALL = ImmutableList.of("*");
 
@@ -50,6 +51,7 @@
   public boolean runtimeVisibleTypeAnnotations = false;
   public boolean runtimeInvisibleTypeAnnotations = false;
   public boolean annotationDefault = false;
+  public boolean stackMapTable = false;
 
   private ProguardKeepAttributes() {
   }
@@ -132,6 +134,7 @@
     runtimeInvisibleTypeAnnotations = update(runtimeInvisibleTypeAnnotations,
         RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, patterns);
     annotationDefault = update(annotationDefault, ANNOTATION_DEFAULT, patterns);
+    stackMapTable = update(stackMapTable, STACK_MAP_TABLE, patterns);
   }
 
   public void ensureValid(
@@ -191,7 +194,8 @@
         && this.runtimeInvisibleParameterAnnotations == other.runtimeInvisibleParameterAnnotations
         && this.runtimeVisibleTypeAnnotations == other.runtimeVisibleTypeAnnotations
         && this.runtimeInvisibleTypeAnnotations == other.runtimeInvisibleTypeAnnotations
-        && this.annotationDefault == other.annotationDefault;
+        && this.annotationDefault == other.annotationDefault
+        && this.stackMapTable == other.stackMapTable;
   }
 
   @Override
@@ -209,7 +213,8 @@
         + (this.runtimeInvisibleParameterAnnotations ? 1 << 10 : 0)
         + (this.runtimeVisibleTypeAnnotations ? 1 << 11 : 0)
         + (this.runtimeInvisibleTypeAnnotations ? 1 << 12 : 0)
-        + (this.annotationDefault ? 1 << 13 : 0);
+        + (this.annotationDefault ? 1 << 13 : 0)
+        + (this.stackMapTable ? 1 << 14 : 0);
   }
 
   public boolean isEmpty() {
@@ -226,7 +231,8 @@
         && !runtimeInvisibleParameterAnnotations
         && !runtimeVisibleTypeAnnotations
         && !runtimeInvisibleTypeAnnotations
-        && !annotationDefault;
+        && !annotationDefault
+        && !stackMapTable;
   }
 
   public StringBuilder append(StringBuilder builder) {
@@ -273,6 +279,9 @@
     if (annotationDefault) {
       attributes.add(ANNOTATION_DEFAULT);
     }
+    if (stackMapTable) {
+      attributes.add(STACK_MAP_TABLE);
+    }
 
     if (attributes.size() > 0) {
       builder.append("-keepattributes ");
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 c59081e..de0537f 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -327,7 +327,12 @@
         application.timing.end();
       }
       return new ConsequentRootSet(
-          neverInline, noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+          neverInline,
+          neverClassInline,
+          noShrinking,
+          noOptimization,
+          noObfuscation,
+          dependentNoShrinking);
     }
 
     /**
@@ -413,6 +418,14 @@
     private void materializeIfRule(ProguardIfRule rule) {
       ProguardIfRule materializedRule = rule.materialize();
 
+      // We need to abort class inlining of classes that could be matched by the condition of this
+      // -if rule.
+      ClassInlineRule neverClassInlineRuleForCondition =
+          materializedRule.neverClassInlineRuleForCondition();
+      if (neverClassInlineRuleForCondition != null) {
+        runPerRule(executorService, futures, neverClassInlineRuleForCondition, materializedRule);
+      }
+
       // If the condition of the -if rule has any members, then we need to keep these members to
       // ensure that the subsequent rule will be applied again in the second round of tree
       // shaking.
@@ -957,7 +970,7 @@
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
       this.forceInline = Collections.unmodifiableSet(forceInline);
       this.neverInline = neverInline;
-      this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
+      this.neverClassInline = neverClassInline;
       this.neverMerge = Collections.unmodifiableSet(neverMerge);
       this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
@@ -1010,6 +1023,7 @@
   // A partial RootSet that becomes live due to the enabled -if rule.
   static class ConsequentRootSet {
     final Set<DexMethod> neverInline;
+    final Set<DexType> neverClassInline;
     final Map<DexDefinition, ProguardKeepRule> noShrinking;
     final Set<DexDefinition> noOptimization;
     final Set<DexDefinition> noObfuscation;
@@ -1017,11 +1031,13 @@
 
     private ConsequentRootSet(
         Set<DexMethod> neverInline,
+        Set<DexType> neverClassInline,
         Map<DexDefinition, ProguardKeepRule> noShrinking,
         Set<DexDefinition> noOptimization,
         Set<DexDefinition> noObfuscation,
         Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
       this.neverInline = Collections.unmodifiableSet(neverInline);
+      this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 4111bed..69d65ea 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -17,6 +17,10 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipOutputStream;
@@ -27,6 +31,9 @@
   private ZipOutputStream stream = null;
   private boolean closed = false;
   private int openCount = 0;
+  private int classesFileIndex = 0;
+  private Map<Integer, DelayedData> delayedClassesDexFiles = new HashMap<>();
+  private SortedSet<DelayedData> delayedWrites = new TreeSet<>();
 
   public ArchiveBuilder(Path archive) {
     this.archive = archive;
@@ -44,6 +51,7 @@
     assert !closed;
     openCount--;
     if (openCount == 0) {
+      writeDelayed(handler);
       closed = true;
       try {
         getStreamRaw().close();
@@ -54,6 +62,20 @@
     }
   }
 
+  private void writeDelayed(DiagnosticsHandler handler) {
+    // We should never have any indexed files at this point
+    assert delayedClassesDexFiles.isEmpty();
+    for (DelayedData data : delayedWrites) {
+      if (data.isDirectory) {
+        assert data.content == null;
+        writeDirectoryNow(data.name, handler);
+      } else {
+        assert data.content != null;
+        writeFileNow(data.name, data.content, handler);
+      }
+    }
+  }
+
   private ZipOutputStream getStreamRaw() throws IOException {
     if (stream != null) {
       return stream;
@@ -85,7 +107,11 @@
   }
 
   @Override
-  public void addDirectory(String name, DiagnosticsHandler handler) {
+  public synchronized void addDirectory(String name, DiagnosticsHandler handler) {
+    delayedWrites.add(DelayedData.createDirectory(name));
+  }
+
+  private void writeDirectoryNow(String name, DiagnosticsHandler handler) {
     if (name.charAt(name.length() - 1) != DataResource.SEPARATOR) {
       name += DataResource.SEPARATOR;
     }
@@ -105,7 +131,10 @@
   @Override
   public void addFile(String name, DataEntryResource content, DiagnosticsHandler handler) {
     try (InputStream in = content.getByteStream()) {
-      addFile(name, ByteDataView.of(ByteStreams.toByteArray(in)), handler);
+      ByteDataView view = ByteDataView.of(ByteStreams.toByteArray(in));
+      synchronized (this) {
+        delayedWrites.add(DelayedData.createFile(name, view));
+      }
     } catch (IOException e) {
       handleIOException(e, handler);
     } catch (ResourceException e) {
@@ -116,6 +145,10 @@
 
   @Override
   public synchronized void addFile(String name, ByteDataView content, DiagnosticsHandler handler) {
+    delayedWrites.add(DelayedData.createFile(name,  ByteDataView.of(content.copyByteData())));
+  }
+
+  private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
     try {
       ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
     } catch (IOException e) {
@@ -123,6 +156,30 @@
     }
   }
 
+  private void writeNextIfAvailable(DiagnosticsHandler handler) {
+    DelayedData data = delayedClassesDexFiles.remove(classesFileIndex);
+    while (data != null) {
+      writeFileNow(data.name, data.content, handler);
+      classesFileIndex++;
+      data = delayedClassesDexFiles.remove(classesFileIndex);
+    }
+  }
+
+  @Override
+  public synchronized void addIndexedClassFile(
+      int index, String name, ByteDataView content, DiagnosticsHandler handler) {
+    if (index == classesFileIndex) {
+      // Fast case, we got the file in order (or we only had one).
+      writeFileNow(name, content, handler);
+      classesFileIndex++;
+      writeNextIfAvailable(handler);
+    } else {
+      // Data is released in the application writer, take a copy.
+      delayedClassesDexFiles.put(index,
+          new DelayedData(name, ByteDataView.of(content.copyByteData()), false));
+    }
+  }
+
   @Override
   public Origin getOrigin() {
     return origin;
@@ -132,4 +189,32 @@
   public Path getPath() {
     return archive;
   }
+
+  private static class DelayedData implements Comparable<DelayedData> {
+    public final String name;
+    public final ByteDataView content;
+    public final boolean isDirectory;
+
+    public static DelayedData createFile(String name, ByteDataView content) {
+      return new DelayedData(name, content, false);
+    }
+
+    public static DelayedData createDirectory(String name) {
+      return new DelayedData(name, null, true);
+    }
+
+    private DelayedData(String name, ByteDataView content, boolean isDirectory) {
+      this.name = name;
+      this.content = content;
+      this.isDirectory = isDirectory;
+    }
+
+    @Override
+    public int compareTo(DelayedData other) {
+      if (other == null) {
+        return name.compareTo(null);
+      }
+      return name.compareTo(other.name);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
index 8e0b054..f78983f 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
@@ -68,6 +68,12 @@
   }
 
   @Override
+  public void addIndexedClassFile(
+      int index, String name, ByteDataView content, DiagnosticsHandler handler) {
+    addFile(name, content, handler);
+  }
+
+  @Override
   public Origin getOrigin() {
     return origin;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 8fa85bf..ee11bcf 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.nio.file.FileSystemException;
 import java.nio.file.Paths;
 import java.util.function.Consumer;
@@ -65,6 +67,13 @@
       } catch (ResourceException e) {
         throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
       } catch (AssertionError e) {
+        // Most of our assertions don't have a message, create a wrapper that has the stack as the
+        // message.
+        if (e.getMessage() == null) {
+          StringWriter stack = new StringWriter();
+          e.printStackTrace(new PrintWriter(stack));
+          e = new AssertionError(stack.toString(), e);
+        }
         throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()), e);
       }
       reporter.failIfPendingErrors();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 41463fa..7c27264 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -156,6 +156,18 @@
     return programConsumer != null;
   }
 
+  public InternalOutputMode getInternalOutputMode() {
+    assert hasConsumer();
+    if (isGeneratingDexIndexed()) {
+      return InternalOutputMode.DexIndexed;
+    } else if (isGeneratingDexFilePerClassFile()) {
+      return InternalOutputMode.DexFilePerClassFile;
+    } else if (isGeneratingClassFiles()) {
+      return InternalOutputMode.ClassFile;
+    }
+    throw new UnsupportedOperationException("Cannot find internal output mode.");
+  }
+
   public boolean isGeneratingDex() {
     return isGeneratingDexIndexed() || isGeneratingDexFilePerClassFile();
   }
@@ -483,7 +495,6 @@
     public boolean forceJumboStringProcessing = false;
     public boolean nondeterministicCycleElimination = false;
     public Set<Inliner.Reason> validInliningReasons = null;
-    public boolean suppressExperimentalCfBackendWarning = false;
   }
 
   private boolean hasMinApi(AndroidApiLevel level) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOutputMode.java b/src/main/java/com/android/tools/r8/utils/InternalOutputMode.java
new file mode 100644
index 0000000..d5fc357
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/InternalOutputMode.java
@@ -0,0 +1,19 @@
+// 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.utils;
+
+public enum InternalOutputMode {
+  DexIndexed,
+  DexFilePerClassFile,
+  ClassFile;
+
+  public boolean isGeneratingClassFiles() {
+    return this == ClassFile;
+  }
+
+  public boolean isGeneratingDex() {
+    return this != ClassFile;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
index 9077f7b..be739e3 100644
--- a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
@@ -23,6 +23,9 @@
 
   void addFile(String name, ByteDataView content, DiagnosticsHandler handler);
 
+  void addIndexedClassFile(
+      int index, String name, ByteDataView content, DiagnosticsHandler handler);
+
   Path getPath();
 
   Origin getOrigin();
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index d14f12d..1aa095a 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -7,9 +7,14 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 
-public class D8TestRunResult extends TestRunResult {
+public class D8TestRunResult extends TestRunResult<D8TestRunResult> {
 
   public D8TestRunResult(AndroidApp app, ProcessResult result) {
     super(app, result);
   }
+
+  @Override
+  protected D8TestRunResult self() {
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index 8312729..190db23 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -67,6 +67,11 @@
   }
 
   @Override
+  public DXTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
+    throw new Unimplemented("No support for adding classfile data directly");
+  }
+
+  @Override
   public DXTestBuilder addProgramFiles(Collection<Path> files) {
     injars.addAll(files);
     return self();
diff --git a/src/test/java/com/android/tools/r8/DXTestRunResult.java b/src/test/java/com/android/tools/r8/DXTestRunResult.java
index c1df193..830cbd8 100644
--- a/src/test/java/com/android/tools/r8/DXTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestRunResult.java
@@ -7,9 +7,14 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 
-public class DXTestRunResult extends TestRunResult {
+public class DXTestRunResult extends TestRunResult<DXTestRunResult> {
 
   public DXTestRunResult(AndroidApp app, ProcessResult result) {
     super(app, result);
   }
+
+  @Override
+  protected DXTestRunResult self() {
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
new file mode 100644
index 0000000..c4425b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -0,0 +1,20 @@
+// 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;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+
+public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
+
+  public Dex2OatTestRunResult(AndroidApp app, ProcessResult result) {
+    super(app, result);
+  }
+
+  @Override
+  protected Dex2OatTestRunResult self() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 68d634e..2c2c5a6 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -40,7 +40,7 @@
           .put("math.BigInteger.ConstructorLjava_lang_String.BigInteger_Constructor_A02", any())
           .put(
               "lang.StringBuffer.insertILjava_lang_Object.StringBuffer_insert_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.JAVA)))
+              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V8_1_0, Runtime.JAVA)))
           .put("lang.StringBuffer.serialization.StringBuffer_serialization_A01", anyDexVm())
           .put(
               "lang.CloneNotSupportedException.serialization.CloneNotSupportedException_serialization_A01",
@@ -50,10 +50,14 @@
               anyDexVm())
           .put(
               "lang.StrictMath.roundF.StrictMath_round_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V7_0_0, Runtime.JAVA)))
+              match(
+                  runtimes(
+                      Runtime.ART_DEFAULT, Runtime.ART_V8_1_0, Runtime.ART_V7_0_0, Runtime.JAVA)))
           .put(
               "lang.StrictMath.roundD.StrictMath_round_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V7_0_0, Runtime.JAVA)))
+              match(
+                  runtimes(
+                      Runtime.ART_DEFAULT, Runtime.ART_V8_1_0, Runtime.ART_V7_0_0, Runtime.JAVA)))
           .put("lang.StrictMath.atan2DD.StrictMath_atan2_A01", any())
           .put("lang.Thread.stop.Thread_stop_A05", any())
           .put("lang.Thread.resume.Thread_resume_A02", anyDexVm())
@@ -63,7 +67,12 @@
           .put("lang.Thread.stop.Thread_stop_A04", any())
           .put(
               "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V7_0_0, Runtime.ART_V6_0_1)))
+              match(
+                  runtimes(
+                      Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
+                      Runtime.ART_V7_0_0,
+                      Runtime.ART_V6_0_1)))
           .put(
               "lang.Thread.getUncaughtExceptionHandler.Thread_getUncaughtExceptionHandler_A01",
               anyDexVm())
@@ -190,7 +199,7 @@
               anyDexVm())
           .put(
               "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A07",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V7_0_0)))
+              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V8_1_0, Runtime.ART_V7_0_0)))
           .put(
               "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A04",
               anyDexVm())
@@ -412,6 +421,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -421,6 +431,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -430,6 +441,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -439,6 +451,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -452,6 +465,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -461,6 +475,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -494,6 +509,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -504,6 +520,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -513,6 +530,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -522,6 +540,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -531,6 +550,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -540,6 +560,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -549,6 +570,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -558,6 +580,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -567,6 +590,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -595,6 +619,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V6_0_1,
                       Runtime.ART_V5_1_1,
@@ -605,7 +630,7 @@
           .put("lang.ref.WeakReference.get.WeakReference_get_A01", any())
           .put(
               "lang.StackTraceElement.toString.StackTraceElement_toString_A01",
-              match(runtimes(Runtime.ART_DEFAULT)))
+              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V8_1_0)))
           .put(
               "lang.NullPointerException.serialization.NullPointerException_serialization_A01",
               anyDexVm())
@@ -661,7 +686,7 @@
               match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
           .put(
               "lang.annotation.IncompleteAnnotationException.ConstructorLjava_lang_ClassLjava_lang_String.IncompleteAnnotationException_Constructor_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.JAVA)))
+              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V8_1_0, Runtime.JAVA)))
           .put(
               "lang.InterruptedException.serialization.InterruptedException_serialization_A01",
               anyDexVm())
@@ -749,6 +774,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V4_4_4,
                       Runtime.ART_V4_0_4,
@@ -788,6 +814,7 @@
               match(
                   runtimes(
                       Runtime.ART_DEFAULT,
+                      Runtime.ART_V8_1_0,
                       Runtime.ART_V7_0_0,
                       Runtime.ART_V4_4_4,
                       Runtime.ART_V4_0_4)))
@@ -1140,7 +1167,7 @@
               anyDexVm())
           .put(
               "lang.reflect.Proxy.h.Proxy_h_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.JAVA)))
+              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V8_1_0, Runtime.JAVA)))
           .put("lang.reflect.Proxy.serialization.Proxy_serialization_A02", any())
           .put(
               "lang.reflect.GenericSignatureFormatError.serialization.GenericSignatureFormatError_serialization_A01",
@@ -1150,7 +1177,7 @@
               anyDexVm())
           .put(
               "lang.reflect.Proxy.ConstructorLjava_lang_reflect_InvocationHandler.Proxy_Constructor_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.JAVA)))
+              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V8_1_0, Runtime.JAVA)))
           .put(
               "lang.reflect.Proxy.newProxyInstanceLjava_lang_ClassLoader_Ljava_lang_ClassLjava_lang_reflect_InvocationHandler.Proxy_newProxyInstance_A01",
               anyDexVm())
@@ -1667,14 +1694,14 @@
               match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
           .put(
               "lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
+              match(and(runtimes(Runtime.ART_V8_1_0), artRuntimesUpTo(Runtime.ART_V4_4_4))))
           .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", anyDexVm())
           .put(
               "lang.ref.WeakReference.enqueue.WeakReference_enqueue_A01",
               match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
           .put(
               "lang.ref.SoftReference.isEnqueued.SoftReference_isEnqueued_A01",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
+              match(and(runtimes(Runtime.ART_V8_1_0), artRuntimesUpTo(Runtime.ART_V4_4_4))))
           .put(
               "lang.ref.SoftReference.enqueue.SoftReference_enqueue_A01",
               match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 4e0bedf..f95e666 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -24,7 +24,7 @@
 import java.util.List;
 import java.util.Set;
 
-public class JvmTestBuilder extends TestBuilder<JvmTestBuilder> {
+public class JvmTestBuilder extends TestBuilder<JvmTestRunResult, JvmTestBuilder> {
 
   private static class ClassFileResource implements ProgramResource {
 
@@ -107,9 +107,9 @@
   }
 
   @Override
-  public TestRunResult run(String mainClass) throws IOException {
+  public JvmTestRunResult run(String mainClass) throws IOException {
     ProcessResult result = ToolHelper.runJava(classpath, mainClass);
-    return new TestRunResult(builder.build(), result);
+    return new JvmTestRunResult(builder.build(), result);
   }
 
   @Override
@@ -147,6 +147,12 @@
         "No support for adding paths directly (we need to compute the descriptor)");
   }
 
+  @Override
+  public JvmTestBuilder addProgramClassFileData(Collection<byte[]> files) {
+    throw new Unimplemented(
+        "No support for adding classfile data directly (we need to compute the descriptor)");
+  }
+
   public JvmTestBuilder addClasspath(Path... paths) {
     return addClasspath(Arrays.asList(paths));
   }
diff --git a/src/test/java/com/android/tools/r8/JvmTestRunResult.java b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
new file mode 100644
index 0000000..b7a71fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
@@ -0,0 +1,20 @@
+// 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;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+
+public class JvmTestRunResult extends TestRunResult<JvmTestRunResult> {
+
+  public JvmTestRunResult(AndroidApp app, ProcessResult result) {
+    super(app, result);
+  }
+
+  @Override
+  protected JvmTestRunResult self() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 28ce1e6..fa4c41c 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -184,6 +184,12 @@
   }
 
   @Override
+  public ProguardTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
+    throw new Unimplemented(
+        "No support for adding classfile data directly (we need to compute the descriptor)");
+  }
+
+  @Override
   public ProguardTestBuilder addKeepRules(Collection<String> rules) {
     config.addAll(rules);
     return self();
diff --git a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
index 9e0d875..e242a38 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
@@ -12,7 +12,7 @@
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 
-public class ProguardTestRunResult extends TestRunResult {
+public class ProguardTestRunResult extends TestRunResult<ProguardTestRunResult> {
 
   private final String proguardMap;
 
@@ -22,6 +22,11 @@
   }
 
   @Override
+  protected ProguardTestRunResult self() {
+    return this;
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     // See comment in base class.
     assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 3618a5e..5123c51 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -96,7 +96,8 @@
       DexVm.Version.V4_4_4,
       DexVm.Version.V5_1_1,
       DexVm.Version.V6_0_1,
-      DexVm.Version.V7_0_0);
+      DexVm.Version.V7_0_0,
+      DexVm.Version.V8_1_0);
 
   // Input jar for jctf tests.
   private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
@@ -439,7 +440,6 @@
       "149-suspend-all-stress",
       "154-gc-loop",
       "155-java-set-resolved-type",
-      "156-register-dex-file-multi-loader",
       "157-void-class",
       "158-app-image-class-table",
       "466-get-live-vreg",
@@ -454,93 +454,128 @@
       "629-vdex-speed",
       "1337-gc-coverage",
 
-      // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
-      "008-exceptions",
-
-      // Fails due to non-matching Exception messages.
-      "201-built-in-except-detail-messages",
-
       // Fails on non-R8/D8 run
       "031-class-attributes"
   );
 
-  private static Map<DexVm.Version, List<String>> expectedToFailRunWithArtVersion = ImmutableMap.of(
-      DexVm.Version.V7_0_0, ImmutableList.of(
-          // Generally fails on non-R8/D8 running.
-          "412-new-array",
-          "610-arraycopy",
-          "625-checker-licm-regressions"
-      ),
-      DexVm.Version.V6_0_1, ImmutableList.of(
-          // Generally fails on non-R8/D8 running.
-          "004-checker-UnsafeTest18",
-          "005-annotations",
-          "008-exceptions",
-          "082-inline-execute",
-          "099-vmdebug",
-          "412-new-array",
-          "530-checker-lse2",
-          "550-new-instance-clinit",
-          "580-checker-round",
-          "594-invoke-super",
-          "625-checker-licm-regressions",
-          "626-const-class-linking"
-      ),
-      DexVm.Version.V5_1_1, ImmutableList.of(
-          // Generally fails on non R8/D8 running.
-          "004-checker-UnsafeTest18",
-          "004-NativeAllocations",
-          "005-annotations",
-          "008-exceptions",
-          "082-inline-execute",
-          "099-vmdebug",
-          "143-string-value",
-          "530-checker-lse2",
-          "536-checker-intrinsic-optimization",
-          "552-invoke-non-existent-super",
-          "580-checker-round",
-          "580-checker-string-fact-intrinsics",
-          "594-invoke-super",
-          "605-new-string-from-bytes",
-          "626-const-class-linking"
-      ),
-      DexVm.Version.V4_4_4, ImmutableList.of(
-          // Generally fails on non R8/D8 running.
-          "004-checker-UnsafeTest18",
-          "004-NativeAllocations",
-          "005-annotations",
-          "008-exceptions",
-          "082-inline-execute",
-          "099-vmdebug",
-          "143-string-value",
-          "530-checker-lse2",
-          "536-checker-intrinsic-optimization",
-          "552-invoke-non-existent-super",
-          "580-checker-round",
-          "580-checker-string-fact-intrinsics",
-          "594-invoke-super",
-          "605-new-string-from-bytes",
-          "626-const-class-linking"
-      ),
-      DexVm.Version.V4_0_4, ImmutableList.of(
-          // Generally fails on non R8/D8 running.
-          "004-checker-UnsafeTest18",
-          "004-NativeAllocations",
-          "005-annotations",
-          "008-exceptions",
-          "082-inline-execute",
-          "099-vmdebug",
-          "143-string-value",
-          "530-checker-lse2",
-          "536-checker-intrinsic-optimization",
-          "552-invoke-non-existent-super",
-          "580-checker-round",
-          "580-checker-string-fact-intrinsics",
-          "594-invoke-super",
-          "605-new-string-from-bytes",
-          "626-const-class-linking"
-      )
-  );
+  private static Map<DexVm.Version, List<String>> expectedToFailRunWithArtVersion;
+
+  static {
+    ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
+    builder
+        .put(DexVm.Version.V8_1_0, ImmutableList.of(
+            // TODO(119938529): Triage.
+            "709-checker-varhandles",
+            "454-get-vreg",
+            "457-regs"
+        ))
+        .put(DexVm.Version.V7_0_0, ImmutableList.of(
+            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
+            "008-exceptions",
+
+            // Fails due to non-matching Exception messages.
+            "201-built-in-except-detail-messages",
+
+            // Generally fails on non-R8/D8 running.
+            "156-register-dex-file-multi-loader",
+            "412-new-array",
+            "610-arraycopy",
+            "625-checker-licm-regressions"))
+        .put(DexVm.Version.V6_0_1, ImmutableList.of(
+            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
+            "008-exceptions",
+
+            // Fails due to non-matching Exception messages.
+            "201-built-in-except-detail-messages",
+
+            // Generally fails on non-R8/D8 running.
+            "004-checker-UnsafeTest18",
+            "005-annotations",
+            "008-exceptions",
+            "082-inline-execute",
+            "099-vmdebug",
+            "156-register-dex-file-multi-loader",
+            "412-new-array",
+            "530-checker-lse2",
+            "550-new-instance-clinit",
+            "580-checker-round",
+            "594-invoke-super",
+            "625-checker-licm-regressions",
+            "626-const-class-linking"))
+        .put(DexVm.Version.V5_1_1, ImmutableList.of(
+            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
+            "008-exceptions",
+
+            // Fails due to non-matching Exception messages.
+            "201-built-in-except-detail-messages",
+
+            // Generally fails on non R8/D8 running.
+            "004-checker-UnsafeTest18",
+            "004-NativeAllocations",
+            "005-annotations",
+            "008-exceptions",
+            "082-inline-execute",
+            "099-vmdebug",
+            "143-string-value",
+            "156-register-dex-file-multi-loader",
+            "530-checker-lse2",
+            "536-checker-intrinsic-optimization",
+            "552-invoke-non-existent-super",
+            "580-checker-round",
+            "580-checker-string-fact-intrinsics",
+            "594-invoke-super",
+            "605-new-string-from-bytes",
+            "626-const-class-linking"))
+        .put(DexVm.Version.V4_4_4, ImmutableList.of(
+            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
+            "008-exceptions",
+
+            // Fails due to non-matching Exception messages.
+            "201-built-in-except-detail-messages",
+
+            // Generally fails on non R8/D8 running.
+            "004-checker-UnsafeTest18",
+            "004-NativeAllocations",
+            "005-annotations",
+            "008-exceptions",
+            "082-inline-execute",
+            "099-vmdebug",
+            "143-string-value",
+            "156-register-dex-file-multi-loader",
+            "530-checker-lse2",
+            "536-checker-intrinsic-optimization",
+            "552-invoke-non-existent-super",
+            "580-checker-round",
+            "580-checker-string-fact-intrinsics",
+            "594-invoke-super",
+            "605-new-string-from-bytes",
+            "626-const-class-linking"))
+        .put(DexVm.Version.V4_0_4, ImmutableList.of(
+            // Addition of checks for super-class-initialization cause this to abort on non-ToT art.
+            "008-exceptions",
+
+            // Fails due to non-matching Exception messages.
+            "201-built-in-except-detail-messages",
+
+            // Generally fails on non R8/D8 running.
+            "004-checker-UnsafeTest18",
+            "004-NativeAllocations",
+            "005-annotations",
+            "008-exceptions",
+            "082-inline-execute",
+            "099-vmdebug",
+            "143-string-value",
+            "156-register-dex-file-multi-loader",
+            "530-checker-lse2",
+            "536-checker-intrinsic-optimization",
+            "552-invoke-non-existent-super",
+            "580-checker-round",
+            "580-checker-string-fact-intrinsics",
+            "594-invoke-super",
+            "605-new-string-from-bytes",
+            "626-const-class-linking"));
+    expectedToFailRunWithArtVersion = builder.build();
+  }
 
   // Tests where the R8/D8 output runs in Art but the original does not.
   private static Multimap<String, TestCondition> failingRunWithArtOriginalOnly =
@@ -616,7 +651,11 @@
               TestCondition.match(
                   TestCondition.D8_COMPILER,
                   TestCondition
-                      .runtimes(DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
+                      .runtimes(
+                          DexVm.Version.V8_1_0,
+                          DexVm.Version.V7_0_0,
+                          DexVm.Version.V6_0_1,
+                          DexVm.Version.V5_1_1)))
           // lib64 libarttest.so: wrong ELF class ELFCLASS64.
           .put("543-env-long-ref",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index f4665a9..7d1a088 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -39,6 +39,10 @@
               ImmutableList.of(
                   "invokecustom-with-shrinking"
               ))
+          .put(DexVm.Version.V8_1_0,
+              ImmutableList.of(
+                  "invokecustom-with-shrinking"
+              ))
           .put(DexVm.Version.DEFAULT,
               ImmutableList.of(
               ))
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 2c42b95..ddea062 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -12,7 +12,7 @@
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 
-public class R8TestRunResult extends TestRunResult {
+public class R8TestRunResult extends TestRunResult<R8TestRunResult> {
 
   private final String proguardMap;
 
@@ -22,10 +22,19 @@
   }
 
   @Override
+  protected R8TestRunResult self() {
+    return this;
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     // See comment in base class.
     assertSuccess();
     assertNotNull(app);
     return new CodeInspector(app, proguardMap);
   }
+
+  public String proguardMap() {
+    return proguardMap;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 177fe89..e0981c4 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -186,6 +186,7 @@
           .put(DexVm.Version.V6_0_1, ImmutableList.of("invokecustom"))
           // Dex version not supported
           .put(DexVm.Version.V7_0_0, ImmutableList.of("invokecustom"))
+          .put(DexVm.Version.V8_1_0, ImmutableList.of("invokecustom"))
           .put(DexVm.Version.DEFAULT, ImmutableList.of())
           .build();
 
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 99ac1f7..8a284ac 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -201,6 +201,10 @@
             // Dex version not supported
             "varhandle"
         ))
+        .put(DexVm.Version.V8_1_0, ImmutableList.of(
+            // Dex version not supported
+            "varhandle"
+        ))
         .put(DexVm.Version.DEFAULT, ImmutableList.of(
             // TODO(mikaelpeltier): Update runtime when the support will be ready
             "varhandle"
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 4977d94..61bf1b3 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -14,7 +14,7 @@
 import java.util.HashSet;
 import java.util.Set;
 
-public abstract class TestBuilder<T extends TestBuilder<T>> {
+public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
 
   private final TestState state;
 
@@ -28,10 +28,10 @@
 
   abstract T self();
 
-  public abstract TestRunResult run(String mainClass)
+  public abstract RR run(String mainClass)
       throws IOException, CompilationFailedException;
 
-  public TestRunResult run(Class mainClass) throws IOException, CompilationFailedException {
+  public RR run(Class mainClass) throws IOException, CompilationFailedException {
     return run(mainClass.getTypeName());
   }
 
@@ -39,6 +39,12 @@
 
   public abstract T addProgramFiles(Collection<Path> files);
 
+  public abstract T addProgramClassFileData(Collection<byte[]> classes);
+
+  public T addProgramClassFileData(byte[]... classes) {
+    return addProgramClassFileData(Arrays.asList(classes));
+  }
+
   public T addProgramClasses(Class<?>... classes) {
     return addProgramClasses(Arrays.asList(classes));
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 12d1990..61f9def 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.TestBase.Backend.DEX;
 
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestConfig;
@@ -98,4 +99,17 @@
     ProcessResult result = ToolHelper.runArtRaw(out.toString(), mainClass);
     return createRunResult(app, result);
   }
+
+  public Dex2OatTestRunResult runDex2Oat() throws IOException {
+    return runDex2Oat(ToolHelper.getDexVm());
+  }
+
+  public Dex2OatTestRunResult runDex2Oat(DexVm vm) throws IOException {
+    assert getBackend() == DEX;
+    Path tmp = state.getNewTempFolder();
+    Path jarFile = tmp.resolve("out.jar");
+    Path oatFile = tmp.resolve("out.oat");
+    app.writeToZip(jarFile, OutputMode.DexIndexed);
+    return new Dex2OatTestRunResult(app, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 810d292..9425df9 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -22,7 +23,7 @@
         CR extends TestCompileResult<RR>,
         RR extends TestRunResult,
         T extends TestCompilerBuilder<C, B, CR, RR, T>>
-    extends TestBuilder<T> {
+    extends TestBuilder<RR, T> {
 
   public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
       new Consumer<InternalOptions>() {
@@ -36,6 +37,7 @@
   // Default initialized setup. Can be overwritten if needed.
   private Path defaultLibrary;
   private ProgramConsumer programConsumer;
+  private StringConsumer mainDexListConsumer;
   private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
   private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
 
@@ -61,6 +63,7 @@
   public CR compile() throws CompilationFailedException {
     AndroidAppConsumers sink = new AndroidAppConsumers();
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
+    builder.setMainDexListConsumer(mainDexListConsumer);
     if (defaultLibrary != null) {
       builder.addLibraryFiles(defaultLibrary);
     }
@@ -71,7 +74,7 @@
   }
 
   @Override
-  public TestRunResult run(String mainClass) throws IOException, CompilationFailedException {
+  public RR run(String mainClass) throws IOException, CompilationFailedException {
     return compile().run(mainClass);
   }
 
@@ -112,6 +115,25 @@
     return self();
   }
 
+  public T setMainDexListConsumer(StringConsumer consumer) {
+    assert consumer != null;
+    this.mainDexListConsumer = consumer;
+    return self();
+  }
+
+  public T addMainDexListFiles(Collection<Path> files) {
+    builder.addMainDexListFiles(files);
+    return self();
+  }
+
+  @Override
+  public T addProgramClassFileData(Collection<byte[]> classes) {
+    for (byte[] clazz : classes) {
+      builder.addClassProgramData(clazz, Origin.unknown());
+    }
+    return self();
+  }
+
   @Override
   public T addProgramFiles(Collection<Path> files) {
     builder.addProgramFiles(files);
@@ -124,4 +146,9 @@
     builder.addLibraryFiles(files);
     return self();
   }
+
+  public T noDesugaring() {
+    builder.setDisableDesugaring(true);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index 865ca12..439b9e9 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -22,6 +22,7 @@
     ART_V5_1_1,
     ART_V6_0_1,
     ART_V7_0_0,
+    ART_V8_1_0,
     ART_DEFAULT,
     JAVA;
 
@@ -40,6 +41,8 @@
           return ART_V6_0_1;
         case V7_0_0:
           return ART_V7_0_0;
+        case V8_1_0:
+          return ART_V8_1_0;
         case DEFAULT:
           return ART_DEFAULT;
         default:
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 46dc4cc..a193bdb 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -15,9 +15,10 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
 import org.hamcrest.Matcher;
 
-public class TestRunResult {
+public abstract class TestRunResult<RR extends TestRunResult<?>> {
   protected final AndroidApp app;
   private final ProcessResult result;
 
@@ -26,33 +27,51 @@
     this.result = result;
   }
 
-  public TestRunResult assertSuccess() {
+  abstract RR self();
+
+  public String getStdOut() {
+    return result.stdout;
+  }
+
+  public String getStdErr() {
+    return result.stderr;
+  }
+
+  public int getExitCode() {
+    return result.exitCode;
+  }
+
+  public RR assertSuccess() {
     assertEquals(errorMessage("Expected run to succeed."), 0, result.exitCode);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertFailure() {
+  public RR assertFailure() {
     assertNotEquals(errorMessage("Expected run to fail."), 0, result.exitCode);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertFailureWithOutput(String expected) {
+  public RR assertFailureWithOutput(String expected) {
     assertFailure();
     assertEquals(errorMessage("Run stdout incorrect.", expected), expected, result.stdout);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertFailureWithErrorThatMatches(Matcher<String> matcher) {
+  public RR assertFailureWithErrorThatMatches(Matcher<String> matcher) {
     assertFailure();
     assertThat(
         errorMessage("Run stderr incorrect.", matcher.toString()), result.stderr, matcher);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertSuccessWithOutput(String expected) {
+  public RR assertSuccessWithOutput(String expected) {
     assertSuccess();
     assertEquals(errorMessage("Run stdout incorrect.", expected), expected, result.stdout);
-    return this;
+    return self();
+  }
+
+  public <R> R map(Function<RR, R> mapper) {
+    return mapper.apply(self());
   }
 
   public CodeInspector inspector() throws IOException, ExecutionException {
@@ -63,10 +82,17 @@
     return new CodeInspector(app);
   }
 
-  public TestRunResult inspect(Consumer<CodeInspector> consumer)
+  public RR inspect(Consumer<CodeInspector> consumer)
       throws IOException, ExecutionException {
     consumer.accept(inspector());
-    return this;
+    return self();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    appendInfo(builder);
+    return builder.toString();
   }
 
   private String errorMessage(String message) {
@@ -102,24 +128,24 @@
     builder.append("COMMAND: ").append(result.command).append('\n').append(result);
   }
 
-  public TestRunResult writeInfo(PrintStream ps) {
+  public RR writeInfo(PrintStream ps) {
     StringBuilder sb = new StringBuilder();
     appendInfo(sb);
     ps.println(sb.toString());
-    return this;
+    return self();
   }
 
-  public TestRunResult writeApplicaion(PrintStream ps) {
+  public RR writeApplicaion(PrintStream ps) {
     StringBuilder sb = new StringBuilder();
     appendApplication(sb);
     ps.println(sb.toString());
-    return this;
+    return self();
   }
 
-  public TestRunResult writeProcessResult(PrintStream ps) {
+  public RR writeProcessResult(PrintStream ps) {
     StringBuilder sb = new StringBuilder();
     appendProcessResult(sb);
     ps.println(sb.toString());
-    return this;
+    return self();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f968b8c..caeebd9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -124,6 +124,8 @@
     ART_6_0_1_HOST(Version.V6_0_1, Kind.HOST),
     ART_7_0_0_TARGET(Version.V7_0_0, Kind.TARGET),
     ART_7_0_0_HOST(Version.V7_0_0, Kind.HOST),
+    ART_8_1_0_TARGET(Version.V8_1_0, Kind.TARGET),
+    ART_8_1_0_HOST(Version.V8_1_0, Kind.HOST),
     ART_DEFAULT(Version.DEFAULT, Kind.HOST);
 
     private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
@@ -137,6 +139,7 @@
       V5_1_1("5.1.1"),
       V6_0_1("6.0.1"),
       V7_0_0("7.0.0"),
+      V8_1_0("8.1.0"),
       DEFAULT("default");
 
       Version(String shortName) {
@@ -413,6 +416,7 @@
   private static final Map<DexVm, String> ART_DIRS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "art")
+          .put(DexVm.ART_8_1_0_HOST, "art-8.1.0")
           .put(DexVm.ART_7_0_0_HOST, "art-7.0.0")
           .put(DexVm.ART_6_0_1_HOST, "art-6.0.1")
           .put(DexVm.ART_5_1_1_HOST, "art-5.1.1")
@@ -421,6 +425,7 @@
   private static final Map<DexVm, String> ART_BINARY_VERSIONS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "bin/art")
+          .put(DexVm.ART_8_1_0_HOST, "bin/art")
           .put(DexVm.ART_7_0_0_HOST, "bin/art")
           .put(DexVm.ART_6_0_1_HOST, "bin/art")
           .put(DexVm.ART_5_1_1_HOST, "bin/art")
@@ -430,6 +435,7 @@
   private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
       ImmutableMap.of(
           DexVm.ART_DEFAULT, "bin/art",
+          DexVm.ART_8_1_0_HOST, "bin/art",
           DexVm.ART_7_0_0_HOST, "bin/art",
           DexVm.ART_6_0_1_HOST, "bin/art");
 
@@ -451,6 +457,7 @@
     ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS)
+        .put(DexVm.ART_8_1_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_6_0_1_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_5_1_1_HOST, ART_BOOT_LIBS)
@@ -459,6 +466,21 @@
     BOOT_LIBS = builder.build();
   }
 
+  private static final Map<DexVm, String> PRODUCT;
+
+  static {
+    ImmutableMap.Builder<DexVm, String> builder = ImmutableMap.builder();
+    builder
+        .put(DexVm.ART_DEFAULT, "angler")
+        .put(DexVm.ART_8_1_0_HOST, "marlin")
+        .put(DexVm.ART_7_0_0_HOST, "angler")
+        .put(DexVm.ART_6_0_1_HOST, "angler")
+        .put(DexVm.ART_5_1_1_HOST, "mako")
+        .put(DexVm.ART_4_4_4_HOST, "<missing>")
+        .put(DexVm.ART_4_0_4_HOST, "<missing>");
+    PRODUCT = builder.build();
+  }
+
   private static final Path DX = getDxExecutablePath();
 
   private static Path getDexVmPath(DexVm vm) {
@@ -478,12 +500,16 @@
     return getDexVmPath(vm).resolve("bin").resolve("dex2oat");
   }
 
-  private static Path getAnglerPath(DexVm vm) {
-    return getDexVmPath(vm).resolve("product").resolve("angler");
+  private static Path getProductPath(DexVm vm) {
+    return getDexVmPath(vm).resolve("product").resolve(PRODUCT.get(vm));
   }
 
-  private static Path getAnglerBootImagePath(DexVm vm) {
-    return getAnglerPath(vm).resolve("system").resolve("framework").resolve("boot.art");
+  private static String getArchString(DexVm vm) {
+    return vm.isOlderThanOrEqual(DexVm.ART_5_1_1_HOST) ? "arm" : "arm64";
+  }
+
+  private static Path getProductBootImagePath(DexVm vm) {
+    return getProductPath(vm).resolve("system").resolve("framework").resolve("boot.art");
   }
 
   public static byte[] getClassAsBytes(Class clazz) throws IOException {
@@ -695,6 +721,8 @@
     switch (dexVm.version) {
       case DEFAULT:
         return AndroidApiLevel.O;
+      case V8_1_0:
+        return AndroidApiLevel.O;
       case V7_0_0:
         return AndroidApiLevel.N;
       case V6_0_1:
@@ -1414,39 +1442,40 @@
   }
 
   public static void runDex2Oat(Path file, Path outFile) throws IOException {
-    DexVm vm = getDexVm();
-    if (vm.isOlderThanOrEqual(DexVm.ART_5_1_1_HOST)) {
-      // TODO(b/79191363): Support running dex2oat for past android versions.
-      // Run default dex2oat for tests on old runtimes.
-      vm = DexVm.ART_DEFAULT;
-    }
-    runDex2Oat(file, outFile, vm);
+    runDex2Oat(file, outFile, getDexVm());
   }
 
   public static void runDex2Oat(Path file, Path outFile, DexVm vm) throws IOException {
-    Assume.assumeTrue(ToolHelper.isDex2OatSupported());
+    ProcessResult result = runDex2OatRaw(file, outFile, vm);
+    if (result.exitCode != 0) {
+      fail("dex2oat failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    if (result.stderr != null && result.stderr.contains("Verification error")) {
+      fail("Verification error: \n" + result.stderr);
+    }
+  }
+
+  public static ProcessResult runDex2OatRaw(Path file, Path outFile, DexVm vm) throws IOException {
     // TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
-    Assume.assumeTrue(!ToolHelper.isWindows());
+    Assume.assumeTrue(ToolHelper.isDex2OatSupported());
+    if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+      // Run default dex2oat for tests on dalvik runtimes.
+      vm = DexVm.ART_DEFAULT;
+    }
     assert Files.exists(file);
     assert ByteStreams.toByteArray(Files.newInputStream(file)).length > 0;
     List<String> command = new ArrayList<>();
     command.add(getDex2OatPath(vm).toString());
-    command.add("--android-root=" + getAnglerPath(vm));
+    command.add("--android-root=" + getProductPath(vm) + "/system");
     command.add("--runtime-arg");
     command.add("-Xnorelocate");
-    command.add("--boot-image=" + getAnglerBootImagePath(vm));
     command.add("--dex-file=" + file.toAbsolutePath());
     command.add("--oat-file=" + outFile.toAbsolutePath());
-    command.add("--instruction-set=arm64");
+    // TODO(zerny): Create a proper interface for invoking dex2oat. Hardcoding arch here is a hack!
+    command.add("--instruction-set=" + getArchString(vm));
     ProcessBuilder builder = new ProcessBuilder(command);
     builder.environment().put("LD_LIBRARY_PATH", getDexVmLibPath(vm).toString());
-    ProcessResult result = runProcess(builder);
-    if (result.exitCode != 0) {
-      fail("dex2oat failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
-    }
-    if (result.stderr.contains("Verification error")) {
-      fail("Verification error: \n" + result.stderr);
-    }
+    return runProcess(builder);
   }
 
   public static ProcessResult runProguardRaw(
diff --git a/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
index 010f903..a9eef53 100644
--- a/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
+++ b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
@@ -64,8 +64,8 @@
 
   @Test
   public void dexTest() throws Exception {
-    TestRunResult d8Result = testForD8().addProgramFiles(inputJar).run("TestClass");
-    TestRunResult dxResult = testForDX().addProgramFiles(inputJar).run("TestClass");
+    TestRunResult<?> d8Result = testForD8().addProgramFiles(inputJar).run("TestClass");
+    TestRunResult<?> dxResult = testForDX().addProgramFiles(inputJar).run("TestClass");
     if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)) {
       d8Result.assertSuccessWithOutput(expectedOutput);
       dxResult.assertSuccessWithOutput(expectedOutput);
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 0607b0e..af238f2 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -41,13 +42,18 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Assert;
 import org.junit.ComparisonFailure;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
@@ -77,6 +83,22 @@
     return result;
   }
 
+  public void assertIdenticalZipFiles(File file1, File file2) throws IOException {
+    try (ZipFile zipFile1 = new ZipFile(file1); ZipFile zipFile2 = new ZipFile(file2)) {
+      final Enumeration<? extends ZipEntry> entries1 = zipFile1.entries();
+      final Enumeration<? extends ZipEntry> entries2 = zipFile2.entries();
+
+      while (entries1.hasMoreElements()) {
+        Assert.assertTrue(entries2.hasMoreElements());
+        ZipEntry entry1 = entries1.nextElement();
+        ZipEntry entry2 = entries2.nextElement();
+        Assert.assertEquals(entry1.getName(), entry2.getName());
+        Assert.assertEquals(entry1.getCrc(), entry2.getCrc());
+        Assert.assertEquals(entry1.getSize(), entry2.getSize());
+      }
+    }
+  }
+
   public AndroidApp runAndCheckVerification(
       CompilerUnderTest compiler,
       CompilationMode mode,
@@ -85,6 +107,19 @@
       Consumer<InternalOptions> optionsConsumer,
       List<String> inputs)
       throws ExecutionException, IOException, CompilationFailedException {
+    return runAndCheckVerification(compiler, mode, referenceApk, pgConfs, optionsConsumer,
+        DexIndexedConsumer::emptyConsumer, inputs);
+  }
+
+  public AndroidApp runAndCheckVerification(
+      CompilerUnderTest compiler,
+      CompilationMode mode,
+      String referenceApk,
+      List<String> pgConfs,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<DexIndexedConsumer> dexIndexedConsumerSupplier,
+      List<String> inputs)
+      throws ExecutionException, IOException, CompilationFailedException {
     assertTrue(referenceApk == null || new File(referenceApk).exists());
     AndroidAppConsumers outputApp;
     if (compiler == CompilerUnderTest.R8) {
@@ -95,7 +130,7 @@
             pgConfs.stream().map(Paths::get).collect(Collectors.toList()));
       }
       builder.setMode(mode);
-      builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      builder.setProgramConsumer(dexIndexedConsumerSupplier.get());
       builder.setMinApiLevel(AndroidApiLevel.L.getLevel());
       ToolHelper.allowPartiallyImplementedProguardOptions(builder);
       ToolHelper.addProguardConfigurationConsumer(
@@ -119,6 +154,7 @@
     return checkVerification(outputApp.build(), referenceApk);
   }
 
+
   public AndroidApp checkVerification(AndroidApp outputApp, String referenceApk)
       throws IOException, ExecutionException {
     Path out = temp.getRoot().toPath().resolve("all.zip");
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
index 09ce657..634ff6f 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
@@ -13,6 +14,7 @@
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public class GMSCoreDeployJarVerificationTest extends GMSCoreCompilationTestBase {
 
@@ -38,4 +40,20 @@
         optionsConsumer,
         Collections.singletonList(base + DEPLOY_JAR));
   }
+
+  public AndroidApp buildFromDeployJar(
+      CompilerUnderTest compiler, CompilationMode mode, String base, boolean hasReference,
+      Consumer<InternalOptions> optionsConsumer, Supplier<DexIndexedConsumer> consumerSupplier)
+      throws ExecutionException, IOException, ProguardRuleParserException,
+      CompilationFailedException {
+    return runAndCheckVerification(
+        compiler,
+        mode,
+        hasReference ? base + REFERENCE_APK : null,
+        null,
+        optionsConsumer,
+        consumerSupplier,
+        Collections.singletonList(base + DEPLOY_JAR));
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index d881185..5803355 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -7,8 +7,12 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.utils.AndroidApp;
+import java.io.File;
+import java.util.function.Supplier;
 import org.junit.Test;
 
 public class R8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
@@ -19,6 +23,9 @@
   @Test
   public void buildFromDeployJar() throws Exception {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
+    File tempFolder = temp.newFolder();
+    File app1Zip = new File(tempFolder, "app1.zip");
+    File app2Zip = new File(tempFolder, "app2.zip");
     AndroidApp app1 =
         buildFromDeployJar(
             CompilerUnderTest.R8,
@@ -27,7 +34,8 @@
             false,
             options ->
                 options.proguardMapConsumer =
-                    (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+                    (proguardMap, handler) -> this.proguardMap1 = proguardMap,
+            ()-> new ArchiveConsumer(app1Zip.toPath(), true));
     AndroidApp app2 =
         buildFromDeployJar(
             CompilerUnderTest.R8,
@@ -36,10 +44,14 @@
             false,
             options ->
                 options.proguardMapConsumer =
-                    (proguardMap, handler) -> this.proguardMap2 = proguardMap);
+                    (proguardMap, handler) -> this.proguardMap2 = proguardMap,
+            ()-> new ArchiveConsumer(app2Zip.toPath(), true));
+
+
 
     // Verify that the result of the two compilations was the same.
     assertIdenticalApplications(app1, app2);
+    assertIdenticalZipFiles(app1Zip, app2Zip);
     assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index bd0f8fb..dfe22c7 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import java.util.Map;
 import java.util.function.BiConsumer;
 import org.junit.Test;
@@ -50,7 +51,7 @@
     DexEncodedMethod foo = codeInspector.clazz(mainClass.getName()).method(signature).getMethod();
     IRCode irCode =
         foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
-    NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
+    NonNullTracker nonNullTracker = new NonNullTracker(appInfo, ImmutableSet.of());
     nonNullTracker.addNonNull(irCode);
     TypeAnalysis analysis = new TypeAnalysis(appInfo, foo);
     analysis.widening(foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 0572c36..55c64ee 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -128,6 +128,7 @@
     blocks.add(block);
 
     InternalOptions options = new InternalOptions();
+    options.debug = true;
     AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
     IRCode code =
         new IRCode(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index e569ad2..0ada569 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
 import java.util.function.Consumer;
 import org.junit.Test;
 
@@ -41,7 +42,7 @@
         foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     checkCountOfNonNull(irCode, 0);
 
-    NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
+    NonNullTracker nonNullTracker = new NonNullTracker(appInfo, ImmutableSet.of());
 
     nonNullTracker.addNonNull(irCode);
     assertTrue(irCode.isConsistentSSA());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 49a9457..eedfc2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -69,9 +69,11 @@
     // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
     // that ended up removing that goto changing the code to start with the unreachable
     // throw.
+    InternalOptions options = new InternalOptions();
+    options.debug = true;
     IRCode code =
         new IRCode(
-            new InternalOptions(),
+            options,
             null,
             blocks,
             new ValueNumberGenerator(),
@@ -150,9 +152,11 @@
     // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
     // that ended up removing that goto changing the code to start with the unreachable
     // throw.
+    InternalOptions options = new InternalOptions();
+    options.debug = true;
     IRCode code =
         new IRCode(
-            new InternalOptions(),
+            options,
             null,
             blocks,
             new ValueNumberGenerator(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 4e6bb21..592aeae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -12,7 +12,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
@@ -38,6 +41,7 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -46,9 +50,9 @@
 import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Iterator;
@@ -73,39 +77,35 @@
     this.backend = backend;
   }
 
-  private String run(AndroidApp app, Class mainClass) throws IOException {
-    if (backend == Backend.DEX) {
-      return runOnArt(app, mainClass);
-    } else {
-      assert backend == Backend.CF;
-      return runOnJava(app, mainClass);
-    }
-  }
-
   @Test
   public void testTrivial() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(TrivialTestClass.class),
-        ToolHelper.getClassAsBytes(TrivialTestClass.Inner.class),
-        ToolHelper.getClassAsBytes(ReferencedFields.class),
-        ToolHelper.getClassAsBytes(EmptyClass.class),
-        ToolHelper.getClassAsBytes(EmptyClassWithInitializer.class),
-        ToolHelper.getClassAsBytes(Iface1.class),
-        ToolHelper.getClassAsBytes(Iface1Impl.class),
-        ToolHelper.getClassAsBytes(Iface2.class),
-        ToolHelper.getClassAsBytes(Iface2Impl.class),
-        ToolHelper.getClassAsBytes(CycleReferenceAB.class),
-        ToolHelper.getClassAsBytes(CycleReferenceBA.class),
-        ToolHelper.getClassAsBytes(ClassWithFinal.class)
+    Class<?> main = TrivialTestClass.class;
+    Class<?>[] classes = {
+        TrivialTestClass.class,
+        TrivialTestClass.Inner.class,
+        ReferencedFields.class,
+        EmptyClass.class,
+        EmptyClassWithInitializer.class,
+        Iface1.class,
+        Iface1Impl.class,
+        Iface2.class,
+        Iface2Impl.class,
+        CycleReferenceAB.class,
+        CycleReferenceBA.class,
+        ClassWithFinal.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(TrivialTestClass.class);
-    String output = run(app, TrivialTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
@@ -162,28 +162,44 @@
 
   @Test
   public void testBuilders() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(BuildersTestClass.class),
-        ToolHelper.getClassAsBytes(BuildersTestClass.Pos.class),
-        ToolHelper.getClassAsBytes(Tuple.class),
-        ToolHelper.getClassAsBytes(Pair.class),
-        ToolHelper.getClassAsBytes(PairBuilder.class),
-        ToolHelper.getClassAsBytes(ControlFlow.class),
+    Class<?> main = BuildersTestClass.class;
+    Class<?>[] classes = {
+        NeverInline.class,
+        BuildersTestClass.class,
+        BuildersTestClass.Pos.class,
+        Tuple.class,
+        Pair.class,
+        PairBuilder.class,
+        ControlFlow.class,
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), BuildersTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .enableProguardTestOptions()
+        .enableInliningAnnotations()
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(BuildersTestClass.class);
-    String output = run(app, BuildersTestClass.class);
-    assertEquals(javaOutput, output);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
-
-    assertEquals(
-        Sets.newHashSet(
-            "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
-            "java.lang.StringBuilder"),
-        collectTypes(clazz, "testSimpleBuilder", "void"));
+    for (int i = 1; i <= 3; i++) {
+      Set<String> expected =
+          Sets.newHashSet(
+              "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
+              "java.lang.StringBuilder");
+      if (backend == Backend.CF && i < 3) {
+        // const-string canonicalization is disabled in CF, which helps ClassInliner identify
+        // PairBuilder as candidate. Concatenated builder calls in test #3 bother that again.
+        expected.add("com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder");
+      }
+      assertEquals(expected,
+          collectTypes(clazz, "testSimpleBuilder" + i, "void"));
+    }
 
     // Note that Pair created instances were also inlined in the following method since
     // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
@@ -193,7 +209,9 @@
         Collections.singleton("java.lang.StringBuilder"),
         collectTypes(clazz, "testSimpleBuilderWithMultipleBuilds", "void"));
 
-    assertFalse(inspector.clazz(PairBuilder.class).isPresent());
+    if (backend == Backend.DEX) {
+      assertFalse(inspector.clazz(PairBuilder.class).isPresent());
+    }
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
@@ -242,7 +260,6 @@
         runOnJavaRaw(mainClass.name, builder.buildClasses().toArray(new byte[2][]));
     assertThat(javaResult.stderr, containsString("IncompatibleClassChangeError"));
 
-    assert backend == Backend.DEX || backend == Backend.CF;
     // Check that the code fails with an IncompatibleClassChangeError with ART.
     ProcessResult result =
         backend == Backend.DEX
@@ -253,19 +270,24 @@
 
   @Test
   public void testCodeSample() throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(C.class),
-        ToolHelper.getClassAsBytes(C.L.class),
-        ToolHelper.getClassAsBytes(C.F.class),
-        ToolHelper.getClassAsBytes(CodeTestClass.class)
+    Class<?> main = CodeTestClass.class;
+    Class<?>[] classes = {
+        C.class,
+        C.L.class,
+        C.F.class,
+        CodeTestClass.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), CodeTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(CodeTestClass.class);
-    String output = run(app, CodeTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
+    CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(C.class);
 
     assertEquals(
@@ -287,22 +309,26 @@
   @Test
   public void testInvalidatedRoot() throws Exception {
     String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
-
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
-        ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+    Class<?> main = InvalidRootsTestClass.class;
+    Class<?>[] classes = {
+        InvalidRootsTestClass.class,
+        InvalidRootsTestClass.A.class,
+        InvalidRootsTestClass.B.class,
+        InvalidRootsTestClass.NeverReturnsNormally.class,
+        InvalidRootsTestClass.InitNeverReturnsNormally.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(InvalidRootsTestClass.class);
-    String output = run(app, InvalidRootsTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
@@ -331,20 +357,25 @@
 
   @Test
   public void testDesugaredLambdas() throws Exception {
-    Assume.assumeFalse(backend == Backend.CF); // No desugaring with CF backend.
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(LambdasTestClass.class),
-        ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
-        ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+    Assume.assumeFalse("No desugaring with CF backend", backend == Backend.CF);
+    Class<?> main = LambdasTestClass.class;
+    Class<?>[] classes = {
+        LambdasTestClass.class,
+        LambdasTestClass.Iface.class,
+        LambdasTestClass.IfaceUtil.class
     };
-    AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+    String javaOutput = runOnJava(main);
+    TestRunResult result = testForR8(backend)
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepRules(
+            "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+        .addOptionsModification(this::configure)
+        .run(main)
+        .assertSuccessWithOutput(javaOutput);
 
-    String javaOutput = runOnJava(LambdasTestClass.class);
-    String output = run(app, LambdasTestClass.class);
-    assertEquals(javaOutput, output);
-
-    CodeInspector inspector = new CodeInspector(app);
-    ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+    CodeInspector inspector = result.inspector();
+    ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
         Sets.newHashSet(
@@ -391,42 +422,6 @@
         .map(fais -> fais.holder().toString());
   }
 
-  private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
-    AndroidApp compiled =
-        compileWithR8(
-            app, getProguardConfig(mainClass.getCanonicalName()), this::configure, backend);
-
-    // Materialize file for execution.
-    Path generatedFile = temp.getRoot().toPath().resolve("classes.jar");
-    compiled.writeToZip(generatedFile, outputMode(backend));
-
-    assert backend == Backend.DEX || backend == Backend.CF;
-
-    String output =
-        backend == Backend.DEX
-            ? ToolHelper.runArtNoVerificationErrors(
-                generatedFile.toString(), mainClass.getCanonicalName())
-            : ToolHelper.runJava(generatedFile, mainClass.getCanonicalName()).stdout;
-
-    // Compare with Java.
-    ToolHelper.ProcessResult javaResult = ToolHelper.runJava(
-        ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
-
-    if (javaResult.exitCode != 0) {
-      System.out.println(javaResult.stdout);
-      System.err.println(javaResult.stderr);
-      fail("JVM on original program failed for: " + mainClass);
-    }
-    assertEquals(
-        backend == Backend.DEX
-            ? "JVM and ART output differ."
-            : "Output of original and processed program differ on JVM.",
-        javaResult.stdout,
-        output);
-
-    return compiled;
-  }
-
   private String getProguardConfig(String main) {
     return StringUtils.joinLines(
         keepMainProguardConfiguration(main),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
index 1516e14..aa6d4dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.builders;
 
+import com.android.tools.r8.NeverInline;
+
 public class BuildersTestClass {
   private static int ID = 0;
 
@@ -17,31 +19,35 @@
 
   public static void main(String[] args) {
     BuildersTestClass test = new BuildersTestClass();
-    test.testSimpleBuilder();
+    test.testSimpleBuilder1();
     test.testSimpleBuilderWithMultipleBuilds();
     test.testBuilderConstructors();
     test.testWithControlFlow();
     test.testWithMoreControlFlow();
   }
 
-  private synchronized void testSimpleBuilder() {
+  @NeverInline
+  private void testSimpleBuilder1() {
     System.out.println(
-        new PairBuilder<String, String>().setFirst("f-" + next()).build().toString());
+        new PairBuilder<String, String>().setFirst("f-" + next()).build());
     testSimpleBuilder2();
     testSimpleBuilder3();
   }
 
-  private synchronized void testSimpleBuilder2() {
+  @NeverInline
+  private void testSimpleBuilder2() {
     System.out.println(
-        new PairBuilder<String, String>().setSecond("s-" + next()).build().toString());
+        new PairBuilder<String, String>().setSecond("s-" + next()).build());
   }
 
-  private synchronized void testSimpleBuilder3() {
+  @NeverInline
+  private void testSimpleBuilder3() {
     System.out.println(new PairBuilder<String, String>()
-        .setFirst("f-" + next()).setSecond("s-" + next()).build().toString());
+        .setFirst("f-" + next()).setSecond("s-" + next()).build());
   }
 
-  private synchronized void testSimpleBuilderWithMultipleBuilds() {
+  @NeverInline
+  private void testSimpleBuilderWithMultipleBuilds() {
     PairBuilder<String, String> builder = new PairBuilder<>();
     Pair p1 = builder.build();
     System.out.println(p1.toString());
@@ -53,13 +59,15 @@
     System.out.println(p3.toString());
   }
 
-  private synchronized void testBuilderConstructors() {
+  @NeverInline
+  private void testBuilderConstructors() {
     System.out.println(new Tuple().toString());
     System.out.println(new Tuple(true, (byte) 77, (short) 9977, '#', 42,
         987654321123456789L, -12.34f, 43210.98765, "s-" + next() + "-s").toString());
   }
 
-  private synchronized void testWithControlFlow() {
+  @NeverInline
+  private void testWithControlFlow() {
     ControlFlow flow = new ControlFlow(-1, 2, 7);
     for (int k = 0; k < 25; k++) {
       if (k % 3 == 0) {
@@ -71,7 +79,8 @@
     System.out.println("flow = " + flow.toString());
   }
 
-  private synchronized void testWithMoreControlFlow() {
+  @NeverInline
+  private void testWithMoreControlFlow() {
     String str = "1234567890";
     Pos pos = new Pos();
     while (pos.y < str.length()) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index f078262..6fea6cd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -134,6 +134,6 @@
         inspector.clazz(TestClass.class).method("void", "bar", ImmutableList.of());
     Iterator<InstructionSubject> barInstructionIterator =
         barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
-    assertEquals(6, Streams.stream(barInstructionIterator).count());
+    assertEquals(4, Streams.stream(barInstructionIterator).count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 09ab7f7..99cce0b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -153,6 +153,53 @@
       // getSimpleName, local
       "-Returned-empty-"
   );
+  private static final String RENAMED_OUTPUT = StringUtils.lines(
+      // getName
+      "com.android.tools.r8.ir.optimize.reflection.e",
+      // getTypeName
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.e",
+      // getCanonicalName
+      "com.android.tools.r8.ir.optimize.reflection.e",
+      // getSimpleName
+      "e",
+      // getName, inner
+      "com.android.tools.r8.ir.optimize.reflection.c",
+      // getTypeName, inner
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.c",
+      // getCanonicalName, inner
+      "com.android.tools.r8.ir.optimize.reflection.c",
+      // getSimpleName, inner
+      "c",
+      // getName, array
+      "[Lcom.android.tools.r8.ir.optimize.reflection.e;",
+      // getTypeName, array
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.e[]",
+      // getCanonicalName, array
+      "com.android.tools.r8.ir.optimize.reflection.e[]",
+      // getSimpleName, array
+      "e[]",
+      // getName, anonymous
+      "com.android.tools.r8.ir.optimize.reflection.a",
+      // getTypeName, anonymous
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.a",
+      // getCanonicalName, anonymous
+      "-Returned-null-",
+      // getSimpleName, anonymous
+      "-Returned-empty-",
+      // getName, local
+      "com.android.tools.r8.ir.optimize.reflection.b",
+      // getTypeName, local
+      // TODO(b/119426668): desugar Type#getTypeName
+      // "com.android.tools.r8.ir.optimize.reflection.b,
+      // getCanonicalName, local
+      "-Returned-null-",
+      // getSimpleName, local
+      "-Returned-empty-"
+  );
   private static final Class<?> MAIN = GetName0Main.class;
 
   public GetNameTest(Backend backend, boolean enableMinification) throws Exception {
@@ -212,18 +259,13 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep class **.GetName0*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
-    TestRunResult result = builder.run(MAIN);
-    if (enableMinification) {
-      // TODO(b/118536394): Check even renamed names.
-      test(result, 11);
-    } else {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-      test(result, 0);
-    }
+    TestRunResult result = builder.run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 0);
   }
 
   @Test
@@ -235,7 +277,8 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep,allowobfuscation class **.GetName0*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
@@ -245,12 +288,11 @@
       if (backend == Backend.CF) {
         return;
       }
-      // TODO(b/118536394): Check even renamed names.
-      test(result, 11);
+      result.assertSuccessWithOutput(RENAMED_OUTPUT);
     } else {
       result.assertSuccessWithOutput(JAVA_OUTPUT);
-      test(result, 0);
     }
+    test(result, 0);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
index ef7a07b..22918c8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
@@ -4,10 +4,13 @@
 package com.android.tools.r8.ir.optimize.reflection;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.Streams;
+import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Collection;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -19,6 +22,7 @@
 
   final Backend backend;
   final boolean enableMinification;
+  Path mapping;
 
   @Parameterized.Parameters(name = "Backend: {0} minification: {1}")
   public static Collection<Object[]> data() {
@@ -30,6 +34,11 @@
     this.enableMinification = enableMinification;
   }
 
+  Path createNewMappingPath() throws IOException {
+    mapping = temp.newFile(ToolHelper.DEFAULT_PROGUARD_MAP_FILE).toPath();
+    return mapping;
+  }
+
   static boolean isNameReflection(DexMethod method) {
     return method.getHolder().toDescriptorString().equals(CLASS_DESCRIPTOR)
         && method.getArity() == 0
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index 54015e3..5bc07da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -88,6 +88,14 @@
       "Local[][][]",
       "[][][]"
   );
+  private static final String RENAMED_OUTPUT = StringUtils.lines(
+      "f",
+      "e",
+      "b",
+      "a",
+      "d[][][]",
+      "[][][]"
+  );
   private static final Class<?> MAIN = ClassGetSimpleName.class;
 
   public GetSimpleNameTest(Backend backend, boolean enableMinification) throws Exception {
@@ -146,16 +154,12 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep class **.ClassGetSimpleName*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
-    TestRunResult result = builder.run(MAIN);
-    if (enableMinification) {
-      // TODO(b/118536394): Check even renamed simple name.
-    } else {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-    }
+    TestRunResult result = builder.run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 0);
   }
 
@@ -168,7 +172,8 @@
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
       builder.addKeepRules("-dontobfuscate");
     }
@@ -178,7 +183,7 @@
       if (backend == Backend.CF) {
         return;
       }
-      // TODO(b/118536394): Check even renamed simple name.
+      result.assertSuccessWithOutput(RENAMED_OUTPUT);
     } else {
       result.assertSuccessWithOutput(JAVA_OUTPUT);
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index 353dd50..284af11 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -30,7 +30,10 @@
   static boolean argCouldBeNull(String arg) {
     return "CONST".contains(arg)
         && "prefix".startsWith(arg)
-        && "suffix".endsWith(arg);
+        && "suffix".endsWith(arg)
+        && "CONST".equals(arg)
+        && "CONST".equalsIgnoreCase(arg)
+        && "CONST".contentEquals(arg);
   }
 
   public static void main(String[] args) {
@@ -39,6 +42,10 @@
       System.out.println(s1.contains("CONST"));
       System.out.println(s1.startsWith("prefix"));
       System.out.println(s1.endsWith("suffix"));
+      System.out.println(s1.equals("prefix-CONST-suffix"));
+      System.out.println(s1.equalsIgnoreCase("PREFIX-const-SUFFIX"));
+      System.out.println(s1.contentEquals("prefix-CONST-suffix"));
+      System.out.println(s1.contentEquals(new StringBuffer("prefix-CONST-suffix")));
     }
 
     {
@@ -47,6 +54,10 @@
       System.out.println(s2.contains("CONST"));
       System.out.println(s2.startsWith("prefix"));
       System.out.println(s2.endsWith("suffix"));
+      System.out.println(s2.equals("prefix-CONST-suffix"));
+      System.out.println(s2.equalsIgnoreCase("pre-con-suf"));
+      System.out.println(s2.contentEquals("prefix-CONST-suffix"));
+      System.out.println(s2.contentEquals(new StringBuffer("prefix-CONST-suffix")));
     }
 
     {
@@ -75,12 +86,28 @@
       "true",
       // s1, endsWith
       "true",
+      // s1, equals
+      "true",
+      // s1, equalsIgnoreCase
+      "true",
+      // s1, contentEquals(CharSequence)
+      "true",
+      // s1, contentEquals(StringBuffer)
+      "true",
       // s2, contains
       "false",
       // s2, startsWith
       "false",
       // s2, endsWith
       "false",
+      // s2, equals
+      "false",
+      // s2, equalsIgnoreCase
+      "false",
+      // s2, contentEquals(CharSequence)
+      "false",
+      // s2, contentEquals(StringBuffer)
+      "false",
       // argCouldBeNull
       "false"
   );
@@ -107,7 +134,10 @@
         && method.proto.returnType.isBooleanType()
         && (method.name.toString().equals("contains")
             || method.name.toString().equals("startsWith")
-            || method.name.toString().equals("endsWith"));
+            || method.name.toString().equals("endsWith")
+            || method.name.toString().equals("equals")
+            || method.name.toString().equals("equalsIgnoreCase")
+            || method.name.toString().equals("contentEquals"));
   }
 
   private long countStringContentChecker(MethodSubject method) {
@@ -131,7 +161,7 @@
         "boolean", "argCouldBeNull", ImmutableList.of("java.lang.String"));
     assertThat(argCouldBeNull, isPresent());
     // Because of nullable argument, all checkers should remain.
-    assertEquals(3, countStringContentChecker(argCouldBeNull));
+    assertEquals(6, countStringContentChecker(argCouldBeNull));
   }
 
   @Test
@@ -143,14 +173,14 @@
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 6);
+    test(result, 14);
 
     result = testForD8()
         .release()
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 3);
+    test(result, 8);
   }
 
   @Test
@@ -162,6 +192,6 @@
         .addKeepMainRule(MAIN)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 3);
+    test(result, 8);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 6642619..3f579c7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -54,13 +54,9 @@
 
   public static void main(String[] args) {
     Foo foo = new Foo();
-    // TODO(b/118536394): valueOf in getter() can be removed if combined with name reflection
-    // optimization, which will replace it with (definitely non-null) const-string.
     System.out.println(foo.getter());
     // Trivial, it's String.
     String str = foo.toString();
-    // TODO(b/119449728): But, it's still nullable.
-    // valueOf can be removed if the nullability of its return value is modeled.
     System.out.println(String.valueOf(str));
     if (str != null) {
       // With an explicit check, it's non-null String.
@@ -195,6 +191,6 @@
         .addKeepRules("-dontobfuscate")
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 3, 1, 1);
+    test(result, 1, 1, 1);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 7e4b76a..5ed3cc5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -21,7 +21,7 @@
 
   private static class MockRegisterAllocator implements RegisterAllocator {
     @Override
-    public void allocateRegisters(boolean debug) {
+    public void allocateRegisters() {
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index 97eb99e..4dba00d 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
+import com.android.tools.r8.VmTestRunner.IgnoreForVmVersions;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.utils.ThrowingBiFunction;
@@ -468,6 +469,8 @@
   }
 
   @Test
+  // TODO(119938529): Triage.
+  @IgnoreForVmVersions(Version.V8_1_0)
   public void testRebindVirtualCallToStatic() throws Exception {
     // Library classes.
     JasminBuilder libraryBuilder = new JasminBuilder();
@@ -501,6 +504,8 @@
   }
 
   @Test
+  // TODO(119938529): Triage.
+  @IgnoreForVmVersions(Version.V8_1_0)
   public void testRebindVirtualCallToPackagePrivateStatic() throws Exception {
     // Library classes.
     JasminBuilder libraryBuilder = new JasminBuilder();
@@ -536,6 +541,8 @@
   }
 
   @Test
+  // TODO(119938529): Triage.
+  @IgnoreForVmVersions(Version.V8_1_0)
   public void testRebindVirtualCallToStaticInPackagePrivateClass() throws Exception {
     // Library classes.
     JasminBuilder libraryBuilder = new JasminBuilder();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index c053e62..9d9f21d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -9,16 +9,19 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.util.stream.Collectors;
 import org.junit.Rule;
@@ -27,6 +30,34 @@
 
 public class MainDexListOutputTest extends TestBase {
 
+  interface MyConsumer<T> {
+    void accept(T element);
+  }
+
+  class TestClass {
+    public void f(MyConsumer<String> s) {
+      s.accept("asdf");
+    }
+
+    public void g() {
+      f(System.out::println);
+    }
+  }
+
+  private static String testClassMainDexName =
+      "com/android/tools/r8/maindexlist/MainDexListOutputTest$TestClass.class";
+
+  private static class TestMainDexListConsumer implements StringConsumer {
+    public boolean called = false;
+
+    @Override
+    public void accept(String string, DiagnosticsHandler handler) {
+      called = true;
+      assertTrue(string.contains(testClassMainDexName));
+      assertTrue(string.contains("Lambda"));
+    }
+  }
+
   class Reporter implements DiagnosticsHandler {
     int errorCount = 0;
 
@@ -77,4 +108,37 @@
             .filter(s -> !s.isEmpty())
             .collect(Collectors.toList()));
   }
+
+  @Test
+  public void testD8DesugaredLambdasInMainDexList() throws IOException, CompilationFailedException {
+    Path mainDexList = writeTextToTempFile(testClassMainDexName);
+    TestMainDexListConsumer consumer = new TestMainDexListConsumer();
+    testForD8()
+        .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class))
+        .addMainDexListFiles(ImmutableList.of(mainDexList))
+        .setMainDexListConsumer(consumer)
+        .compile();
+    assertTrue(consumer.called);
+  }
+
+  @Test
+  public void testD8DesugaredLambdasInMainDexListMerging()
+      throws IOException, CompilationFailedException {
+    Path mainDexList = writeTextToTempFile(testClassMainDexName);
+    Path dexOutput = temp.getRoot().toPath().resolve("classes.zip");
+    // Build intermediate dex code first.
+    testForD8()
+        .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class))
+        .setIntermediate(true)
+        .setProgramConsumer(new ArchiveConsumer(dexOutput))
+        .compile();
+    // Now test that when merging with a main dex list it is correctly updated.
+    TestMainDexListConsumer consumer = new TestMainDexListConsumer();
+    testForD8()
+        .addProgramFiles(dexOutput)
+        .addMainDexListFiles(ImmutableList.of(mainDexList))
+        .setMainDexListConsumer(consumer)
+        .compile();
+    assertTrue(consumer.called);
+  }
 }
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 ddb0e7d..e013b02 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -20,15 +20,21 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -301,6 +307,34 @@
         nonLambdaOffset++;
       }
     }
+    testZipfileOrder(out);
+  }
+
+  private void testZipfileOrder(Path out) throws IOException {
+    // Sanity check that the classes.dex files are in the correct order
+    ZipFile outZip = new ZipFile(out.toFile());
+    Enumeration<? extends ZipEntry> entries = outZip.entries();
+    int index = 0;
+    LinkedList<String> entryNames = new LinkedList<>();
+    // We expect classes*.dex files first, in order, then the rest of the files, in order.
+    while(entries.hasMoreElements()) {
+      ZipEntry entry = entries.nextElement();
+      if (!entry.getName().startsWith("classes") || !entry.getName().endsWith(".dex")) {
+        entryNames.add(entry.getName());
+        continue;
+      }
+      if (index == 0) {
+        Assert.assertEquals("classes.dex", entry.getName());
+      } else {
+        Assert.assertEquals("classes" + (index + 1) + ".dex", entry.getName());
+      }
+      index++;
+    }
+    // Everything else should be sorted according to name.
+    String[] entriesUnsorted = entryNames.toArray(new String[0]);
+    String[] entriesSorted = entryNames.toArray(new String[0]);
+    Arrays.sort(entriesSorted);
+    Assert.assertArrayEquals(entriesUnsorted, entriesSorted);
   }
 
   private boolean isLambda(String mainDexEntry) {
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 095d7e9..bfd3b76 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -265,7 +265,6 @@
         options -> {
           options.dataResourceConsumer = dataResourceConsumer;
           options.proguardMapConsumer = proguardMapConsumer;
-          options.testing.suppressExperimentalCfBackendWarning = true;
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index c3311a4..410ae0a 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -325,10 +325,7 @@
                     .setProgramConsumer(emptyConsumer(backend))
                     .addLibraryFiles(runtimeJar(backend))
                     .setProguardMapConsumer(StringConsumer.emptyConsumer())
-                    .build(),
-                options -> {
-                  options.testing.suppressExperimentalCfBackendWarning = true;
-                }));
+                    .build()));
     // All classes are kept, and renamed.
     assertThat(inspector.clazz("Simple"), isRenamed());
     assertThat(inspector.clazz("Base"), isRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 6877953..1c4e31b 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -190,10 +190,7 @@
                     .setProgramConsumer(emptyConsumer(backend))
                     .addLibraryFiles(runtimeJar(backend))
                     .setProguardMapConsumer(StringConsumer.emptyConsumer())
-                    .build(),
-                options -> {
-                  options.testing.suppressExperimentalCfBackendWarning = true;
-                }));
+                    .build()));
     // All classes are kept, and renamed.
     ClassSubject clazz = inspector.clazz("Fields");
     assertThat(clazz, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index 706bdd4..3c74a5b 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -212,10 +212,7 @@
                     .setProgramConsumer(emptyConsumer(backend))
                     .addLibraryFiles(runtimeJar(backend))
                     .setProguardMapConsumer(StringConsumer.emptyConsumer())
-                    .build(),
-                options -> {
-                  options.testing.suppressExperimentalCfBackendWarning = true;
-                }));
+                    .build()));
     // All classes are kept, and renamed.
     ClassSubject clazz = inspector.clazz("Methods");
     assertThat(clazz, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
index 9ebb6cc..74e6471 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -123,7 +123,6 @@
         o -> {
           o.enableInlining = false;
           o.forceProguardCompatibility = forceProguardCompatibility;
-          o.testing.suppressExperimentalCfBackendWarning = true;
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
new file mode 100644
index 0000000..4c2fedc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -0,0 +1,180 @@
+// 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.reachabilitysensitive;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.AddIntLit8;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import dalvik.annotation.optimization.ReachabilitySensitive;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class TestClass {
+  public void method() {
+    int i = 2;
+    int j = i + 1;
+    int k = j + 2;
+    System.out.println(k);
+  }
+}
+
+class TestClassWithAnnotatedField {
+  @ReachabilitySensitive private final long field = 0;
+
+  public void method() {
+    int i = 2;
+    int j = i + 1;
+    int k = j + 2;
+    System.out.println(k);
+  }
+}
+
+class TestClassWithAnnotatedMethod {
+
+  @ReachabilitySensitive
+  public void unrelatedAnnotatedMethod() {}
+
+  public void method() {
+    int i = 2;
+    int j = i + 1;
+    int k = j + 2;
+    System.out.println(k);
+  }
+}
+
+@RunWith(Parameterized.class)
+public class ReachabilitySensitiveTest extends TestBase {
+
+  private final Tool tool;
+
+  @Parameters(name = "{0}")
+  public static List<Object> data() {
+    return ImmutableList.of(Tool.D8, Tool. R8);
+  }
+
+  public ReachabilitySensitiveTest(Tool tool) {
+    this.tool = tool;
+  }
+
+  @Test
+  public void testNoAnnotation()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    CodeInspector inspector = tool == Tool.R8
+        ? compileR8(TestClass.class)
+        : compile(TestClass.class);
+    DexCode code =
+        inspector.method(TestClass.class.getMethod("method")).getMethod().getCode().asDexCode();
+    // Computation of k is constant folded and the value takes up one register. System.out takes
+    // up another register and the receiver is the last.
+    assertEquals(3, code.registerSize);
+    checkNoLocals(code);
+  }
+
+  @Test
+  public void testFieldAnnotation()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    CodeInspector inspector = tool == Tool.R8
+        ? compileR8(TestClassWithAnnotatedField.class)
+        : compile(TestClassWithAnnotatedField.class);
+    checkAnnotatedCode(
+        inspector
+            .method(TestClassWithAnnotatedField.class.getMethod("method"))
+            .getMethod()
+            .getCode()
+            .asDexCode());
+  }
+
+  @Test
+  public void testMethodAnnotation()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    CodeInspector inspector = tool == Tool.R8
+        ? compileR8(TestClassWithAnnotatedMethod.class)
+        : compile(TestClassWithAnnotatedMethod.class);
+    checkAnnotatedCode(
+        inspector
+            .method(TestClassWithAnnotatedMethod.class.getMethod("method"))
+            .getMethod()
+            .getCode()
+            .asDexCode());
+  }
+
+  private void checkNoLocals(DexCode code) {
+    // Even if we preserve live range of locals, we do not output locals information
+    // as this is a release build.
+    assertTrue((code.getDebugInfo() == null) ||
+        Arrays.stream(code.getDebugInfo().events)
+            .allMatch(event -> !(event instanceof StartLocal)));
+  }
+
+  private void checkAnnotatedCode(DexCode code) {
+    // All live at the same time: receiver, i, j, k, System.out.
+    assertEquals(5, code.registerSize);
+    Instruction first = code.instructions[0];
+    Instruction second = code.instructions[1];
+    Instruction third = code.instructions[2];
+    // None of the local declarations overwrite other locals.
+    assertTrue(first instanceof Const4);
+    assertTrue(second instanceof AddIntLit8);
+    assertTrue(third instanceof AddIntLit8);
+    int firstRegister = ((Const4) first).A;
+    int secondRegister = ((AddIntLit8) second).AA;
+    int thirdRegister = ((AddIntLit8) third).AA;
+    assertFalse(firstRegister == secondRegister);
+    assertFalse(firstRegister == thirdRegister);
+    assertFalse(secondRegister == thirdRegister);
+    checkNoLocals(code);
+  }
+
+  private CodeInspector compile(Class... classes)
+      throws CompilationFailedException, IOException, ExecutionException {
+    return testForD8()
+        .addProgramClasses(classes)
+        .setMode(CompilationMode.RELEASE)
+        .compile()
+        .inspector();
+  }
+
+  private CodeInspector compileR8(Class... classes)
+      throws CompilationFailedException, IOException, ExecutionException {
+    List<String> keepRules =
+        Arrays.stream(classes)
+            .map(c -> "-keep class " + c.getCanonicalName() + " { <methods>; }")
+            .collect(Collectors.toList());
+    return testForR8(Backend.DEX)
+        .addProgramClasses(classes)
+        // TODO(ager): This will be in android.jar over time. For now, make it part of the app.
+        .addProgramClasses(ReachabilitySensitive.class)
+        .setMode(CompilationMode.RELEASE)
+        // Keep the input class and its methods.
+        .addKeepRules(keepRules)
+        // Keep the annotation class.
+        .addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
+        // Keep the annotation so R8 can find it and honor it. It also needs to be available
+        // at runtime so that the Art runtime can honor it as well, so if it is not kept we
+        // do not have to honor it as the runtime will not know to do so in any case.
+        .addKeepRules("-keepattributes RuntimeVisibleAnnotations")
+        .compile()
+        .inspector();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java
new file mode 100644
index 0000000..12bd452
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.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.regress.b118075510;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Regress118075510Runner extends AsmTestBase {
+
+  public static final Class<?> CLASS = Regress118075510Test.class;
+  public static final String EXPECTED = StringUtils.lines("0", "0");
+
+  @Test
+  public void test()
+      throws CompilationFailedException, IOException, ExecutionException, NoSuchMethodException {
+    testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
+
+    D8TestCompileResult d8Result =
+        testForD8().addProgramClasses(CLASS).setMinApi(AndroidApiLevel.M).release().compile();
+
+    CodeInspector inspector = d8Result.inspector();
+    checkMethodContainsLongSignum(inspector, "fooNoTryCatch");
+    checkMethodContainsLongSignum(inspector, "fooWithTryCatch");
+    // Check the program runs on ART/Dalvik
+    d8Result.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    // Check the program can be dex2oat compiled to arm64. This will diverge without the fixup.
+    d8Result.runDex2Oat().assertSuccess();
+  }
+
+  private void checkMethodContainsLongSignum(CodeInspector inspector, String methodName)
+      throws NoSuchMethodException {
+    MethodSubject method = inspector.method(CLASS.getMethod(methodName, long.class, long.class));
+    Assert.assertTrue(
+        "Did not contain Long.signum workaround in "
+            + methodName
+            + ":\n"
+            + method.getMethod().codeToString(),
+        method
+            .streamInstructions()
+            .anyMatch(
+                i ->
+                    i.isInvoke() && i.getMethod().qualifiedName().equals("java.lang.Long.signum")));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Test.java b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Test.java
new file mode 100644
index 0000000..cc4dece
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Test.java
@@ -0,0 +1,38 @@
+// 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.regress.b118075510;
+
+public class Regress118075510Test {
+
+  public static void fooNoTryCatch(long a, long b) {
+    // Call a method on the runner class that will not be on the classpath at runtime.
+    // This causes the optimizing 6.0.1 compiler to delegate to the old quick compiler.
+    if (a == b) Regress118075510Runner.class.getMethods();
+    // The else branch of the conditional here ends up with an invalid previous pointer at its
+    // block header (ie, previous of mul-long (2addr) points to epilogue-end which is self-linked.
+    System.out.println(a < b ? 0 : a * b + b);
+  }
+
+  public static void fooWithTryCatch(long a, long b) {
+    // Call a method on the runner class that will not be on the classpath at runtime.
+    // This causes the optimizing 6.0.1 compiler to delegate to the old quick compiler.
+    if (a == b) Regress118075510Runner.class.getMethods();
+    // The else branch of the conditional here ends up with an invalid previous pointer at its
+    // block header (ie, previous of mul-long (2addr) points to epilogue-end which is self-linked.
+    try {
+      if (a < b) {
+        System.out.println((long) 0);
+      } else {
+        System.out.println(a * b + b);
+      }
+    } catch (RuntimeException e) {
+      e.printStackTrace();
+    }
+  }
+
+  public static void main(String[] args) {
+    fooNoTryCatch(args.length, 456);
+    fooWithTryCatch(456, args.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
index cbc6e1b..3ab3467 100644
--- a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
+++ b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
@@ -5,29 +5,20 @@
 
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
-import java.nio.file.Path;
 import org.junit.Test;
 
 public class Regress77842465 extends AsmTestBase {
 
   @Test
   public void test() throws CompilationFailedException, IOException {
-
-    Path dexOut = temp.getRoot().toPath().resolve("out.jar");
-    Path oatOut = temp.getRoot().toPath().resolve("out.odex");
-
-    D8.run(D8Command.builder()
-        .addClassProgramData(Regress77842465Dump.dump(), Origin.unknown())
-        .setOutput(dexOut, OutputMode.DexIndexed)
-        .setDisableDesugaring(true)
-        .build());
-
-    ToolHelper.runDex2Oat(dexOut, oatOut);
+    testForD8()
+        .addProgramClassFileData(Regress77842465Dump.dump())
+        .noDesugaring()
+        .setMinApi(AndroidApiLevel.M)
+        .compile()
+        .runDex2Oat()
+        .assertSuccess();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
index 99f8c55..6ee7392 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
@@ -51,6 +51,7 @@
         case V4_0_4:
         case V4_4_4:
         case V7_0_0:
+        case V8_1_0:
         case DEFAULT:
           assertNotEquals(-1, d8Result.stderr.indexOf("java.lang.VerifyError"));
           assertNotEquals(-1, r8Result.stderr.indexOf("java.lang.VerifyError"));
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index ab8d23c..4d00bba 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
 import java.util.List;
@@ -152,77 +151,6 @@
     }
   }
 
-  private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload 0",
-        "    lookupswitch",
-        "      " + key1 + " : case_1",
-        "      " + key2 + " : case_2",
-        "      default : case_default",
-        "  case_1:",
-        "    iconst_3",
-        "    goto return_",
-        "  case_2:",
-        "    iconst_4",
-        "    goto return_",
-        "  case_default:",
-        "    iconst_5",
-        "  return_:",
-        "    ireturn");
-
-    clazz.addMainMethod(
-        "    .limit stack 2",
-        "    .limit locals 1",
-        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "    ldc 2",
-        "    invokestatic Test/test(I)I",
-        "    invokevirtual java/io/PrintStream/print(I)V",
-        "    return");
-
-    AndroidApp app =
-        ToolHelper.runR8(
-            ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
-                .addLibraryFiles(runtimeJar(backend))
-                .build());
-
-    MethodSubject method =
-        getMethodSubject(app, "Test", new MethodSignature("test", "int", ImmutableList.of("int")));
-    Statistics stat = countInstructions(method.iterateInstructions());
-    if (SwitchRewritingTest.twoCaseWillUsePackedSwitch(key1, key2)) {
-      int expectedPackedSwitchCount = 1;
-      int expectedSparseSwitchCount = 0;
-      // TODO(b/113648554) Implement packed (table) switch support in the CF backend.
-      if (backend == Backend.CF) {
-        expectedSparseSwitchCount += expectedPackedSwitchCount;
-        expectedPackedSwitchCount = 0;
-      }
-      assertEquals(new Statistics(0, expectedPackedSwitchCount, expectedSparseSwitchCount), stat);
-    } else {
-      assertEquals(new Statistics(2, 0, 0), stat);
-    }
-  }
-
-  @Test
-  public void twoCaseSparseToPackedJar() throws Exception {
-    for (int delta = 1; delta <= 3; delta++) {
-      runTwoCaseSparseToPackedJarTest(0, delta);
-      runTwoCaseSparseToPackedJarTest(-delta, 0);
-      runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
-      runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
-    }
-    runTwoCaseSparseToPackedJarTest(-1, 1);
-    runTwoCaseSparseToPackedJarTest(-2, 1);
-    runTwoCaseSparseToPackedJarTest(-1, 2);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
-  }
-
   private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
       Integer additionalLastKey) throws Exception {
     JasminBuilder builder = new JasminBuilder();
@@ -287,12 +215,6 @@
       expectedSparseSwitchCount = 1;
     }
 
-    // TODO(b/113648554) Implement packed (table) switch support in the CF backend.
-    if (backend == Backend.CF) {
-      expectedSparseSwitchCount += expectedPackedSwitchCount;
-      expectedPackedSwitchCount = 0;
-    }
-
     assertEquals(new Statistics(0, expectedPackedSwitchCount, expectedSparseSwitchCount), stat);
   }
 
@@ -364,12 +286,6 @@
                 .method("int", "test", ImmutableList.of("int"))
                 .iterateInstructions());
 
-    // TODO(b/113648554) Implement packed (table) switch support in the CF backend.
-    if (backend == Backend.CF) {
-      expectedSparseSwitches += expectedPackedSwitches;
-      expectedPackedSwitches = 0;
-    }
-
     assertEquals(new Statistics(expectedIfs, expectedPackedSwitches, expectedSparseSwitches), stat);
 
     // Run the code
@@ -389,30 +305,139 @@
     runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
     runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
     runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1, 2), -100, 3, 0, 0);
+    if (backend == Backend.DEX) {
+      runConvertCasesToIf(ImmutableList.of(1000, 2000, 3000, 4000, 5000), -100, 5, 0, 0);
+      runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3), -100, 4, 0, 0);
+      runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4), -100, 5, 0, 0);
+      runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4, 5), -100, 6, 0, 0);
+      runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4, 5, 6), -100, 0, 1, 0);
+    } else {
+      runConvertCasesToIf(ImmutableList.of(1000, 2000, 3000, 4000, 5000), -100, 0, 0, 1);
+      runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3), -100, 0, 1, 0);
+    }
+    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003), -100, 0, 1, 0);
 
     // Switches that are completely converted to ifs and one switch.
     runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
     runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 3, 1000, 1001, 1002, 1003, 1004, 1005), -100, 2, 1, 0);
+    runConvertCasesToIf(
+        ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, 1005), -100, 1, 1, 0);
+    runConvertCasesToIf(
+        ImmutableList.of(1000, 1001, 1002, 1003, 1004, 1005, Integer.MAX_VALUE), -100, 1, 1, 0);
+    runConvertCasesToIf(
+        ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 1005, 2000), -100, 2, 1, 0);
+    runConvertCasesToIf(
+        ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, 1005, Integer.MAX_VALUE),
+        -100, 2, 1, 0);
 
-    // Switches that are completely converted to ifs and two switches.
-    runConvertCasesToIf(ImmutableList.of(
-        0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
+    // Switches that are completely converted to a combination of ifs and switches.
+    if (backend == Backend.DEX) {
+      runConvertCasesToIf(
+          ImmutableList.of(100, 200, 300, 400, 500, 600, 700, 800, 900, 1000), -100, 10, 0, 0);
+      runConvertCasesToIf(
+          ImmutableList.of(0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 5, 1, 0);
+      runConvertCasesToIf(
+          ImmutableList.of(-1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 1005),
+          -100,6,1,0);
+      runConvertCasesToIf(
+          ImmutableList.of(-1000, 0, 1, 2, 3, 4, 5, 6, 1000, 1001, 1002, 1003, 1004, 1005, 1006),
+          -100,1,2,0);
+    } else {
+      // runConvertCasesToIf(ImmutableList.of(1000, 2000, 3000, 4000, 5000, 6000), -100, 0, 0, 1);
+      runConvertCasesToIf(ImmutableList.of(0, 1, 2, 3, 4), -100, 0, 1, 0);
+      runConvertCasesToIf(
+          ImmutableList.of(0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
+      runConvertCasesToIf(
+          ImmutableList.of(-1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
+      runConvertCasesToIf(
+          ImmutableList.of(-1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000),
+          -100,2,2,0);
+    }
+    runConvertCasesToIf(
+        ImmutableList.of(
+            -1000, -900, -800, -700, -600, -500, -400, -300,
+            1000, 1001, 1002, 1003, 1004,
+            2000, 2100, 2200, 2300, 2400, 2500),
+        -100,0,1,1);
+    // For small keys and 0 having If's is marginally better.
+    runConvertCasesToIf(
+        ImmutableList.of(
+            -1000, -900, -800, -700, -600, -500, -400, -300, -200, -100, -1, 0, 1,
+            1000, 1001, 1002, 1003, 1004,
+            2000, 2100, 2200, 2300, 2400, 2500),
+        -100,3,1,1);
 
-    // Switches that are completely converted two switches (one sparse and one packed).
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, -900, -800, -700, -600, -500, -400, -300,
-        1000, 1001, 1002, 1003, 1004,
-        2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
+    // Switches that hit maximum number of switchs and ifs.
+    runConvertCasesToIf(
+        ImmutableList.of(100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100), -100, 0, 0, 1);
+    if (backend == Backend.DEX) {
+      runConvertCasesToIf(
+          ImmutableList.of(
+              0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+              101, 102, 103, 104, 105, 106, 107, 108, 109,
+              201, 202, 203, 204, 205, 206, 207, 208, 209,
+              301, 302, 303, 304, 305, 306, 307, 308, 309,
+              401, 402, 403, 404, 405, 406, 407, 408, 409,
+              501, 502, 503, 504, 505, 506, 507, 508, 509,
+              601, 602, 603, 604, 605, 606, 607, 608, 609,
+              701, 702, 703, 704, 705, 706, 707, 708, 709,
+              801, 802, 803, 804, 805, 806, 807, 808, 809,
+              901, 902, 903, 904, 905, 906, 907, 908, 909,
+              1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+              1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109),
+          -100,8,9,1);
+      runConvertCasesToIf(
+          ImmutableList.of(
+              -2000, -1000,
+              0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+              101, 102, 103, 104, 105, 106, 107, 108, 109,
+              201, 202, 203, 204, 205, 206, 207, 208, 209,
+              301, 302, 303, 304, 305, 306, 307, 308, 309,
+              401, 402, 403, 404, 405, 406, 407, 408, 409,
+              501, 502, 503, 504, 505, 506, 507, 508, 509,
+              601, 602, 603, 604, 605, 606, 607, 608, 609,
+              701, 702, 703, 704, 705, 706, 707, 708, 709,
+              801, 802, 803, 804, 805, 806, 807, 808, 809,
+              901, 902, 903, 904, 905, 906, 907, 908, 909,
+              1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+              1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109,
+              10000, 11000),
+          -100,8,9,1);
+    } else {
+      runConvertCasesToIf(
+          ImmutableList.of(
+              0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+              101, 102, 103, 104, 105, 106, 107, 108, 109,
+              201, 202, 203, 204, 205, 206, 207, 208, 209,
+              301, 302, 303, 304, 305, 306, 307, 308, 309,
+              401, 402, 403, 404, 405, 406, 407, 408, 409,
+              501, 502, 503, 504, 505, 506, 507, 508, 509,
+              601, 602, 603, 604, 605, 606, 607, 608, 609,
+              701, 702, 703, 704, 705, 706, 707, 708, 709,
+              801, 802, 803, 804, 805, 806, 807, 808, 809,
+              901, 902, 903, 904, 905, 906, 907, 908, 909,
+              1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+              1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109),
+          -100,0,9,1);
+      runConvertCasesToIf(
+          ImmutableList.of(
+              -2000, -1000,
+              0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+              101, 102, 103, 104, 105, 106, 107, 108, 109,
+              201, 202, 203, 204, 205, 206, 207, 208, 209,
+              301, 302, 303, 304, 305, 306, 307, 308, 309,
+              401, 402, 403, 404, 405, 406, 407, 408, 409,
+              501, 502, 503, 504, 505, 506, 507, 508, 509,
+              601, 602, 603, 604, 605, 606, 607, 608, 609,
+              701, 702, 703, 704, 705, 706, 707, 708, 709,
+              801, 802, 803, 804, 805, 806, 807, 808, 809,
+              901, 902, 903, 904, 905, 906, 907, 908, 909,
+              1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
+              1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109,
+              10000, 11000),
+          -100,0,9,1);
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
index 354f3c2..12562ac 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -30,11 +30,6 @@
 
 public class SwitchRewritingTest extends SmaliTestBase {
 
-  static boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
-    assert key1 != key2;
-    return Math.abs((long) key1 - (long) key2) == 1;
-  }
-
   private boolean some16BitConst(Instruction instruction) {
     return instruction instanceof Const4
         || instruction instanceof ConstHigh16
@@ -112,74 +107,6 @@
     }
   }
 
-  private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2)
-      throws CompilationFailedException {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
-    MethodSignature signature = builder.addStaticMethod(
-        "int",
-        DEFAULT_METHOD_NAME,
-        ImmutableList.of("int"),
-        0,
-        "    sparse-switch p0, :sparse_switch_data",
-        "    const/4 v0, 0x5",
-        "    goto :return",
-        "  :case_1",
-        "    const/4 v0, 0x3",
-        "    goto :return",
-        "  :case_2",
-        "    const/4 v0, 0x4",
-        "  :return",
-        "    return v0",
-        "  :sparse_switch_data",
-        "  .sparse-switch",
-        "    " + key1 + " -> :case_1",
-        "    " + key2 + " -> :case_2",
-        "  .end sparse-switch");
-
-    builder.addMainMethod(
-        2,
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const/4             v1, 0",
-        "    invoke-static       { v1 }, LTest;->method(I)I",
-        "    move-result         v1",
-        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
-        "    return-void"
-    );
-
-    AndroidApp originalApplication = buildApplication(builder);
-    AndroidApp processedApplication = processApplication(originalApplication);
-    DexEncodedMethod method = getMethod(processedApplication, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (twoCaseWillUsePackedSwitch(key1, key2)) {
-      assertTrue(code.instructions[0] instanceof PackedSwitch);
-    } else {
-      if (key1 == 0) {
-        // Const instruction may be before if.
-        assertTrue(code.instructions[0] instanceof IfEqz || code.instructions[1] instanceof IfEqz);
-      } else {
-        // Const instruction before if.
-        assertTrue(code.instructions[1] instanceof IfEq);
-      }
-    }
-  }
-
-  @Test
-  public void twoCaseSparseToPackedOrIfsDex() throws CompilationFailedException {
-    for (int delta = 1; delta <= 3; delta++) {
-      runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
-      runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
-      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
-      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
-    }
-    runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
-    runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
-    runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
-  }
-
   private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
       Integer additionalLastKey) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
new file mode 100644
index 0000000..225ddd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
@@ -0,0 +1,71 @@
+// 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 org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InstantiatedLambdaReceiverTest extends TestBase {
+
+  private Backend backend;
+
+  private static final String expectedOutput = "In C.m()";
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public InstantiatedLambdaReceiverTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void jvmTest() throws Exception {
+    assumeTrue(
+        "JVM test independent of Art version - only run when testing on latest",
+        ToolHelper.getDexVm().getVersion().isLatest());
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void dexTest() throws Exception {
+    testForR8(backend)
+        .addInnerClasses(InstantiatedLambdaReceiverTest.class)
+        .addKeepMainRule(TestClass.class)
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  interface I {
+    void m();
+  }
+
+  interface II extends I {}
+
+  static class C implements I {
+
+    @Override
+    public void m() {
+      System.out.print("In C.m()");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I i = new C();
+      II x = i::m; // This should mark II as being instantiated!
+      x.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 2e065ed..5362c38 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -126,15 +126,15 @@
     jasminBuilder.writeJar(inputJar);
 
     if (backend == Backend.CF) {
-      TestRunResult jvmResult = testForJvm().addClasspath(inputJar).run(mainClass.name);
+      TestRunResult<?> jvmResult = testForJvm().addClasspath(inputJar).run(mainClass.name);
       checkTestRunResult(jvmResult, false);
     } else {
       assert backend == Backend.DEX;
 
-      TestRunResult dxResult = testForDX().addProgramFiles(inputJar).run(mainClass.name);
+      TestRunResult<?> dxResult = testForDX().addProgramFiles(inputJar).run(mainClass.name);
       checkTestRunResult(dxResult, false);
 
-      TestRunResult d8Result = testForD8().addProgramFiles(inputJar).run(mainClass.name);
+      TestRunResult<?> d8Result = testForD8().addProgramFiles(inputJar).run(mainClass.name);
       checkTestRunResult(d8Result, false);
     }
 
@@ -147,7 +147,7 @@
     checkTestRunResult(r8Result, true);
   }
 
-  private void checkTestRunResult(TestRunResult result, boolean isR8) {
+  private void checkTestRunResult(TestRunResult<?> result, boolean isR8) {
     switch (mode) {
       case NO_INVOKE:
         result.assertSuccessWithOutput(getExpectedOutput(isR8));
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
new file mode 100644
index 0000000..a7bdf0b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -0,0 +1,117 @@
+// 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.annotations;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AnnotationsOnTargetedMethodTest extends TestBase {
+
+  private static final String expectedOutput =
+      StringUtils.lines(
+          "In InterfaceImpl.targetedMethod()",
+          "In OtherInterfaceImpl.targetedMethod()",
+          MyAnnotation.class.getName());
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public AnnotationsOnTargetedMethodTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void jvmTest() throws Exception {
+    assumeTrue(
+        "JVM test independent of Art version - only run when testing on latest",
+        ToolHelper.getDexVm().getVersion().isLatest());
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void r8Test() throws Exception {
+    testForR8(backend)
+        .addInnerClasses(AnnotationsOnTargetedMethodTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keepattributes *Annotation*", "-dontobfuscate")
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      test(new InterfaceImpl());
+      test(new OtherInterfaceImpl());
+
+      Method method = Interface.class.getDeclaredMethods()[0];
+      for (Annotation annotation : method.getAnnotations()) {
+        visitAnnotation((MyAnnotation) annotation);
+      }
+    }
+
+    @NeverInline
+    private static void test(Interface obj) {
+      obj.targetedMethod();
+    }
+
+    @NeverInline
+    private static void visitAnnotation(MyAnnotation annotation) {
+      System.out.println(annotation.annotationType().getName());
+    }
+  }
+
+  @NeverMerge
+  interface Interface {
+
+    @NeverInline
+    @MyAnnotation
+    void targetedMethod();
+  }
+
+  static class InterfaceImpl implements Interface {
+
+    @NeverInline
+    @Override
+    public void targetedMethod() {
+      System.out.println("In InterfaceImpl.targetedMethod()");
+    }
+  }
+
+  static class OtherInterfaceImpl implements Interface {
+
+    @NeverInline
+    @Override
+    public void targetedMethod() {
+      System.out.println("In OtherInterfaceImpl.targetedMethod()");
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD})
+  @interface MyAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java
new file mode 100644
index 0000000..caa06d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java
@@ -0,0 +1,106 @@
+// 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.ifrule.classinlining;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfRuleWithClassInlining extends TestBase {
+
+  private final Backend backend;
+  private final boolean enableClassInlining;
+  private final boolean enableIfRule;
+
+  public IfRuleWithClassInlining(
+      Backend backend, boolean enableClassInlining, boolean enableIfRule) {
+    this.backend = backend;
+    this.enableClassInlining = enableClassInlining;
+    this.enableIfRule = enableIfRule;
+  }
+
+  @Parameters(name = "Backend: {0}, class inlining: {1}, with if rule: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(Backend.values(), BooleanUtils.values(), BooleanUtils.values());
+  }
+
+  @Test
+  public void r8Test() throws Exception {
+    String ifRule =
+        StringUtils.lines(
+            "-if class " + StringBox.Builder.class.getTypeName(),
+            "-keep class " + Unused.class.getTypeName());
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(IfRuleWithClassInlining.class)
+            .addKeepMainRule(TestClass.class)
+            // TODO(b/120061431): Should not be needed for this example.
+            .addKeepRules("-allowaccessmodification")
+            .addKeepRules(enableIfRule ? ifRule : "")
+            .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
+            .compile()
+            .inspector();
+    if (enableIfRule || !enableClassInlining) {
+      assertThat(inspector.clazz(StringBox.Builder.class), isPresent());
+    } else {
+      assertThat(inspector.clazz(StringBox.Builder.class), not(isPresent()));
+    }
+    if (enableIfRule) {
+      assertThat(inspector.clazz(Unused.class), isPresent());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      StringBox box = StringBox.builder().setString("Hello world").build();
+      System.out.println(box.getString());
+    }
+  }
+
+  static class StringBox {
+
+    static class Builder {
+
+      private String string = null;
+
+      public Builder setString(String string) {
+        this.string = string;
+        return this;
+      }
+
+      public StringBox build() {
+        return new StringBox(string);
+      }
+    }
+
+    private final String string;
+
+    public StringBox(String string) {
+      this.string = string;
+    }
+
+    public String getString() {
+      return string;
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+  }
+
+  static class Unused {}
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 1e21552..98bb8d0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -5,8 +5,10 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.google.common.collect.Streams;
 import java.util.Iterator;
 import java.util.function.Predicate;
+import java.util.stream.Stream;
 
 public abstract class MethodSubject extends MemberSubject {
 
@@ -40,4 +42,8 @@
   public abstract LineNumberTable getLineNumberTable();
 
   public abstract boolean hasLocalVariableTable();
+
+  public Stream<InstructionSubject> streamInstructions() {
+    return Streams.stream(iterateInstructions());
+  }
 }
diff --git a/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java b/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java
new file mode 100644
index 0000000..04fc705
--- /dev/null
+++ b/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java
@@ -0,0 +1,16 @@
+// 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 dalvik.annotation.optimization;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// This is a copy of libcore/dalvik/annotation/optimiztion/ReachabilitySensitive.java.
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface ReachabilitySensitive {}
diff --git a/tools/dex2oat.py b/tools/dex2oat.py
index eab424f..1e2cffd 100755
--- a/tools/dex2oat.py
+++ b/tools/dex2oat.py
@@ -16,8 +16,7 @@
   'default',
   '7.0.0',
   '6.0.1',
-  # TODO(b/79191363): Build a boot image for 5.1.1 dex2oat.
-  # '5.1.1',
+  '5.1.1',
 ]
 
 DIRS = {
@@ -34,6 +33,13 @@
   '5.1.1': 'mako',
 }
 
+ARCHS = {
+  'default': 'arm64',
+  '7.0.0': 'arm64',
+  '6.0.1': 'arm64',
+  '5.1.1': 'arm',
+}
+
 def ParseOptions():
   parser = optparse.OptionParser()
   parser.add_option('--version',
@@ -74,15 +80,15 @@
       oatfile = os.path.join(temp, "out.oat")
     base = os.path.join(LINUX_DIR, DIRS[version])
     product = PRODUCTS[version]
+    arch = ARCHS[version]
     cmd = [
       os.path.join(base, 'bin', 'dex2oat'),
-      '--android-root=' + os.path.join(base, 'product', product),
+      '--android-root=' + os.path.join(base, 'product', product, 'system'),
       '--runtime-arg',
       '-Xnorelocate',
-      '--boot-image=' + os.path.join(base, 'product', product, 'system', 'framework', 'boot.art'),
       '--dex-file=' + dexfile,
       '--oat-file=' + oatfile,
-      '--instruction-set=arm64',
+      '--instruction-set=' + arch,
     ]
     env = {"LD_LIBRARY_PATH": os.path.join(base, 'lib')}
     utils.PrintCmd(cmd)
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index ceb30a7..1512a64 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -42,6 +42,28 @@
   <continue with repo sync as above>
 
 
+art-8.1.0 (Android O MR1)
+-------------------------
+Build from branch android-8.1.0_r51.
+
+export BRANCH=android-8.1.0_r51
+mkdir ${BRANCH}
+cd ${BRANCH}
+repo init -u https://android.googlesource.com/platform/manifest -b ${BRANCH}
+repo sync -cq -j24
+source build/envsetup.sh
+lunch aosp_marlin-userdebug
+m -j24
+m -j24 build-art
+m -j24 test-art-host
+
+Collected into tools/linux/art-8.1.0.
+
+  scripts/update-host-art.sh \
+    --android-checkout /usr/local/ssd/android/${BRANCH} \
+    --art-dir art-8.1.0 \
+    --android-product marlin
+
 art-7.0.0
 ---------
 Build from branch android-7.0.0_r21 with the following patch:
diff --git a/tools/linux/art-5.1.1.tar.gz.sha1 b/tools/linux/art-5.1.1.tar.gz.sha1
index 2b6f07a..d7f86b0 100644
--- a/tools/linux/art-5.1.1.tar.gz.sha1
+++ b/tools/linux/art-5.1.1.tar.gz.sha1
@@ -1 +1 @@
-737072daed9a7cd4ef046b7ea8a60aa7b1b6c65d
\ No newline at end of file
+7c1b10e333a5a028db7f74a989d9edfe168900bf
\ No newline at end of file
diff --git a/tools/linux/art-8.1.0.tar.gz.sha1 b/tools/linux/art-8.1.0.tar.gz.sha1
new file mode 100644
index 0000000..91aea2f
--- /dev/null
+++ b/tools/linux/art-8.1.0.tar.gz.sha1
@@ -0,0 +1 @@
+0394edc4e2dbe9e5b04a81efede7578ecf20b853
\ No newline at end of file
diff --git a/tools/run-jdwp-tests.py b/tools/run-jdwp-tests.py
index 45f79e6..db69197 100755
--- a/tools/run-jdwp-tests.py
+++ b/tools/run-jdwp-tests.py
@@ -15,6 +15,7 @@
 
 VERSIONS = [
   'default',
+  '8.1.0',
   '7.0.0',
   '6.0.1',
   '5.1.1',
diff --git a/tools/test.py b/tools/test.py
index f1543f1..31b2134 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -18,7 +18,7 @@
 import upload_to_x20
 
 
-ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1", "4.4.4", "4.0.4"]
+ALL_ART_VMS = ["default", "8.1.0", "7.0.0", "6.0.1", "5.1.1", "4.4.4", "4.0.4"]
 BUCKET = 'r8-test-results'
 
 def ParseOptions():