Merge "Don't preload classpath"
diff --git a/build.gradle b/build.gradle
index 1ad9b98..9539df2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1068,7 +1068,9 @@
     if (project.hasProperty('dex_vm') && project.property('dex_vm') != 'default') {
         println "Running with non default vm: " + project.property('dex_vm')
         systemProperty 'dex_vm', project.property('dex_vm')
-        if (project.property('dex_vm') == '5.1.1' || project.property('dex_vm') == '6.0.1') {
+        if (project.property('dex_vm').startsWith('4.4.4') ||
+            project.property('dex_vm').startsWith('5.1.1') ||
+            project.property('dex_vm').startsWith('6.0.1')) {
             // R8 and D8 compute the dex file version number based on the input.
             // Jack generates dex files with version 37 which art 5.1.1 and 6.0.1 will not run.
             // Therefore we skip the jack generated art tests with those art versions.
@@ -1183,6 +1185,8 @@
 //    out/host/linux-x86/bin directory of the android checkout. Currently this is version pre 2.2.1,
 //    if that is updated the call to smali in "task "${smaliToDexTask}"(type: Exec)" below might
 //    need to change as smali got a completely new command line interface in version 2.2.1.
+//    After Android O, Jack is no longer alive, do not forget to uncomment call to buildArtTest for
+//    Jack if you build an android version using Jack.
 //
 //    PATH=$HOME/android/master/out/host/linux-x86/bin:$PATH tools/gradle.py -Pandroid_source buildArtTests
 //
@@ -1301,7 +1305,8 @@
                     if (!(name in skippedTestsDx)) {
                         dependsOn buildArtTest(androidCheckoutDir, artTestBuildDir, dir, DexTool.DX);
                     }
-                    dependsOn buildArtTest(androidCheckoutDir, artTestBuildDir, dir, DexTool.JACK);
+                    // After Android O, Jack is no longer alive
+                    //dependsOn buildArtTest(androidCheckoutDir, artTestBuildDir, dir, DexTool.JACK);
                 }
             }
         }
@@ -1424,7 +1429,13 @@
     }
 
     task "${smaliToDexTask}"(type: Exec) {
-        workingDir "${testDir}/smali"
+        // Directory that contains smali files is either smali, or smali/art
+        def smali_dir = file("${dir}/smali/art")
+        if (smali_dir.exists()) {
+            workingDir "${testDir}/smali/art"
+        } else  {
+            workingDir "${testDir}/smali"
+        }
         executable "/bin/bash"
         // This is the command line options for smali prior to 2.2.1, where smali got a new
         // command line interface.
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 72cfbab..426197b 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -22,6 +22,7 @@
   private final CompilationMode mode;
   private final int minApiLevel;
   private final DiagnosticsHandler diagnosticsHandler;
+  private final boolean enableDesugaring;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -30,6 +31,7 @@
     mode = null;
     minApiLevel = 0;
     diagnosticsHandler = new DefaultDiagnosticsHandler();
+    enableDesugaring = true;
   }
 
   BaseCompilerCommand(
@@ -38,7 +40,8 @@
       OutputMode outputMode,
       CompilationMode mode,
       int minApiLevel,
-      DiagnosticsHandler diagnosticsHandler) {
+      DiagnosticsHandler diagnosticsHandler,
+      boolean enableDesugaring) {
     super(app);
     assert mode != null;
     assert minApiLevel > 0;
@@ -47,6 +50,7 @@
     this.mode = mode;
     this.minApiLevel = minApiLevel;
     this.diagnosticsHandler = diagnosticsHandler;
+    this.enableDesugaring = enableDesugaring;
   }
 
   public Path getOutputPath() {
@@ -69,6 +73,10 @@
     return diagnosticsHandler;
   }
 
+  boolean getEnableDesugaring() {
+    return enableDesugaring;
+  }
+
   abstract public static class Builder<C extends BaseCompilerCommand, B extends Builder<C, B>>
       extends BaseCommand.Builder<C, B> {
 
@@ -152,6 +160,10 @@
       return self();
     }
 
+    protected boolean getEnableDesugaring() {
+      return true;
+    }
+
     protected void validate() throws CompilationException {
       super.validate();
       if (getAppBuilder().hasMainDexList() && outputMode == OutputMode.FilePerInputClass) {
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index ac86781..3ce1ea1 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -99,6 +99,7 @@
           getMode(),
           getMinApiLevel(),
           getDiagnosticsHandler(),
+          getEnableDesugaring(),
           intermediate);
     }
   }
@@ -196,8 +197,10 @@
       CompilationMode mode,
       int minApiLevel,
       DiagnosticsHandler diagnosticsHandler,
+      boolean enableDesugaring,
       boolean intermediate) {
-    super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler);
+    super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler,
+        enableDesugaring);
     this.intermediate = intermediate;
   }
 
@@ -226,6 +229,7 @@
     internal.outline.enabled = false;
     internal.outputMode = getOutputMode();
     internal.diagnosticsHandler = getDiagnosticsHandler();
+    internal.enableDesugaring = getEnableDesugaring();
     return internal;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/Diagnostic.java b/src/main/java/com/android/tools/r8/Diagnostic.java
