Merge "Insert reads of all locals that are not live on all outgoing edges."
diff --git a/build.gradle b/build.gradle
index 7362238..82feb00 100644
--- a/build.gradle
+++ b/build.gradle
@@ -435,6 +435,20 @@
     }
 }
 
+task maindex(type: Jar) {
+    from sourceSets.main.output
+    baseName 'maindex'
+    manifest {
+      attributes 'Main-Class': 'com.android.tools.r8.GenerateMainDexList'
+    }
+    // In order to build without dependencies, pass the exclude_deps property using:
+    // gradle -Pexclude_deps maindex
+    if (!project.hasProperty('exclude_deps')) {
+        // Also include dependencies
+        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+    }
+}
+
 task ExtractMarker(type: Jar) {
     from sourceSets.main.output
     baseName 'extractmarker'
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 9ddbd9e..cbd8dd6 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -3,51 +3,33 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
 
+/**
+ * Base class for commands and command builders for applications/tools which take an Android
+ * application (and a main-dex list) as input.
+ */
 abstract class BaseCommand {
 
   private final boolean printHelp;
   private final boolean printVersion;
 
   private final AndroidApp app;
-  private final Path outputPath;
-  private final OutputMode outputMode;
-  private final CompilationMode mode;
-  private final int minApiLevel;
 
   BaseCommand(boolean printHelp, boolean printVersion) {
     this.printHelp = printHelp;
     this.printVersion = printVersion;
     // All other fields are initialized with stub/invalid values.
     this.app = null;
-    this.outputPath = null;
-    this.outputMode = OutputMode.Indexed;
-    this.mode = null;
-    this.minApiLevel = 0;
   }
 
-  BaseCommand(
-      AndroidApp app,
-      Path outputPath,
-      OutputMode outputMode,
-      CompilationMode mode,
-      int minApiLevel) {
+  BaseCommand(AndroidApp app) {
     assert app != null;
-    assert mode != null;
-    assert minApiLevel > 0;
     this.app = app;
-    this.outputPath = outputPath;
-    this.outputMode = outputMode;
-    this.mode = mode;
-    this.minApiLevel = minApiLevel;
     // Print options are not set.
     printHelp = false;
     printVersion = false;
@@ -69,52 +51,27 @@
   // Internal access to the internal options.
   abstract InternalOptions getInternalOptions();
 
-  public Path getOutputPath() {
-    return outputPath;
-  }
-
-  public CompilationMode getMode() {
-    return mode;
-  }
-
-  public int getMinApiLevel() {
-    return minApiLevel;
-  }
-
-  public OutputMode getOutputMode() {
-    return outputMode;
-  }
-
-  abstract static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
+  abstract public static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
 
     private boolean printHelp = false;
     private boolean printVersion = false;
     private final AndroidApp.Builder app;
-    private Path outputPath = null;
-    private OutputMode outputMode = OutputMode.Indexed;
-    private CompilationMode mode;
-    private int minApiLevel = Constants.DEFAULT_ANDROID_API;
 
-    // Internal flag used by CompatDx to ignore dex files in archives.
-    protected boolean ignoreDexInArchive = false;
-
-    protected Builder(CompilationMode mode) {
-      this(AndroidApp.builder(), mode, false);
+    protected Builder() {
+      this(AndroidApp.builder(), false);
     }
 
-    protected Builder(CompilationMode mode, boolean ignoreDexInArchive) {
-      this(AndroidApp.builder(), mode, ignoreDexInArchive);
+    protected Builder(boolean ignoreDexInArchive) {
+      this(AndroidApp.builder(), ignoreDexInArchive);
     }
 
     // Internal constructor for testing.
     Builder(AndroidApp app, CompilationMode mode) {
-      this(AndroidApp.builder(app), mode, false);
+      this(AndroidApp.builder(app), false);
     }
 
-    private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) {
-      assert mode != null;
+    protected Builder(AndroidApp.Builder builder, boolean ignoreDexInArchive) {
       this.app = builder;
-      this.mode = mode;
       app.setIgnoreDexInArchive(ignoreDexInArchive);
     }
 
@@ -181,52 +138,6 @@
       return self();
     }
 
-    /** Get current compilation mode. */
-    public CompilationMode getMode() {
-      return mode;
-    }
-
-    /** Set compilation mode. */
-    public B setMode(CompilationMode mode) {
-      assert mode != null;
-      this.mode = mode;
-      return self();
-    }
-
-    /** Get the output path. Null if not set. */
-    public Path getOutputPath() {
-      return outputPath;
-    }
-
-    /** Get the output mode. */
-    public OutputMode getOutputMode() {
-      return outputMode;
-    }
-
-    /** Set an output path. Must be an existing directory or a zip file. */
-    public B setOutputPath(Path outputPath) {
-      this.outputPath = outputPath;
-      return self();
-    }
-
-    /** Set an output mode. */
-    public B setOutputMode(OutputMode outputMode) {
-      this.outputMode = outputMode;
-      return self();
-    }
-
-    /** Get the minimum API level (aka SDK version). */
-    public int getMinApiLevel() {
-      return minApiLevel;
-    }
-
-    /** Set the minimum required API level (aka SDK version). */
-    public B setMinApiLevel(int minApiLevel) {
-      assert minApiLevel > 0;
-      this.minApiLevel = minApiLevel;
-      return self();
-    }
-
     /**
      * Add main-dex list files.
      *
@@ -300,11 +211,7 @@
     }
 
     protected void validate() throws CompilationException {
-      if (app.hasMainDexList() && outputMode == OutputMode.FilePerClass) {
-        throw new CompilationException(
-            "Option --main-dex-list cannot be used with --file-per-class");
-      }
-      FileUtils.validateOutputFile(outputPath);
+      // Currently does nothing.
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
new file mode 100644
index 0000000..5e8f975
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -0,0 +1,146 @@
+// 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;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OutputMode;
+import java.nio.file.Path;
+
+/**
+ * Base class for commands and command builders for compiler applications/tools which besides an
+ * Android application (and a main-dex list) also takes compilation output, compilation mode and
+ * min API level as input.
+ */
+abstract class BaseCompilerCommand extends BaseCommand {
+
+  private final Path outputPath;
+  private final OutputMode outputMode;
+  private final CompilationMode mode;
+  private final int minApiLevel;
+
+  BaseCompilerCommand(boolean printHelp, boolean printVersion) {
+    super(printHelp, printVersion);
+
+    this.outputPath = null;
+    this.outputMode = OutputMode.Indexed;
+    this.mode = null;
+    this.minApiLevel = 0;
+  }
+
+  BaseCompilerCommand(
+      AndroidApp app,
+      Path outputPath,
+      OutputMode outputMode,
+      CompilationMode mode,
+      int minApiLevel) {
+    super(app);
+    assert mode != null;
+    assert minApiLevel > 0;
+    this.outputPath = outputPath;
+    this.outputMode = outputMode;
+    this.mode = mode;
+    this.minApiLevel = minApiLevel;
+  }
+
+  public Path getOutputPath() {
+    return outputPath;
+  }
+
+  public CompilationMode getMode() {
+    return mode;
+  }
+
+  public int getMinApiLevel() {
+    return minApiLevel;
+  }
+
+  public OutputMode getOutputMode() {
+    return outputMode;
+  }
+
+  abstract public static class Builder<C extends BaseCompilerCommand, B extends Builder<C, B>>
+      extends BaseCommand.Builder<C, B> {
+
+    private Path outputPath = null;
+    private OutputMode outputMode = OutputMode.Indexed;
+    private CompilationMode mode;
+    private int minApiLevel = Constants.DEFAULT_ANDROID_API;
+
+    protected Builder(CompilationMode mode) {
+      this(AndroidApp.builder(), mode, false);
+    }
+
+    protected Builder(CompilationMode mode, boolean ignoreDexInArchive) {
+      this(AndroidApp.builder(), mode, ignoreDexInArchive);
+    }
+
+    // Internal constructor for testing.
+    Builder(AndroidApp app, CompilationMode mode) {
+      this(AndroidApp.builder(app), mode, false);
+    }
+
+    private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) {
+      super(builder, ignoreDexInArchive);
+      assert mode != null;
+      this.mode = mode;
+    }
+
+    /** Get current compilation mode. */
+    public CompilationMode getMode() {
+      return mode;
+    }
+
+    /** Set compilation mode. */
+    public B setMode(CompilationMode mode) {
+      assert mode != null;
+      this.mode = mode;
+      return self();
+    }
+
+    /** Get the output path. Null if not set. */
+    public Path getOutputPath() {
+      return outputPath;
+    }
+
+    /** Get the output mode. */
+    public OutputMode getOutputMode() {
+      return outputMode;
+    }
+
+    /** Set an output path. Must be an existing directory or a zip file. */
+    public B setOutputPath(Path outputPath) {
+      this.outputPath = outputPath;
+      return self();
+    }
+
+    /** Set an output mode. */
+    public B setOutputMode(OutputMode outputMode) {
+      this.outputMode = outputMode;
+      return self();
+    }
+
+    /** Get the minimum API level (aka SDK version). */
+    public int getMinApiLevel() {
+      return minApiLevel;
+    }
+
+    /** Set the minimum required API level (aka SDK version). */
+    public B setMinApiLevel(int minApiLevel) {
+      assert minApiLevel > 0;
+      this.minApiLevel = minApiLevel;
+      return self();
+    }
+
+    protected void validate() throws CompilationException {
+      super.validate();
+      if (getAppBuilder().hasMainDexList() && outputMode == OutputMode.FilePerClass) {
+        throw new CompilationException(
+            "Option --main-dex-list cannot be used with --file-per-class");
+      }
+      FileUtils.validateOutputFile(outputPath);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 3618ab5..5b2e5cf 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -27,12 +27,12 @@
  *     .build();
  * </pre>
  */
-public class D8Command extends BaseCommand {
+public class D8Command extends BaseCompilerCommand {
 
   /**
    * Builder for constructing a D8Command.
    */
-  public static class Builder extends BaseCommand.Builder<D8Command, Builder> {
+  public static class Builder extends BaseCompilerCommand.Builder<D8Command, Builder> {
 
     private boolean intermediate = false;
 
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 4e4a19b..c27deb5 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -7,11 +7,9 @@
 import com.android.tools.r8.dex.Segment;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.Closer;
 import java.io.IOException;
-import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
@@ -22,10 +20,6 @@
     public static class Builder
         extends BaseCommand.Builder<Command, Builder> {
 
-      private Builder() {
-        super(CompilationMode.RELEASE);
-      }
-
       @Override
       Command.Builder self() {
         return this;
@@ -38,8 +32,7 @@
           return new Command(isPrintHelp());
         }
         validate();
-        return new Command(
-            getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+        return new Command(getAppBuilder().build());
       }
     }
 
@@ -77,13 +70,8 @@
       }
     }
 
-    private Command(
-        AndroidApp inputApp,
-        Path outputPath,
-        OutputMode outputMode,
-        CompilationMode mode,
-        int minApiLevel) {
-      super(inputApp, outputPath, outputMode, mode, minApiLevel);
+    private Command(AndroidApp inputApp) {
+      super(inputApp);
     }
 
     private Command(boolean printHelp) {
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index a7bfdc9..bb72d5d 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -15,13 +14,13 @@
 public class Disassemble {
   public static class DisassembleCommand extends BaseCommand {
 
+    private final Path outputPath;
+
     public static class Builder
         extends BaseCommand.Builder<DisassembleCommand, DisassembleCommand.Builder> {
-      private boolean useSmali = false;
 
-      private Builder() {
-        super(CompilationMode.RELEASE);
-      }
+      private Path outputPath = null;
+      private boolean useSmali = false;
 
       @Override
       DisassembleCommand.Builder self() {
@@ -33,6 +32,15 @@
         return this;
       }
 
+      public Path getOutputPath() {
+        return outputPath;
+      }
+
+      public DisassembleCommand.Builder setOutputPath(Path outputPath) {
+        this.outputPath = outputPath;
+        return this;
+      }
+
       public DisassembleCommand.Builder setUseSmali(boolean useSmali) {
         this.useSmali = useSmali;
         return this;
@@ -46,13 +54,7 @@
         }
 
         validate();
-        return new DisassembleCommand(
-            getAppBuilder().build(),
-            getOutputPath(),
-            getOutputMode(),
-            getMode(),
-            getMinApiLevel(),
-            useSmali);
+        return new DisassembleCommand(getAppBuilder().build(), getOutputPath(), useSmali);
       }
     }
 
@@ -93,6 +95,9 @@
           builder.setUseSmali(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
+        } else if (arg.equals("--output")) {
+          String outputPath = args[++i];
+          builder.setOutputPath(Paths.get(outputPath));
         } else {
           if (arg.startsWith("--")) {
             throw new CompilationException("Unknown option: " + arg);
@@ -102,23 +107,22 @@
       }
     }
 
-    private DisassembleCommand(
-        AndroidApp inputApp,
-        Path outputPath,
-        OutputMode outputMode,
-        CompilationMode mode,
-        int minApiLevel,
-        boolean useSmali) {
-      super(inputApp, outputPath, outputMode, mode, minApiLevel);
-      assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
+    private DisassembleCommand(AndroidApp inputApp, Path outputPath, boolean useSmali) {
+      super(inputApp);
+      this.outputPath = outputPath;
       this.useSmali = useSmali;
     }
 
     private DisassembleCommand(boolean printHelp, boolean printVersion) {
       super(printHelp, printVersion);
+      this.outputPath = null;
       this.useSmali = false;
     }
 
+    public Path getOutputPath() {
+      return outputPath;
+    }
+
     public boolean useSmali() {
       return useSmali;
     }
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 0db183f..d798781 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -3,18 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.google.common.collect.ImmutableList;
-
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
 import com.android.tools.r8.utils.Timing;
-
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
-import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutionException;
 
@@ -24,10 +20,6 @@
     public static class Builder
         extends BaseCommand.Builder<ExtractMarker.Command, ExtractMarker.Command.Builder> {
 
-      private Builder() {
-        super(CompilationMode.RELEASE);
-      }
-
       @Override
       ExtractMarker.Command.Builder self() {
         return this;
@@ -40,8 +32,7 @@
           return new ExtractMarker.Command(isPrintHelp());
         }
         validate();
-        return new ExtractMarker.Command(
-            getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+        return new ExtractMarker.Command(getAppBuilder().build());
       }
     }
 
@@ -79,13 +70,8 @@
       }
     }
 
-    private Command(
-        AndroidApp inputApp,
-        Path outputPath,
-        OutputMode outputMode,
-        CompilationMode mode,
-        int minApiLevel) {
-      super(inputApp, outputPath, outputMode, mode, minApiLevel);
+    private Command(AndroidApp inputApp) {
+      super(inputApp);
     }
 
     private Command(boolean printHelp) {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
new file mode 100644
index 0000000..44d523e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -0,0 +1,124 @@
+// 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;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.MainDexListBuilder;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+public class GenerateMainDexList {
+  private static final String VERSION = "v0.2.0";
+  private final Timing timing = new Timing("maindex");
+  private final InternalOptions options;
+
+  private GenerateMainDexList(InternalOptions options) {
+    this.options = options;
+  }
+
+  private List<String> run(AndroidApp app) throws IOException, ExecutionException {
+    ExecutorService executor = ThreadUtils.getExecutorService(options);
+    DexApplication application = new ApplicationReader(app, options, timing).read(executor);
+    AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+    RootSet mainDexRootSet =
+        new RootSetBuilder(application, appInfo, options.mainDexKeepRules).run(executor);
+    Set<DexType> mainDexBaseClasses = new Enqueuer(appInfo).traceMainDex(mainDexRootSet, timing);
+    Set<DexType> mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+
+    List<String> result = mainDexClasses.stream()
+        .map(c -> c.toSourceString().replace('.', '/') + ".class")
+        .sorted()
+        .collect(Collectors.toList());
+
+    if (options.printMainDexListFile != null) {
+      try (OutputStream mainDexOut = Files.newOutputStream(options.printMainDexListFile,
+          StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+        PrintWriter writer = new PrintWriter(mainDexOut);
+        result.forEach(writer::println);
+        writer.flush();
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Main API entry for computing the main-dex list.
+   *
+   * The main-dex list is represented as a list of strings, each string specifies one class to
+   * keep in the primary dex file (<code>classes.dex</code>).
+   *
+   * A class is specified using the following format: "com/example/MyClass.class". That is
+   * "/" as separator between package components, and a trailing ".class".
+   *
+   * @param command main dex-list generator command.
+   * @return classes to keep in the primary dex file.
+   */
+  public static List<String> run(GenerateMainDexListCommand command)
+      throws IOException, ExecutionException {
+    ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
+    try {
+      return run(command, executorService);
+    } finally {
+      executorService.shutdown();
+    }
+  }
+
+  /**
+   * Main API entry for computing the main-dex list.
+   *
+   * The main-dex list is represented as a list of strings, each string specifies one class to
+   * keep in the primary dex file (<code>classes.dex</code>).
+   *
+   * A class is specified using the following format: "com/example/MyClass.class". That is
+   * "/" as separator between package components, and a trailing ".class".
+   *
+   * @param command main dex-list generator command.
+   * @param executor executor service from which to get threads for multi-threaded processing.
+   * @return classes to keep in the primary dex file.
+   */
+  public static List<String> run(GenerateMainDexListCommand command, ExecutorService executor)
+      throws IOException, ExecutionException {
+    AndroidApp app = command.getInputApp();
+    InternalOptions options = command.getInternalOptions();
+    return new GenerateMainDexList(options).run(app);
+  }
+
+  public static void main(String[] args)
+      throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+    GenerateMainDexListCommand.Builder builder = GenerateMainDexListCommand.parse(args);
+    GenerateMainDexListCommand command = builder.build();
+    if (command.isPrintHelp()) {
+      System.out.println(GenerateMainDexListCommand.USAGE_MESSAGE);
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("MainDexListGenerator " + VERSION);
+      return;
+    }
+    List<String> result = run(command);
+    if (command.getMainDexListOutputPath() == null) {
+      result.forEach(System.out::println);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
new file mode 100644
index 0000000..76d7d56
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -0,0 +1,200 @@
+// 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;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GenerateMainDexListCommand extends BaseCommand {
+
+  private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+  private final Path mainDexListOutput;
+  private final DexItemFactory factory;
+
+  /**
+   * Get the output path for the main-dex list. Null if not set.
+   */
+  public Path getMainDexListOutputPath() {
+    return mainDexListOutput;
+  }
+
+  public static class Builder extends BaseCommand.Builder<GenerateMainDexListCommand, Builder> {
+
+    private final DexItemFactory factory = new DexItemFactory();
+    private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
+    private Path mainDexListOutput = null;
+
+    @Override
+    GenerateMainDexListCommand.Builder self() {
+      return this;
+    }
+
+    /**
+     * Add proguard configuration file resources for automatic main dex list calculation.
+     */
+    public GenerateMainDexListCommand.Builder addMainDexRulesFiles(Path... paths) {
+      for (Path path : paths) {
+        mainDexRules.add(new ProguardConfigurationSourceFile(path));
+      }
+      return self();
+    }
+
+    /**
+     * Add proguard configuration file resources for automatic main dex list calculation.
+     */
+    public GenerateMainDexListCommand.Builder addMainDexRulesFiles(List<Path> paths) {
+      for (Path path : paths) {
+        mainDexRules.add(new ProguardConfigurationSourceFile(path));
+      }
+      return self();
+    }
+
+    /**
+     * Add proguard configuration for automatic main dex list calculation.
+     */
+    public GenerateMainDexListCommand.Builder addMainDexRules(List<String> lines) {
+      mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
+      return self();
+    }
+
+    /**
+     * Get the output path for the main-dex list. Null if not set.
+     */
+    public Path getMainDexListOutputPath() {
+      return mainDexListOutput;
+    }
+
+    /**
+     * Set the output file for the main-dex list.
+     *
+     * If the file exists it will be overwritten.
+     */
+    public GenerateMainDexListCommand.Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
+      mainDexListOutput = mainDexListOutputPath;
+      return self();
+    }
+
+
+    @Override
+    public GenerateMainDexListCommand build() throws CompilationException, IOException {
+      // If printing versions ignore everything else.
+      if (isPrintHelp() || isPrintVersion()) {
+        return new GenerateMainDexListCommand(isPrintHelp(), isPrintVersion());
+      }
+
+      validate();
+      ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+      if (this.mainDexRules.isEmpty()) {
+        mainDexKeepRules = ImmutableList.of();
+      } else {
+        ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
+        try {
+          parser.parse(mainDexRules);
+        } catch (ProguardRuleParserException e) {
+          throw new CompilationException(e.getMessage(), e.getCause());
+        }
+        mainDexKeepRules = parser.getConfig().getRules();
+      }
+
+      return new GenerateMainDexListCommand(
+          factory,
+          getAppBuilder().build(),
+          mainDexKeepRules,
+          mainDexListOutput);
+    }
+  }
+
+  static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+      "Usage: maindex [options] <input-files>",
+      " where <input-files> are JAR files",
+      " and options are:",
+      "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
+      "                           # primary dex file.",
+      "  --main-dex-list <file>   # List of classes to place in the primary dex file.",
+      "  --main-dex-list-output <file>  # Output the full main-dex list in <file>.",
+      "  --version                # Print the version.",
+      "  --help                   # Print this message."));
+
+
+  public static GenerateMainDexListCommand.Builder builder() {
+    return new GenerateMainDexListCommand.Builder();
+  }
+
+  public static GenerateMainDexListCommand.Builder parse(String[] args)
+      throws CompilationException, IOException {
+    GenerateMainDexListCommand.Builder builder = builder();
+    parse(args, builder);
+    return builder;
+  }
+
+  private static void parse(String[] args, GenerateMainDexListCommand.Builder builder)
+      throws CompilationException, IOException {
+    for (int i = 0; i < args.length; i++) {
+      String arg = args[i].trim();
+      if (arg.length() == 0) {
+        continue;
+      } else if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--main-dex-rules")) {
+        builder.addMainDexRulesFiles(Paths.get(args[++i]));
+      } else if (arg.equals("--main-dex-list")) {
+        builder.addMainDexListFiles(Paths.get(args[++i]));
+      } else if (arg.equals("--main-dex-list-output")) {
+        builder.setMainDexListOutputPath(Paths.get(args[++i]));
+      } else {
+        if (arg.startsWith("--")) {
+          throw new CompilationException("Unknown option: " + arg);
+        }
+        builder.addProgramFiles(Paths.get(arg));
+      }
+    }
+  }
+
+  private GenerateMainDexListCommand(
+      DexItemFactory factory,
+      AndroidApp inputApp,
+      ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
+      Path mainDexListOutput) {
+    super(inputApp);
+    this.factory = factory;
+    this.mainDexKeepRules = mainDexKeepRules;
+    this.mainDexListOutput = mainDexListOutput;
+  }
+
+  private GenerateMainDexListCommand(boolean printHelp, boolean printVersion) {
+    super(printHelp, printVersion);
+    this.factory = new DexItemFactory();
+    this.mainDexKeepRules = ImmutableList.of();
+    this.mainDexListOutput = null;
+  }
+
+  @Override
+  InternalOptions getInternalOptions() {
+    InternalOptions internal = new InternalOptions(factory);
+    internal.mainDexKeepRules = mainDexKeepRules;
+    if (mainDexListOutput != null) {
+      internal.printMainDexListFile = mainDexListOutput;
+    }
+    internal.minimalMainDex = internal.debug;
+    internal.removeSwitchMaps = false;
+    internal.inlineAccessors = false;
+    return internal;
+  }
+}
+
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 318b3e6..890d570 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -24,9 +24,9 @@
 import java.util.Optional;
 import java.util.function.Consumer;
 
-public class R8Command extends BaseCommand {
+public class R8Command extends BaseCompilerCommand {
 
-  public static class Builder extends BaseCommand.Builder<R8Command, Builder> {
+  public static class Builder extends BaseCompilerCommand.Builder<R8Command, Builder> {
 
     private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
     private Path mainDexListOutput = null;
@@ -96,7 +96,7 @@
     }
 
     /**
-     * Add proguard configuration file resources for automatic main dex list calculation.
+     * Add proguard configuration for automatic main dex list calculation.
      */
     public Builder addMainDexRules(List<String> lines) {
       mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
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 8ae2147..b698fc5 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
@@ -50,7 +50,9 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -61,6 +63,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1545,7 +1548,7 @@
         // First rewrite zero comparison.
         rewriteIfWithConstZero(block);
 
-        if (simplifyKnownBooleanCondition(dominator, block)) {
+        if (simplifyKnownBooleanCondition(code, dominator, block)) {
           continue;
         }
 
@@ -1608,8 +1611,19 @@
    *
    * which can be replaced by a fallthrough and the phi value can be replaced
    * with the boolean value itself.
+   *
+   * We also consider the forms:
+   *
+   *    ifeqz booleanValue       ifnez booleanValue
+   *      /        \              /        \
+   *      \        /              \        /
+   *      phi(1, 0)                phi(0, 1)
+   *
+   *  which can be replaced by a fallthrough and the phi value can be replaced
+   * by an xor instruction which is smaller.
    */
-  private boolean simplifyKnownBooleanCondition(DominatorTree dominator, BasicBlock block) {
+  private boolean simplifyKnownBooleanCondition(IRCode code, DominatorTree dominator,
+      BasicBlock block) {
     If theIf = block.exit().asIf();
     Value testValue = theIf.inValues().get(0);
     if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
@@ -1639,6 +1653,19 @@
                       falseNumber.isIntegerZero())) {
                 phi.replaceUsers(testValue);
                 deadPhis++;
+              } else if ((theIf.getType() == Type.NE &&
+                           trueNumber.isIntegerZero() &&
+                           falseNumber.isIntegerOne()) ||
+                         (theIf.getType() == Type.EQ &&
+                           trueNumber.isIntegerOne() &&
+                           falseNumber.isIntegerZero())) {
+                Value newOutValue = code.createValue(phi.outType(), phi.getLocalInfo());
+                phi.replaceUsers(newOutValue);
+                Instruction newInstruction = new Xor(NumericType.INT, newOutValue, testValue,
+                    trueNumber.isIntegerOne() ? trueValue : falseValue);
+                newInstruction.setBlock(phi.getBlock());
+                phi.getBlock().getInstructions().add(0, newInstruction);
+                deadPhis++;
               }
             }
           }
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 a11d924..dfe10cf 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,6 +20,7 @@
 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;
@@ -272,12 +273,17 @@
       return true;
     }
 
+    // We cannot invoke <init> on other values than |this| on Dalvik 4.4.4. Compute whether
+    // the receiver to the call was the this value at the call-site.
+    boolean receiverOfInnerCallIsThisOfOuter = invoke.asInvokeDirect().getReceiver().isThis();
+
     // Don't allow inlining a constructor into a non-constructor if the first use of the
     // un-initialized object is not an argument of an invoke of <init>.
     // Also, we cannot inline a constructor if it initializes final fields, as such is only allowed
     // from within a constructor of the corresponding class.
     // Lastly, we can only inline a constructor, if its own <init> call is on the method's class. If
-    // we inline into a constructor, calls to super.<init> are also OK.
+    // we inline into a constructor, calls to super.<init> are also OK if the receiver of the
+    // super.<init> call is the this argument.
     InstructionIterator iterator = code.instructionIterator();
     Instruction instruction = iterator.next();
     // A constructor always has the un-initialized object as the first argument.
@@ -290,11 +296,17 @@
         if (instruction.isInvokeDirect() && !seenSuperInvoke) {
           DexMethod target = instruction.asInvokeDirect().getInvokedMethod();
           seenSuperInvoke = appInfo.dexItemFactory.isConstructor(target);
+          boolean callOnSupertypeOfThisInConstructor =
+              methodHolder.isImmediateSubtypeOf(target.holder)
+                  && instruction.asInvokeDirect().getReceiver() == unInitializedObject
+                  && receiverOfInnerCallIsThisOfOuter
+                  && methodIsConstructor;
           if (seenSuperInvoke
               // Calls to init on same class are always OK.
               && target.holder != methodHolder
-              // If we are inlining into a constructor, calls to superclass init are OK.
-              && (!methodHolder.isImmediateSubtypeOf(target.holder) || !methodIsConstructor)) {
+              // If we are inlining into a constructor, calls to superclass init are OK on the
+              // |this| value in the outer context.
+              && !callOnSupertypeOfThisInConstructor) {
             return false;
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 52653a7..bc93fcf 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1018,9 +1018,6 @@
   }
 
   private int getSpillRegister(LiveIntervals intervals) {
-    if (intervals.isArgumentInterval()) {
-      return intervals.getSplitParent().getRegister();
-    }
     int registerNumber = nextUnusedRegisterNumber++;
     maxRegisterNumber = registerNumber;
     if (intervals.getType() == MoveType.WIDE) {
@@ -1572,17 +1569,19 @@
       LiveIntervals unhandledInterval,
       boolean needsRegisterPair,
       int candidate) {
+    List<LiveIntervals> newInactive = new ArrayList<>();
     Iterator<LiveIntervals> inactiveIterator = inactive.iterator();
     while (inactiveIterator.hasNext()) {
       LiveIntervals intervals = inactiveIterator.next();
       if ((intervals.usesRegister(candidate) ||
           (needsRegisterPair && intervals.usesRegister(candidate + 1))) &&
           intervals.overlaps(unhandledInterval)) {
-        // If these assertions trigger we have changed the way blocked parts of intervals
-        // are handled. If we ever get intervals with fixed registers in here, we need
-        // to split them before the first use in the same way that we do when spilling
-        // overlapping active intervals.
-        assert !intervals.isLinked() || intervals.isArgumentInterval();
+        if (intervals.isLinked() && !intervals.isArgumentInterval()) {
+          int nextUsePosition = intervals.firstUseAfter(unhandledInterval.getStart());
+          LiveIntervals split = intervals.splitBefore(nextUsePosition);
+          split.setRegister(intervals.getRegister());
+          newInactive.add(split);
+        }
         if (intervals.getStart() > unhandledInterval.getStart()) {
           // The inactive live intervals hasn't started yet. Clear the temporary register
           // assignment and move back to unhandled for register reassignment.
@@ -1597,6 +1596,7 @@
         }
       }
     }
+    inactive.addAll(newInactive);
   }
 
   private void spillOverlappingActiveIntervals(
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 0d8c6b8..3c2210d 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -88,6 +88,9 @@
   }
 
   public boolean isRematerializable(LinearScanRegisterAllocator registerAllocator) {
+    if (value.isArgument()) {
+      return true;
+    }
     // TODO(ager): rematerialize const string as well.
     if (!value.isConstNumber()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index 4a21e8a..313497c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -26,7 +26,7 @@
     this.dst = dst;
     this.src = LinearScanRegisterAllocator.NO_REGISTER;
     this.type = type;
-    assert definition.isConstInstruction();
+    assert definition.isConstInstruction() || definition.isArgument();
     this.definition = definition;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index 007aa6f..a669737 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -121,8 +121,14 @@
     Instruction instruction;
     Value to = new FixedRegisterValue(move.type, move.dst);
     if (move.definition != null) {
-      ConstNumber number = move.definition.asConstNumber();
-      instruction = new ConstNumber(number.type, to, number.getRawValue());
+      if (move.definition.isArgument()) {
+        int argumentRegister = move.definition.outValue().getLiveIntervals().getRegister();
+        Value from = new FixedRegisterValue(move.type, argumentRegister);
+        instruction = new Move(to, from);
+      } else {
+        ConstNumber number = move.definition.asConstNumber();
+        instruction = new ConstNumber(number.type, to, number.getRawValue());
+      }
     } else {
       Value from = new FixedRegisterValue(move.type, valueMap.get(move.src));
       instruction = new Move(to, from);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index 643a3c1..cd1d880 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -31,8 +31,6 @@
   // The register allocator generating moves.
   private LinearScanRegisterAllocator allocator;
   // All registers below this number are arguments.
-  // TODO(ager): Get rid of this field, we should deal with arguments and other values that can
-  // be rematerialized differently.
   private final int argumentRegisterLimit;
   // Mapping from instruction numbers to the block that start with that instruction if any.
   private final Map<Integer, BasicBlock> blockStartMap = new HashMap<>();
@@ -257,6 +255,10 @@
   // disallowed at this point we know that argument registers do not change value and
   // therefore we don't have to perform spill moves. Performing spill moves will also
   // make art reject the code because it loses type information for the argument.
+  //
+  // TODO(ager): We are dealing with some of these moves as rematerialization. However,
+  // we are still generating actual moves back to the original argument register.
+  // We should get rid of this method and avoid generating the moves in the first place.
   private void removeArgumentRestores(Set<SpillMove> moves) {
     Iterator<SpillMove> moveIterator = moves.iterator();
     while (moveIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 5e7b42d..4dd1880 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -28,6 +28,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -572,5 +573,29 @@
       return Collections
           .unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
     }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append("RootSet");
+
+      builder.append("\nnoShrinking: " + noShrinking.size());
+      builder.append("\nnoOptimization: " + noOptimization.size());
+      builder.append("\nnoObfuscation: " + noObfuscation.size());
+      builder.append("\nreasonAsked: " + reasonAsked.size());
+      builder.append("\nkeepPackageName: " + keepPackageName.size());
+      builder.append("\ncheckDiscarded: " + checkDiscarded.size());
+      builder.append("\nnoSideEffects: " + noSideEffects.size());
+      builder.append("\nassumedValues: " + assumedValues.size());
+      builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
+
+      builder.append("\n\nNo Shrinking:");
+      noShrinking.keySet().stream()
+          .sorted(Comparator.comparing(DexItem::toSourceString))
+          .forEach(a -> builder
+              .append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
+      builder.append("\n");
+      return builder.toString();
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4d707a8..925ba5b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -135,7 +135,7 @@
    */
   protected AndroidApp compileWithR8(List<Class> classes, String proguardConfig)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
-    return compileWithR8(readClasses(classes), writeTextToTempFile(proguardConfig));
+    return compileWithR8(readClasses(classes), proguardConfig);
   }
 
   /**
@@ -170,10 +170,22 @@
   /**
    * Compile an application with R8 using the supplied proguard configuration.
    */
+  protected AndroidApp compileWithR8(AndroidApp app, String proguardConfig)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    return compileWithR8(app, proguardConfig, null);
+  }
+
+  /**
+   * Compile an application with R8 using the supplied proguard configuration.
+   */
   protected AndroidApp compileWithR8(
       AndroidApp app, String proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
-    return compileWithR8(app, writeTextToTempFile(proguardConfig), optionsConsumer);
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(app)
+            .addProguardConfiguration(ImmutableList.of(proguardConfig))
+            .build();
+    return ToolHelper.runR8(command, optionsConsumer);
   }
 
   /**
@@ -194,7 +206,15 @@
    * of the specified class.
    */
   public String keepMainProguardConfiguration(Class clazz) {
-    return "-keep public class " + clazz.getCanonicalName() + " {\n"
+    return keepMainProguardConfiguration(clazz.getCanonicalName());
+  }
+
+  /**
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method
+   * of the specified class.
+   */
+  public String keepMainProguardConfiguration(String clazz) {
+    return "-keep public class " + clazz + " {\n"
         + "  public static void main(java.lang.String[]);\n"
         + "}\n";
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9952f70..4f84303 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -603,13 +603,31 @@
         .toArray(new String[0]));
   }
 
+  public static ProcessResult forkGenerateMainDexList(Path dir, List<String> args1, String... args2)
+      throws IOException, InterruptedException {
+    List<String> args = new ArrayList<>();
+    args.addAll(args1);
+    args.addAll(Arrays.asList(args2));
+    return forkJava(dir, GenerateMainDexList.class, args);
+  }
+
+  public static ProcessResult forkGenerateMainDexList(Path dir, String... args)
+      throws IOException, InterruptedException {
+    return forkJava(dir, GenerateMainDexList.class, args);
+  }
+
   private static ProcessResult forkJava(Path dir, Class clazz, String... args)
       throws IOException, InterruptedException {
+    return forkJava(dir, clazz, Arrays.asList(args));
+  }
+
+  private static ProcessResult forkJava(Path dir, Class clazz, List<String> args)
+      throws IOException, InterruptedException {
     List<String> command = new ImmutableList.Builder<String>()
         .add(getJavaExecutable())
         .add("-cp").add(System.getProperty("java.class.path"))
         .add(clazz.getCanonicalName())
-        .addAll(Arrays.asList(args))
+        .addAll(args)
         .build();
     return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
new file mode 100644
index 0000000..091a43a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
@@ -0,0 +1,120 @@
+// 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress63598979 extends JasminTestBase {
+
+  @Test
+  public void testSimplifyIf() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addStaticMethod("test1", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifne L2",
+        "L1:",
+        "  iconst_1",
+        "  goto L3",
+        "L2:",
+        "  iconst_0",
+        "L3:",
+        "  ireturn");
+
+    clazz.addStaticMethod("test2", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifne L2",
+        "L1:",
+        "  iconst_0",
+        "  goto L3",
+        "L2:",
+        "  iconst_1",
+        "L3:",
+        "  ireturn");
+
+    clazz.addStaticMethod("test3", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifeq L2",
+        "L1:",
+        "  iconst_0",
+        "  goto L3",
+        "L2:",
+        "  iconst_1",
+        "L3:",
+        "  ireturn");
+
+
+    clazz.addStaticMethod("test4", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifeq L2",
+        "L1:",
+        "  iconst_1",
+        "  goto L3",
+        "L2:",
+        "  iconst_0",
+        "L3:",
+        "  ireturn");
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test1(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test1(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test2(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test2(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test3(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test3(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test4(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test4(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  return");
+
+    String expected = runOnJava(builder, clazz.name);
+    String artResult = runOnArtD8(builder, clazz.name);
+    assertEquals(expected, artResult);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index c7e0c18..ba5092f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -9,16 +9,18 @@
 import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
 
 import com.android.tools.r8.CompilationResult;
+import com.android.tools.r8.GenerateMainDexList;
+import com.android.tools.r8.GenerateMainDexListCommand;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
@@ -107,7 +109,6 @@
         mainDexRules,
         expectedMainDexList,
         minSdk,
-        R8Command.builder(),
         (options) -> {
           options.inlineAccessors = false;
         });
@@ -120,48 +121,86 @@
       Path mainDexRules,
       Path expectedMainDexList,
       int minSdk,
-      R8Command.Builder builder,
       Consumer<InternalOptions> optionsConsumer)
       throws Throwable {
     Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
 
     Path inputJar = Paths.get(buildDir, packageName + JAR_EXTENSION);
-    builder.setMinApiLevel(minSdk);
     try {
-      R8Command command = builder
+      // Build main-dex list using GenerateMainDexList.
+      GenerateMainDexListCommand.Builder mdlCommandBuilder = GenerateMainDexListCommand.builder();
+      GenerateMainDexListCommand command2 = mdlCommandBuilder
           .addProgramFiles(inputJar)
-          .addLibraryFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION),
-              Paths.get(ToolHelper.getAndroidJar(minSdk)))
+          .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+          .addMainDexRulesFiles(mainDexRules)
+          .build();
+      List<String> mainDexGeneratorMainDexList =
+          GenerateMainDexList.run(command2).stream()
+              .map(this::mainDexStringToDescriptor)
+              .sorted()
+              .collect(Collectors.toList());
+
+      // Build main-dex list using R8.
+      R8Command.Builder r8CommandBuilder = R8Command.builder();
+      R8Command command = r8CommandBuilder
+          .setMinApiLevel(minSdk)
+          .addProgramFiles(inputJar)
+          .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+          .addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(minSdk)))
           .setOutputPath(out)
           .addMainDexRulesFiles(mainDexRules)
           .build();
       CompilationResult result = ToolHelper.runR8WithFullResult(command, optionsConsumer);
-      List<String> resultMainDexList =
+      List<String> r8MainDexList =
           result.dexApplication.mainDexList.stream()
               .filter(dexType -> isApplicationClass(dexType, result))
               .map(dexType -> dexType.descriptor.toString())
+              .sorted()
               .collect(Collectors.toList());
-      Collections.sort(resultMainDexList);
+
+      // Check that both generated lists are the same as the reference list, except for lambda
+      // classes which are only produced when running R8.
       String[] refList = new String(Files.readAllBytes(
           expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+      int nonLambdaOffset = 0;
       for (int i = 0; i < refList.length; i++) {
         String reference = refList[i].trim();
-        String computed = resultMainDexList.get(i);
-        if (reference.contains("-$$Lambda$")) {
-          // For lambda classes we check that there is a lambda class for the right containing
-          // class. However, we do not check the hash for the generated lambda class. The hash
-          // changes for different compiler versions because different compiler versions generate
-          // different lambda implementation method names.
-          reference = reference.substring(0, reference.lastIndexOf('$'));
-          computed = computed.substring(0, computed.lastIndexOf('$'));
+        checkSameMainDexEntry(reference, r8MainDexList.get(i));
+        // The main dex list generator does not do any lambda desugaring.
+        if (!isLambda(reference)) {
+          checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
+        } else {
+          nonLambdaOffset++;
         }
-        Assert.assertEquals(reference, computed);
       }
     } catch (ExecutionException e) {
       throw e.getCause();
     }
   }
 
+  private boolean isLambda(String mainDexEntry) {
+    return mainDexEntry.contains("-$$Lambda$");
+  }
+
+  private String mainDexStringToDescriptor(String mainDexString) {
+    final String CLASS_EXTENSION = ".class";
+    Assert.assertTrue(mainDexString.endsWith(CLASS_EXTENSION));
+    return DescriptorUtils.getDescriptorFromClassBinaryName(
+        mainDexString.substring(0, mainDexString.length() - CLASS_EXTENSION.length()));
+  }
+
+  private void checkSameMainDexEntry(String reference, String computed) {
+    if (isLambda(reference)) {
+      // For lambda classes we check that there is a lambda class for the right containing
+      // class. However, we do not check the hash for the generated lambda class. The hash
+      // changes for different compiler versions because different compiler versions generate
+      // different lambda implementation method names.
+      reference = reference.substring(0, reference.lastIndexOf('$'));
+      computed = computed.substring(0, computed.lastIndexOf('$'));
+    }
+    Assert.assertEquals(reference, computed);
+  }
+
   private boolean isApplicationClass(DexType dexType, CompilationResult result) {
     DexClass clazz = result.appInfo.definitionFor(dexType);
     return clazz != null && clazz.isProgramClass();
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
new file mode 100644
index 0000000..674124a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -0,0 +1,118 @@
+// 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.smali;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class RemoveWriteOfUnusedFieldsTest extends SmaliTestBase {
+
+  @Test
+  public void unreadStaticFieldsRemoved() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    // All these static fields are set but never read.
+    builder.addStaticField("booleanField", "Z");
+    builder.addStaticField("byteField", "B");
+    builder.addStaticField("shortField", "S");
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("longField", "J");
+    builder.addStaticField("floatField", "F");
+    builder.addStaticField("doubleField", "D");
+    builder.addStaticField("charField", "C");
+    builder.addStaticField("objectField", "Ljava/lang/Object;");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+    builder.addStaticField("testField", "LTest;");
+
+    builder.addStaticMethod("void", "test", ImmutableList.of(),
+        2,
+        "const               v0, 0",
+        "sput-byte           v0, LTest;->booleanField:Z",
+        "sput-byte           v0, LTest;->byteField:B",
+        "sput-short          v0, LTest;->shortField:S",
+        "sput                v0, LTest;->intField:I",
+        "sput                v0, LTest;->floatField:F",
+        "sput-char           v0, LTest;->charField:C",
+        "sput-object         v0, LTest;->objectField:Ljava/lang/Object;",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "sput-object         v0, LTest;->testField:LTest;",
+        "const-wide          v0, 0",
+        "sput-wide           v0, LTest;->longField:J",
+        "sput-wide           v0, LTest;->doubleField:D",
+        "return-void");
+
+    builder.addMainMethod(
+        0,
+        "    invoke-static       { }, LTest;->test()V",
+        "    return-void                             ");
+
+    AndroidApp app = compileWithR8(
+        AndroidApp.fromDexProgramData(builder.compile()),
+        keepMainProguardConfiguration("Test"),
+        options -> options.inlineAccessors = false);
+
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
+    DexCode code = method.getMethod().getCode().asDexCode();
+    assertTrue(code.isEmptyVoidMethod());
+  }
+
+  @Test
+  public void unreadInstanceFieldsRemoved() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    // All these instance fields are set but never read.
+    builder.addInstanceField("booleanField", "Z");
+    builder.addInstanceField("byteField", "B");
+    builder.addInstanceField("shortField", "S");
+    builder.addInstanceField("intField", "I");
+    builder.addInstanceField("longField", "J");
+    builder.addInstanceField("floatField", "F");
+    builder.addInstanceField("doubleField", "D");
+    builder.addInstanceField("charField", "C");
+    builder.addInstanceField("objectField", "Ljava/lang/Object;");
+    builder.addInstanceField("stringField", "Ljava/lang/String;");
+    builder.addInstanceField("testField", "LTest;");
+
+    builder.addInstanceMethod("void", "test", ImmutableList.of(),
+        2,
+        "const               v0, 0",
+        "iput-byte           v0, p0, LTest;->booleanField:Z",
+        "iput-byte           v0, p0, LTest;->byteField:B",
+        "iput-short          v0, p0, LTest;->shortField:S",
+        "iput                v0, p0, LTest;->intField:I",
+        "iput                v0, p0, LTest;->floatField:F",
+        "iput-char           v0, p0, LTest;->charField:C",
+        "iput-object         v0, p0, LTest;->objectField:Ljava/lang/Object;",
+        "iput-object         v0, p0, LTest;->stringField:Ljava/lang/String;",
+        "iput-object         v0, p0, LTest;->testField:LTest;",
+        "const-wide          v0, 0",
+        "iput-wide           v0, p0, LTest;->longField:J",
+        "iput-wide           v0, p0, LTest;->doubleField:D",
+        "return-void");
+
+    builder.addMainMethod(
+        1,
+        "    new-instance         v0, LTest;",
+        "    invoke-virtual       { v0 }, LTest;->test()V",
+        "    return-void                             ");
+
+    AndroidApp app = compileWithR8(
+        AndroidApp.fromDexProgramData(builder.compile()),
+        keepMainProguardConfiguration("Test"),
+        options -> options.inlineAccessors = false);
+
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
+    DexCode code = method.getMethod().getCode().asDexCode();
+    assertTrue(code.isEmptyVoidMethod());
+  }
+}
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 e2006d0..56059f6 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
@@ -235,6 +234,45 @@
       addStaticField(name, type, null);
     }
 
+    public void addInstanceField(String name, String type) {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".field ");
+      builder.append(name);
+      builder.append(":");
+      builder.append(type);
+      getSource(currentClassName).add(builder.toString());
+    }
+
+    private MethodSignature addMethod(String flags, String returnType, String name,
+        List<String> parameters, int locals, String code) {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".method ");
+      if (flags != null && flags.length() > 0) {
+        builder.append(flags);
+        builder.append(" ");
+      }
+      builder.append(name);
+      builder.append("(");
+      for (String parameter : parameters) {
+        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+      }
+      builder.append(")");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
+      builder.append("\n");
+      if (locals >= 0) {
+        builder.append(".locals ");
+        builder.append(locals);
+        builder.append("\n\n");
+        assert code != null;
+        builder.append(code);
+      } else {
+        assert code == null;
+      }
+      builder.append(".end method");
+      getSource(currentClassName).add(builder.toString());
+      return new MethodSignature(currentClassName, name, returnType, parameters);
+    }
+
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String... instructions) {
       StringBuilder builder = new StringBuilder();
@@ -266,43 +304,27 @@
     private MethodSignature addStaticMethod(String flags, String returnType, String name,
         List<String> parameters, int locals, String code) {
       StringBuilder builder = new StringBuilder();
-      builder.append(".method public static ");
-      if (flags != null && flags.length() > 0) {
-        builder.append(flags);
-        builder.append(" ");
-      }
-      builder.append(name);
-      builder.append("(");
-      for (String parameter : parameters) {
-        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
-      }
-      builder.append(")");
-      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
-      builder.append("\n");
-      builder.append(".locals ");
-      builder.append(locals);
-      builder.append("\n\n");
-      builder.append(code);
-      builder.append(".end method");
-      getSource(currentClassName).add(builder.toString());
-      return new MethodSignature(currentClassName, name, returnType, parameters);
+      return addMethod("public static " + flags, returnType, name, parameters, locals, code);
     }
 
     public MethodSignature addAbstractMethod(
         String returnType, String name, List<String> parameters) {
+      return addMethod("public abstract", returnType, name, parameters, -1, null);
+    }
+
+    public MethodSignature addInstanceMethod(String returnType, String name, List<String> parameters,
+        int locals, String... instructions) {
       StringBuilder builder = new StringBuilder();
-      builder.append(".method public abstract ");
-      builder.append(name);
-      builder.append("(");
-      for (String parameter : parameters) {
-        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+      for (String instruction : instructions) {
+        builder.append(instruction);
+        builder.append("\n");
       }
-      builder.append(")");
-      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
-      builder.append("\n");
-      builder.append(".end method");
-      getSource(currentClassName).add(builder.toString());
-      return new MethodSignature(currentClassName, name, returnType, parameters);
+      return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
+    }
+
+    public MethodSignature addInstanceMethod(String returnType, String name, List<String> parameters,
+        int locals, String code) {
+      return addMethod("public", returnType, name, parameters, locals, code);
     }
 
     public MethodSignature addMainMethod(int locals, String... instructions) {
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
new file mode 100644
index 0000000..64e7a5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -0,0 +1,144 @@
+// 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.JAR_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.ToolHelper;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public class GenerateMainDexListCommandTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void emptyCommand() throws Throwable {
+    verifyEmptyCommand(GenerateMainDexListCommand.builder().build());
+    verifyEmptyCommand(parse());
+    verifyEmptyCommand(parse(""));
+    verifyEmptyCommand(parse("", ""));
+    verifyEmptyCommand(parse(" "));
+    verifyEmptyCommand(parse(" ", " "));
+    verifyEmptyCommand(parse("\t"));
+    verifyEmptyCommand(parse("\t", "\t"));
+  }
+
+  private void verifyEmptyCommand(GenerateMainDexListCommand command) throws IOException {
+    assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
+    assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
+    assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
+  }
+
+  // Add the jars used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
+  private void addInputJarsToCommandLine(List<String> args) {
+    args.add(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "multidex001" + JAR_EXTENSION)
+        .toAbsolutePath().toString());
+    args.add(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION)
+        .toAbsolutePath().toString());
+  }
+
+  // Add main-dex rules used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
+  private void addMainDexRuleToCommandLine(List<String> args) {
+    args.add("--main-dex-rules");
+    args.add(Paths.get(ToolHelper.EXAMPLES_DIR, "multidex", "main-dex-rules.txt")
+        .toAbsolutePath().toString());
+  }
+
+  @Test
+  public void defaultOutIsCwd() throws Throwable {
+    Path working = temp.getRoot().toPath();
+    String mainDexListOutput = "main-dex-list.txt";
+    Path output = working.resolve(mainDexListOutput);
+    assertFalse(Files.exists(output));
+    List<String> args = new ArrayList<>();
+    addInputJarsToCommandLine(args);
+    addMainDexRuleToCommandLine(args);
+    assertEquals(0, ToolHelper.forkGenerateMainDexList(
+        working, args, "--main-dex-list-output", mainDexListOutput).exitCode);
+    assertTrue(Files.exists(output));
+    assertTrue(Files.size(output) > 0);
+  }
+
+  @Test
+  public void validOutputPath() throws Throwable {
+    Path existingFile = temp.getRoot().toPath().resolve("existing_output");
+    try (OutputStream existingFileOut = Files.newOutputStream(existingFile,
+        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+      PrintWriter writer = new PrintWriter(existingFileOut);
+      writer.println("Hello, world!");
+      writer.flush();
+    }
+    Path nonExistingFile = temp.getRoot().toPath().resolve("non_existing_output");
+    assertEquals(
+        existingFile,
+        GenerateMainDexListCommand.builder().setMainDexListOutputPath(existingFile).build()
+            .getMainDexListOutputPath());
+    assertEquals(
+        nonExistingFile,
+        GenerateMainDexListCommand.builder().setMainDexListOutputPath(nonExistingFile).build()
+            .getMainDexListOutputPath());
+    assertEquals(
+        existingFile,
+        parse("--main-dex-list-output", existingFile.toString()).getMainDexListOutputPath());
+    assertEquals(
+        nonExistingFile,
+        parse("--main-dex-list-output", nonExistingFile.toString()).getMainDexListOutputPath());
+  }
+
+  @Test
+  public void nonExistingOutputFileInNonExistingDir() throws Throwable {
+    Path nonExistingFileInNonExistingDir =
+        temp.getRoot().toPath().resolve("a/path/that/does/not/exist");
+    assertEquals(
+        nonExistingFileInNonExistingDir,
+        GenerateMainDexListCommand.builder()
+            .setMainDexListOutputPath(nonExistingFileInNonExistingDir).build()
+            .getMainDexListOutputPath());
+    assertEquals(
+        nonExistingFileInNonExistingDir,
+        parse("--main-dex-list-output",
+            nonExistingFileInNonExistingDir.toString()).getMainDexListOutputPath());
+  }
+
+  @Test
+  public void mainDexRules() throws Throwable {
+    Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
+    Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
+    parse("--main-dex-rules", mainDexRules1.toString());
+    parse(
+        "--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
+  }
+
+  @Test
+  public void mainDexList() throws Throwable {
+    Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+    Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+    parse("--main-dex-list", mainDexList1.toString());
+    parse("--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
+  }
+
+  private GenerateMainDexListCommand parse(String... args) throws Throwable {
+    return GenerateMainDexListCommand.parse(args).build();
+  }
+}
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
index 942c691..f9d8e24 100644
--- a/third_party/kotlin.tar.gz.sha1
+++ b/third_party/kotlin.tar.gz.sha1
@@ -1 +1 @@
-21b6244cb0a5bca1f1d046d00540a610c02cd714
\ No newline at end of file
+28f3708659a1daa879bc8c707da54e3617a2070f
\ No newline at end of file
diff --git a/tools/notify.py b/tools/notify.py
new file mode 100644
index 0000000..f7ae50e
--- /dev/null
+++ b/tools/notify.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# 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.
+
+try:
+  import gi
+  from gi.repository import Notify
+  Notify.init("R8 build tools")
+
+  def notify(message):
+    try:
+      Notify.Notification.new("R8 build tools", message).show()
+    except:
+      return
+
+except ImportError:
+  def notify(message):
+    return
diff --git a/tools/test.py b/tools/test.py
index ab4b0d6..c0a1e02 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -14,6 +14,7 @@
 import sys
 import utils
 import uuid
+import notify
 
 ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1"]
 BUCKET = 'r8-test-results'
@@ -149,4 +150,9 @@
       return return_code
 
 if __name__ == '__main__':
-  sys.exit(Main())
+  return_code = Main()
+  if return_code != 0:
+    notify.notify("Tests failed.")
+  else:
+    notify.notify("Tests passed.")
+  sys.exit(return_code)