Merge "Handle several implementations of default methods"
diff --git a/src/main/java/com/android/tools/r8/BaseOutput.java b/src/main/java/com/android/tools/r8/BaseOutput.java
index 16e8264..b1b9166 100644
--- a/src/main/java/com/android/tools/r8/BaseOutput.java
+++ b/src/main/java/com/android/tools/r8/BaseOutput.java
@@ -8,6 +8,7 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.List;
 
 abstract class BaseOutput {
 
@@ -39,9 +40,9 @@
    *   resources.get(N - 1) ~=~ classesN.dex (where N > 0).
    * </pre>
    *
-   * @return list of compiled DEX resources.
+   * @return an immutable list of compiled DEX resources.
    */
-  public ImmutableList<Resource> getDexResources() {
+  public List<Resource> getDexResources() {
     return ImmutableList.copyOf(app.getDexProgramResources());
   }
 
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index cbd16fd..6ac5f1e 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -33,6 +33,9 @@
    * Builder for constructing a D8Command.
    */
   public static class Builder extends BaseCommand.Builder<D8Command, Builder> {
+
+    private boolean intermediate = false;
+
     private Builder() {
       super(CompilationMode.DEBUG);
     }
@@ -44,18 +47,23 @@
     /** Add classpath file resources. */
     public Builder addClasspathFiles(Path... files) throws IOException {
       getAppBuilder().addClasspathFiles(files);
-      return this;
+      return self();
     }
 
     /** Add classpath file resources. */
     public Builder addClasspathFiles(Collection<Path> files) throws IOException {
       getAppBuilder().addClasspathFiles(files);
-      return this;
+      return self();
     }
 
     public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) {
       getAppBuilder().addClasspathResourceProvider(provider);
-      return this;
+      return self();
+    }
+
+    public Builder setIntermediate(boolean value) {
+      this.intermediate = value;
+      return self();
     }
 
     @Override
@@ -74,7 +82,12 @@
 
       validate();
       return new D8Command(
-          getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+          getAppBuilder().build(),
+          getOutputPath(),
+          getOutputMode(),
+          getMode(),
+          getMinApiLevel(),
+          intermediate);
     }
   }
 
@@ -89,10 +102,13 @@
       "  --lib <file>        # Add <file> as a library resource.",
       "  --classpath <file>  # Add <file> as a classpath resource.",
       "  --min-api           # Minimum Android API level compatibility",
+      "  --intermediate      # Compile an intermediate result intended for later merging.",
       "  --file-per-class    # Produce a separate dex file per class",
       "  --version           # Print the version of d8.",
       "  --help              # Print this message."));
 
+  private boolean intermediate = false;
+
   public static Builder builder() {
     return new Builder();
   }