index 6be2748..3991c4f 100644
--- a/src/main/java/com/android/tools/r8/Diagnostic.java
+++ b/src/main/java/com/android/tools/r8/Diagnostic.java
@@ -7,5 +7,5 @@
  * Interface for all diagnostic message produced by D8 and R8.
  */
 public interface Diagnostic {
-  String toString();
+  String getDiagnosticMessage();
 }
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 19d6fdc..3548644 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -16,7 +16,7 @@
    * @param warning Diagnostic containing warning information.
    */
   default void warning(Diagnostic warning) {
-    System.err.println(warning.toString());
+    System.err.println(warning.getDiagnosticMessage());
   }
 
   /**
@@ -25,6 +25,6 @@
    * @param info Diagnostic containing the information.
    */
   default void info(Diagnostic info) {
-    System.out.println(info.toString());
+    System.out.println(info.getDiagnosticMessage());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5b28e78..c6fcc22 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -45,6 +45,7 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.io.ByteStreams;
@@ -206,11 +207,11 @@
         missingClasses = filterMissingClasses(
             missingClasses, options.proguardConfiguration.getDontWarnPatterns());
         if (!missingClasses.isEmpty()) {
-          System.err.println();
-          System.err.println("WARNING, some classes are missing:");
-          missingClasses.forEach(clazz -> {
-            System.err.println(" - " + clazz.toSourceString());
-          });
+          missingClasses.forEach(
+              clazz -> {
+                options.diagnosticsHandler.warning(
+                    new StringDiagnostic("Missing class: " + clazz.toSourceString()));
+              });
           if (!options.ignoreMissingClasses) {
             throw new CompilationError(
                 "Shrinking can't be performed because some library classes are missing.");
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a6a1c33..502dd3c 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -238,6 +238,7 @@
           getMode(),
           getMinApiLevel(),
           getDiagnosticsHandler(),
+          getEnableDesugaring(),
           useTreeShaking,
           useDiscardedChecker,
           useMinification,
@@ -394,12 +395,14 @@
       CompilationMode mode,
       int minApiLevel,
       DiagnosticsHandler diagnosticsHandler,
+      boolean enableDesugaring,
       boolean useTreeShaking,
       boolean useDiscardedChecker,
       boolean useMinification,
       boolean ignoreMissingClasses,
       Path proguardMapOutput) {
-    super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler);
+    super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler,
+        enableDesugaring);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
@@ -461,7 +464,6 @@
     internal.keepAttributes.applyPatterns(proguardConfiguration.getKeepAttributesPatterns());
     internal.ignoreMissingClasses |= proguardConfiguration.isIgnoreWarnings();
     assert !internal.verbose;
-    internal.verbose |= proguardConfiguration.isVerbose();
     internal.mainDexKeepRules = mainDexKeepRules;
     internal.minimalMainDex = internal.debug;
     if (mainDexListOutput != null) {
diff --git a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
new file mode 100644
index 0000000..d95de6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
@@ -0,0 +1,158 @@
+// 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.compatdexbuilder;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Output;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class CompatDexBuilder {
+
+  private String input = null;
+  private String output = null;
+  private int numberOfThreads = 8;
+  private boolean noLocals = false;
+  private boolean verbose = false;
+
+  public static void main(String[] args)
+      throws IOException, InterruptedException, ExecutionException {
+    new CompatDexBuilder().run(args);
+  }
+
+  private void run(String[] args) throws IOException, InterruptedException, ExecutionException {
+    System.out.println("CompatDexBuilder " + String.join(" ", args));
+
+    List<String> flags = new ArrayList<>();
+
+    for (String arg : args) {
+      if (arg.startsWith("@")) {
+        flags.addAll(Files.readAllLines(Paths.get(arg.substring(1))));
+      } else {
+        flags.add(arg);
+      }
+    }
+
+    for (int i = 0; i < flags.size(); i++) {
+      String flag = flags.get(i);
+      if (flag.startsWith("--positions")) {
+        continue;
+      }
+      if (flag.startsWith("--num-threads=")) {
+        numberOfThreads = Integer.parseInt(flag.substring("--num-threads=".length()));
+      }
+      switch (flag) {
+        case "--input_jar":
+          input = flags.get(++i);
+          break;
+        case "--output_zip":
+          output = flags.get(++i);
+          break;
+        case "--verify-dex-file":
+        case "--no-verify-dex-file":
+        case "--show_flags":
+        case "--no-optimize":
+        case "--help":
+          // Ignore
+          break;
+        case "--verbose":
+          verbose = true;
+          break;
+        case "--nolocals":
+          noLocals = true;
+          break;
+      }
+    }
+
+    if (input == null) {
+      System.err.println("No input jar specified");
+      System.exit(1);
+    }
+
+    if (output == null) {
+      System.err.println("No output jar specified");
+      System.exit(1);
+    }
+
+    ExecutorService executor = ThreadUtils.getExecutorService(numberOfThreads);
+    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(Paths.get(output)))) {
+
+      List<ZipEntry> toDex = new ArrayList<>();
+
+      try (ZipFile zipFile = new ZipFile(input)) {
+        final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+        while (entries.hasMoreElements()) {
+          ZipEntry entry = entries.nextElement();
+          if (!entry.getName().endsWith(".class")) {
+            try (InputStream stream = zipFile.getInputStream(entry)) {
+              addEntry(entry.getName(), stream, entry.getTime(), out);
+            }
+          } else {
+            toDex.add(entry);
+          }
+        }
+
+        List<Future<D8Output>> futures = new ArrayList<>(toDex.size());
+        for (int i = 0; i < toDex.size(); i++) {
+          ZipEntry classEntry = toDex.get(i);
+          futures.add(executor.submit(() -> dexEntry(zipFile, classEntry, executor)));
+        }
+        for (int i = 0; i < futures.size(); i++) {
+          D8Output result = futures.get(i).get();
+          ZipEntry entry = toDex.get(i);
+          assert result.getDexResources().size() == 1;
+          try (InputStream dexStream = result.getDexResources().get(0).getStream()) {
+            addEntry(entry.getName() + ".dex", dexStream, entry.getTime(), out);
+          }
+        }
+      }
+    } finally {
+      executor.shutdown();
+    }
+  }
+
+  private D8Output dexEntry(ZipFile zipFile, ZipEntry classEntry, ExecutorService executor)
+      throws IOException, CompilationException {
+    try (InputStream stream = zipFile.getInputStream(classEntry)) {
+      CompatDexBuilderCommandBuilder builder = new CompatDexBuilderCommandBuilder();
+      builder
+          .addClassProgramData(ByteStreams.toByteArray(stream))
+          .setMode(noLocals ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+          .setMinApiLevel(AndroidApiLevel.H_MR2.getLevel());
+      return D8.run(builder.build(), executor);
+    }
+  }
+
+  private static void addEntry(String name, InputStream in, long time, ZipOutputStream out)
+      throws IOException {
+    ZipEntry zipEntry = new ZipEntry(name);
+    byte[] bytes = ByteStreams.toByteArray(in);
+    CRC32 crc32 = new CRC32();
+    crc32.update(bytes);
+    zipEntry.setSize(bytes.length);
+    zipEntry.setMethod(ZipEntry.STORED);
+    zipEntry.setCrc(crc32.getValue());
+    zipEntry.setTime(time);
+    out.putNextEntry(zipEntry);
+    out.write(bytes);
+    out.closeEntry();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderCommandBuilder.java b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderCommandBuilder.java
new file mode 100644
index 0000000..5773d7d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderCommandBuilder.java
@@ -0,0 +1,17 @@
+// 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.compatdexbuilder;
+
+import com.android.tools.r8.D8Command;
+
+public class CompatDexBuilderCommandBuilder extends D8Command.Builder {
+  CompatDexBuilderCommandBuilder() {
+    super(true);
+  }
+
+  @Override
+  protected boolean getEnableDesugaring() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
index 55206a4..3e8a006 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
@@ -10,4 +10,9 @@
   CompatDxCommandBuilder() {
     super(true);
   }
+
+  @Override
+  protected boolean getEnableDesugaring() {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 2ea5e56..1ad589f 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -12,6 +12,16 @@
 import java.nio.file.Paths;
 import java.util.List;
 
+/**
+ * Proguard + Dx compatibility interface for r8.
+ *
+ * This should become a mostly drop-in replacement for uses of Proguard followed by Dx.
+ *
+ * It accepts all Proguard flags supported by r8, except -outjars.
+ *
+ * The flag -outjars does not make sense as r8 (like Proguard + Dx) produces Dex output.
+ * For output use --output as for R8 proper.
+ */
 public class CompatProguard {
   public static class CompatProguardOptions {
     public final String output;
@@ -24,7 +34,7 @@
       this.proguardConfig = proguardConfig;
     }
 
-    public static CompatProguardOptions parse(String[] args) {
+    public static CompatProguardOptions parse(String[] args) throws CompilationException {
       String output = null;
       int minApi = 1;
       ImmutableList.Builder<String> builder = ImmutableList.builder();
@@ -35,8 +45,11 @@
           if (arg.charAt(0) == '-') {
             if (arg.equals("--min-api")) {
               minApi = Integer.valueOf(args[++i]);
-            } else if (arg.equals("-outjars")) {
+            } else if (arg.equals("--output")) {
               output = args[++i];
+            } else if (arg.equals("-outjars")) {
+              throw new CompilationException(
+                  "Proguard argument -outjar is not supported. Use R8 compatible --output flag");
             } else {
               builder.add(currentLine.toString());
               currentLine = new StringBuilder(arg);
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 752c66b..61e1022 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -144,9 +144,9 @@
       } else if (!options.canUseMultidex()
           && options.mainDexKeepRules.isEmpty()
           && application.mainDexList.isEmpty()) {
-        distributor = new VirtualFile.MonoDexDistributor(this);
+        distributor = new VirtualFile.MonoDexDistributor(this, options);
       } else {
-        distributor = new VirtualFile.FillFilesDistributor(this, options.minimalMainDex);
+        distributor = new VirtualFile.FillFilesDistributor(this, options);
       }
       Map<Integer, VirtualFile> newFiles = distributor.run();
 
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 eeb5023..34ec68c 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -23,6 +23,8 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -267,9 +269,11 @@
     protected Set<DexProgramClass> classes;
     protected Map<DexProgramClass, String> originalNames;
     protected final VirtualFile mainDexFile;
+    protected final InternalOptions options;
 
-    DistributorBase(ApplicationWriter writer) {
+    DistributorBase(ApplicationWriter writer, InternalOptions options) {
       super(writer);
+      this.options = options;
 
       // Create the primary dex file. The distribution will add more if needed.
       mainDexFile = new VirtualFile(0, writer.namingLens);
@@ -293,10 +297,11 @@
             mainDexFile.addClass(programClass);
             classes.remove(programClass);
           } else {
-            System.out.println(
-                "WARNING: Application does not contain `"
-                    + type.toSourceString()
-                    + "` as referenced in main-dex-list.");
+            options.diagnosticsHandler.warning(
+                new StringDiagnostic(
+                    "Application does not contain `"
+                        + type.toSourceString()
+                        + "` as referenced in main-dex-list."));
           }
           mainDexFile.commitTransaction();
         }
@@ -338,12 +343,10 @@
   }
 
   public static class FillFilesDistributor extends DistributorBase {
-    boolean minimalMainDex;
     private final FillStrategy fillStrategy;
 
-    FillFilesDistributor(ApplicationWriter writer, boolean minimalMainDex) {
-      super(writer);
-      this.minimalMainDex = minimalMainDex;
+    FillFilesDistributor(ApplicationWriter writer, InternalOptions options) {
+      super(writer, options);
       this.fillStrategy = FillStrategy.FILL_MAX;
     }
 
@@ -357,7 +360,7 @@
       }
 
       Map<Integer, VirtualFile> filesForDistribution = nameToFileMap;
-      if (minimalMainDex && !mainDexFile.isEmpty()) {
+      if (options.minimalMainDex && !mainDexFile.isEmpty()) {
         assert !nameToFileMap.get(0).isEmpty();
         // The main dex file is filtered out, so create ensure at least one file for the remaining
         // classes
@@ -378,8 +381,8 @@
   }
 
   public static class MonoDexDistributor extends DistributorBase {
-    MonoDexDistributor(ApplicationWriter writer) {
-      super(writer);
+    MonoDexDistributor(ApplicationWriter writer, InternalOptions options) {
+      super(writer, options);
     }
 
     @Override
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 b6fbdee..2324f8e 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
@@ -119,7 +119,7 @@
     return successors;
   }
 
-  public List<BasicBlock> getNormalSucessors() {
+  public List<BasicBlock> getNormalSuccessors() {
     if (!hasCatchHandlers()) {
       return successors;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 131e0d0..7d61783 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
@@ -192,11 +194,6 @@
       listIterator.remove();
     }
 
-    // If splitting the normal exit block, the new block is now the normal exit block.
-    if (code.getNormalExitBlock() == block) {
-      code.setNormalExitBlock(newBlock);
-    }
-
     // Insert the new block in the block list right after the current block.
     if (blocksIterator == null) {
       blocks.add(blocks.indexOf(block) + 1, newBlock);
@@ -371,22 +368,22 @@
     BasicBlock inlineEntry = inlinee.blocks.getFirst();
 
     BasicBlock inlineExit = null;
-    if (inlinee.getNormalExitBlock() == null) {
+    ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
+    if (normalExits.isEmpty()) {
       assert inlineeCanThrow;
       // TODO(sgjesse): Remove this restriction.
       assert !invokeBlock.hasCatchHandlers();
       blocksToRemove.addAll(
           invokePredecessor.unlink(invokeBlock, new DominatorTree(code, blocksToRemove)));
     } else {
-      // Locate inlinee return.
-      InstructionListIterator inlineeIterator = inlinee.getNormalExitBlock().listIterator();
-      inlineeIterator.nextUntil(Instruction::isReturn);
-      Return ret = inlineeIterator.previous().asReturn();
+      // Ensure and locate the single return instruction of the inlinee.
+      InstructionListIterator inlineeIterator = ensureSingleReturnInstruction(inlinee, normalExits);
 
-      // Map return value if used.
+      // Replace the invoke value with the return value if non-void.
+      assert inlineeIterator.peekNext().isReturn();
       if (invoke.outValue() != null) {
-        assert !ret.isReturnVoid();
-        invoke.outValue().replaceUsers(ret.returnValue());
+        Return returnInstruction = inlineeIterator.peekNext().asReturn();
+        invoke.outValue().replaceUsers(returnInstruction.returnValue());
       }
 
       // Split before return and unlink return.
@@ -439,4 +436,55 @@
 
     return invokeSuccessor;
   }
+
+  private InstructionListIterator ensureSingleReturnInstruction(
+      IRCode code,
+      ImmutableList<BasicBlock> normalExits) {
+    if (normalExits.size() == 1) {
+      InstructionListIterator it = normalExits.get(0).listIterator();
+      it.nextUntil(Instruction::isReturn);
+      it.previous();
+      return it;
+    }
+    BasicBlock newExitBlock = new BasicBlock();
+    newExitBlock.setNumber(code.getHighestBlockNumber() + 1);
+    Return newReturn;
+    if (normalExits.get(0).exit().asReturn().isReturnVoid()) {
+      newReturn = new Return();
+    } else {
+      boolean same = true;
+      List<Value> operands = new ArrayList<>(normalExits.size());
+      for (BasicBlock exitBlock : normalExits) {
+        Value retValue = exitBlock.exit().asReturn().returnValue();
+        operands.add(retValue);
+        same = same && retValue == operands.get(0);
+      }
+      Value value;
+      if (same) {
+        value = operands.get(0);
+      } else {
+        Phi phi =
+            new Phi(
+                code.valueNumberGenerator.next(),
+                newExitBlock,
+                operands.get(0).outType(),
+                null);
+        phi.addOperands(operands);
+        value = phi;
+      }
+      newReturn = new Return(value, value.outType());
+    }
+    newExitBlock.add(newReturn);
+    for (BasicBlock exitBlock : normalExits) {
+      InstructionListIterator it = exitBlock.listIterator(exitBlock.getInstructions().size());
+      Instruction oldExit = it.previous();
+      assert oldExit.isReturn();
+      it.replaceCurrentInstruction(new Goto());
+      exitBlock.link(newExitBlock);
+    }
+    newExitBlock.close(null);
+    code.blocks.add(newExitBlock);
+    assert code.isConsistentSSA();
+    return newExitBlock.listIterator();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
index 74c2768..26305b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
@@ -86,9 +86,6 @@
       iterator.remove();
     }
     listIterator.remove();
-    if (current == code.getNormalExitBlock()) {
-      code.setNormalExitBlock(null);
-    }
     current = null;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index c8a17dc2..90a9b29 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -13,6 +13,7 @@
 
   private BasicBlock[] sorted;
   private BasicBlock[] doms;
+  private final BasicBlock normalExitBlock = new BasicBlock();
 
   public DominatorTree(IRCode code) {
     this(code, Collections.emptyList());
@@ -20,7 +21,15 @@
 
   // TODO(sgjesse) Get rid of this constructor and blocksToIgnore.
   DominatorTree(IRCode code, List<BasicBlock> blocksToIgnore) {
-    this.sorted = code.topologicallySortedBlocks(blocksToIgnore);
+    BasicBlock[] blocks = code.topologicallySortedBlocks(blocksToIgnore);
+    // Add the internal exit block to the block list.
+    sorted = new BasicBlock[blocks.length + 1];
+    System.arraycopy(blocks, 0, sorted, 0, blocks.length);
+    sorted[blocks.length] = normalExitBlock;
+    // Link internal exit block to each actual exit block.
+    for (BasicBlock block : code.computeNormalExitBlocks()) {
+      normalExitBlock.getPredecessors().add(block);
+    }
     numberBlocks();
     build();
   }
@@ -54,7 +63,7 @@
    * @return wether {@code subject} is strictly dominated by {@code dominator}
    */
   public boolean strictlyDominatedBy(BasicBlock subject, BasicBlock dominator) {
-    if (subject.getNumber() == 0) {
+    if (subject.getNumber() == 0 || subject == normalExitBlock) {
       return false;
     }
     while (true) {
@@ -87,27 +96,26 @@
     return dominator;
   }
 
-  /**
-   * Returns an iterator over all blocks dominated by dominator, including dominator itself.
-   */
-  public Iterable<BasicBlock> dominatedBlocks(BasicBlock domintator) {
-    return () -> new Iterator<BasicBlock>() {
-      private int current = domintator.getNumber();
+  /** Returns an iterator over all blocks dominated by dominator, including dominator itself. */
+  public Iterable<BasicBlock> dominatedBlocks(BasicBlock dominator) {
+    return () ->
+        new Iterator<BasicBlock>() {
+          private int current = dominator.getNumber();
 
-      @Override
-      public boolean hasNext() {
-        return dominatedBy(sorted[current], domintator);
-      }
+          @Override
+          public boolean hasNext() {
+            return dominatedBy(sorted[current], dominator);
+          }
 
-      @Override
-      public BasicBlock next() {
-        if (!hasNext()) {
-          return null;
-        } else {
-          return sorted[current++];
-        }
-      }
-    };
+          @Override
+          public BasicBlock next() {
+            if (!hasNext()) {
+              return null;
+            } else {
+              return sorted[current++];
+            }
+          }
+        };
   }
 
   /**
@@ -143,6 +151,10 @@
     };
   }
 
+  public Iterable<BasicBlock> normalExitDominatorBlocks() {
+    return dominatorBlocks(normalExitBlock);
+  }
+
   public BasicBlock[] getSortedBlocks() {
     return sorted;
   }
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 2e8b683..d9fdaf2 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.utils.CfgPrinter;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -27,18 +28,15 @@
   public LinkedList<BasicBlock> blocks;
   public final ValueNumberGenerator valueNumberGenerator;
 
-  private BasicBlock normalExitBlock;
   private boolean numbered = false;
   private int nextInstructionNumber = 0;
 
   public IRCode(
       DexEncodedMethod method,
       LinkedList<BasicBlock> blocks,
-      BasicBlock normalExitBlock,
       ValueNumberGenerator valueNumberGenerator) {
     this.method = method;
     this.blocks = blocks;
-    this.normalExitBlock = normalExitBlock;
     this.valueNumberGenerator = valueNumberGenerator;
   }
 
@@ -110,18 +108,12 @@
       BasicBlock block = blockIterator.next();
       if (block.isMarked()) {
         blockIterator.remove();
-        if (block == normalExitBlock) {
-          normalExitBlock = null;
-        }
       }
     }
   }
 
   public void removeBlocks(List<BasicBlock> blocksToRemove) {
     blocks.removeAll(blocksToRemove);
-    if (blocksToRemove.contains(normalExitBlock)) {
-      normalExitBlock = null;
-    }
   }
 
   /**
@@ -175,7 +167,6 @@
     assert consistentPredecessorSuccessors();
     assert consistentCatchHandlers();
     assert consistentBlockInstructions();
-    assert normalExitBlock == null || normalExitBlock.exit().isReturn();
     return true;
   }
 
@@ -353,16 +344,14 @@
     return new IRCodeInstructionsIterator(this);
   }
 
-  void setNormalExitBlock(BasicBlock block) {
-    normalExitBlock = block;
-  }
-
-  public BasicBlock getNormalExitBlock() {
-    return normalExitBlock;
-  }
-
-  public void invalidateNormalExitBlock() {
-    normalExitBlock = null;
+  public ImmutableList<BasicBlock> computeNormalExitBlocks() {
+    ImmutableList.Builder<BasicBlock> builder = ImmutableList.builder();
+    for (BasicBlock block : blocks) {
+      if (block.exit().isReturn()) {
+        builder.add(block);
+      }
+    }
+    return builder.build();
   }
 
   public ListIterator<BasicBlock> listIterator() {
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 91b51f4..35cd7fa 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
@@ -80,7 +80,6 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -262,9 +261,6 @@
   private Long2ObjectMap<ConstNumber> doubleConstants = new Long2ObjectArrayMap<>();
   private Long2ObjectMap<ConstNumber> nullConstants = new Long2ObjectArrayMap<>();
 
-  private List<BasicBlock> exitBlocks = new ArrayList<>();
-  private BasicBlock normalExitBlock;
-
   private List<BasicBlock.Pair> needGotoToCatchBlocks = new ArrayList<>();
 
   final private ValueNumberGenerator valueNumberGenerator;
@@ -378,9 +374,6 @@
     // but before handle-exit (which does not maintain predecessor counts).
     assert verifyFilledPredecessors();
 
-    // If there are multiple returns create an exit block.
-    handleExitBlock();
-
     // Clear all reaching definitions to free up memory (and avoid invalid use).
     for (BasicBlock block : blocks) {
       block.clearCurrentDefinitions();
@@ -395,7 +388,7 @@
     splitCriticalEdges();
 
     // Package up the IR code.
-    IRCode ir = new IRCode(method, blocks, normalExitBlock, valueNumberGenerator);
+    IRCode ir = new IRCode(method, blocks, valueNumberGenerator);
 
     if (options.testing.invertConditionals) {
       invertConditionalsForTesting(ir);
@@ -436,7 +429,7 @@
     for (BlockInfo info : targets.values()) {
       if (info != null && info.block == block) {
         assert info.predecessorCount() == block.getPredecessors().size();
-        assert info.normalSuccessors.size() == block.getNormalSucessors().size();
+        assert info.normalSuccessors.size() == block.getNormalSuccessors().size();
         if (block.hasCatchHandlers()) {
           assert info.exceptionalSuccessors.size()
               == block.getCatchHandlers().getUniqueTargets().size();
@@ -1258,14 +1251,14 @@
 
   public void addReturn(MoveType type, int value) {
     Value in = readRegister(value, type);
+    source.buildPostlude(this);
     addInstruction(new Return(in, type));
-    exitBlocks.add(currentBlock);
     closeCurrentBlock();
   }
 
   public void addReturn() {
+    source.buildPostlude(this);
     addInstruction(new Return());
-    exitBlocks.add(currentBlock);
     closeCurrentBlock();
   }
 
@@ -1839,78 +1832,6 @@
     closeCurrentBlock();
   }
 
-  void handleExitBlock() {
-    if (exitBlocks.size() > 0) {
-      // Create and populate the exit block if needed (eg, synchronized support for jar).
-      setCurrentBlock(new BasicBlock());
-      source.buildPostlude(this);
-      // If the new exit block is empty and we only have one exit, abort building a new exit block.
-      if (currentBlock.getInstructions().isEmpty() && exitBlocks.size() == 1) {
-        normalExitBlock = exitBlocks.get(0);
-        setCurrentBlock(null);
-        return;
-      }
-      // Commit to creating the new exit block.
-      normalExitBlock = currentBlock;
-      normalExitBlock.setNumber(nextBlockNumber++);
-      blocks.add(normalExitBlock);
-      // Add the return instruction possibly creating a phi of return values.
-      Return origReturn = exitBlocks.get(0).exit().asReturn();
-      Phi phi = null;
-      if (origReturn.isReturnVoid()) {
-        normalExitBlock.add(new Return());
-      } else {
-        Value returnValue = origReturn.returnValue();
-        MoveType returnType = origReturn.getReturnType();
-        assert origReturn.getLocalInfo() == null;
-        phi = new Phi(
-            valueNumberGenerator.next(), normalExitBlock, returnValue.outType(), null);
-        normalExitBlock.add(new Return(phi, returnType));
-        assert returnType == MoveType.fromDexType(method.method.proto.returnType);
-      }
-      closeCurrentBlock();
-
-      // Collect the debug values which are live on all returns.
-      Set<Value> debugValuesForReturn = Sets.newIdentityHashSet();
-      for (Value value : exitBlocks.get(0).exit().getDebugValues()) {
-        boolean include = true;
-        for (int i = 1; i < exitBlocks.size() && include; i++) {
-          include = exitBlocks.get(i).exit().getDebugValues().contains(value);
-        }
-        if (include) {
-          debugValuesForReturn.add(value);
-        }
-      }
-
-      // Move all these debug values to the new return.
-      for (Value value : debugValuesForReturn) {
-        for (BasicBlock block : exitBlocks) {
-          block.exit().moveDebugValue(value, normalExitBlock.exit());
-        }
-      }
-
-      // Replace each return instruction with a goto to the new exit block.
-      List<Value> operands = new ArrayList<>();
-      for (BasicBlock block : exitBlocks) {
-        List<Instruction> instructions = block.getInstructions();
-        Return ret = block.exit().asReturn();
-        if (!ret.isReturnVoid()) {
-          operands.add(ret.returnValue());
-          ret.returnValue().removeUser(ret);
-        }
-        Goto gotoExit = new Goto();
-        gotoExit.setBlock(block);
-        ret.moveDebugValues(gotoExit);
-        instructions.set(instructions.size() - 1, gotoExit);
-        block.link(normalExitBlock);
-        gotoExit.setTarget(normalExitBlock);
-      }
-      if (phi != null) {
-        phi.addOperands(operands);
-      }
-    }
-  }
-
   private void handleFallthroughToCatchBlock() {
     // When a catch handler for a block goes to the same block as the fallthrough for that
     // block the graph only has one edge there. In these cases we add an additional block so the
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index cfc1495..184087a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -87,7 +87,6 @@
       GraphLense graphLense,
       InternalOptions options,
       CfgPrinter printer,
-      boolean enableDesugaring,
       boolean enableWholeProgramOptimizations) {
     assert application != null;
     assert appInfo != null;
@@ -99,9 +98,9 @@
     this.options = options;
     this.printer = printer;
     this.codeRewriter = new CodeRewriter(appInfo, libraryMethodsReturningReceiver());
-    this.lambdaRewriter = enableDesugaring ? new LambdaRewriter(this) : null;
+    this.lambdaRewriter = options.enableDesugaring ? new LambdaRewriter(this) : null;
     this.interfaceMethodRewriter =
-        (enableDesugaring && enableInterfaceMethodDesugaring())
+        (options.enableDesugaring && enableInterfaceMethodDesugaring())
             ? new InterfaceMethodRewriter(this, options) : null;
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasSubtyping();
@@ -130,20 +129,7 @@
       DexApplication application,
       AppInfo appInfo,
       InternalOptions options) {
-    this(null, application, appInfo, null, options, null, true, false);
-  }
-
-  /**
-   * Create an IR converter for processing methods with full program optimization disabled.
-   *
-   * The argument <code>enableDesugaring</code> if desugaring is enabled.
-   */
-  public IRConverter(
-      DexApplication application,
-      AppInfo appInfo,
-      InternalOptions options,
-      boolean enableDesugaring) {
-    this(null, application, appInfo, null, options, null, enableDesugaring, false);
+    this(null, application, appInfo, null, options, null, false);
   }
 
   /**
@@ -155,7 +141,7 @@
       AppInfo appInfo,
       InternalOptions options,
       CfgPrinter printer) {
-    this(timing, application, appInfo, null, options, printer, true, false);
+    this(timing, application, appInfo, null, options, printer, false);
   }
 
   /**
@@ -168,7 +154,7 @@
       InternalOptions options,
       CfgPrinter printer,
       GraphLense graphLense) {
-    this(timing, application, appInfo, graphLense, options, printer, true, true);
+    this(timing, application, appInfo, graphLense, options, printer, true);
   }
 
   private boolean enableInterfaceMethodDesugaring() {
@@ -566,7 +552,7 @@
     DeadCodeRemover.removeDeadCode(code, codeRewriter, options);
     assert code.isConsistentSSA();
 
-    if (enableTryWithResourcesDesugaring()) {
+    if (options.enableDesugaring && enableTryWithResourcesDesugaring()) {
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
     }
 
@@ -595,8 +581,6 @@
 
     printMethod(code, "Optimized IR (SSA)");
 
-    codeRewriter.inlineReturnBlock(code);
-
     // Perform register allocation.
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
     method.setCode(code, registerAllocator, appInfo.dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 810039f..8ea7b32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -87,7 +87,6 @@
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -206,7 +205,7 @@
 
   private static int getThrowsColorForSuccessors(BasicBlock block) {
     int color = CANNOT_THROW;
-    for (BasicBlock successor : block.getNormalSucessors()) {
+    for (BasicBlock successor : block.getNormalSuccessors()) {
       if (successor.hasColor(CAN_THROW)) {
         return CAN_THROW;
       }
@@ -760,24 +759,38 @@
 
   public void identifyReturnsArgument(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    if (code.getNormalExitBlock() != null) {
-      Return ret = code.getNormalExitBlock().exit().asReturn();
-      if (!ret.isReturnVoid()) {
-        Value returnValue = ret.returnValue();
-        if (returnValue.isArgument()) {
-          // Find the argument number.
-          int index = code.collectArguments().indexOf(returnValue);
-          assert index != -1;
-          feedback.methodReturnsArgument(method, index);
-        }
-        if (returnValue.isConstant() && returnValue.definition.isConstNumber()) {
-          long value = returnValue.definition.asConstNumber().getRawValue();
-          feedback.methodReturnsConstant(method, value);
-        }
-        if (returnValue.isNeverNull()) {
-          feedback.methodNeverReturnsNull(method);
-        }
+    List<BasicBlock> normalExits = code.computeNormalExitBlocks();
+    if (normalExits.isEmpty()) {
+      return;
+    }
+    Return firstExit = normalExits.get(0).exit().asReturn();
+    if (firstExit.isReturnVoid()) {
+      return;
+    }
+    Value returnValue = firstExit.returnValue();
+    boolean isNeverNull = returnValue.isNeverNull();
+    for (int i = 1; i < normalExits.size(); i++) {
+      Return exit = normalExits.get(i).exit().asReturn();
+      Value value = exit.returnValue();
+      if (value != returnValue) {
+        returnValue = null;
       }
+      isNeverNull = isNeverNull && value.isNeverNull();
+    }
+    if (returnValue != null) {
+      if (returnValue.isArgument()) {
+        // Find the argument number.
+        int index = code.collectArguments().indexOf(returnValue);
+        assert index != -1;
+        feedback.methodReturnsArgument(method, index);
+      }
+      if (returnValue.isConstant() && returnValue.definition.isConstNumber()) {
+        long value = returnValue.definition.asConstNumber().getRawValue();
+        feedback.methodReturnsConstant(method, value);
+      }
+    }
+    if (isNeverNull) {
+      feedback.methodNeverReturnsNull(method);
     }
   }
 
@@ -933,14 +946,13 @@
     // section 5.5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5), this
     // does not matter (except maybe for removal of const-string instructions, but that is
     // acceptable).
-    DominatorTree dominatorTree = new DominatorTree(code);
-    BasicBlock exit = code.getNormalExitBlock();
-    if (exit == null) {
+    if (code.computeNormalExitBlocks().isEmpty()) {
       return;
     }
+    DominatorTree dominatorTree = new DominatorTree(code);
     Set<StaticPut> puts = Sets.newIdentityHashSet();
     Map<DexField, StaticPut> dominatingPuts = Maps.newIdentityHashMap();
-    for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+    for (BasicBlock block : dominatorTree.normalExitDominatorBlocks()) {
       InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
       while (iterator.hasPrevious()) {
         Instruction current = iterator.previous();
@@ -1036,7 +1048,7 @@
 
       // Remove the static put instructions now replaced by static filed initial values.
       List<Instruction> toRemove = new ArrayList<>();
-      for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+      for (BasicBlock block : dominatorTree.normalExitDominatorBlocks()) {
         InstructionListIterator iterator = block.listIterator();
         while (iterator.hasNext()) {
           Instruction current = iterator.next();
@@ -1059,7 +1071,7 @@
 
       // Remove the instructions collected for removal.
       if (toRemove.size() > 0) {
-        for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+        for (BasicBlock block : dominatorTree.normalExitDominatorBlocks()) {
           InstructionListIterator iterator = block.listIterator();
           while (iterator.hasNext()) {
             if (toRemove.contains(iterator.next())) {
@@ -1514,87 +1526,6 @@
     }
   }
 
-  /**
-   * Inline the return block at its targets.
-   *
-   * The inlining of return mostly undoes the merge performed at IR build time. This helps avoid
-   * unneeded moves as values are forced into the same register at all returns, which there can be
-   * a lot of when compiling in debug mode. Measurements show that iterating the inlining of returns
-   * does not pay off as it can lead to code size increase, eg, when a sequence of ifs all jump to
-   * a common return.
-   */
-  public void inlineReturnBlock(IRCode code) {
-    BasicBlock block = code.getNormalExitBlock();
-    code.invalidateNormalExitBlock();
-    if (block == null
-        || block.getPredecessors().size() <= 1
-        || block.getInstructions().size() > 1) {
-      return;
-    }
-    int predIndex = 0;
-    for (BasicBlock pred : block.getPredecessors()) {
-      ListIterator<Instruction> iterator = pred.listIterator(pred.exit());
-      iterator.previous();
-      for (Instruction origInstruction : block.getInstructions()) {
-        assert origInstruction.isReturn();
-        // Create an instruction copy replacing phi values of this block by their operands.
-        Instruction instruction;
-        Return ret = origInstruction.asReturn();
-        if (ret.isReturnVoid()) {
-          instruction = new Return();
-        } else {
-          Value origValue = ret.returnValue();
-          Value copyValue = origValue.isPhi() && block.getPhis().contains(origValue)
-              ? origValue.asPhi().getOperand(predIndex)
-              : origValue;
-          instruction = new Return(copyValue, ret.getReturnType());
-        }
-        // Copy over each debug value replacing phi values of this block by their operands.
-        for (Value value : origInstruction.getDebugValues()) {
-          assert value.hasLocalInfo();
-          if (value.isPhi() && block.getPhis().contains(value)) {
-            Phi phi = value.asPhi();
-            Value operand = phi.getOperand(predIndex);
-            if (phi.getLocalInfo() == operand.getLocalInfo()) {
-              instruction.addDebugValue(operand);
-            } else {
-              // If the phi and its operand are different locals insert a local write.
-              Value localValue = code.createValue(phi.outType(), phi.getLocalInfo());
-              DebugLocalWrite write = new DebugLocalWrite(localValue, operand);
-              write.setBlock(pred);
-              iterator.add(write);
-              instruction.addDebugValue(localValue);
-            }
-          } else {
-            instruction.addDebugValue(value);
-          }
-        }
-        instruction.setBlock(pred);
-        iterator.add(instruction);
-      }
-      iterator.previous();
-      Instruction ret = iterator.next();
-      Instruction jump = iterator.next();
-      assert !iterator.hasNext();
-      jump.moveDebugValues(ret);
-      iterator.remove();
-      assert pred.exit().isReturn();
-      pred.removeSuccessor(block);
-      predIndex++;
-    }
-    // Clean out all users and remove the inlined block.
-    while (!block.getPredecessors().isEmpty()) {
-      block.removePredecessor(block.getPredecessors().get(0));
-    }
-    for (Instruction instruction : block.getInstructions()) {
-      for (Value value : instruction.inValues()) {
-        value.removeUser(instruction);
-      }
-      instruction.clearDebugValues();
-    }
-    code.removeBlocks(Collections.singletonList(block));
-  }
-
   private static class ExpressionEquivalence extends Equivalence<Instruction> {
 
     @Override
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 5d1cbe5..9699ec1 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
@@ -20,7 +20,6 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -361,7 +360,7 @@
                 .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
             if (inlinee != null) {
               // TODO(64432527): Get rid of this additional check by improved inlining.
-              if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
+              if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
                 continue;
               }
               // If this code did not go through the full pipeline, apply inlining to make sure
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index 35cadf2..e6916b8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -46,6 +47,14 @@
    */
   private static void shareIdenticalBlockSuffix(IRCode code, RegisterAllocator allocator) {
     Collection<BasicBlock> blocks = code.blocks;
+    BasicBlock normalExit = null;
+    ImmutableList<BasicBlock> normalExits = code.computeNormalExitBlocks();
+    if (normalExits.size() > 1) {
+      normalExit = new BasicBlock();
+      normalExit.getPredecessors().addAll(normalExits);
+      blocks = new ArrayList<>(code.blocks);
+      blocks.add(normalExit);
+    }
     do {
       int startNumberOfNewBlock = code.getHighestBlockNumber() + 1;
       Map<BasicBlock, BasicBlock> newBlocks = new IdentityHashMap<>();
@@ -60,14 +69,22 @@
           // and allow both a goto edge and exception edges when the target does not start with a
           // MoveException instruction. However, that would require us to require rewriting of
           // catch handlers as well.
-          if (pred.exit().isGoto() &&
-              pred.getSuccessors().size() == 1 &&
-              pred.getInstructions().size() > 1) {
+          if (pred.exit().isGoto()
+              && pred.getSuccessors().size() == 1
+              && pred.getInstructions().size() > 1) {
             List<Instruction> instructions = pred.getInstructions();
             Instruction lastInstruction = instructions.get(instructions.size() - 2);
             List<BasicBlock> value = lastInstructionToBlocks.computeIfAbsent(
                 equivalence.wrap(lastInstruction), (k) -> new ArrayList<>());
             value.add(pred);
+          } else if (pred.exit().isReturn()
+              && pred.getSuccessors().isEmpty()
+              && pred.getInstructions().size() > 2) {
+            Instruction lastInstruction = pred.exit();
+            List<BasicBlock> value =
+                lastInstructionToBlocks.computeIfAbsent(
+                    equivalence.wrap(lastInstruction), (k) -> new ArrayList<>());
+            value.add(pred);
           }
         }
         // For each group of predecessors of size 2 or more, find the largest common suffix and
@@ -80,16 +97,20 @@
           int commonSuffixSize = firstPred.getInstructions().size();
           for (int i = 1; i < predsWithSameLastInstruction.size(); i++) {
             BasicBlock pred = predsWithSameLastInstruction.get(i);
-            assert pred.exit().isGoto();
-            commonSuffixSize = Math.min(
-                commonSuffixSize, sharedSuffixSizeExcludingExit(firstPred, pred, allocator));
+            assert pred.exit().isGoto() || pred.exit().isReturn();
+            commonSuffixSize =
+                Math.min(commonSuffixSize, sharedSuffixSize(firstPred, pred, allocator));
           }
-          if (commonSuffixSize == 0) {
+          if (commonSuffixSize <= 1) {
             continue;
           }
           int blockNumber = startNumberOfNewBlock + newBlocks.size();
-          BasicBlock newBlock = createAndInsertBlockForSuffix(
-              blockNumber, commonSuffixSize, predsWithSameLastInstruction, block);
+          BasicBlock newBlock =
+              createAndInsertBlockForSuffix(
+                  blockNumber,
+                  commonSuffixSize,
+                  predsWithSameLastInstruction,
+                  block == normalExit ? null : block);
           newBlocks.put(predsWithSameLastInstruction.get(0), newBlock);
         }
       }
@@ -109,19 +130,26 @@
       int blockNumber, int suffixSize, List<BasicBlock> preds, BasicBlock successorBlock) {
     BasicBlock newBlock = BasicBlock.createGotoBlock(blockNumber);
     BasicBlock first = preds.get(0);
-    InstructionListIterator from = first.listIterator(first.getInstructions().size() - 1);
-    Int2ReferenceMap<DebugLocalInfo> newBlockEntryLocals = successorBlock.getLocalsAtEntry() == null
-        ? null
-        : new Int2ReferenceOpenHashMap<>(successorBlock.getLocalsAtEntry());
+    assert (successorBlock != null && first.exit().isGoto())
+        || (successorBlock == null && first.exit().isReturn());
+    int offsetFromEnd = successorBlock == null ? 0 : 1;
+    if (successorBlock == null) {
+      newBlock.getInstructions().removeLast();
+    }
+    InstructionListIterator from =
+        first.listIterator(first.getInstructions().size() - offsetFromEnd);
+    Int2ReferenceMap<DebugLocalInfo> newBlockEntryLocals =
+        (successorBlock == null || successorBlock.getLocalsAtEntry() == null)
+            ? new Int2ReferenceOpenHashMap<>()
+            : new Int2ReferenceOpenHashMap<>(successorBlock.getLocalsAtEntry());
     boolean movedThrowingInstruction = false;
-    for (int i = 0; i < suffixSize; i++) {
+    for (int i = offsetFromEnd; i < suffixSize; i++) {
       Instruction instruction = from.previous();
       movedThrowingInstruction = movedThrowingInstruction || instruction.instructionTypeCanThrow();
       newBlock.getInstructions().addFirst(instruction);
       instruction.setBlock(newBlock);
       if (instruction.isDebugLocalsChange()) {
         // Replay the debug local changes backwards to compute the entry state.
-        assert newBlockEntryLocals != null;
         DebugLocalsChange change = instruction.asDebugLocalsChange();
         for (int starting : change.getStarting().keySet()) {
           newBlockEntryLocals.remove(starting);
@@ -136,27 +164,35 @@
     }
     for (BasicBlock pred : preds) {
       LinkedList<Instruction> instructions = pred.getInstructions();
-      Instruction exit = instructions.removeLast();
       for (int i = 0; i < suffixSize; i++) {
         instructions.removeLast();
       }
-      instructions.add(exit);
+      Goto jump = new Goto();
+      jump.setBlock(pred);
+      instructions.add(jump);
       newBlock.getPredecessors().add(pred);
-      pred.replaceSuccessor(successorBlock, newBlock);
-      successorBlock.getPredecessors().remove(pred);
+      if (successorBlock != null) {
+        pred.replaceSuccessor(successorBlock, newBlock);
+        successorBlock.getPredecessors().remove(pred);
+      } else {
+        pred.getSuccessors().add(newBlock);
+      }
       if (movedThrowingInstruction) {
         pred.clearCatchHandlers();
       }
     }
     newBlock.setLocalsAtEntry(newBlockEntryLocals);
-    newBlock.link(successorBlock);
+    if (successorBlock != null) {
+      newBlock.link(successorBlock);
+    }
     return newBlock;
   }
 
-  private static int sharedSuffixSizeExcludingExit(
+  private static int sharedSuffixSize(
       BasicBlock block0, BasicBlock block1, RegisterAllocator allocator) {
-    InstructionListIterator it0 = block0.listIterator(block0.getInstructions().size() - 1);
-    InstructionListIterator it1 = block1.listIterator(block1.getInstructions().size() - 1);
+    assert block0.exit().isGoto() || block0.exit().isReturn();
+    InstructionListIterator it0 = block0.listIterator(block0.getInstructions().size());
+    InstructionListIterator it1 = block1.listIterator(block1.getInstructions().size());
     int suffixSize = 0;
     while (it0.hasPrevious() && it1.hasPrevious()) {
       Instruction i0 = it0.previous();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 2560fd6..239f9bc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -45,6 +45,7 @@
     private Path classObfuscationDictionary;
     private Path packageObfuscationDictionary;
     private boolean useUniqueClassMemberNames;
+    private boolean keepParameterNames;
 
     private Builder(DexItemFactory dexItemFactory) {
       this.dexItemFactory = dexItemFactory;
@@ -164,6 +165,14 @@
       return useUniqueClassMemberNames;
     }
 
+    public void setKeepParameterNames(boolean keepParameterNames) {
+      this.keepParameterNames = keepParameterNames;
+    }
+
+    boolean isKeepParameterNames() {
+      return keepParameterNames;
+    }
+
     public ProguardConfiguration build() {
       return new ProguardConfiguration(
           dexItemFactory,
@@ -191,7 +200,8 @@
           DictionaryReader.readAllNames(obfuscationDictionary),
           DictionaryReader.readAllNames(classObfuscationDictionary),
           DictionaryReader.readAllNames(packageObfuscationDictionary),
-          useUniqueClassMemberNames);
+          useUniqueClassMemberNames,
+          keepParameterNames);
     }
   }
 
@@ -221,6 +231,7 @@
   private final ImmutableList<String> classObfuscationDictionary;
   private final ImmutableList<String> packageObfuscationDictionary;
   private boolean useUniqueClassMemberNames;
+  private boolean keepParameterNames;
 
   private ProguardConfiguration(
       DexItemFactory factory,
@@ -248,7 +259,8 @@
       ImmutableList<String> obfuscationDictionary,
       ImmutableList<String> classObfuscationDictionary,
       ImmutableList<String> packageObfuscationDictionary,
-      boolean useUniqueClassMemberNames) {
+      boolean useUniqueClassMemberNames,
+      boolean keepParameterNames) {
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
     this.libraryjars = ImmutableList.copyOf(libraryjars);
@@ -275,6 +287,7 @@
     this.classObfuscationDictionary = classObfuscationDictionary;
     this.packageObfuscationDictionary = packageObfuscationDictionary;
     this.useUniqueClassMemberNames = useUniqueClassMemberNames;
+    this.keepParameterNames = keepParameterNames;
   }
 
   /**
@@ -388,6 +401,10 @@
     return useUniqueClassMemberNames;
   }
 
+  public boolean isKeepParameterNames() {
+    return keepParameterNames;
+  }
+
   public static ProguardConfiguration defaultConfiguration(DexItemFactory dexItemFactory) {
     return new DefaultProguardConfiguration(dexItemFactory);
   }
@@ -420,7 +437,8 @@
           ImmutableList.of()    /* obfuscationDictionary */,
           ImmutableList.of()    /* classObfuscationDictionary */,
           ImmutableList.of()    /* packageObfuscationDictionary */,
-          false                 /* useUniqueClassMemberNames*/);
+          false                 /* useUniqueClassMemberNames*/,
+          false                 /* keepParameterNames */);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 4784387..08a7eca 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -85,6 +85,13 @@
       throw new ProguardRuleParserException("-useuniqueulassmembernames is not supported");
     }
 
+    if (configurationBuilder.isKeepParameterNames()
+        && configurationBuilder.isObfuscating()) {
+      // The flag -keepparameternames has only effect when minifying, so ignore it if we
+      // are not.
+      throw new ProguardRuleParserException("-keepparameternames is not supported");
+    }
+
     return configurationBuilder.build();
   }
 
@@ -156,6 +163,8 @@
       } else if (acceptString("keeppackagenames")) {
         ProguardKeepPackageNamesRule rule = parseKeepPackageNamesRule();
         configurationBuilder.addRule(rule);
+      } else if (acceptString("keepparameternames")) {
+        configurationBuilder.setKeepParameterNames(true);
       } else if (acceptString("checkdiscard")) {
         ProguardCheckDiscardRule rule = parseCheckDiscardRule();
         configurationBuilder.addRule(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
index 09bbad6..e7baec9 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
@@ -613,7 +613,7 @@
         if (needsCleanup) {
           DominatorTree updatedTree = new DominatorTree(code);
           BasicBlock fallThrough = switchInstr.fallthroughBlock();
-          List<BasicBlock> successors = ImmutableList.copyOf(current.getNormalSucessors());
+          List<BasicBlock> successors = ImmutableList.copyOf(current.getNormalSuccessors());
           for (BasicBlock successor : successors) {
             if (successor != fallThrough && !liveBlocks.contains(successor)) {
               deadBlocks.addAll(current.unlink(successor, updatedTree));
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 5c4909a..c8308a9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -86,6 +86,8 @@
   public boolean intermediate = false;
   public List<String> logArgumentsFilter = ImmutableList.of();
 
+  // Flag to turn on/off desugaring in D8/R8.
+  public boolean enableDesugaring = true;
   // Defines interface method rewriter behavior.
   public OffOrAuto interfaceMethodDesugaring = OffOrAuto.Auto;
   // Defines try-with-resources rewriter behavior.
diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
index 0606be2..9c690ff 100644
--- a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
@@ -13,7 +13,7 @@
   }
 
   @Override
-  public String toString() {
+  public String getDiagnosticMessage() {
     return message;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/DeviceRunner.java b/src/test/java/com/android/tools/r8/DeviceRunner.java
index fc91032..c68c8f2 100644
--- a/src/test/java/com/android/tools/r8/DeviceRunner.java
+++ b/src/test/java/com/android/tools/r8/DeviceRunner.java
@@ -3,9 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.base.Joiner;
-
 import com.android.ddmlib.AdbCommandRejectedException;
 import com.android.ddmlib.AndroidDebugBridge;
 import com.android.ddmlib.FileListingService;
@@ -14,7 +11,8 @@
 import com.android.ddmlib.ShellCommandUnresponsiveException;
 import com.android.ddmlib.SyncException;
 import com.android.ddmlib.TimeoutException;
-
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.base.Joiner;
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -320,6 +318,7 @@
     }
   }
 
+  @SuppressWarnings("deprecation")
   private void executeShellCommand(
       String command,
       IDevice device,
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 5fe7c16..140e18f 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -48,6 +48,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiFunction;
 import java.util.stream.Collectors;
+import org.junit.AssumptionViolatedException;
 import org.junit.ComparisonFailure;
 import org.junit.Rule;
 import org.junit.rules.ExpectedException;
@@ -973,7 +974,8 @@
       this.nativeLibrary = nativeLibrary;
       this.directory = directory;
       this.skipArt = skipArt;
-      this.skipTest = skipTest;
+      this.skipTest =
+          skipTest || (ToolHelper.isWindows() && ToolHelper.getDexVm().getKind() == Kind.HOST);
       this.failsWithX8 = failsWithX8;
       this.failsWithArt = failsWithArt;
       this.failsWithArtOutput = failsWithArtOutput;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index b97bd7b..79be8a8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -475,7 +475,7 @@
       return ImmutableSet.of(artVersionEnum);
     } else {
       if (isWindows()) {
-        throw new RuntimeException("You need to specify a runtime with 'dex_vm' property");
+        return Collections.emptySet();
       } else if (isLinux()) {
         return ART_BINARY_VERSIONS.keySet();
       } else {
@@ -499,11 +499,6 @@
   public static DexVm getDexVm() {
     String artVersion = System.getProperty("dex_vm");
     if (artVersion == null) {
-      if (isWindows()) {
-        throw new RuntimeException(
-            "Default Art version is not supported on Windows. Please specify a non-host runtime "
-                + "with property 'dex_vm'");
-      }
       return DexVm.ART_DEFAULT;
     } else {
       DexVm artVersionEnum = DexVm.fromShortName(artVersion);
@@ -543,11 +538,15 @@
   }
 
   public static boolean artSupported() {
-    if (!isLinux() && !isMac()  && !isWindows()) {
+    if (!isLinux() && !isMac() && !isWindows()) {
       System.err.println("Testing on your platform is not fully supported. " +
           "Art does not work on on your platform.");
       return false;
     }
+    if (isWindows() && getDexVm().getKind() == Kind.HOST) {
+      System.err.println("Testing on host is not supported on Windows.");
+      return false;
+    }
     return true;
   }
 
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 a9b945d..ec32bd3 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -94,8 +94,6 @@
   // Set to true to enable verbose logs
   private static final boolean DEBUG_TESTS = false;
 
-  private static final List<DexVm> UNSUPPORTED_ART_VERSIONS = ImmutableList.of();
-
   private static final Path JDWP_JAR = ToolHelper
       .getJdwpTestsJarPath(ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
   private static final Path DEBUGGEE_JAR = Paths
@@ -292,9 +290,6 @@
     Assume.assumeTrue("Skipping test " + testName.getMethodName()
             + " because debug tests are not yet supported on Windows",
         !ToolHelper.isWindows());
-    Assume.assumeFalse(
-        "Skipping failing test " + testName.getMethodName() + " for runtime " + ToolHelper
-            .getDexVm(), UNSUPPORTED_ART_VERSIONS.contains(ToolHelper.getDexVm()));
 
     String[] paths = new String[extraPaths.size() + 2];
     int indexPath = 0;
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 6fb5db1..8258324 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -736,55 +736,58 @@
 
   private void runInlineAlwaysThrowsMultiple(boolean twoGuards, int expectedA, int expectedB)
       throws Exception {
-    // Run code without inlining.
-    TestApplication test = codeForInlineAlwaysThrows(twoGuards);
-    String result = test.run();
-    assertEquals(Integer.toString(expectedA), result);
-
-    InstructionListIterator iterator;
-
-    // Run code inlining all invokes with a.
-    test = codeForInlineAlwaysThrowsMultiple(twoGuards);
-    ListIterator<BasicBlock> blocksIterator = test.code.blocks.listIterator();
-    Iterator<IRCode> inlinee = test.additionalCode.listIterator();  // IR code for a's.
-    List<BasicBlock> blocksToRemove = new ArrayList<>();
-    while (blocksIterator.hasNext()) {
-      BasicBlock block = blocksIterator.next();
-      if (blocksToRemove.contains(block)) {
-        continue;
-      }
-      iterator = block.listIterator();
-      Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
-      if (invoke != null) {
-        iterator.previous();
-        iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
-        assert !blocksToRemove.isEmpty();
-      }
+    {
+      // Run code without inlining.
+      TestApplication test = codeForInlineAlwaysThrows(twoGuards);
+      String result = test.run();
+      assertEquals(Integer.toString(expectedA), result);
     }
-    test.code.removeBlocks(blocksToRemove);
-    result = test.run();
-    assertEquals(Integer.toString(expectedA), result);
-
-    // Run code inlining all invokes with b.
-    test = codeForInlineAlwaysThrowsMultiple(twoGuards);
-    blocksIterator = test.code.blocks.listIterator();
-    inlinee = test.additionalCode.listIterator(3);  // IR code for b's.
-    while (blocksIterator.hasNext()) {
-      BasicBlock block = blocksIterator.next();
-      if (blocksToRemove.contains(block)) {
-        continue;
+    {
+      // Run code inlining all invokes with a.
+      TestApplication test = codeForInlineAlwaysThrowsMultiple(twoGuards);
+      ListIterator<BasicBlock> blocksIterator = test.code.blocks.listIterator();
+      Iterator<IRCode> inlinee = test.additionalCode.listIterator(); // IR code for a's.
+      List<BasicBlock> blocksToRemove = new ArrayList<>();
+      InstructionListIterator iterator;
+      while (blocksIterator.hasNext()) {
+        BasicBlock block = blocksIterator.next();
+        if (blocksToRemove.contains(block)) {
+          continue;
+        }
+        iterator = block.listIterator();
+        Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
+        if (invoke != null) {
+          iterator.previous();
+          iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
+        }
       }
-      iterator = block.listIterator();
-      Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
-      if (invoke != null) {
-        iterator.previous();
-        iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
-        assert !blocksToRemove.isEmpty();
-      }
+      test.code.removeBlocks(blocksToRemove);
+      String result = test.run();
+      assertEquals(Integer.toString(expectedA), result);
     }
-    test.code.removeBlocks(blocksToRemove);
-    result = test.run();
-    assertEquals(Integer.toString(expectedB), result);
+    {
+      // Run code inlining all invokes with b.
+      TestApplication test = codeForInlineAlwaysThrowsMultiple(twoGuards);
+      ListIterator<BasicBlock> blocksIterator = test.code.blocks.listIterator();
+      Iterator<IRCode> inlinee = test.additionalCode.listIterator(3); // IR code for b's.
+      List<BasicBlock> blocksToRemove = new ArrayList<>();
+      InstructionListIterator iterator;
+      while (blocksIterator.hasNext()) {
+        BasicBlock block = blocksIterator.next();
+        if (blocksToRemove.contains(block)) {
+          continue;
+        }
+        iterator = block.listIterator();
+        Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
+        if (invoke != null) {
+          iterator.previous();
+          iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
+        }
+      }
+      test.code.removeBlocks(blocksToRemove);
+      String result = test.run();
+      assertEquals(Integer.toString(expectedB), result);
+    }
   }
 
   @Test
@@ -890,55 +893,58 @@
 
   private void runInlineAlwaysThrowsMultipleWithControlFlow(
       int a, boolean twoGuards, int expectedA, int expectedB) throws Exception {
-    // Run code without inlining.
-    TestApplication test = codeForInlineAlwaysThrows(twoGuards);
-    String result = test.run();
-    assertEquals(Integer.toString(expectedA), result);
-
-    InstructionListIterator iterator;
-
-    // Run code inlining all invokes with a.
-    test = codeForInlineAlwaysThrowsMultipleWithControlFlow(a, twoGuards);
-    ListIterator<BasicBlock> blocksIterator = test.code.blocks.listIterator();
-    Iterator<IRCode> inlinee = test.additionalCode.listIterator();  // IR code for a's.
-    List<BasicBlock> blocksToRemove = new ArrayList<>();
-    while (blocksIterator.hasNext()) {
-      BasicBlock block = blocksIterator.next();
-      if (blocksToRemove.contains(block)) {
-        continue;
-      }
-      iterator = block.listIterator();
-      Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
-      if (invoke != null) {
-        iterator.previous();
-        iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
-        assert !blocksToRemove.isEmpty();
-      }
+    {
+      // Run code without inlining.
+      TestApplication test = codeForInlineAlwaysThrows(twoGuards);
+      String result = test.run();
+      assertEquals(Integer.toString(expectedA), result);
     }
-    test.code.removeBlocks(blocksToRemove);
-    result = test.run();
-    assertEquals(Integer.toString(expectedA), result);
-
-    // Run code inlining all invokes with b.
-    test = codeForInlineAlwaysThrowsMultipleWithControlFlow(a, twoGuards);
-    blocksIterator = test.code.blocks.listIterator();
-    inlinee = test.additionalCode.listIterator(3);  // IR code for b's.
-    while (blocksIterator.hasNext()) {
-      BasicBlock block = blocksIterator.next();
-      if (blocksToRemove.contains(block)) {
-        continue;
+    {
+      // Run code inlining all invokes with a.
+      TestApplication test = codeForInlineAlwaysThrowsMultipleWithControlFlow(a, twoGuards);
+      ListIterator<BasicBlock> blocksIterator = test.code.blocks.listIterator();
+      Iterator<IRCode> inlinee = test.additionalCode.listIterator(); // IR code for a's.
+      List<BasicBlock> blocksToRemove = new ArrayList<>();
+      InstructionListIterator iterator;
+      while (blocksIterator.hasNext()) {
+        BasicBlock block = blocksIterator.next();
+        if (blocksToRemove.contains(block)) {
+          continue;
+        }
+        iterator = block.listIterator();
+        Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
+        if (invoke != null) {
+          iterator.previous();
+          iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
+        }
       }
-      iterator = block.listIterator();
-      Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
-      if (invoke != null) {
-        iterator.previous();
-        iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
-        assert !blocksToRemove.isEmpty();
-      }
+      test.code.removeBlocks(blocksToRemove);
+      String result = test.run();
+      assertEquals(Integer.toString(expectedA), result);
     }
-    test.code.removeBlocks(blocksToRemove);
-    result = test.run();
-    assertEquals(Integer.toString(expectedB), result);
+    {
+      // Run code inlining all invokes with b.
+      TestApplication test = codeForInlineAlwaysThrowsMultipleWithControlFlow(a, twoGuards);
+      ListIterator<BasicBlock> blocksIterator = test.code.blocks.listIterator();
+      Iterator<IRCode> inlinee = test.additionalCode.listIterator(3); // IR code for b's.
+      List<BasicBlock> blocksToRemove = new ArrayList<>();
+      InstructionListIterator iterator;
+      while (blocksIterator.hasNext()) {
+        BasicBlock block = blocksIterator.next();
+        if (blocksToRemove.contains(block)) {
+          continue;
+        }
+        iterator = block.listIterator();
+        Instruction invoke = iterator.nextUntil(Instruction::isInvoke);
+        if (invoke != null) {
+          iterator.previous();
+          iterator.inlineInvoke(test.code, inlinee.next(), blocksIterator, blocksToRemove, null);
+        }
+      }
+      test.code.removeBlocks(blocksToRemove);
+      String result = test.run();
+      assertEquals(Integer.toString(expectedB), result);
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 04a55b5..c911d37 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 
@@ -311,7 +312,7 @@
   }
 
   public void runWithIfTest(boolean hitTrueBranch) throws Exception {
-    final int initialBlockCount = 4;
+    final int initialBlockCount = 3;
     final int argumentInstructions = 2;
     final int firstBlockInstructions = 3;
     // Try split between all non-argument instructions in the first block.
@@ -352,24 +353,27 @@
   public void splitBeforeReturn(boolean hitTrueBranch) throws Exception {
     TestApplication test = codeWithIf(hitTrueBranch);
     IRCode code = test.code;
-    // Locate the exit block and split before the return (the first instruction in the block).
-    BasicBlock originalReturnBlock = code.getNormalExitBlock();
-    BasicBlock newReturnBlock = originalReturnBlock.listIterator().split(code);
-    // Modify the code to make the inserted block add the constant 10 to the original return value.
-    Value newConstValue = new Value(test.valueNumberGenerator.next(), MoveType.SINGLE, null);
-    Value newReturnValue = new Value(test.valueNumberGenerator.next(), MoveType.SINGLE, null);
-    Value oldReturnValue = newReturnBlock.listIterator().next().asReturn().returnValue();
-    newReturnBlock.listIterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
-    Instruction constInstruction = new ConstNumber(ConstType.INT, newConstValue, 10);
-    Instruction addInstruction = new Add(
-        NumericType.INT,
-        newReturnValue,
-        oldReturnValue,
-        newConstValue);
-    InstructionListIterator iterator = originalReturnBlock.listIterator();
-    iterator.add(constInstruction);
-    iterator.add(addInstruction);
-
+    // Locate the exit blocks and split before the return.
+    List<BasicBlock> exitBlocks = new ArrayList<>(code.computeNormalExitBlocks());
+    for (BasicBlock originalReturnBlock : exitBlocks) {
+      InstructionListIterator iterator =
+          originalReturnBlock.listIterator(originalReturnBlock.getInstructions().size());
+      Instruction ret = iterator.previous();
+      assert ret.isReturn();
+      BasicBlock newReturnBlock = iterator.split(code);
+      // Modify the code to make the inserted block add the constant 10 to the original return
+      // value.
+      Value newConstValue = new Value(test.valueNumberGenerator.next(), MoveType.SINGLE, null);
+      Value newReturnValue = new Value(test.valueNumberGenerator.next(), MoveType.SINGLE, null);
+      Value oldReturnValue = newReturnBlock.listIterator().next().asReturn().returnValue();
+      newReturnBlock.listIterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
+      Instruction constInstruction = new ConstNumber(ConstType.INT, newConstValue, 10);
+      Instruction addInstruction =
+          new Add(NumericType.INT, newReturnValue, oldReturnValue, newConstValue);
+      iterator.previous();
+      iterator.add(constInstruction);
+      iterator.add(addInstruction);
+    }
     // Run code and check result (code in the test object is updated).
     String result = test.run();
     assertEquals(hitTrueBranch ? "10" : "11", result);
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 9f19536..3f44025 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -680,4 +680,40 @@
     config = parser.getConfig();
     assertTrue(config.isUseUniqueClassMemberNames());
   }
+
+  @Test
+  public void parseKeepParameterNames() throws Exception {
+    try {
+      ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+      parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(
+          "-keepparameternames"
+      )));
+      parser.getConfig();
+      fail();
+    } catch (ProguardRuleParserException e) {
+      System.out.println(e);
+      assertTrue(e.getMessage().contains("-keepparameternames is not supported"));
+    }
+  }
+
+  @Test
+  public void parseKeepParameterNamesWithoutMinification() throws Exception {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(
+        "-keepparameternames",
+        "-dontobfuscate"
+    )));
+    ProguardConfiguration config = parser.getConfig();
+    assertTrue(config.isKeepParameterNames());
+
+    parser = new ProguardConfigurationParser(new DexItemFactory());
+    parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(
+        "-keepparameternames"
+    )));
+    parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(
+        "-dontobfuscate"
+    )));
+    config = parser.getConfig();
+    assertTrue(config.isKeepParameterNames());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
index 9c41f21..5dc1e75 100644
--- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
+++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -444,6 +444,6 @@
     // TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the
     // expectation might need changing with other optimizations.
     // TODO(zerny): Consider optimizing the fallthrough branch of conditionals to not be return.
-    assertEquals(28, code.instructions.length);
+    assertEquals(27, code.instructions.length);
   }
 }