Merge "Ensure that dual nullable TypeLattice is synchronized."
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index bdbf44f..29d54b4 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.11-dev";
+  public static final String LABEL = "1.4.12-dev";
 
   private Version() {
   }
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 0773237..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,
@@ -325,6 +327,45 @@
       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 c2f4943..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;
@@ -271,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/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 6288eaa..f0d5d7b 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,7 +36,6 @@
 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.Value;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
@@ -94,7 +93,6 @@
 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 {
@@ -1163,9 +1161,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);
@@ -1243,116 +1243,20 @@
     }
   }
 
-  /**
-   * 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::isNotPseudoInstruction);
-      if (!isLongMulOrDiv(firstMaterializing)) {
-        continue;
-      }
-      Instruction secondMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
-      if (!isLongAddOrSub(secondMaterializing)) {
-        continue;
-      }
-      if (isFallthoughTarget(block)) {
-        continue;
-      }
-      Value outOfMulOrDiv = firstMaterializing.outValue();
-      for (Value inOfAddOrSub : secondMaterializing.inValues()) {
-        if (isAliasOf(inOfAddOrSub, outOfMulOrDiv)) {
-          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;
-        }
+      Instruction firstMaterializing =
+          it.nextUntil(IRConverter::isMaterializingInstructionOnArtArmVersionM);
+      if (needsInstructionBeforeLongOperation(firstMaterializing)) {
+        ensureInstructionBefore(code, firstMaterializing, it);
       }
     }
   }
 
-  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 isLongMulOrDiv(Instruction instruction) {
-    return instruction != null
-        && instruction.outValue() != null
-        && instruction.outValue().getTypeLattice().isLong()
-        && (instruction.isMul() || instruction.isDiv());
-  }
-
-  private static boolean isLongAddOrSub(Instruction instruction) {
-    return instruction != null
-        && instruction.outValue() != null
-        && instruction.outValue().getTypeLattice().isLong()
-        && (instruction.isAdd() || instruction.isSub());
-  }
-
-  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 the block and copy back catch handlers to the header block.
-      BasicBlock split = it.split(code);
-      block.copyCatchHandlers(code, code.listIterator(code.blocks.size()), split);
-      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.
@@ -1371,6 +1275,33 @@
     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/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 190db23..8312729 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -67,11 +67,6 @@
   }
 
   @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/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 6feb460..44a3bed 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,12 +147,6 @@
         "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 fa4c41c..28ce1e6 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -184,12 +184,6 @@
   }
 
   @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/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/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 7f200c1..0675118 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,12 +39,6 @@
 
   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 bf704ea..12d1990 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -6,7 +6,6 @@
 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;
@@ -99,17 +98,4 @@
     ProcessResult result = ToolHelper.runArtRaw(out.toString(), mainClass);
     return createRunResult(app, result);
   }
-
-  public TestRunResult runDex2Oat() throws IOException {
-    return runDex2Oat(ToolHelper.getDexVm());
-  }
-
-  public TestRunResult 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 TestRunResult(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 9452a63..f60db7f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -5,7 +5,6 @@
 
 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;
@@ -23,7 +22,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>() {
@@ -72,7 +71,7 @@
   }
 
   @Override
-  public TestRunResult run(String mainClass) throws IOException, CompilationFailedException {
+  public RR run(String mainClass) throws IOException, CompilationFailedException {
     return compile().run(mainClass);
   }
 
@@ -114,14 +113,6 @@
   }
 
   @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);
     return self();
@@ -133,9 +124,4 @@
     builder.addLibraryFiles(files);
     return self();
   }
-
-  public T noDesugaring() {
-    builder.setDisableDesugaring(true);
-    return self();
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 8b6461f..f3ec339 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,6 +27,8 @@
     this.result = result;
   }
 
+  abstract RR self();
+
   public String getStdOut() {
     return result.stdout;
   }
@@ -34,33 +37,41 @@
     return result.stderr;
   }
 
-  public TestRunResult assertSuccess() {
+  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 {
@@ -71,10 +82,10 @@
     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();
   }
 
   private String errorMessage(String message) {
@@ -110,24 +121,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 70be34e..caeebd9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -475,7 +475,7 @@
         .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, "<missing>")
+        .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();
@@ -504,6 +504,10 @@
     return getDexVmPath(vm).resolve("product").resolve(PRODUCT.get(vm));
   }
 
+  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");
   }
@@ -1452,14 +1456,12 @@
   }
 
   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.isDex2OatSupported());
-    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.
+    if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+      // Run default dex2oat for tests on dalvik runtimes.
       vm = DexVm.ART_DEFAULT;
     }
-    // TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
-    Assume.assumeTrue(!ToolHelper.isWindows());
     assert Files.exists(file);
     assert ByteStreams.toByteArray(Files.newInputStream(file)).length > 0;
     List<String> command = new ArrayList<>();
@@ -1469,8 +1471,8 @@
     command.add("-Xnorelocate");
     command.add("--dex-file=" + file.toAbsolutePath());
     command.add("--oat-file=" + outFile.toAbsolutePath());
-    // TODO(zerny): Create a proper interface for invoking dex2oat. Hardcoding arm64 here is a hack!
-    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());
     return runProcess(builder);
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/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index edd6fd6..4c2fedc 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -10,7 +10,6 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.code.AddIntLit8;
 import com.android.tools.r8.code.Const4;
@@ -29,7 +28,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 class TestClass {
@@ -124,7 +122,8 @@
   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(Arrays.stream(code.getDebugInfo().events)
+    assertTrue((code.getDebugInfo() == null) ||
+        Arrays.stream(code.getDebugInfo().events)
             .allMatch(event -> !(event instanceof StartLocal)));
   }
 
@@ -171,10 +170,10 @@
         .addKeepRules(keepRules)
         // Keep the annotation class.
         .addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
-        // Keep the annotation and debug information which is needed for debug mode to actually
-        // keep things alive.
-        .addKeepRules("-keepattributes *Annotations*,LineNumberTable," +
-            "LocalVariableTable,LocalVariableTypeTable")
+        // 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
deleted file mode 100644
index 12bd452..0000000
--- a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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
deleted file mode 100644
index cc4dece..0000000
--- a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Test.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// 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 3ab3467..cbc6e1b 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,20 +5,29 @@
 
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.utils.AndroidApiLevel;
+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 java.io.IOException;
+import java.nio.file.Path;
 import org.junit.Test;
 
 public class Regress77842465 extends AsmTestBase {
 
   @Test
   public void test() throws CompilationFailedException, IOException {
-    testForD8()
-        .addProgramClassFileData(Regress77842465Dump.dump())
-        .noDesugaring()
-        .setMinApi(AndroidApiLevel.M)
-        .compile()
-        .runDex2Oat()
-        .assertSuccess();
+
+    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);
   }
 }
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/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 98bb8d0..1e21552 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,10 +5,8 @@
 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 {
 
@@ -42,8 +40,4 @@
   public abstract LineNumberTable getLineNumberTable();
 
   public abstract boolean hasLocalVariableTable();
-
-  public Stream<InstructionSubject> streamInstructions() {
-    return Streams.stream(iterateInstructions());
-  }
 }
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/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