@@ -142,6 +158,8 @@
           builder.addClasspathFiles(Paths.get(args[++i]));
         } else if (arg.equals("--min-api")) {
           builder.setMinApiLevel(Integer.valueOf(args[++i]));
+        } else if (arg.equals("--intermediate")) {
+          builder.setIntermediate(true);
         } else {
           if (arg.startsWith("--")) {
             throw new CompilationException("Unknown option: " + arg);
@@ -160,8 +178,10 @@
       Path outputPath,
       OutputMode outputMode,
       CompilationMode mode,
-      int minApiLevel) {
+      int minApiLevel,
+      boolean intermediate) {
     super(inputApp, outputPath, outputMode, mode, minApiLevel);
+    this.intermediate = intermediate;
   }
 
   private D8Command(boolean printHelp, boolean printVersion) {
@@ -174,6 +194,7 @@
     assert !internal.debug;
     internal.debug = getMode() == CompilationMode.DEBUG;
     internal.minApiLevel = getMinApiLevel();
+    internal.intermediate = intermediate;
     // Assert and fixup defaults.
     assert !internal.skipMinification;
     internal.skipMinification = true;
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 9902d3b..3827cae 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -110,7 +110,7 @@
     Map<String, Integer> result = new HashMap<>();
     try (Closer closer = Closer.create()) {
       for (Resource resource : app.getDexProgramResources()) {
-        for (Segment segment: DexFileReader.parseMapFrom(resource.getStream(closer))) {
+        for (Segment segment: DexFileReader.parseMapFrom(closer.register(resource.getStream()))) {
           int value = result.computeIfAbsent(segment.typeName(), (key) -> 0);
           result.put(segment.typeName(), value + segment.size());
         }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3f6fcd4..19fc358 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -68,8 +68,7 @@
   private final Timing timing = new Timing("R8");
   private final InternalOptions options;
 
-  // TODO(zerny): Refactor tests to go through testing methods and make this private.
-  public R8(InternalOptions options) {
+  private R8(InternalOptions options) {
     this.options = options;
     options.itemFactory.resetSortedIndices();
   }
@@ -93,14 +92,25 @@
     }
   }
 
-  public DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
+  static DexApplication optimize(
+      DexApplication application,
+      AppInfoWithSubtyping appInfo,
+      InternalOptions options)
+      throws ProguardRuleParserException, ExecutionException, IOException {
+    return new R8(options).optimize(application, appInfo);
+  }
+
+  private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
       throws IOException, ProguardRuleParserException, ExecutionException {
     return optimize(application, appInfo, GraphLense.getIdentityLense(),
         Executors.newSingleThreadExecutor());
   }
 
-  public DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo,
-      GraphLense graphLense, ExecutorService executorService)
+  private DexApplication optimize(
+      DexApplication application,
+      AppInfoWithSubtyping appInfo,
+      GraphLense graphLense,
+      ExecutorService executorService)
       throws IOException, ProguardRuleParserException, ExecutionException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 71a193c..3b88ba8 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -50,7 +50,7 @@
      */
     public Builder setTreeShaking(boolean useTreeShaking) {
       treeShaking = Optional.of(useTreeShaking);
-      return this;
+      return self();
     }
 
     /**
@@ -58,7 +58,7 @@
      */
     public Builder setMinification(boolean useMinification) {
       minification = Optional.of(useMinification);
-      return this;
+      return self();
     }
 
     /**
@@ -66,7 +66,7 @@
      */
     public Builder addMainDexRules(Path... paths) {
       Collections.addAll(mainDexRules, paths);
-      return this;
+      return self();
     }
 
     /**
@@ -74,7 +74,7 @@
      */
     public Builder addMainDexRules(List<Path> paths) {
       mainDexRules.addAll(paths);
-      return this;
+      return self();
     }
 
     /**
@@ -85,14 +85,14 @@
      */
     public Builder setMinimalMainDex(boolean value) {
       minimalMainDex = value;
-      return this;
+      return self();
     }
     /**
      * Add proguard configuration file resources.
      */
     public Builder addProguardConfigurationFiles(Path... paths) {
       Collections.addAll(proguardConfigFiles, paths);
-      return this;
+      return self();
     }
 
     /**
@@ -100,7 +100,7 @@
      */
     public Builder addProguardConfigurationFiles(List<Path> paths) {
       proguardConfigFiles.addAll(paths);
-      return this;
+      return self();
     }
 
     /**
@@ -108,7 +108,7 @@
      */
     public Builder setProguardMapFile(Path path) {
       getAppBuilder().setProguardMapFile(path);
-      return this;
+      return self();
     }
 
     /**
@@ -116,7 +116,7 @@
      */
     public Builder setPackageDistributionFile(Path path) {
       getAppBuilder().setPackageDistributionFile(path);
-      return this;
+      return self();
     }
 
     /**
@@ -126,7 +126,7 @@
      */
     Builder setIgnoreMissingClasses(boolean ignoreMissingClasses) {
       this.ignoreMissingClasses = ignoreMissingClasses;
-      return this;
+      return self();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/Resource.java b/src/main/java/com/android/tools/r8/Resource.java
index eaabc99..3208e52 100644
--- a/src/main/java/com/android/tools/r8/Resource.java
+++ b/src/main/java/com/android/tools/r8/Resource.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8;
 
-import com.google.common.io.Closer;
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -48,7 +47,7 @@
   public abstract Set<String> getClassDescriptors();
 
   /** Get the resource as a stream. */
-  public abstract InputStream getStream(Closer closer) throws IOException;
+  public abstract InputStream getStream() throws IOException;
 
   /** File based application resource. */
   private static class FileResource extends Resource {
@@ -66,8 +65,8 @@
     }
 
     @Override
-    public InputStream getStream(Closer closer) throws IOException {
-      return closer.register(new FileInputStream(file.toFile()));
+    public InputStream getStream() throws IOException {
+      return new FileInputStream(file.toFile());
     }
   }
 
@@ -89,8 +88,7 @@
     }
 
     @Override
-    public InputStream getStream(Closer closer) throws IOException {
-      // Note: closing a byte-array input stream is a no-op.
+    public InputStream getStream() throws IOException {
       return new ByteArrayInputStream(bytes);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index c871e75..6bbb406 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -472,8 +472,7 @@
                 + "Reduce the input-program size or run with --multi-dex enabled");
       }
       if (isDexFile(output)) {
-        try (Closer closer = Closer.create()) {
-          InputStream stream = result.getDexResources().get(0).getStream(closer);
+        try (InputStream stream = result.getDexResources().get(0).getStream()) {
           Files.copy(stream, output, StandardCopyOption.REPLACE_EXISTING);
         }
         return;
@@ -544,7 +543,7 @@
         // Add dex files.
         List<Resource> dexProgramSources = output.getDexResources();
         for (int i = 0; i < dexProgramSources.size(); i++) {
-          addEntry(getDexFileName(i), dexProgramSources.get(i).getStream(closer), out);
+          addEntry(getDexFileName(i), closer.register(dexProgramSources.get(i).getStream()), out);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 36b65d7..355080f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -183,7 +183,7 @@
         List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
         int computedMinApiLevel = options.minApiLevel;
         for (Resource input : dexSources) {
-          DexFile file = new DexFile(input.getStream(closer));
+          DexFile file = new DexFile(closer.register(input.getStream()));
           computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
           fileReaders.add(new DexFileReader(file, classKind, itemFactory));
         }
@@ -207,7 +207,7 @@
       JarClassFileReader reader = new JarClassFileReader(
           application, classKind.bridgeConsumer(classes::add));
       for (Resource input : classSources) {
-        reader.read(DEFAULT_DEX_FILENAME, classKind, input.getStream(closer));
+        reader.read(DEFAULT_DEX_FILENAME, classKind, closer.register(input.getStream()));
       }
     }
 
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 c9da10b..6586a57 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -135,9 +135,9 @@
         assert packageDistribution == null :
             "Cannot combine package distribution definition with file-per-class option.";
         distributor = new FilePerClassDistributor(this);
-      } else if (options.minApiLevel < Constants.ANDROID_L_API
-            && options.mainDexKeepRules.isEmpty()
-            && application.mainDexList.isEmpty()) {
+      } else if (!options.canUseMultidex()
+          && options.mainDexKeepRules.isEmpty()
+          && application.mainDexList.isEmpty()) {
         if (packageDistribution != null) {
           throw new CompilationError("Cannot apply package distribution. Multidex is not"
               + " supported with API level " + options.minApiLevel +"."
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index a5645a1..977d703 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -12,6 +12,7 @@
   public static final int ANDROID_N_API = 24;
   public static final int ANDROID_L_API = 21;
   public static final int ANDROID_K_API = 19;
+  public static final int ANDROID_I_API = 14;
   public static final int DEFAULT_ANDROID_API = 1;
 
   /** dex file version number for Android O (API level 26) */
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index c468613..ffaf42b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.MoveException;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -51,12 +52,6 @@
   private DexString emittedFile = null;
   private Int2ReferenceMap<DebugLocalInfo> emittedLocals;
 
-  // If lastMoveInstructionPc != NO_PC_INFO, then the last pc-advancing instruction was a
-  // move-exception at lastMoveInstructionPc. This is needed to maintain the art/dx specific
-  // behaviour that the move-exception pc is associated with the catch-declaration line.
-  // See debug.ExceptionTest.testStepOnCatch().
-  private int lastMoveInstructionPc = NO_PC_INFO;
-
   // Emitted events.
   private final List<DexDebugEvent> events = new ArrayList<>();
 
@@ -81,23 +76,21 @@
     }
     assert pendingLocals != null;
 
-    // If this is a position emit and exit as it always emits events.
     if (instruction.isDebugPosition()) {
       emitDebugPosition(pc, instruction.asDebugPosition());
-      return;
-    }
-
-    if (instruction.isArgument()) {
+    } else if (instruction.isMoveException()) {
+      MoveException move = instruction.asMoveException();
+      if (move.getPosition() != null) {
+        emitDebugPosition(pc, move.getPosition());
+      }
+    } else if (instruction.isArgument()) {
       startArgument(instruction.asArgument());
     } else if (instruction.isDebugLocalsChange()) {
       updateLocals(instruction.asDebugLocalsChange());
     } else if (instruction.getBlock().exit() == instruction) {
-      // If this is the end of the block clear out the pending state and exit.
+      // If this is the end of the block clear out the pending state.
       pendingLocals = null;
       pendingLocalChanges = false;
-      return;
-    } else if (instruction.isMoveException()) {
-      lastMoveInstructionPc = pc;
     } else {
       // For non-exit / pc-advancing instructions emit any pending changes.
       emitLocalChanges(pc);
@@ -111,7 +104,7 @@
     if (startLine == NO_LINE_INFO) {
       return null;
     }
-    DexString[] params = new DexString[method.method.proto.parameters.values.length];
+    DexString[] params = new DexString[method.method.getArity()];
     if (arguments != null) {
       assert params.length == arguments.size();
       for (int i = 0; i < arguments.size(); i++) {
@@ -159,7 +152,7 @@
 
   private void startArgument(Argument argument) {
     if (arguments == null) {
-      arguments = new ArrayList<>(method.method.proto.parameters.values.length);
+      arguments = new ArrayList<>(method.method.getArity());
     }
     if (!argument.outValue().isThis()) {
       arguments.add(argument.getLocalInfo());
@@ -191,18 +184,16 @@
   }
 
   private void emitDebugPosition(int pc, int line, DexString file) {
-    int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
-    lastMoveInstructionPc = NO_PC_INFO;
     // The position requires a pc change event and possible events for line, file and local changes.
     // Verify that we do not ever produce two subsequent positions at the same pc.
-    assert emittedPc != emitPc;
+    assert emittedPc != pc;
     if (startLine == NO_LINE_INFO) {
       assert emittedLine == NO_LINE_INFO;
       startLine = line;
       emittedLine = line;
     }
-    emitAdvancementEvents(emittedPc, emittedLine, emittedFile, emitPc, line, file, events, factory);
-    emittedPc = emitPc;
+    emitAdvancementEvents(emittedPc, emittedLine, emittedFile, pc, line, file, events, factory);
+    emittedPc = pc;
     emittedLine = line;
     emittedFile = file;
     if (localsChanged()) {
@@ -215,11 +206,9 @@
   private void emitLocalChanges(int pc) {
     // If pc advanced since the locals changed and locals indeed have changed, emit the changes.
     if (localsChanged()) {
-      int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
-      lastMoveInstructionPc = NO_PC_INFO;
       emitAdvancementEvents(
-          emittedPc, emittedLine, emittedFile, emitPc, emittedLine, emittedFile, events, factory);
-      emittedPc = emitPc;
+          emittedPc, emittedLine, emittedFile, pc, emittedLine, emittedFile, events, factory);
+      emittedPc = pc;
       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
       pendingLocalChanges = false;
       assert localsEqual(emittedLocals, pendingLocals);
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 572bea9..400b42f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -144,7 +144,7 @@
   public void setCode(
       IRCode ir, RegisterAllocator registerAllocator, DexItemFactory dexItemFactory) {
     final DexBuilder builder = new DexBuilder(ir, registerAllocator, dexItemFactory);
-    code = builder.build(method.proto.parameters.values.length);
+    code = builder.build(method.getArity());
   }
 
   // Replaces the dex code in the method by setting code to result of compiling the IR.
@@ -152,7 +152,7 @@
       DexItemFactory dexItemFactory, DexString firstJumboString) {
     final DexBuilder builder =
         new DexBuilder(ir, registerAllocator, dexItemFactory, firstJumboString);
-    code = builder.build(method.proto.parameters.values.length);
+    code = builder.build(method.getArity());
   }
 
   public String toString() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index a066d24..f8b3c8c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -32,6 +32,10 @@
     return "Method " + holder + "." + name + " " + proto.toString();
   }
 
+  public int getArity() {
+    return proto.parameters.size();
+  }
+
   @Override
   public void collectIndexedItems(IndexedItemCollection indexedItems) {
     if (indexedItems.addMethod(this)) {
@@ -132,7 +136,7 @@
     builder.append(".");
     builder.append(name);
     builder.append("(");
-    for (int i = 0; i < proto.parameters.values.length; i++) {
+    for (int i = 0; i < getArity(); i++) {
       if (i != 0) {
         builder.append(", ");
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 39226d8..5de71a1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -58,6 +58,10 @@
     return values.length == 0;
   }
 
+  public int size() {
+    return values.length;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 290a1a2..16bc790 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1120,11 +1120,13 @@
     List<BasicBlock> predecessors = this.getPredecessors();
     boolean hasMoveException = entry().isMoveException();
     MoveException move = null;
+    DebugPosition position = null;
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
+      position = move.getPosition();
       assert move.getPreviousLocalValue() == null;
-      this.getInstructions().remove(0);
+      getInstructions().remove(0);
     }
     // Create new predecessor blocks.
     List<BasicBlock> newPredecessors = new ArrayList<>();
@@ -1140,7 +1142,11 @@
         Value value = new Value(
             valueNumberGenerator.next(), MoveType.OBJECT, move.getDebugInfo());
         values.add(value);
-        newBlock.add(new MoveException(value));
+        MoveException newMove = new MoveException(value);
+        newBlock.add(newMove);
+        if (position != null) {
+          newMove.setPosition(new DebugPosition(position.line, position.file));
+        }
       }
       newBlock.add(new Goto());
       newBlock.close(null);
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 e1e9a99..7c37649 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
@@ -363,7 +363,7 @@
       }
     }
     assert arguments.size()
-        == method.method.proto.parameters.values.length + (method.accessFlags.isStatic() ? 0 : 1);
+        == method.method.getArity() + (method.accessFlags.isStatic() ? 0 : 1);
     return arguments;
   }
 
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 2691eed..554e71f 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
@@ -9,6 +9,8 @@
 
 public class MoveException extends Instruction {
 
+  private DebugPosition position = null;
+
   public MoveException(Value dest) {
     super(dest);
   }
@@ -17,6 +19,14 @@
     return outValue;
   }
 
+  public DebugPosition getPosition() {
+    return position;
+  }
+
+  public void setPosition(DebugPosition position) {
+    this.position = position;
+  }
+
   @Override
   public void buildDex(DexBuilder builder) {
     int dest = builder.allocatedRegister(dest(), getNumber());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 9b1aa3f..9e07ad6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -234,7 +234,7 @@
   }
 
   private List<MoveType> computeArgumentTypes() {
-    List<MoveType> types = new ArrayList<>(proto.parameters.values.length);
+    List<MoveType> types = new ArrayList<>(proto.parameters.size());
     String shorty = proto.shorty.toString();
     for (int i = 1; i < proto.shorty.size; i++) {
       MoveType moveType = MoveType.fromTypeDescriptorChar(shorty.charAt(i));
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 ba63d80..809e3ac 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
@@ -86,6 +86,8 @@
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -241,11 +243,11 @@
   private BasicBlock currentBlock = null;
 
   // Mappings for canonicalizing constants of a given type at IR construction time.
-  private Map<Long, ConstNumber> intConstants = new HashMap<>();
-  private Map<Long, ConstNumber> longConstants = new HashMap<>();
-  private Map<Long, ConstNumber> floatConstants = new HashMap<>();
-  private Map<Long, ConstNumber> doubleConstants = new HashMap<>();
-  private Map<Long, ConstNumber> nullConstants = new HashMap<>();
+  private Long2ObjectMap<ConstNumber> intConstants = new Long2ObjectArrayMap<>();
+  private Long2ObjectMap<ConstNumber> longConstants = new Long2ObjectArrayMap<>();
+  private Long2ObjectMap<ConstNumber> floatConstants = new Long2ObjectArrayMap<>();
+  private Long2ObjectMap<ConstNumber> doubleConstants = new Long2ObjectArrayMap<>();
+  private Long2ObjectMap<ConstNumber> nullConstants = new Long2ObjectArrayMap<>();
 
   private List<BasicBlock> exitBlocks = new ArrayList<>();
   private BasicBlock normalExitBlock;
@@ -669,7 +671,7 @@
   // to disable constant canonicalization in debug builds to make sure we have separate values
   // for separate locals.
   private void canonicalizeAndAddConst(
-      ConstType type, int dest, long value, Map<Long, ConstNumber> table) {
+      ConstType type, int dest, long value, Long2ObjectMap<ConstNumber> table) {
     ConstNumber existing = table.get(value);
     if (existing != null) {
       currentBlock.writeCurrentDefinition(dest, existing.outValue(), ThrowingInfo.NO_THROW);
@@ -1977,6 +1979,16 @@
     if (currentDebugPosition != null) {
       DebugPosition position = currentDebugPosition;
       currentDebugPosition = null;
+      if (!currentBlock.getInstructions().isEmpty()) {
+        MoveException move = currentBlock.getInstructions().getLast().asMoveException();
+        if (move != null && move.getPosition() == null) {
+          // Set the position on the move-exception instruction.
+          // ART/DX associates the move-exception pc with the catch-declaration line.
+          // See debug.ExceptionTest.testStepOnCatch().
+          move.setPosition(position);
+          return;
+        }
+      }
       addInstruction(position);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index d1b1577..a84e082 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -62,8 +62,7 @@
         // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
         dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
         assert (dexCode.getDebugInfo() == null)
-            || (companionMethod.proto.parameters.values.length
-                == dexCode.getDebugInfo().parameters.length);
+            || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
 
         companionMethods.add(new DexEncodedMethod(companionMethod,
             newFlags, virtual.annotations, virtual.parameterAnnotations, code));
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 c2600b4..55e6296 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
@@ -459,8 +459,7 @@
           DexCode dexCode = newMethod.getCode().asDexCode();
           dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
           assert (dexCode.getDebugInfo() == null)
-              || (callTarget.proto.parameters.values.length
-              == dexCode.getDebugInfo().parameters.length);
+              || (callTarget.getArity() == dexCode.getDebugInfo().parameters.length);
           directMethods[i] = newMethod;
           return true;
         }
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 0bd7188..f51c136 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
@@ -28,7 +28,6 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.HashMultiset;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multiset;
 import com.google.common.collect.Multiset.Entry;
 import com.google.common.collect.Multisets;
@@ -288,7 +287,7 @@
     }
 
     for (BasicBlock block : blocks) {
-      boolean blockEntry = true;
+      block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
       ListIterator<Instruction> instructionIterator = block.listIterator();
       while (instructionIterator.hasNext()) {
         Instruction instruction = instructionIterator.next();
@@ -307,8 +306,8 @@
           }
         }
         while (nextStartingRange != null && nextStartingRange.start <= index) {
-          // If the full range is between the two debug positions ignore it.
-          if (nextStartingRange.end > index) {
+          // If the range is live at this index open it.
+          if (index < nextStartingRange.end) {
             openRanges.add(nextStartingRange);
             assert !currentLocals.containsKey(nextStartingRange.register);
             currentLocals.put(nextStartingRange.register, nextStartingRange.local);
@@ -317,10 +316,7 @@
           }
           nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
         }
-        if (blockEntry) {
-          blockEntry = false;
-          block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
-        } else if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
+        if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
           DebugLocalsChange change = createLocalsChange(ending, starting);
           if (change != null) {
             if (instruction.isDebugPosition() || instruction.isJumpInstruction()) {
@@ -368,18 +364,6 @@
         || (instruction.isGoto() && instruction.asGoto().getTarget() == code.getNormalExitBlock());
   }
 
-  private boolean verifyLocalsEqual(
-      ImmutableMap<Integer, DebugLocalInfo> a, Map<Integer, DebugLocalInfo> b) {
-    int size = 0;
-    for (Map.Entry<Integer, DebugLocalInfo> entry : b.entrySet()) {
-      if (entry.getValue() != null) {
-        assert a.get(entry.getKey()) == entry.getValue();
-        ++size;
-      }
-    }
-    return a.size() == size;
-  }
-
   private void clearState() {
     liveAtEntrySets = null;
     liveIntervals = null;
@@ -1664,14 +1648,8 @@
         for (Phi phi : successor.getPhis()) {
           LiveIntervals toIntervals = phi.getLiveIntervals().getSplitCovering(toInstruction);
           Value operand = phi.getOperand(predIndex);
-          LiveIntervals fromIntervals = operand.getLiveIntervals();
-          if (operand.isPhi() && operand != phi && successor.getPhis().contains(operand)) {
-            // If the input to this phi is another phi in this block we want the value after
-            // merging which is the value for that phi at the from instruction.
-            fromIntervals = fromIntervals.getSplitCovering(fromInstruction);
-          } else {
-            fromIntervals = fromIntervals.getSplitCovering(fromInstruction);
-          }
+          LiveIntervals fromIntervals =
+              operand.getLiveIntervals().getSplitCovering(fromInstruction);
           if (fromIntervals != toIntervals && !toIntervals.isArgumentInterval()) {
             assert block.getSuccessors().size() == 1;
             spillMoves.addPhiMove(fromInstruction - 1, toIntervals, fromIntervals);
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 9c45617..1537f05 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -271,7 +271,7 @@
     }
 
     public static MethodSignature fromDexMethod(DexMethod method) {
-      String[] paramNames = new String[method.proto.parameters.values.length];
+      String[] paramNames = new String[method.getArity()];
       DexType[] values = method.proto.parameters.values;
       for (int i = 0; i < values.length; i++) {
         paramNames[i] = values[i].toSourceString();
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 1a28332..f538125 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
-
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -40,8 +39,7 @@
       method.getCode().registerReachableDefinitions(targetExtractor);
       DexMethod target = targetExtractor.getTarget();
       InvokeKind kind = targetExtractor.getKind();
-      if (target != null &&
-          target.proto.parameters.values.length == method.method.proto.parameters.values.length) {
+      if (target != null && target.getArity() == method.method.getArity()) {
         assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
         target = lense.lookupMethod(target, method);
         if (kind == InvokeKind.STATIC) {
@@ -73,7 +71,6 @@
   }
 
 
-
   private static class BridgeLense extends GraphLense {
 
     private final GraphLense previousLense;
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index d03c74b..e01439d 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -571,7 +571,7 @@
       for (DexMethod method : invokes) {
         Int2IntMap positionsMap = seenPositions.get(method.name);
         if (positionsMap != null) {
-          int arity = method.proto.parameters.values.length;
+          int arity = method.getArity();
           int previous = positionsMap.get(arity);
           if (previous != NOT_FOUND) {
             assert previous != 0;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 15ca87b..e767030 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -182,7 +182,7 @@
    * Get the input stream of the dead-code resource if exists.
    */
   public InputStream getDeadCode(Closer closer) throws IOException {
-    return deadCode == null ? null : deadCode.getStream(closer);
+    return deadCode == null ? null : closer.register(deadCode.getStream());
   }
 
   /**
@@ -196,7 +196,7 @@
    * Get the input stream of the proguard-map resource if it exists.
    */
   public InputStream getProguardMap(Closer closer) throws IOException {
-    return proguardMap == null ? null : proguardMap.getStream(closer);
+    return proguardMap == null ? null : closer.register(proguardMap.getStream());
   }
 
   /**
@@ -210,7 +210,7 @@
    * Get the input stream of the proguard-seeds resource if it exists.
    */
   public InputStream getProguardSeeds(Closer closer) throws IOException {
-    return proguardSeeds == null ? null : proguardSeeds.getStream(closer);
+    return proguardSeeds == null ? null : closer.register(proguardSeeds.getStream());
   }
 
   /**
@@ -224,7 +224,7 @@
    * Get the input stream of the package distribution resource if it exists.
    */
   public InputStream getPackageDistribution(Closer closer) throws IOException {
-    return packageDistribution == null ? null : packageDistribution.getStream(closer);
+    return packageDistribution == null ? null : closer.register(packageDistribution.getStream());
   }
 
   /**
@@ -238,7 +238,7 @@
    * Get the input stream of the main dex list resource if it exists.
    */
   public InputStream getMainDexList(Closer closer) throws IOException {
-    return mainDexList == null ? null : mainDexList.getStream(closer);
+    return mainDexList == null ? null : closer.register(mainDexList.getStream());
   }
 
   /**
@@ -271,7 +271,7 @@
         if (!Files.exists(filePath.getParent())) {
           Files.createDirectories(filePath.getParent());
         }
-        Files.copy(dexProgramSources.get(i).getStream(closer), filePath, options);
+        Files.copy(closer.register(dexProgramSources.get(i).getStream()), filePath, options);
       }
     }
   }
@@ -307,7 +307,7 @@
       List<Resource> dexProgramSources = getDexProgramResources();
       for (int i = 0; i < dexProgramSources.size(); i++) {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
-        ByteStreams.copy(dexProgramSources.get(i).getStream(closer), out);
+        ByteStreams.copy(closer.register(dexProgramSources.get(i).getStream()), out);
         dex.add(out.toByteArray());
       }
       // TODO(sgjesse): Add Proguard map and seeds.
@@ -326,7 +326,8 @@
         List<Resource> dexProgramSources = getDexProgramResources();
         for (int i = 0; i < dexProgramSources.size(); i++) {
           ZipEntry zipEntry = new ZipEntry(outputMode.getOutputPath(dexProgramSources.get(i), i));
-          byte[] bytes = ByteStreams.toByteArray(dexProgramSources.get(i).getStream(closer));
+          byte[] bytes =
+              ByteStreams.toByteArray(closer.register(dexProgramSources.get(i).getStream()));
           zipEntry.setSize(bytes.length);
           out.putNextEntry(zipEntry);
           out.write(bytes);
diff --git a/src/main/java/com/android/tools/r8/utils/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
index 25abdce..63a847a 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -100,7 +100,7 @@
         try (Closer closer = Closer.create()) {
           JarClassFileReader classReader =
               new JarClassFileReader(reader, classKind.bridgeConsumer(classConsumer));
-          classReader.read(DEFAULT_DEX_FILENAME, classKind, resource.getStream(closer));
+          classReader.read(DEFAULT_DEX_FILENAME, classKind, closer.register(resource.getStream()));
         } catch (IOException e) {
           throw new CompilationError("Failed to load class: " + descriptor, e);
         }
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 d1f8290..eb746d7 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -47,6 +47,8 @@
 
   public List<String> methodsFilter = ImmutableList.of();
   public int minApiLevel = Constants.DEFAULT_ANDROID_API;
+  // Skipping min_api check and compiling an intermediate result intended for later merging.
+  public boolean intermediate = false;
   public List<String> logArgumentsFilter = ImmutableList.of();
 
   // Defines interface method rewriter behavior.
@@ -273,6 +275,14 @@
     return minApiLevel >= Constants.ANDROID_N_API;
   }
 
+  public boolean canUsePrivateInterfaceMethods() {
+    return minApiLevel >= Constants.ANDROID_N_API;
+  }
+
+  public boolean canUseMultidex() {
+    return intermediate || minApiLevel >= Constants.ANDROID_L_API;
+  }
+
   public boolean canUseLongCompareAndObjectsNonNull() {
     return minApiLevel >= Constants.ANDROID_K_API;
   }
@@ -281,10 +291,6 @@
     return minApiLevel >= Constants.ANDROID_K_API;
   }
 
-  public boolean canUsePrivateInterfaceMethods() {
-    return minApiLevel >= Constants.ANDROID_N_API;
-  }
-
   // APIs for accessing parameter names annotations are not available before Android O, thus does
   // not emit them to avoid wasting space in Dex files because runtimes before Android O will ignore
   // them.
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index d8b9bbc..803d760 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -294,7 +294,7 @@
     ByteArrayOutputStream output = new ByteArrayOutputStream();
     byte[] buffer = new byte[16384];
     try (Closer closer = Closer.create()) {
-      InputStream stream = resource.getStream(closer);
+      InputStream stream = closer.register(resource.getStream());
       int read;
       while ((read = stream.read(buffer, 0, buffer.length)) != -1) {
         output.write(buffer, 0, read);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 9aec981..38868b6 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -61,18 +61,16 @@
     }
 
     TestRunner withClassCheck(Consumer<FoundClassSubject> check) {
-      withDexCheck(inspector -> inspector.forAllClasses(check));
-      return this;
+      return withDexCheck(inspector -> inspector.forAllClasses(check));
     }
 
     TestRunner withMethodCheck(Consumer<FoundMethodSubject> check) {
-      withClassCheck(clazz -> clazz.forAllMethods(check));
-      return this;
+      return withClassCheck(clazz -> clazz.forAllMethods(check));
     }
 
-    <T extends InstructionSubject> TestRunner
-    withInstructionCheck(Predicate<InstructionSubject> filter, Consumer<T> check) {
-      withMethodCheck(method -> {
+    <T extends InstructionSubject> TestRunner withInstructionCheck(
+        Predicate<InstructionSubject> filter, Consumer<T> check) {
+      return withMethodCheck(method -> {
         if (method.isAbstract()) {
           return;
         }
@@ -81,7 +79,6 @@
           check.accept(iterator.next());
         }
       });
-      return this;
     }
 
     TestRunner withOptionConsumer(Consumer<InternalOptions> consumer) {
@@ -267,7 +264,7 @@
   @Test
   public void paramNames() throws Throwable {
     test("paramnames", "paramnames", "ParameterNames")
-        .withMinApiLevel(26)
+        .withMinApiLevel(ANDROID_O_API)
         .withOptionConsumer((internalOptions) -> internalOptions.allowParameterName = true)
         .run();
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e01ecc0..d0032cd 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
@@ -56,7 +57,7 @@
   public static final String LINE_SEPARATOR = System.getProperty("line.separator");
 
   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
-  private static final int DEFAULT_MIN_SDK = 14;
+  private static final int DEFAULT_MIN_SDK = Constants.ANDROID_I_API;
 
   public enum DexVm {
     ART_4_4_4("4.4.4"),
@@ -487,6 +488,14 @@
             .build());
   }
 
+  public static DexApplication optimizeWithR8(
+      DexApplication application,
+      AppInfoWithSubtyping appInfo,
+      InternalOptions options)
+      throws ProguardRuleParserException, ExecutionException, IOException {
+    return R8.optimize(application, appInfo, options);
+  }
+
   public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException {
     return runD8(app, null);
   }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 8fdcfa5..fc9ee3f 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -62,8 +62,8 @@
       List<Resource> files2 = app2.getDexProgramResources();
       assertEquals(files1.size(), files2.size());
       for (int index = 0; index < files1.size(); index++) {
-        InputStream file1 = files1.get(index).getStream(closer);
-        InputStream file2 = files2.get(index).getStream(closer);
+        InputStream file1 = closer.register(files1.get(index).getStream());
+        InputStream file2 = closer.register(files2.get(index).getStream());
         byte[] bytes1 = ByteStreams.toByteArray(file1);
         byte[] bytes2 = ByteStreams.toByteArray(file2);
         assertArrayEquals("File index " + index, bytes1, bytes2);
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
index d28c37d..fa193d4 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
@@ -34,7 +34,7 @@
     int bytes = 0;
     try (Closer closer = Closer.create()) {
       for (Resource dex : app.getDexProgramResources()) {
-        bytes += ByteStreams.toByteArray(dex.getStream(closer)).length;
+        bytes += ByteStreams.toByteArray(closer.register(dex.getStream())).length;
       }
     }
     assertTrue("Expected max size of " + maxSize + ", got " + bytes, bytes < maxSize);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
index b26d1aa..8ee3674 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
@@ -36,7 +36,7 @@
     int bytes = 0;
     try (Closer closer = Closer.create()) {
       for (Resource dex : app.getDexProgramResources()) {
-        bytes += ByteStreams.toByteArray(dex.getStream(closer)).length;
+        bytes += ByteStreams.toByteArray(closer.register(dex.getStream())).length;
       }
     }
     assertTrue("Expected max size of " + maxSize + ", got " + bytes, bytes < maxSize);
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index e5bbad6..04d84e4 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -132,6 +132,6 @@
 
   protected static DexApplication process(DexApplication app, InternalOptions options)
       throws IOException, ProguardRuleParserException, ExecutionException {
-    return new R8(options).optimize(app, new AppInfoWithSubtyping(app));
+    return ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), options);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 001883f..69fdd65 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -121,6 +121,10 @@
     return generatedApplicationsFolder.getRoot().toPath().resolve("many-classes-stereo.zip");
   }
 
+  private static Path getManyClassesForceMultiDexAppPath() {
+    return generatedApplicationsFolder.getRoot().toPath().resolve("many-classes-stereo-forced.zip");
+  }
+
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
@@ -308,6 +312,32 @@
     }
   }
 
+  @Test
+  public void checkIntermediateMultiDex() throws Exception {
+    // Generates an application with many classes, every even in one package and every odd in
+    // another. Add enough methods so the application cannot fit into one dex file.
+    // Notice that this one allows multidex while using lower API.
+    AndroidApp generated = generateApplication(
+        MANY_CLASSES, Constants.ANDROID_K_API, true, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
+    generated.write(getManyClassesForceMultiDexAppPath(), OutputMode.Indexed);
+    // Make sure the generated app indeed has multiple dex files.
+    assertTrue(generated.getDexProgramResources().size() > 1);
+  }
+
+  @Test
+  public void testMultiDexFailDueToMinApi() throws Exception {
+    // Generates an application with many classes, every even in one package and every odd in
+    // another. Add enough methods so the application cannot fit into one dex file.
+    // Notice that this one fails due to the min API.
+    try {
+      generateApplication(
+          MANY_CLASSES, Constants.ANDROID_K_API, false, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
+      fail("Expect to fail, for there are many classes while multidex is not enabled.");
+    } catch (CompilationError e) {
+      assertTrue(e.getMessage().contains("Cannot fit all classes in a single dex file."));
+    }
+  }
+
   private void addMainListFile(ArrayList<Path> mainLists, List<String> content)
       throws IOException {
     Path listFile = temp.newFile().toPath();
@@ -393,9 +423,16 @@
 
   public static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
       throws IOException, ExecutionException {
+    return generateApplication(classes, minApi, false, methodCount);
+  }
+
+  private static AndroidApp generateApplication(
+      List<String> classes, int minApi, boolean intermediate, int methodCount)
+      throws IOException, ExecutionException {
     Timing timing = new Timing("MainDexListTests");
     InternalOptions options = new InternalOptions();
     options.minApiLevel = minApi;
+    options.intermediate = intermediate;
     DexItemFactory factory = options.itemFactory;
     DexApplication.Builder builder = new DexApplication.Builder(factory, timing);
     for (String clazz : classes) {
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 2549ff1..57c8cf6 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.maindexlist;
 
+import static com.android.tools.r8.dex.Constants.ANDROID_I_API;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
 
@@ -44,7 +45,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
-        14);
+        ANDROID_I_API);
   }
 
   @Test
@@ -55,7 +56,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "main-dex-rules-2.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-2.txt"),
-        14);
+        ANDROID_I_API);
   }
 
   @Test
@@ -66,7 +67,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex002", "ref-list-1.txt"),
-        14);
+        ANDROID_I_API);
   }
 
   @Test
@@ -77,7 +78,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex003", "ref-list-1.txt"),
-        14);
+        ANDROID_I_API);
   }
 
   @Test
@@ -88,7 +89,7 @@
         EXAMPLE_O_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
-        14);
+        ANDROID_I_API);
   }
 
   private void doTest(
@@ -137,9 +138,9 @@
       CompilationResult result = ToolHelper.runR8WithFullResult(command, optionsConsumer);
       List<String> resultMainDexList =
           result.dexApplication.mainDexList.stream()
-          .filter(dexType -> isApplicationClass(dexType, result) != null)
-          .map(dexType -> dexType.descriptor.toString())
-          .collect(Collectors.toList());
+              .filter(dexType -> isApplicationClass(dexType, result))
+              .map(dexType -> dexType.descriptor.toString())
+              .collect(Collectors.toList());
       Collections.sort(resultMainDexList);
       String[] refList = new String(Files.readAllBytes(
           expectedMainDexList), StandardCharsets.UTF_8).split("\n");
@@ -161,7 +162,7 @@
     }
   }
 
-  private Object isApplicationClass(DexType dexType, CompilationResult result) {
+  private boolean isApplicationClass(DexType dexType, CompilationResult result) {
     DexClass clazz = result.appInfo.definitionFor(dexType);
     return clazz != null && clazz.isProgramClass();
   }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 9a12d3e..f070130 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -385,8 +385,7 @@
 
   protected DexApplication processApplication(DexApplication application, InternalOptions options) {
     try {
-      R8 r8 = new R8(options);
-      return r8.optimize(application, new AppInfoWithSubtyping(application));
+      return ToolHelper.optimizeWithR8(application, new AppInfoWithSubtyping(application), options);
     } catch (IOException | ProguardRuleParserException | ExecutionException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index e49ea84..f10add0 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.code.Const;
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.ConstHigh16;
@@ -284,7 +285,7 @@
         "    return");
 
     DexApplication app = builder.read();
-    app = new R8(new InternalOptions()).optimize(app, new AppInfoWithSubtyping(app));
+    app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
 
     MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
     DexEncodedMethod method = getMethod(app, signature);
@@ -344,7 +345,7 @@
         "    return");
 
     DexApplication app = builder.read();
-    app = new R8(new InternalOptions()).optimize(app, new AppInfoWithSubtyping(app));
+    app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
 
     MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
     DexEncodedMethod method = getMethod(app, signature);
@@ -417,7 +418,7 @@
         "    return");
 
     DexApplication app = builder.read();
-    app = new R8(new InternalOptions()).optimize(app, new AppInfoWithSubtyping(app));
+    app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
 
     MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
     DexEncodedMethod method = getMethod(app, signature);