Merge "Cleanup of access to methods in DexClass."
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/dex/DexFile.java b/src/main/java/com/android/tools/r8/dex/DexFile.java
index a2c65d3..3441346 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFile.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFile.java
@@ -62,13 +62,13 @@
     int version;
     switch (versionByte) {
       case '8':
-        version = 38;
+        version = Constants.ANDROID_O_DEX_VERSION;
         break;
       case '7':
-        version = 37;
+        version = Constants.ANDROID_N_DEX_VERSION;
         break;
       case '5':
-        version = 35;
+        version = Constants.ANDROID_PRE_N_DEX_VERSION;
         break;
       default:
         throw new CompilationError("Dex file has invalid version number: " + name);
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index f336a3d..f309670 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -174,6 +174,35 @@
     return isFull(transaction.getNumberOfMethods(), transaction.getNumberOfFields(), MAX_ENTRIES);
   }
 
+  void throwIfFull(boolean multiDexEnabled) {
+    if (!isFull()) {
+      return;
+    }
+    StringBuilder messageBuilder = new StringBuilder();
+    // General message: Cannot fit.
+    messageBuilder.append("Cannot fit requested classes in ");
+    messageBuilder.append(multiDexEnabled ? "the main-" : "a single ");
+    messageBuilder.append("dex file.\n");
+    // Suggest supplying the main-dex list or explicitly mention that main-dex list is too large.
+    if (multiDexEnabled) {
+      messageBuilder.append("The list of classes for the main-dex list is too large.\n");
+    } else {
+      messageBuilder.append("Try supplying a main-dex list.\n");
+    }
+    // Show the numbers of methods and/or fields that exceed the limit.
+    if (transaction.getNumberOfMethods() > MAX_ENTRIES) {
+      messageBuilder.append("# methods: ");
+      messageBuilder.append(transaction.getNumberOfMethods());
+      messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
+    }
+    if (transaction.getNumberOfFields() > MAX_ENTRIES) {
+      messageBuilder.append("# fields: ");
+      messageBuilder.append(transaction.getNumberOfFields());
+      messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
+    }
+    throw new CompilationError(messageBuilder.toString());
+  }
+
   private boolean isFilledEnough(FillStrategy fillStrategy) {
     return isFull(
         transaction.getNumberOfMethods(),
@@ -202,7 +231,7 @@
     protected final ApplicationWriter writer;
     protected final Map<Integer, VirtualFile> nameToFileMap = new HashMap<>();
 
-    public Distributor(ApplicationWriter writer) {
+    Distributor(ApplicationWriter writer) {
       this.application = writer.application;
       this.writer = writer;
     }
@@ -212,7 +241,7 @@
 
   public static class FilePerClassDistributor extends Distributor {
 
-    public FilePerClassDistributor(ApplicationWriter writer) {
+    FilePerClassDistributor(ApplicationWriter writer) {
       super(writer);
     }
 
@@ -232,7 +261,7 @@
     protected Set<DexProgramClass> classes;
     protected Map<DexProgramClass, String> originalNames;
 
-    public DistributorBase(ApplicationWriter writer) {
+    DistributorBase(ApplicationWriter writer) {
       super(writer);
 
       classes = Sets.newHashSet(application.classes());
@@ -247,9 +276,7 @@
           if (clazz != null && clazz.isProgramClass()) {
             DexProgramClass programClass = (DexProgramClass) clazz;
             mainDexFile.addClass(programClass);
-            if (mainDexFile.isFull()) {
-              throw new CompilationError("Cannot fit requested classes in main-dex file.");
-            }
+            mainDexFile.throwIfFull(true);
             classes.remove(programClass);
           } else {
             System.out.println(
@@ -298,7 +325,7 @@
   public static class FillFilesDistributor extends DistributorBase {
     private final FillStrategy fillStrategy;
 
-    public FillFilesDistributor(ApplicationWriter writer, boolean minimalMainDex) {
+    FillFilesDistributor(ApplicationWriter writer, boolean minimalMainDex) {
       super(writer);
       this.fillStrategy = minimalMainDex ? FillStrategy.MINIMAL_MAIN_DEX : FillStrategy.FILL_MAX;
     }
@@ -326,7 +353,7 @@
   }
 
   public static class MonoDexDistributor extends DistributorBase {
-    public MonoDexDistributor(ApplicationWriter writer) {
+    MonoDexDistributor(ApplicationWriter writer) {
       super(writer);
     }
 
@@ -337,9 +364,7 @@
 
       for (DexProgramClass programClass : classes) {
         mainDexFile.addClass(programClass);
-        if (mainDexFile.isFull()) {
-          throw new CompilationError("Cannot fit all classes in a single dex file.");
-        }
+        mainDexFile.throwIfFull(false);
       }
       mainDexFile.commitTransaction();
       return nameToFileMap;
@@ -350,7 +375,7 @@
     private final PackageDistribution packageDistribution;
     private final ExecutorService executorService;
 
-    public PackageMapDistributor(
+    PackageMapDistributor(
         ApplicationWriter writer,
         PackageDistribution packageDistribution,
         ExecutorService executorService) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3619b9c..3601ee9 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -226,17 +226,44 @@
         ((DexEncodedMethod) dexItem).getCode() != null;
   }
 
-  private void checkIfMethodIsAmbiguous(DexItem previousResult, DexItem newResult) {
+  /** Returns if <code>interface1</code> is a super interface of <code>interface2</code> */
+  private boolean isSuperInterfaceOf(DexType interface1, DexType interface2) {
+    assert definitionFor(interface1).isInterface();
+    DexClass holder = definitionFor(interface2);
+    assert holder.isInterface();
+    for (DexType iface : holder.interfaces.values) {
+      if (iface == interface1 || isSuperInterfaceOf(interface1, iface)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private <S extends DexItem> S resolveAmbiguousResult(S previousResult, S newResult) {
+    // For default methods return the item found lowest in the interface hierarchy. Different
+    // implementations can come from different paths in a diamond.
+    // See §9.4.1 in The Java® Language Specification, Java SE 8 Edition.
     if (previousResult != null
         && previousResult != newResult
         && isDefaultMethod(previousResult)
         && isDefaultMethod(newResult)) {
+      DexEncodedMethod previousMethod = (DexEncodedMethod) previousResult;
+      DexEncodedMethod newMethod = (DexEncodedMethod) newResult;
+      if (isSuperInterfaceOf(previousMethod.method.getHolder(), newMethod.method.getHolder())) {
+        return newResult;
+      }
+      if (isSuperInterfaceOf(newMethod.method.getHolder(), previousMethod.method.getHolder())) {
+        return previousResult;
+      }
       throw new CompilationError("Duplicate default methods named "
           + previousResult.toSourceString()
           + " are inherited from the types "
-          + ((DexEncodedMethod) previousResult).method.holder.getName()
+          + previousMethod.method.holder.getName()
           + " and "
-          + ((DexEncodedMethod) newResult).method.holder.getName());
+          + newMethod.method.holder.getName());
+    } else {
+      // Return the first item found for everything except default methods.
+      return previousResult != null ? previousResult : newResult;
     }
   }
 
@@ -256,21 +283,13 @@
     for (DexType iface : holder.interfaces.values) {
       S localResult = lookupTargetAlongSuperAndInterfaceChain(iface, desc, lookup);
       if (localResult != null) {
-        checkIfMethodIsAmbiguous(result, localResult);
-        // Return the first item found, we only continue to detect ambiguous method call.
-        if (result == null) {
-          result = localResult;
-        }
+        result = resolveAmbiguousResult(result, localResult);
       }
     }
     if (holder.superType != null) {
       S localResult = lookupTargetAlongInterfaceChain(holder.superType, desc, lookup);
       if (localResult != null) {
-        checkIfMethodIsAmbiguous(result, localResult);
-        // Return the first item found, we only continue to detect ambiguous method call.
-        if (result == null) {
-          result = localResult;
-        }
+        result = resolveAmbiguousResult(result, localResult);
       }
     }
     return result;
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..44721ae 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
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -378,6 +379,18 @@
     return instructions.get(instructions.size() - 1).asJumpInstruction();
   }
 
+  public Instruction exceptionalExit() {
+    assert hasCatchHandlers();
+    ListIterator<Instruction> it = listIterator(instructions.size());
+    while (it.hasPrevious()) {
+      Instruction instruction = it.previous();
+      if (instruction.instructionTypeCanThrow()) {
+        return instruction;
+      }
+    }
+    throw new Unreachable();
+  }
+
   public void clearUserInfo() {
     phis = null;
     instructions.forEach(Instruction::clearUserInfo);
@@ -1120,11 +1133,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 +1155,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/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index a08b044..0a1cc1b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -18,6 +18,7 @@
   public DebugLocalsChange(
       Int2ReferenceMap<DebugLocalInfo> ending, Int2ReferenceMap<DebugLocalInfo> starting) {
     super(null);
+    assert !ending.isEmpty() || !starting.isEmpty();
     this.ending = ending;
     this.starting = starting;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 0b8d876..065d3df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -41,11 +41,13 @@
     return field;
   }
 
+  abstract DexEncodedField lookupTarget(DexType type, AppInfo appInfo);
+
   @Override
   public Constraint inliningConstraint(AppInfo info, DexType holder) {
     // Resolve the field if possible and decide whether the instruction can inlined.
     DexType fieldHolder = field.getHolder();
-    DexEncodedField target = info.lookupInstanceTarget(fieldHolder, field);
+    DexEncodedField target = lookupTarget(fieldHolder, info);
     DexClass fieldClass = info.definitionFor(fieldHolder);
     if ((target != null) && (fieldClass != null) && !fieldClass.isLibraryClass()) {
       DexAccessFlags flags = target.accessFlags;
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/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index f38614f..1d9e853 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -13,7 +13,10 @@
 import com.android.tools.r8.code.IgetWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 
 public class InstanceGet extends FieldInstruction {
@@ -95,6 +98,11 @@
   }
 
   @Override
+  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+    return appInfo.lookupInstanceTarget(type, field);
+  }
+
+  @Override
   public boolean isInstanceGet() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 7ec8eaf..2642de1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -13,7 +13,10 @@
 import com.android.tools.r8.code.IputWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.List;
 
@@ -96,6 +99,11 @@
   }
 
   @Override
+  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+    return appInfo.lookupInstanceTarget(type, field);
+  }
+
+  @Override
   public boolean isInstancePut() {
     return true;
   }
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/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index ad340c2..bb79d5e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -12,7 +12,10 @@
 import com.android.tools.r8.code.SgetWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 
 public class StaticGet extends FieldInstruction {
@@ -91,6 +94,11 @@
   }
 
   @Override
+  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+    return appInfo.lookupStaticTarget(type, field);
+  }
+
+  @Override
   public String toString() {
     return super.toString() + "; field: " + field.toSourceString();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index e8a1fed..6736d7e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -12,7 +12,10 @@
 import com.android.tools.r8.code.SputWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 
 public class StaticPut extends FieldInstruction {
@@ -93,6 +96,11 @@
   }
 
   @Override
+  DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+    return appInfo.lookupStaticTarget(type, field);
+  }
+
+  @Override
   public String toString() {
     return super.toString() + "; field: " + field.toSourceString();
   }
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/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 4ede5a8..a2b3ba2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -302,9 +302,9 @@
                 continue;
               }
               // Ensure the container is compatible with the target.
-             if (!forceInline
-                 && !result.target.isPublicInlining()
-                 && (method.method.getHolder() != result.target.method.getHolder())) {
+              if (!forceInline
+                  && !result.target.isPublicInlining()
+                  && (method.method.getHolder() != result.target.method.getHolder())) {
                 continue;
               }
               DexType downcast = null;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 0bd7188..6f1a9b2 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,11 +28,11 @@
 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;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
 import it.unimi.dsi.fastutil.ints.IntIterator;
@@ -76,6 +76,7 @@
   }
 
   private static class LocalRange implements Comparable<LocalRange> {
+    final Value value;
     final DebugLocalInfo local;
     final int register;
     final int start;
@@ -83,6 +84,7 @@
 
     LocalRange(Value value, int register, int start, int end) {
       assert value.getLocalInfo() != null;
+      this.value = value;
       this.local = value.getLocalInfo();
       this.register = register;
       this.start = start;
@@ -253,7 +255,7 @@
           // information, we always use the argument register whenever a local corresponds to an
           // argument value. That avoids ending and restarting locals whenever we move arguments
           // to lower register.
-          int register = getRegisterForValue(value, value.isArgument() ? 0 : start);
+          int register = getArgumentOrAllocateRegisterForValue(value, start);
           ranges.add(new LocalRange(value, register, start, nextEnd));
           Integer nextStart = nextInRange(nextEnd, end, starts);
           if (nextStart == null) {
@@ -263,7 +265,8 @@
           start = nextStart;
         }
         if (start >= 0) {
-          ranges.add(new LocalRange(value, getRegisterForValue(value, start), start, end));
+          ranges.add(new LocalRange(value, getArgumentOrAllocateRegisterForValue(value, start),
+              start, end));
         }
       }
     }
@@ -307,8 +310,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,18 +320,25 @@
           }
           nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
         }
+
         if (blockEntry) {
           blockEntry = false;
-          block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
-        } else if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
-          DebugLocalsChange change = createLocalsChange(ending, starting);
-          if (change != null) {
-            if (instruction.isDebugPosition() || instruction.isJumpInstruction()) {
-              instructionIterator.previous();
-              instructionIterator.add(new DebugLocalsChange(ending, starting));
-              instructionIterator.next();
-            } else {
-              instructionIterator.add(new DebugLocalsChange(ending, starting));
+          if (instruction.isMoveException()) {
+            fixupSpillMovesAtMoveException(block, instructionIterator, openRanges, currentLocals);
+          } else {
+            block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
+          }
+        } else {
+          if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
+            DebugLocalsChange change = createLocalsChange(ending, starting);
+            if (change != null) {
+              if (instruction.isDebugPosition() || instruction.isJumpInstruction()) {
+                instructionIterator.previous();
+                instructionIterator.add(change);
+                instructionIterator.next();
+              } else {
+                instructionIterator.add(change);
+              }
             }
           }
         }
@@ -337,13 +347,83 @@
     }
   }
 
+  private void fixupSpillMovesAtMoveException(
+      BasicBlock block,
+      ListIterator<Instruction> instructionIterator,
+      List<LocalRange> openRanges,
+      Int2ReferenceMap<DebugLocalInfo> finalLocals) {
+    Int2ReferenceMap<DebugLocalInfo> initialLocals = new Int2ReferenceOpenHashMap<>();
+    int exceptionalIndex = block.getPredecessors().get(0).exceptionalExit().getNumber();
+    for (LocalRange open : openRanges) {
+      int exceptionalRegister = getArgumentOrAllocateRegisterForValue(open.value, exceptionalIndex);
+      initialLocals.put(exceptionalRegister, open.local);
+    }
+    block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(initialLocals));
+    Int2ReferenceMap<DebugLocalInfo> clobberedLocals = new Int2ReferenceOpenHashMap<>();
+    Iterator<Instruction> moveIterator = block.iterator();
+    assert block.entry().isMoveException();
+    int index = block.entry().getNumber();
+    moveIterator.next();
+    while (moveIterator.hasNext()) {
+      Instruction next = moveIterator.next();
+      if (next.getNumber() != -1) {
+        break;
+      }
+      if (clobberedLocals.isEmpty()) {
+        // Advance the iterator so it ends up at the first move that clobbers a local.
+        instructionIterator.next();
+      }
+      if (next.isMove()) {
+        Move move = next.asMove();
+        int dstRegister = getArgumentOrAllocateRegisterForValue(move.dest(), index);
+        DebugLocalInfo dstInitialLocal = initialLocals.get(dstRegister);
+        DebugLocalInfo dstFinalLocal = finalLocals.get(dstRegister);
+        if (dstInitialLocal != null && dstInitialLocal != dstFinalLocal) {
+          initialLocals.remove(dstRegister);
+          clobberedLocals.put(dstRegister, dstInitialLocal);
+        }
+      }
+    }
+    // Add an initial local change for all clobbered locals after the first clobbered local.
+    if (!clobberedLocals.isEmpty()) {
+      instructionIterator.add(new DebugLocalsChange(
+          clobberedLocals, Int2ReferenceMaps.emptyMap()));
+    }
+    // Compute the final change in locals and emit it after all spill moves.
+    while (instructionIterator.hasNext()) {
+      if (instructionIterator.next().getNumber() != -1) {
+        instructionIterator.previous();
+        break;
+      }
+    }
+    Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
+    Int2ReferenceMap<DebugLocalInfo> starting = new Int2ReferenceOpenHashMap<>();
+    for (Entry<DebugLocalInfo> initialLocal : initialLocals.int2ReferenceEntrySet()) {
+      if (finalLocals.get(initialLocal.getIntKey()) != initialLocal.getValue()) {
+        ending.put(initialLocal.getIntKey(), initialLocal.getValue());
+      }
+    }
+    for (Entry<DebugLocalInfo> finalLocal : finalLocals.int2ReferenceEntrySet()) {
+      if (initialLocals.get(finalLocal.getIntKey()) != finalLocal.getValue()) {
+        starting.put(finalLocal.getIntKey(), finalLocal.getValue());
+      }
+    }
+    DebugLocalsChange change = createLocalsChange(ending, starting);
+    if (change != null) {
+      instructionIterator.add(change);
+    }
+  }
+
   private DebugLocalsChange createLocalsChange(
       Int2ReferenceMap<DebugLocalInfo> ending, Int2ReferenceMap<DebugLocalInfo> starting) {
+    if (ending.isEmpty() && starting.isEmpty()) {
+      return null;
+    }
     if (ending.isEmpty() || starting.isEmpty()) {
       return new DebugLocalsChange(ending, starting);
     }
     IntSet unneeded = new IntArraySet(Math.min(ending.size(), starting.size()));
-    for (Int2ReferenceMap.Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
+    for (Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
       if (starting.get(entry.getIntKey()) == entry.getValue()) {
         unneeded.add(entry.getIntKey());
       }
@@ -368,18 +448,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;
@@ -1128,7 +1196,7 @@
           map.add(operandRegister);
         }
       }
-      for (Entry<Integer> entry : Multisets.copyHighestCountFirst(map).entrySet()) {
+      for (Multiset.Entry<Integer> entry : Multisets.copyHighestCountFirst(map).entrySet()) {
         int register = entry.getElement();
         if (tryHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair,
             register)) {
@@ -1664,14 +1732,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/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index be25097..6a5f738 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -325,6 +325,10 @@
     return new JUnit3Wrapper.Command.SetLocalCommand(localName, newValue);
   }
 
+  protected final JUnit3Wrapper.Command getLocal(String localName, Consumer<Value> inspector) {
+    return t -> inspector.accept(t.debuggeeState.getLocalValues().get(localName));
+  }
+
   @Ignore("Prevents Gradle from running the wrapper as a test.")
   static class JUnit3Wrapper extends JDWPTestCase {
 
@@ -643,7 +647,8 @@
           assert valuesCount == 1;
           Value localValue = replyPacket.getNextValueAsValue();
 
-          Assert.assertEquals(expectedValue, localValue);
+          Assert.assertEquals("Incorrect value for local '" + localName + "'",
+              expectedValue, localValue);
         }
 
         public String getClassName() {
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 6a6a20f..1fd4bc9 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -317,4 +317,35 @@
         run());
   }
 
+  @Test
+  public void testInvokeRangeLongThrowOnDiv() throws Throwable {
+    final int initialValueOfX = 21;
+    final long expectedValueOfL = (long) initialValueOfX * 2;
+    runDebugTest("Locals",
+        breakpoint("Locals", "foo"),
+        run(),
+        // Initialize obj to 42 using original value of x.
+        stepOver(),
+        // Set value of x to zero which will cause a div-by-zero arithmetic exception below.
+        checkLocal("x", Value.createInt(initialValueOfX)),
+        setLocal("x", Value.createInt(0)),
+        // Single step until the catch handler triggers.
+        checkLine(SOURCE_FILE, 166), stepOver(),
+        checkLine(SOURCE_FILE, 168), stepOver(),
+        checkLine(SOURCE_FILE, 169), stepOver(),
+        // At the catch handler, inspect the initial state of locals.
+        checkLine(SOURCE_FILE, 172),
+        checkLocal("x", Value.createInt(0)),
+        getLocal("obj", value -> Assert.assertEquals(Tag.OBJECT_TAG, value.getTag())),
+        checkLocal("l", Value.createLong(expectedValueOfL)),
+        // Step onto first line of catch handler and inspect again, including the exception local.
+        stepOver(),
+        checkLine(SOURCE_FILE, 173),
+        getLocal("e", value -> Assert.assertEquals(Tag.OBJECT_TAG, value.getTag())),
+        checkLocal("x", Value.createInt(0)),
+        getLocal("obj", value -> Assert.assertEquals(Tag.OBJECT_TAG, value.getTag())),
+        checkLocal("l", Value.createLong(expectedValueOfL)),
+        run());
+  }
+
 }
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..a5e2309 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();
 
@@ -146,8 +150,15 @@
 
   @Test
   public void cannotFitBothIntoMainDex() throws Throwable {
-    thrown.expect(CompilationError.class);
-    verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
+    try {
+      verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
+      fail("Expect to fail, for there are too many classes for the main-dex list.");
+    } catch (CompilationError e) {
+      // Make sure {@link MonoDexDistributor} was _not_ used.
+      assertFalse(e.getMessage().contains("single dex file"));
+      // Make sure what exceeds the limit is the number of methods.
+      assertTrue(e.getMessage().contains("# methods"));
+    }
   }
 
   @Test
@@ -179,8 +190,15 @@
 
   @Test
   public void cannotFitAllIntoMainDex() throws Throwable {
-    thrown.expect(CompilationError.class);
-    verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
+    try {
+      verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
+      fail("Expect to fail, for there are too many classes for the main-dex list.");
+    } catch (CompilationError e) {
+      // Make sure {@link MonoDexDistributor} was _not_ used.
+      assertFalse(e.getMessage().contains("single dex file"));
+      // Make sure what exceeds the limit is the number of methods.
+      assertTrue(e.getMessage().contains("# methods"));
+    }
   }
 
   @Test
@@ -308,6 +326,35 @@
     }
   }
 
+  @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) {
+      // Make sure {@link MonoDexDistributor} was used.
+      assertTrue(e.getMessage().contains("single dex file"));
+      // Make sure what exceeds the limit is the number of methods.
+      assertTrue(e.getMessage().contains("# methods"));
+    }
+  }
+
   private void addMainListFile(ArrayList<Path> mainLists, List<String> content)
       throws IOException {
     Path listFile = temp.newFile().toPath();
@@ -393,9 +440,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) {
@@ -439,8 +493,8 @@
     }
     DexApplication application = builder.build();
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
-    ApplicationWriter writer =
-        new ApplicationWriter(application, appInfo, options, null, NamingLens.getIdentityLens(), null);
+    ApplicationWriter writer = new ApplicationWriter(
+        application, appInfo, options, null, NamingLens.getIdentityLens(), null);
     ExecutorService executor = ThreadUtils.getExecutorService(options);
     try {
       return writer.write(null, executor);
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/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
new file mode 100644
index 0000000..70d4efc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b63935662;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApp;
+import java.nio.file.Path;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Regress63935662 extends TestBase {
+
+  void run(AndroidApp app, Class mainClass) throws Exception {
+    if (!ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1)) {
+      return;
+    }
+    Path proguardConfig = writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(app)
+            .addProguardConfigurationFiles(proguardConfig)
+            .setMinApiLevel(Constants.ANDROID_N_API)
+            .build();
+    String resultFromJava = runOnJava(mainClass);
+    app = ToolHelper.runR8(command);
+    String resultFromArt = runOnArt(app, mainClass);
+    Assert.assertEquals(resultFromJava, resultFromArt);
+  }
+
+  @Test
+  public void test() throws Exception {
+    Class mainClass = TestClass.class;
+    AndroidApp app = readClasses(
+        TestClass.Top.class, TestClass.Left.class, TestClass.Right.class, TestClass.Bottom.class,
+        TestClass.X1.class, TestClass.X2.class, TestClass.X3.class, TestClass.X4.class, TestClass.X5.class,
+        mainClass);
+    run(app, mainClass);
+  }
+
+  @Test
+  public void test2() throws Exception {
+    Class mainClass = TestFromBug.class;
+    AndroidApp app = readClasses(
+        TestFromBug.Map.class, TestFromBug.AbstractMap.class,
+        TestFromBug.ConcurrentMap.class, TestFromBug.ConcurrentHashMap.class,
+        mainClass);
+    run(app, mainClass);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
new file mode 100644
index 0000000..878fcff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b63935662;
+
+public class TestClass {
+
+  interface Top {
+    default String name() { return "unnamed"; }
+  }
+
+  interface Left extends Top {
+    default String name() { return getClass().getName(); }
+  }
+
+  interface Right extends Top {
+    /* No override of default String name() */
+  }
+
+  interface Bottom extends Left, Right {}
+
+  static class X1 implements Bottom {
+    void test() {
+      System.out.println(name());
+    }
+  }
+
+  static class X2 implements Left, Right  {
+    void test() {
+      System.out.println(name());
+    }
+  }
+
+  static class X3 implements Right, Left   {
+    void test() {
+      System.out.println(name());
+    }
+  }
+
+  static class X4 implements Left, Right, Top  {
+    void test() {
+      System.out.println(name());
+    }
+  }
+
+  static class X5 implements Right, Left, Top   {
+    void test() {
+      System.out.println(name());
+    }
+  }
+
+  public static void main(String[] args) {
+    new X1().test();
+    new X2().test();
+    new X3().test();
+    new X4().test();
+    new X5().test();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java b/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java
new file mode 100644
index 0000000..9d3efc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b63935662;
+
+import java.util.function.BiConsumer;
+
+public class TestFromBug {
+
+  public interface Map<K, V> {
+    default void forEach(BiConsumer<? super K, ? super V> action) {
+      System.out.println("Map.forEach");
+    }
+  }
+
+  public interface ConcurrentMap<K, V> extends Map<K,V> {
+    @Override
+    default void forEach(BiConsumer<? super K, ? super V> action) {
+      System.out.println("ConcurrentMap.forEach");
+    }
+  }
+
+  public static abstract class AbstractMap<K,V> implements Map<K, V> {}
+  public static class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V> {}
+
+  public static void main(String[] args) {
+    new ConcurrentHashMap<String, String>().forEach(null);
+  }
+}
\ No newline at end of file
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);