Merge "Update Art and Art tests from aosp master"
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/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..7cb186f 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();
@@ -983,6 +995,10 @@
       for (StaticPut put : dominatingPuts.values()) {
         DexField field = put.getField();
         DexEncodedField encodedField = appInfo.definitionFor(field);
+        if (encodedField == null) {
+          // See b/67468748.
+          continue;
+        }
         if (field.type == dexItemFactory.stringType) {
           if (put.inValue().isConstant()) {
             if (put.inValue().isConstNumber()) {
@@ -1036,7 +1052,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 +1075,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 +1530,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/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 439c1d9..6064b64 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -778,7 +778,7 @@
         throw new FileNotFoundException("Non-existent input file: " + file);
       }
       if (isArchive(file)) {
-        providerList.add(PreloadedClassFileProvider.fromArchive(classPath));
+        providerList.add(ArchiveClassFileProvider.fromArchive(classPath));
       } else if (Files.isDirectory(file) ) {
         // This is only used for D8 incremental compilation.
         assert classPath.isUnfiltered();
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveClassFileProvider.java
new file mode 100644
index 0000000..2828610
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveClassFileProvider.java
@@ -0,0 +1,110 @@
+// 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.utils;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+import static com.android.tools.r8.utils.FileUtils.isClassFile;
+
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.shaking.FilteredClassPath;
+import com.google.common.io.ByteStreams;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Lazy Java class file resource provider loading class files form a zip archive.
+ */
+public final class ArchiveClassFileProvider implements ClassFileResourceProvider {
+
+  private final Set<String> descriptors = new HashSet<>();
+  private final ZipFile zipFile;
+
+  public static ClassFileResourceProvider fromArchive(FilteredClassPath archive)
+      throws IOException {
+    return new ArchiveClassFileProvider(archive);
+  }
+
+  // Guess class descriptor from location of the class file.
+  static String guessTypeDescriptor(Path name) {
+    return guessTypeDescriptor(name.toString());
+  }
+
+  // Guess class descriptor from location of the class file.
+  private static String guessTypeDescriptor(String name) {
+    assert name != null;
+    assert name.endsWith(CLASS_EXTENSION) :
+        "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
+    String fileName =
+        File.separatorChar == '/' ? name.toString() :
+            name.toString().replace(File.separatorChar, '/');
+    String descriptor = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
+    if (descriptor.contains(".")) {
+      throw new CompilationError("Unexpected file name in the archive: " + fileName);
+    }
+    return 'L' + descriptor + ';';
+  }
+
+  private ArchiveClassFileProvider(FilteredClassPath archive) throws IOException {
+    assert isArchive(archive.getPath());
+    zipFile = new ZipFile(archive.getPath().toFile());
+    final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+    while (entries.hasMoreElements()) {
+      ZipEntry entry = entries.nextElement();
+      String name = entry.getName();
+      Path entryPath = Paths.get(name);
+      if (isClassFile(entryPath) && archive.matchesFile(entryPath)) {
+        descriptors.add(guessTypeDescriptor(name));
+      }
+    }
+  }
+
+  @Override
+  public Set<String> getClassDescriptors() {
+    return Collections.unmodifiableSet(descriptors);
+  }
+
+  @Override
+  public Resource getResource(String descriptor) {
+    if (!descriptors.contains(descriptor)) {
+      return null;
+    }
+
+    try (InputStream inputStream = zipFile.getInputStream(getZipEntryFromDescriptor(descriptor))) {
+      return Resource.fromBytes(
+          Resource.Kind.CLASSFILE,
+          ByteStreams.toByteArray(inputStream),
+          Collections.singleton(descriptor));
+    } catch (IOException e) {
+      throw new CompilationError(
+          "Failed to read '" + descriptor + "' from '" + zipFile.getName() + "'");
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable {
+    zipFile.close();
+    super.finalize();
+  }
+
+  @Override
+  public String toString() {
+    return descriptors.size() + " resources from '" + zipFile.getName() +"'";
+  }
+
+  private ZipEntry getZipEntryFromDescriptor(String descriptor) {
+    return zipFile.getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
+  }
+}
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/PreloadedClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
index b819a84..3f402d0 100644
--- a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
@@ -3,28 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static com.android.tools.r8.utils.FileUtils.isArchive;
-import static com.android.tools.r8.utils.FileUtils.isClassFile;
-
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.Resource;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.shaking.FilteredClassPath;
 import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 /**
  * Lazy Java class file resource provider based on preloaded/prebuilt context.
@@ -51,57 +36,12 @@
     return Resource.fromBytes(Resource.Kind.CLASSFILE, bytes, Collections.singleton(descriptor));
   }
 
-  /**
-   * Create preloaded content resource provider from archive file.
-   */
-  public static ClassFileResourceProvider fromArchive(FilteredClassPath archive)
-      throws IOException {
-    assert isArchive(archive.getPath());
-    Builder builder = builder();
-    try (ZipFile zipFile = new ZipFile(archive.getPath().toFile())) {
-      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
-      while (entries.hasMoreElements()) {
-        ZipEntry entry = entries.nextElement();
-        String name = entry.getName();
-        Path entryPath = Paths.get(name);
-        if (isClassFile(entryPath) && archive.matchesFile(entryPath)) {
-          try (InputStream entryStream = zipFile.getInputStream(entry)) {
-            builder.addResource(guessTypeDescriptor(name), ByteStreams.toByteArray(entryStream));
-          }
-        }
-      }
-    }
-
-    return builder.build();
-  }
-
-  public static ClassFileResourceProvider fromClassData(String descriptor, byte[] data)
-      throws IOException {
+  public static ClassFileResourceProvider fromClassData(String descriptor, byte[] data) {
     Builder builder = builder();
     builder.addResource(descriptor, data);
     return builder.build();
   }
 
-  // Guess class descriptor from location of the class file.
-  static String guessTypeDescriptor(Path name) {
-    return guessTypeDescriptor(name.toString());
-  }
-
-  // Guess class descriptor from location of the class file.
-  private static String guessTypeDescriptor(String name) {
-    assert name != null;
-    assert name.endsWith(CLASS_EXTENSION) :
-        "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
-    String fileName =
-        File.separatorChar == '/' ? name.toString() :
-            name.toString().replace(File.separatorChar, '/');
-    String descriptor = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
-    if (descriptor.contains(".")) {
-      throw new CompilationError("Unexpected file name in the archive: " + fileName);
-    }
-    return 'L' + descriptor + ';';
-  }
-
   @Override
   public String toString() {
     return content.size() + " preloaded resources";
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 57bcd68..fa6d5fb 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -55,7 +55,7 @@
                 dexResources.add(resource);
               }
             } else if (isClassFile(name)) {
-              String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
+              String descriptor = ArchiveClassFileProvider.guessTypeDescriptor(name);
               Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
                   ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
               classResources.add(resource);
diff --git a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
index 94fadf1..7de4f7f 100644
--- a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveClassFileProvider;
 import com.android.tools.r8.utils.DirectoryClassFileProvider;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.OffOrAuto;
@@ -52,7 +53,7 @@
     @Override
     void addLibraryReference(Builder builder, Path location) throws IOException {
       builder.addLibraryResourceProvider(
-          PreloadedClassFileProvider.fromArchive(FilteredClassPath.unfiltered(location)));
+          ArchiveClassFileProvider.fromArchive(FilteredClassPath.unfiltered(location)));
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 54fdde3..c094e5b 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;
@@ -1015,7 +1016,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/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e46ff48..89a7a26 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -101,6 +101,15 @@
   }
 
   /**
+   * Compile an application with D8.
+   */
+  protected AndroidApp compileWithD8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
+      throws CompilationException, ExecutionException, IOException {
+    D8Command command = ToolHelper.prepareD8CommandBuilder(app).build();
+    return ToolHelper.runD8(command, optionsConsumer);
+  }
+
+  /**
    * Compile an application with R8.
    */
   protected AndroidApp compileWithR8(Class... classes)
@@ -249,11 +258,25 @@
   /**
    * Run application on Art with the specified main class.
    */
-  protected String runOnArt(AndroidApp app, Class mainClass) throws IOException {
+  protected ProcessResult runOnArtRaw(AndroidApp app, String mainClass) throws IOException {
     Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
     app.writeToZip(out, OutputMode.Indexed);
-    return ToolHelper.runArtNoVerificationErrors(
-        ImmutableList.of(out.toString()), mainClass.getCanonicalName(), null);
+    return ToolHelper.runArtNoVerificationErrorsRaw(
+        ImmutableList.of(out.toString()), mainClass, null);
+  }
+
+  /**
+   * Run application on Art with the specified main class.
+   */
+  protected ProcessResult runOnArtRaw(AndroidApp app, Class mainClass) throws IOException {
+    return runOnArtRaw(app, mainClass.getCanonicalName());
+  }
+
+  /**
+   * Run application on Art with the specified main class.
+   */
+  protected String runOnArt(AndroidApp app, Class mainClass) throws IOException {
+    return runOnArtRaw(app, mainClass).stdout;
   }
 
   /**
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index b97bd7b..d435ae5 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 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.graph.DexItemFactory;
@@ -475,7 +474,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 +498,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 +537,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;
   }
 
@@ -774,18 +772,29 @@
     return Paths.get(System.getProperty("java.home"), "bin", "java").toString();
   }
 
+  public static ProcessResult runArtNoVerificationErrorsRaw(String file, String mainClass)
+      throws IOException {
+    return runArtNoVerificationErrorsRaw(Collections.singletonList(file), mainClass, null);
+  }
+
   public static String runArtNoVerificationErrors(String file, String mainClass)
       throws IOException {
-    return runArtNoVerificationErrors(Collections.singletonList(file), mainClass, null);
+    return runArtNoVerificationErrorsRaw(file, mainClass).stdout;
+  }
+
+  public static ProcessResult runArtNoVerificationErrorsRaw(List<String> files, String mainClass,
+      Consumer<ArtCommandBuilder> extras)
+      throws IOException {
+    return runArtNoVerificationErrorsRaw(files, mainClass, extras, null);
   }
 
   public static String runArtNoVerificationErrors(List<String> files, String mainClass,
       Consumer<ArtCommandBuilder> extras)
       throws IOException {
-    return runArtNoVerificationErrors(files, mainClass, extras, null);
+    return runArtNoVerificationErrorsRaw(files, mainClass, extras).stdout;
   }
 
-  public static String runArtNoVerificationErrors(List<String> files, String mainClass,
+  public static ProcessResult runArtNoVerificationErrorsRaw(List<String> files, String mainClass,
       Consumer<ArtCommandBuilder> extras,
       DexVm version)
       throws IOException {
@@ -796,18 +805,30 @@
     if (extras != null) {
       extras.accept(builder);
     }
-    return runArtNoVerificationErrors(builder);
+    return runArtNoVerificationErrorsRaw(builder);
   }
 
-  public static String runArtNoVerificationErrors(ArtCommandBuilder builder) throws IOException {
-    ProcessResult result = runArtProcess(builder);
+  public static String runArtNoVerificationErrors(List<String> files, String mainClass,
+      Consumer<ArtCommandBuilder> extras,
+      DexVm version)
+      throws IOException {
+    return runArtNoVerificationErrorsRaw(files, mainClass, extras, version).stdout;
+  }
+
+  public static ProcessResult runArtNoVerificationErrorsRaw(ArtCommandBuilder builder)
+      throws IOException {
+    ProcessResult result = runArtProcessRaw(builder);
     if (result.stderr.contains("Verification error")) {
       fail("Verification error: \n" + result.stderr);
     }
-    return result.stdout;
+    return result;
   }
 
-  private static ProcessResult runArtProcess(ArtCommandBuilder builder) throws IOException {
+  public static String runArtNoVerificationErrors(ArtCommandBuilder builder) throws IOException {
+    return runArtNoVerificationErrorsRaw(builder).stdout;
+  }
+
+  private static ProcessResult runArtProcessRaw(ArtCommandBuilder builder) throws IOException {
     Assume.assumeTrue(ToolHelper.artSupported());
     ProcessResult result;
     if (builder.isForDevice()) {
@@ -819,6 +840,11 @@
     } else {
       result = runProcess(builder.asProcessBuilder());
     }
+    return result;
+  }
+
+  private static ProcessResult runArtProcess(ArtCommandBuilder builder) throws IOException {
+    ProcessResult result = runArtProcessRaw(builder);
     if (result.exitCode != 0) {
       fail("Unexpected art failure: '" + result.stderr + "'\n" + result.stdout);
     }
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/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 8cf6e9b..ab94090 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -39,6 +39,7 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
+import java.util.function.Consumer;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
 
@@ -80,6 +81,11 @@
     return ToolHelper.runD8(builder.build());
   }
 
+  protected AndroidApp compileWithD8(
+      JasminBuilder builder, Consumer<InternalOptions> optionsConsumer) throws Exception {
+    return ToolHelper.runD8(builder.build(), optionsConsumer);
+  }
+
   protected String runOnArtD8(JasminBuilder builder, String main) throws Exception {
     return runOnArt(compileWithD8(builder), main);
   }
@@ -132,10 +138,16 @@
     return ToolHelper.runArtNoVerificationErrors(dex.toString(), main);
   }
 
+  protected ProcessResult runOnArtRaw(AndroidApp app, String main) throws IOException {
+    Path out = temp.getRoot().toPath().resolve("out.zip");
+    app.writeToZip(out, OutputMode.Indexed);
+    return ToolHelper.runArtNoVerificationErrorsRaw(out.toString(), main);
+  }
+
   protected String runOnArt(AndroidApp app, String main) throws IOException {
     Path out = temp.getRoot().toPath().resolve("out.zip");
     app.writeToZip(out, OutputMode.Indexed);
-    return ToolHelper.runArtNoVerificationErrors(ImmutableList.of(out.toString()), main, null);
+    return ToolHelper.runArtNoVerificationErrors(out.toString(), main);
   }
 
   protected static DexApplication process(DexApplication app, InternalOptions options)
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/B67468748.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/B67468748.java
new file mode 100644
index 0000000..6d9f582
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/B67468748.java
@@ -0,0 +1,59 @@
+// 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.rewrite.staticvalues;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class B67468748 extends JasminTestBase {
+
+  @Test
+  public void jarInput() throws Exception {
+    final String CLASS_NAME = "Test";
+
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass(CLASS_NAME);
+
+    // The static field Test/intField is not defined even though it is written in the
+    // <clinit> code. This class cannot load, but we can still process it to dex, where Art also
+    // cannot load the class.
+    clazz.addStaticMethod("<clinit>", ImmutableList.of(), "V",
+        ".limit stack 1",
+        ".limit locals 1",
+        "iconst_1",
+        "putstatic Test/intField I",
+        "return");
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "getstatic Test/intField I",
+        "invokevirtual java/io/PrintStream/print(I)V",
+        "return");
+
+    // The code does not run on the Java VM, as there is a missing field.
+    ProcessResult result = runOnJavaRaw(builder, CLASS_NAME);
+    assertEquals(1, result.exitCode);
+    assertTrue(result.stderr.contains("java.lang.NoSuchFieldError"));
+
+    // Run in release mode to turn on initializer defaults rewriting.
+    AndroidApp application = compileWithD8(builder, options -> options.debug = false);
+
+    // The code does not run on Art, as there is a missing field.
+    result = runOnArtRaw(application, CLASS_NAME);
+    assertEquals(1, result.exitCode);
+    assertTrue(result.stderr.contains("java.lang.NoSuchFieldError"));
+  }
+
+  // Test with dex input is in StaticValuesTest.
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 9e5a839..1d26039 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -7,7 +7,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.Sput;
 import com.android.tools.r8.code.SputObject;
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
@@ -572,4 +573,43 @@
     assertEquals(StringUtils.lines("2"), result);
   }
 
+  @Test
+  public void b67468748() throws Exception {
+    final String CLASS_NAME = "Test";
+
+    SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
+
+    // The static field LTest;->intField:I is not defined even though it is written in the
+    // <clinit> code. This class cannot load, but we can still process it to output which still
+    // cannot load.
+    builder.addStaticInitializer(
+        2,
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "return-void"
+    );
+
+    AndroidApp application = builder.build();
+
+    // The code does not run on Art, as there is a missing field.
+    ProcessResult result = runOnArtRaw(application, CLASS_NAME);
+    assertEquals(1, result.exitCode);
+    assertTrue(result.stderr.contains("java.lang.NoSuchFieldError"));
+
+    // Run in release mode to turn on initializer defaults rewriting.
+    application = compileWithD8(application, options -> options.debug = false);
+
+    // The code does still not run on Art, as there is still a missing field.
+    result = runOnArtRaw(application, CLASS_NAME);
+    assertEquals(1, result.exitCode);
+    assertTrue(result.stderr.contains("java.lang.NoSuchFieldError"));
+  }
+
 }
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);
   }
 }
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 f2f1220..41f73d1 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -361,7 +361,7 @@
       getSource(currentClassName).add(builder.toString());
     }
 
-    public List<String> build() {
+    public List<String> buildSource() {
       List<String> result = new ArrayList<>(classes.size());
       for (String clazz : classes.keySet()) {
         Builder classBuilder = classes.get(clazz);
@@ -372,12 +372,18 @@
 
     public byte[] compile()
         throws IOException, RecognitionException, DexOverflowException, ExecutionException {
-      return Smali.compile(build());
+      return Smali.compile(buildSource());
     }
 
+    public AndroidApp build()
+        throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+      return AndroidApp.fromDexProgramData(compile());
+    }
+
+
     @Override
     public String toString() {
-      return String.join("\n\n", build());
+      return String.join("\n\n", buildSource());
     }
   }