Merge "Add tests for (package-)naming."
diff --git a/build.gradle b/build.gradle
index 67d455a..e2c21b6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -101,6 +101,7 @@
 
 dependencies {
     compile 'net.sf.jopt-simple:jopt-simple:4.6'
+    compile 'com.googlecode.json-simple:json-simple:1.1.1'
     compile group: 'com.google.guava', name: 'guava', version: '19.0'
     compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
     compile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
@@ -397,6 +398,20 @@
     }
 }
 
+task ExtractMarker(type: Jar) {
+    from sourceSets.main.output
+    baseName 'extractmarker'
+    manifest {
+      attributes 'Main-Class': 'com.android.tools.r8.ExtractMarker'
+    }
+    // In order to build without dependencies, pass the exclude_deps property using:
+    // gradle -Pexclude_deps ExtractMarker
+    if (!project.hasProperty('exclude_deps')) {
+        // Also include dependencies
+        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+    }
+}
+
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
@@ -534,7 +549,7 @@
         classpath = sourceSets.examples.compileClasspath
         sourceCompatibility = JavaVersion.VERSION_1_7
         targetCompatibility = JavaVersion.VERSION_1_7
-        options.compilerArgs += ["-Xlint:-options"]
+        options.compilerArgs = ["-Xlint:none"]
     }
     examplesDir.eachDir { dir ->
         def name = dir.getName();
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 5eb67c4..430624b 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -219,9 +219,53 @@
       return self();
     }
 
-    /** Set the main-dex list file. */
-    public B setMainDexListFile(Path file) {
-      app.setMainDexListFile(file);
+    /**
+     * Add main-dex list files.
+     *
+     * Each line in each of the files 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".
+     */
+    public B addMainDexListFiles(Path... files) throws IOException {
+      app.addMainDexListFiles(files);
+      return self();
+    }
+
+    /**
+     * Add main-dex list files.
+     *
+     * @see #addMainDexListFiles(Path...)
+     */
+    public B addMainDexListFiles(Collection<Path> files) throws IOException {
+      app.addMainDexListFiles(files);
+      return self();
+    }
+
+    /**
+     * Add main-dex classes.
+     *
+     * Add classes to keep in the primary dex file (<code>classes.dex</code>).
+     *
+     * NOTE: The name of the classes is specified using the Java fully qualified names format
+     * (e.g. "com.example.MyClass"), and <i>not</i> the format used by the main-dex list file.
+     */
+    public B addMainDexClasses(String... classes) {
+      app.addMainDexClasses(classes);
+      return self();
+    }
+
+    /**
+     * Add main-dex classes.
+     *
+     * Add classes to keep in the primary dex file (<code>classes.dex</code>).
+     *
+     * NOTE: The name of the classes is specified using the Java fully qualified names format
+     * (e.g. "com.example.MyClass"), and <i>not</i> the format used by the main-dex list file.
+     */
+    public B addMainDexClasses(Collection<String> classes) {
+      app.addMainDexClasses(classes);
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index bca7a9f..a6da2ec 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -7,6 +7,8 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.MainDexError;
 import com.android.tools.r8.graph.AppInfo;
@@ -152,6 +154,14 @@
     }
   }
 
+  // Compute the marker to be placed in the main dex file.
+  private static Marker getMarker(InternalOptions options) {
+    if (options.customizedMarker != null) {
+      return options.customizedMarker;
+    }
+    return new Marker(Tool.D8);
+  }
+
   private static CompilationResult runForTesting(
       AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
     try {
@@ -174,11 +184,11 @@
         options.methodsFilter.forEach((m) -> System.out.println("  - " + m));
         return null;
       }
-
+      Marker marker = getMarker(options);
       CompilationResult output =
           new CompilationResult(
               new ApplicationWriter(
-                  app, appInfo, options, null, NamingLens.getIdentityLens(), null)
+                  app, appInfo, options, marker, null, NamingLens.getIdentityLens(), null)
                   .write(null, executor),
               app,
               appInfo);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b856556..2937205 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -131,7 +131,6 @@
   public static Builder parse(String[] args) throws CompilationException, IOException {
     CompilationMode modeSet = null;
     Path outputPath = null;
-    String mainDexList = null;
     Builder builder = builder();
     try {
       for (int i = 0; i < args.length; i++) {
@@ -168,11 +167,7 @@
         } else if (arg.equals("--classpath")) {
           builder.addClasspathFiles(Paths.get(args[++i]));
         } else if (arg.equals("--main-dex-list")) {
-          if (mainDexList != null) {
-            throw new CompilationException("Only one --main-dex-list supported");
-          }
-          mainDexList = args[++i];
-          builder.setMainDexListFile(Paths.get(mainDexList));
+          builder.addMainDexListFiles(Paths.get(args[++i]));
         } else if (arg.equals("--min-api")) {
           builder.setMinApiLevel(Integer.valueOf(args[++i]));
         } else if (arg.equals("--intermediate")) {
@@ -210,6 +205,7 @@
     InternalOptions internal = new InternalOptions(new DexItemFactory());
     assert !internal.debug;
     internal.debug = getMode() == CompilationMode.DEBUG;
+    internal.minimalMainDex = internal.debug;
     internal.minApiLevel = getMinApiLevel();
     internal.intermediate = intermediate;
     // Assert and fixup defaults.
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
new file mode 100644
index 0000000..c1234b8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2017, the Rex 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.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.shaking.ProguardRuleParserException;
+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 java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+
+public class ExtractMarker {
+  private static class Command extends BaseCommand {
+
+    public static class Builder
+        extends BaseCommand.Builder<ExtractMarker.Command, ExtractMarker.Command.Builder> {
+
+      private Builder() {
+        super(CompilationMode.RELEASE);
+      }
+
+      @Override
+      ExtractMarker.Command.Builder self() {
+        return this;
+      }
+
+      @Override
+      public ExtractMarker.Command build() throws CompilationException, IOException {
+        // If printing versions ignore everything else.
+        if (isPrintHelp()) {
+          return new ExtractMarker.Command(isPrintHelp());
+        }
+        validate();
+        return new ExtractMarker.Command(
+            getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+      }
+    }
+
+    static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+        "Usage: extractmarker [options] <input-files>",
+        " where <input-files> are dex files",
+        "  --version               # Print the version of r8.",
+        "  --help                  # Print this message."));
+
+    public static ExtractMarker.Command.Builder builder() {
+      return new ExtractMarker.Command.Builder();
+    }
+
+    public static ExtractMarker.Command.Builder parse(String[] args)
+        throws CompilationException, IOException {
+      ExtractMarker.Command.Builder builder = builder();
+      parse(args, builder);
+      return builder;
+    }
+
+    private static void parse(String[] args, ExtractMarker.Command.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.startsWith("--")) {
+            throw new CompilationException("Unknown option: " + arg);
+          }
+          builder.addProgramFiles(Paths.get(arg));
+        }
+      }
+    }
+
+    private Command(
+        AndroidApp inputApp,
+        Path outputPath,
+        OutputMode outputMode,
+        CompilationMode mode,
+        int minApiLevel) {
+      super(inputApp, outputPath, outputMode, mode, minApiLevel);
+    }
+
+    private Command(boolean printHelp) {
+      super(printHelp, false);
+    }
+
+    @Override
+    InternalOptions getInternalOptions() {
+      return new InternalOptions();
+    }
+  }
+
+  public static void main(String[] args)
+      throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+    ExtractMarker.Command.Builder builder = ExtractMarker.Command.parse(args);
+    ExtractMarker.Command command = builder.build();
+    if (command.isPrintHelp()) {
+      System.out.println(ExtractMarker.Command.USAGE_MESSAGE);
+      return;
+    }
+    AndroidApp app = command.getInputApp();
+    DexApplication dexApp =
+        new ApplicationReader(app, new InternalOptions(), new Timing("ExtractMarker")).read();
+    Marker readMarker = dexApp.dexItemFactory.extractMarker();
+    if (readMarker == null) {
+      System.out.println("D8/R8 marker not found.");
+      System.exit(1);
+    } else {
+      System.out.println(readMarker.toString());
+      System.exit(0);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 50f79dc..4393a3e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.MainDexError;
 import com.android.tools.r8.graph.AppInfo;
@@ -77,6 +79,14 @@
     options.itemFactory.resetSortedIndices();
   }
 
+  // Compute the marker to be placed in the main dex file.
+  private static Marker getMarker(InternalOptions options) {
+    if (options.customizedMarker != null) {
+      return options.customizedMarker;
+    }
+    return new Marker(Tool.R8);
+  }
+
   public static AndroidApp writeApplication(
       ExecutorService executorService,
       DexApplication application,
@@ -88,8 +98,9 @@
       InternalOptions options)
       throws ExecutionException {
     try {
+      Marker marker = getMarker(options);
       return new ApplicationWriter(
-          application, appInfo, options, deadCode, namingLens, proguardSeedsData)
+          application, appInfo, options, marker, deadCode, namingLens, proguardSeedsData)
           .write(packageDistribution, executorService);
     } catch (IOException e) {
       throw new RuntimeException("Cannot write dex application", e);
@@ -432,7 +443,7 @@
         outputApp.writeProguardSeeds(closer, seedsOut);
       }
     }
-    if (options.printMainDexList && outputApp.hasMainDexList()) {
+    if (outputApp.hasMainDexListOutput()) {
       try (Closer closer = Closer.create()) {
         OutputStream mainDexOut =
             FileUtils.openPathWithDefault(
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d3d937..241746b 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -26,7 +26,7 @@
   public static class Builder extends BaseCommand.Builder<R8Command, Builder> {
 
     private final List<Path> mainDexRules = new ArrayList<>();
-    private boolean minimalMainDex = false;
+    private Path mainDexListOutput = null;
     private final List<Path> proguardConfigFiles = new ArrayList<>();
     private Optional<Boolean> treeShaking = Optional.empty();
     private Optional<Boolean> minification = Optional.empty();
@@ -77,14 +77,8 @@
       return self();
     }
 
-    /**
-     * Request minimal main dex generated when main dex rules are used.
-     *
-     * The main purpose of this is to verify that the main dex rules are sufficient
-     * for running on a platform without native multi dex support.
-     */
-    public Builder setMinimalMainDex(boolean value) {
-      minimalMainDex = value;
+    public Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
+      mainDexListOutput = mainDexListOutputPath;
       return self();
     }
 
@@ -132,9 +126,9 @@
 
     protected void validate() throws CompilationException {
       super.validate();
-      if (minimalMainDex && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
+      if (mainDexListOutput != null && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
         throw new CompilationException(
-            "Option --minimal-main-dex require --main-dex-rules");
+            "Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
       }
     }
 
@@ -182,7 +176,7 @@
           getOutputPath(),
           getOutputMode(),
           mainDexKeepRules,
-          minimalMainDex,
+          mainDexListOutput,
           configuration,
           getMode(),
           getMinApiLevel(),
@@ -215,13 +209,13 @@
       "  --no-minification        # Force disable minification of names.",
       "  --main-dex-rules <file>  # Proguard keep rules for classes to place in the",
       "                           # primary dex file.",
-      "  --minimal-main-dex       # Only place classes specified by --main-dex-rules",
-      "                           # 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 of r8.",
       "  --help                   # Print this message."));
 
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
-  private final boolean minimalMainDex;
+  private final Path mainDexListOutput;
   private final ProguardConfiguration proguardConfiguration;
   private final boolean useTreeShaking;
   private final boolean useMinification;
@@ -285,8 +279,10 @@
         builder.setMinification(false);
       } else if (arg.equals("--main-dex-rules")) {
         builder.addMainDexRules(Paths.get(args[++i]));
-      } else if (arg.equals("--minimal-main-dex")) {
-        builder.setMinimalMainDex(true);
+      } 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.equals("--pg-conf")) {
         builder.addProguardConfigurationFiles(Paths.get(args[++i]));
       } else if (arg.equals("--pg-map")) {
@@ -328,7 +324,7 @@
       Path outputPath,
       OutputMode outputMode,
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
-      boolean minimalMainDex,
+      Path mainDexListOutput,
       ProguardConfiguration proguardConfiguration,
       CompilationMode mode,
       int minApiLevel,
@@ -340,7 +336,7 @@
     assert mainDexKeepRules != null;
     assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
     this.mainDexKeepRules = mainDexKeepRules;
-    this.minimalMainDex = minimalMainDex;
+    this.mainDexListOutput = mainDexListOutput;
     this.proguardConfiguration = proguardConfiguration;
     this.useTreeShaking = useTreeShaking;
     this.useMinification = useMinification;
@@ -350,7 +346,7 @@
   private R8Command(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
     mainDexKeepRules = ImmutableList.of();
-    minimalMainDex = false;
+    mainDexListOutput = null;
     proguardConfiguration = null;
     useTreeShaking = false;
     useMinification = false;
@@ -409,7 +405,10 @@
     internal.classObfuscationDictionary = proguardConfiguration.getClassObfuscationDictionary();
     internal.obfuscationDictionary = proguardConfiguration.getObfuscationDictionary();
     internal.mainDexKeepRules = mainDexKeepRules;
-    internal.minimalMainDex = minimalMainDex;
+    internal.minimalMainDex = internal.debug;
+    if (mainDexListOutput != null) {
+      internal.printMainDexListFile = mainDexListOutput;
+    }
     internal.keepRules = proguardConfiguration.getRules();
     internal.dontWarnPatterns = proguardConfiguration.getDontWarnPatterns();
     internal.outputMode = getOutputMode();
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 4fe3773..ee4477d 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -177,7 +177,7 @@
       throws IOException, ExecutionException {
     InternalOptions options = new InternalOptions();
     AppInfo appInfo = new AppInfo(app);
-    ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null);
+    ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null, null);
     AndroidApp outApp = writer.write(null, executor);
     outApp.writeToDirectory(output, OutputMode.Indexed);
   }
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index 840efae..e5d2952 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -10,7 +10,7 @@
 
 abstract class Format10t extends Base1Format {
 
-  public final /* offset */ byte AA;
+  public /* offset */ byte AA;
 
   // +AA | op
   Format10t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index 89d740b..b3e3627 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -10,7 +10,7 @@
 
 abstract class Format20t extends Base2Format {
 
-  public final /* offset */ short AAAA;
+  public /* offset */ short AAAA;
 
   // øø | op | +AAAA
   Format20t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 12b752b..26f7650 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -11,10 +11,10 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
-abstract class Format21t extends Base2Format {
+public abstract class Format21t extends Base2Format {
 
   public final short AA;
-  public final /* offset */ short BBBB;
+  public /* offset */ short BBBB;
 
   // AA | op | +BBBB
   Format21t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 6cd51d3..3ff5190 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -11,11 +11,11 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
-abstract class Format22t extends Base2Format {
+public abstract class Format22t extends Base2Format {
 
   public final byte A;
   public final byte B;
-  public final /* offset */ short CCCC;
+  public /* offset */ short CCCC;
 
   // vB | vA | op | +CCCC
   Format22t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22x.java b/src/main/java/com/android/tools/r8/code/Format22x.java
index bca2636..fba570b 100644
--- a/src/main/java/com/android/tools/r8/code/Format22x.java
+++ b/src/main/java/com/android/tools/r8/code/Format22x.java
@@ -46,11 +46,11 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", v" + BBBB);
+    return formatString("v" + AA + ", v" + (int)BBBB);
   }
 
   public String toSmaliString(ClassNameMapper naming) {
-    return formatSmaliString("v" + AA + ", v" + BBBB);
+    return formatSmaliString("v" + AA + ", v" + (int)BBBB);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index 927c879..77cb49d 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -10,7 +10,7 @@
 
 abstract class Format30t extends Base3Format {
 
-  public final /* offset */ int AAAAAAAA;
+  public /* offset */ int AAAAAAAA;
 
   // øø | op | AAAAlo | AAAAhi
   Format30t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 6bbb406..6750f4c 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -450,13 +450,15 @@
     ExecutorService executor = ThreadUtils.getExecutorService(numberOfThreads);
     D8Output result;
     try {
-       result = D8.run(
-          D8Command.builder()
-              .addProgramFiles(inputs)
-              .setMode(mode)
-              .setMinApiLevel(dexArgs.minApiLevel)
-              .setMainDexListFile(mainDexList)
-              .build());
+      D8Command.Builder builder = D8Command.builder();
+      builder
+          .addProgramFiles(inputs)
+          .setMode(mode)
+          .setMinApiLevel(dexArgs.minApiLevel);
+      if (mainDexList != null) {
+        builder.addMainDexListFiles(mainDexList);
+      }
+      result = D8.run(builder.build());
     } finally {
       executor.shutdown();
     }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 355080f..2553650 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ClassProvider;
 import com.android.tools.r8.utils.ClasspathClassCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LibraryClassCollection;
 import com.android.tools.r8.utils.MainDexList;
@@ -45,6 +46,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.stream.Collectors;
 
 public class ApplicationReader {
 
@@ -149,11 +151,18 @@
     if (inputApp.hasMainDexList()) {
       futures.add(executorService.submit(() -> {
         try {
-          InputStream input = inputApp.getMainDexList(closer);
-          builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+          for (Resource resource : inputApp.getMainDexListResources()) {
+            InputStream input = closer.register(resource.getStream());
+            builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+          }
         } catch (IOException e) {
           throw new RuntimeException(e);
         }
+        builder.addToMainDexList(
+            inputApp.getMainDexClasses()
+                .stream()
+                .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
+                .collect(Collectors.toList()));
       }));
     }
   }
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 5613169..e6ec968 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
@@ -43,6 +44,7 @@
   public final NamingLens namingLens;
   public final byte[] proguardSeedsData;
   public final InternalOptions options;
+  public DexString markerString;
 
   private static class SortAnnotations extends MixedSectionCollection {
 
@@ -105,6 +107,7 @@
       DexApplication application,
       AppInfo appInfo,
       InternalOptions options,
+      Marker marker,
       byte[] deadCode,
       NamingLens namingLens,
       byte[] proguardSeedsData) {
@@ -113,6 +116,9 @@
     this.appInfo = appInfo;
     assert options != null;
     this.options = options;
+    this.markerString = (marker == null)
+        ? null
+        : application.dexItemFactory.createString(marker.toString());
     this.deadCode = deadCode;
     this.namingLens = namingLens;
     this.proguardSeedsData = proguardSeedsData;
@@ -123,6 +129,8 @@
     application.timing.begin("DexApplication.write");
     try {
       application.dexItemFactory.sort(namingLens);
+      assert this.markerString == null || application.dexItemFactory.extractMarker() != null;
+
       SortAnnotations sortAnnotations = new SortAnnotations();
       application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
 
@@ -148,7 +156,8 @@
         distributor =
             new VirtualFile.PackageMapDistributor(this, packageDistribution, executorService);
       } else {
-        distributor = new VirtualFile.FillFilesDistributor(this, options.minimalMainDex);
+        boolean minimal = options.minimalMainDex && !application.mainDexList.isEmpty();
+        distributor = new VirtualFile.FillFilesDistributor(this, minimal);
       }
       Map<Integer, VirtualFile> newFiles = distributor.run();
 
@@ -186,7 +195,7 @@
       }
       byte[] mainDexList = writeMainDexList();
       if (mainDexList != null) {
-        builder.setMainDexListData(mainDexList);
+        builder.setMainDexListOutputData(mainDexList);
       }
       return builder.build();
     } finally {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 332f292..a8f56a7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -5,8 +5,6 @@
 
 import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
 
-import com.google.common.collect.Sets;
-
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
@@ -43,7 +41,6 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramClassVisitor;
-import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -51,12 +48,11 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LebUtils;
-
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-
 import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -156,14 +152,16 @@
     return this;
   }
 
-  private void rewriteCodeWithJumboStrings(IRConverter converter, DexEncodedMethod method) {
+  private void rewriteCodeWithJumboStrings(DexEncodedMethod method) {
     if (method.getCode() == null) {
       return;
     }
     DexCode code = method.getCode().asDexCode();
     if (code.highestSortingString != null) {
       if (mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
-        converter.processJumboStrings(method, mapping.getFirstJumboString());
+        JumboStringRewriter rewriter =
+            new JumboStringRewriter(method, mapping.getFirstJumboString(), options.itemFactory);
+        rewriter.rewrite();
       }
     }
   }
@@ -179,9 +177,8 @@
       return this;
     }
     // At least one method needs a jumbo string.
-    IRConverter converter = new IRConverter(application, appInfo, options, false);
     for (DexProgramClass clazz : classes) {
-      clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(converter, method));
+      clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(method));
     }
     return this;
   }
@@ -297,7 +294,7 @@
       }
 
     } else {
-      if (method.accessFlags.isConstructor()) {
+      if (method.isInstanceInitializer()) {
         throw new CompilationError(
             "Interface must not have constructors: " + method.method.toSourceString());
       }
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
new file mode 100644
index 0000000..4b97c51
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -0,0 +1,568 @@
+// 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.dex;
+
+import static com.android.tools.r8.graph.DexCode.TryHandler.NO_HANDLER;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.FillArrayDataPayload;
+import com.android.tools.r8.code.Format21t;
+import com.android.tools.r8.code.Format22t;
+import com.android.tools.r8.code.Format31t;
+import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.Goto16;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfGe;
+import com.android.tools.r8.code.IfGez;
+import com.android.tools.r8.code.IfGt;
+import com.android.tools.r8.code.IfGtz;
+import com.android.tools.r8.code.IfLe;
+import com.android.tools.r8.code.IfLez;
+import com.android.tools.r8.code.IfLt;
+import com.android.tools.r8.code.IfLtz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.SwitchPayload;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class JumboStringRewriter {
+
+  private static class TryTargets {
+    private Instruction start;
+    private Instruction end;
+    private boolean endsAfterLastInstruction;
+
+    TryTargets(Instruction start, Instruction end, boolean endsAfterLastInstruction) {
+      assert start != null;
+      assert end != null;
+      this.start = start;
+      this.end = end;
+      this.endsAfterLastInstruction = endsAfterLastInstruction;
+    }
+
+    void replaceTarget(Instruction target, Instruction newTarget) {
+      if (start == target) {
+        start = newTarget;
+      }
+      if (end == target) {
+        end = newTarget;
+      }
+    }
+
+    int getStartOffset() {
+      return start.getOffset();
+    }
+
+    int getStartToEndDelta() {
+      if (endsAfterLastInstruction) {
+        return end.getOffset() + end.getSize() - start.getOffset();
+      }
+      return end.getOffset() - start.getOffset();
+    }
+  }
+
+  private final DexEncodedMethod method;
+  private final DexString firstJumboString;
+  private final DexItemFactory factory;
+  private final Map<Instruction, List<Instruction>> instructionTargets = new IdentityHashMap<>();
+  private final Int2ReferenceMap<Instruction> debugEventTargets
+      = new Int2ReferenceOpenHashMap<>();
+  private final Map<Instruction, Instruction> payloadToSwitch = new IdentityHashMap<>();
+  private final Map<Try, TryTargets> tryTargets = new IdentityHashMap<>();
+  private final Map<TryHandler, List<Instruction>> handlerTargets = new IdentityHashMap<>();
+
+  public JumboStringRewriter(
+      DexEncodedMethod method, DexString firstJumboString, DexItemFactory factory) {
+    this.method = method;
+    this.firstJumboString = firstJumboString;
+    this.factory = factory;
+  }
+
+  public void rewrite() {
+    // Build maps from everything in the code that uses offsets or direct addresses to reference
+    // instructions to the actual instruction referenced.
+    recordTargets();
+    // Expand the code by rewriting jumbo strings and branching instructions.
+    List<Instruction> newInstructions = expandCode();
+    // Commit to the new instruction offsets and update instructions, try-catch structures
+    // and debug info with the new offsets.
+    rewriteInstructionOffsets(newInstructions);
+    Try[] newTries = rewriteTryOffsets();
+    TryHandler[] newHandlers = rewriteHandlerOffsets();
+    DexDebugInfo newDebugInfo = rewriteDebugInfoOffsets();
+    // Set the new code on the method.
+    DexCode code = method.getCode().asDexCode();
+    method.setDexCode(new DexCode(
+        code.registerSize,
+        code.incomingRegisterSize,
+        code.outgoingRegisterSize,
+        newInstructions.toArray(new Instruction[newInstructions.size()]),
+        newTries,
+        newHandlers,
+        newDebugInfo,
+        code.highestSortingString));
+  }
+
+  private void rewriteInstructionOffsets(List<Instruction> instructions) {
+    for (Instruction instruction : instructions) {
+      if (instruction instanceof Format22t) {  // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+        Format22t condition = (Format22t) instruction;
+        int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+        assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+        condition.CCCC = (short) offset;
+      } else if (instruction instanceof Format21t) {  // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+        Format21t condition = (Format21t) instruction;
+        int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+        assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+        condition.BBBB = (short) offset;
+      } else if (instruction instanceof Goto) {
+        Goto jump = (Goto) instruction;
+        int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+        assert Byte.MIN_VALUE <= offset && offset <= Byte.MAX_VALUE;
+        jump.AA = (byte) offset;
+      } else if (instruction instanceof Goto16) {
+        Goto16 jump = (Goto16) instruction;
+        int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+        assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+        jump.AAAA = (short) offset;
+      } else if (instruction instanceof Goto32) {
+        Goto32 jump = (Goto32) instruction;
+        int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+        jump.AAAAAAAA = offset;
+      } else if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+        Format31t payloadUser = (Format31t) instruction;
+        int offset =
+            instructionTargets.get(payloadUser).get(0).getOffset() - instruction.getOffset();
+        payloadUser.setPayloadOffset(offset);
+      } else if (instruction instanceof SwitchPayload) {
+        SwitchPayload payload = (SwitchPayload) instruction;
+        Instruction switchInstruction = payloadToSwitch.get(payload);
+        List<Instruction> switchTargets = instructionTargets.get(payload);
+        int[] targets = payload.switchTargetOffsets();
+        for (int i = 0; i < switchTargets.size(); i++) {
+          Instruction target = switchTargets.get(i);
+          targets[i] = target.getOffset() - switchInstruction.getOffset();
+        }
+      }
+    }
+  }
+
+  private Try[] rewriteTryOffsets() {
+    DexCode code = method.getCode().asDexCode();
+    Try[] result = new Try[code.tries.length];
+    for (int i = 0; i < code.tries.length; i++) {
+      Try theTry = code.tries[i];
+      TryTargets targets = tryTargets.get(theTry);
+      result[i] = new Try(targets.getStartOffset(), targets.getStartToEndDelta(), -1);
+      result[i].handlerIndex = theTry.handlerIndex;
+    }
+    return result;
+  }
+
+  private TryHandler[] rewriteHandlerOffsets() {
+    DexCode code = method.getCode().asDexCode();
+    if (code.handlers == null) {
+      return null;
+    }
+    TryHandler[] result = new TryHandler[code.handlers.length];
+    for (int i = 0; i < code.handlers.length; i++) {
+      TryHandler handler = code.handlers[i];
+      List<Instruction> targets = handlerTargets.get(handler);
+      Iterator<Instruction> it = targets.iterator();
+      int catchAllAddr = NO_HANDLER;
+      if (handler.catchAllAddr != NO_HANDLER) {
+        catchAllAddr = it.next().getOffset();
+      }
+      TypeAddrPair[] newPairs = new TypeAddrPair[handler.pairs.length];
+      for (int j = 0; j < handler.pairs.length; j++) {
+        TypeAddrPair pair = handler.pairs[j];
+        newPairs[j] = new TypeAddrPair(pair.type, it.next().getOffset());
+      }
+      result[i] = new TryHandler(newPairs, catchAllAddr);
+    }
+    return result;
+  }
+
+  private DexDebugInfo rewriteDebugInfoOffsets() {
+    DexCode code = method.getCode().asDexCode();
+    if (debugEventTargets.size() != 0) {
+      int lastOriginalOffset = 0;
+      int lastNewOffset = 0;
+      List<DexDebugEvent> events = new ArrayList<>();
+      for (DexDebugEvent event : code.getDebugInfo().events) {
+        if (event instanceof AdvancePC) {
+          AdvancePC advance = (AdvancePC) event;
+          lastOriginalOffset += advance.delta;
+          Instruction target = debugEventTargets.get(lastOriginalOffset);
+          int pcDelta = target.getOffset() - lastNewOffset;
+          addAdvancementEvents(0, pcDelta, events);
+          lastNewOffset = target.getOffset();
+        } else if (event instanceof Default) {
+          Default defaultEvent = (Default) event;
+          lastOriginalOffset += defaultEvent.getPCDelta();
+          Instruction target = debugEventTargets.get(lastOriginalOffset);
+          int lineDelta = defaultEvent.getLineDelta();
+          int pcDelta = target.getOffset() - lastNewOffset;
+          addAdvancementEvents(lineDelta, pcDelta, events);
+          lastNewOffset = target.getOffset();
+        } else {
+          events.add(event);
+        }
+      }
+      return new DexDebugInfo(
+          code.getDebugInfo().startLine,
+          code.getDebugInfo().parameters,
+          events.toArray(new DexDebugEvent[events.size()]));
+    }
+    return code.getDebugInfo();
+  }
+
+  private void addAdvancementEvents(int lineDelta, int pcDelta, List<DexDebugEvent> events) {
+    if (lineDelta < Constants.DBG_LINE_BASE
+        || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
+      events.add(factory.createAdvanceLine(lineDelta));
+      lineDelta = 0;
+      if (pcDelta == 0) {
+        return;
+      }
+    }
+    if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
+      events.add(factory.createAdvancePC(pcDelta));
+      pcDelta = 0;
+      if (lineDelta == 0) {
+        return;
+      }
+    }
+    int specialOpcode =
+        0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
+    assert specialOpcode >= 0x0a;
+    assert specialOpcode <= 0xff;
+    events.add(factory.createDefault(specialOpcode));
+  }
+
+  private List<Instruction> expandCode() {
+    LinkedList<Instruction> instructions = new LinkedList<>();
+    Collections.addAll(instructions, method.getCode().asDexCode().instructions);
+    int offsetDelta;
+    do {
+      ListIterator<Instruction> it = instructions.listIterator();
+      offsetDelta = 0;
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        instruction.setOffset(instruction.getOffset() + offsetDelta);
+        if (instruction instanceof ConstString) {
+          ConstString string = (ConstString) instruction;
+          if (string.getString().compareTo(firstJumboString) >= 0) {
+            ConstStringJumbo jumboString = new ConstStringJumbo(string.AA, string.getString());
+            jumboString.setOffset(string.getOffset());
+            offsetDelta++;
+            it.set(jumboString);
+            replaceTarget(instruction, jumboString);
+          }
+        } else if (instruction instanceof Format22t) {  // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+          Format22t condition = (Format22t) instruction;
+          int offset =
+              instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+          if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+            Format22t newCondition = null;
+            switch (condition.getType().inverted()) {
+              case EQ:
+                newCondition = new IfEq(condition.A, condition.B, 0);
+                break;
+              case GE:
+                newCondition = new IfGe(condition.A, condition.B, 0);
+                break;
+              case GT:
+                newCondition = new IfGt(condition.A, condition.B, 0);
+                break;
+              case LE:
+                newCondition = new IfLe(condition.A, condition.B, 0);
+                break;
+              case LT:
+                newCondition = new IfLt(condition.A, condition.B, 0);
+                break;
+              case NE:
+                newCondition = new IfNe(condition.A, condition.B, 0);
+                break;
+            }
+            offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+          }
+        } else if (instruction instanceof Format21t) {  // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+          Format21t condition = (Format21t) instruction;
+          int offset =
+              instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+          if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+            Format21t newCondition = null;
+            switch (condition.getType().inverted()) {
+              case EQ:
+                newCondition = new IfEqz(condition.AA, 0);
+                break;
+              case GE:
+                newCondition = new IfGez(condition.AA, 0);
+                break;
+              case GT:
+                newCondition = new IfGtz(condition.AA, 0);
+                break;
+              case LE:
+                newCondition = new IfLez(condition.AA, 0);
+                break;
+              case LT:
+                newCondition = new IfLtz(condition.AA, 0);
+                break;
+              case NE:
+                newCondition = new IfNez(condition.AA, 0);
+                break;
+            }
+            offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+          }
+        } else if (instruction instanceof Goto) {
+          Goto jump = (Goto) instruction;
+          int offset =
+              instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+          if (Byte.MIN_VALUE > offset || offset > Byte.MAX_VALUE) {
+            Instruction newJump;
+            if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+              newJump = new Goto32(offset);
+            } else {
+              newJump = new Goto16(offset);
+            }
+            newJump.setOffset(jump.getOffset());
+            it.set(newJump);
+            offsetDelta += (newJump.getSize() - jump.getSize());
+            replaceTarget(jump, newJump);
+            List<Instruction> targets = instructionTargets.remove(jump);
+            instructionTargets.put(newJump, targets);
+          }
+        } else if (instruction instanceof Goto16) {
+          Goto16 jump = (Goto16) instruction;
+          int offset =
+              instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+          if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+            Instruction newJump = new Goto32(offset);
+            newJump.setOffset(jump.getOffset());
+            it.set(newJump);
+            offsetDelta += (newJump.getSize() - jump.getSize());
+            replaceTarget(jump, newJump);
+            List<Instruction> targets = instructionTargets.remove(jump);
+            instructionTargets.put(newJump, targets);
+          }
+        } else if (instruction instanceof Goto32) {
+          // Instruction big enough for any offset.
+        } else if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+          // Instruction big enough for any offset.
+        } else if (instruction instanceof SwitchPayload
+            || instruction instanceof FillArrayDataPayload) {
+          if (instruction.getOffset() % 2 != 0) {
+            offsetDelta++;
+            it.previous();
+            Nop nop = new Nop();
+            nop.setOffset(instruction.getOffset());
+            it.add(nop);
+            it.next();
+            instruction.setOffset(instruction.getOffset() + 1);
+          }
+          // Instruction big enough for any offset.
+        }
+      }
+    } while (offsetDelta > 0);
+    return instructions;
+  }
+
+  private int rewriteIfToIfAndGoto(
+      int offsetDelta,
+      ListIterator<Instruction> it,
+      Instruction condition,
+      Instruction newCondition) {
+    int jumpOffset = condition.getOffset() + condition.getSize();
+    Goto32 jump = new Goto32(0);
+    jump.setOffset(jumpOffset);
+    newCondition.setOffset(condition.getOffset());
+    it.set(newCondition);
+    replaceTarget(condition, newCondition);
+    it.add(jump);
+    offsetDelta += jump.getSize();
+    instructionTargets.put(jump, instructionTargets.remove(condition));
+    Instruction fallthroughInstruction = it.next();
+    instructionTargets.put(newCondition, Lists.newArrayList(fallthroughInstruction));
+    it.previous();
+    return offsetDelta;
+  }
+
+  private void replaceTarget(Instruction target, Instruction newTarget) {
+    for (List<Instruction> instructions : instructionTargets.values()) {
+      instructions.replaceAll((i) -> i == target ? newTarget : i);
+    }
+    for (Int2ReferenceMap.Entry<Instruction> entry : debugEventTargets.int2ReferenceEntrySet()) {
+      if (entry.getValue() == target) {
+        entry.setValue(newTarget);
+      }
+    }
+    for (Entry<Try, TryTargets> entry : tryTargets.entrySet()) {
+      entry.getValue().replaceTarget(target, newTarget);
+    }
+    for (List<Instruction> instructions : handlerTargets.values()) {
+      instructions.replaceAll((i) -> i == target ? newTarget : i);
+    }
+  }
+
+  private void recordInstructionTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+    Instruction[] instructions = method.getCode().asDexCode().instructions;
+    for (Instruction instruction : instructions) {
+      if (instruction instanceof Format22t) {  // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+        Format22t condition = (Format22t) instruction;
+        Instruction target = offsetToInstruction.get(condition.getOffset() + condition.CCCC);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Format21t) {  // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+        Format21t condition = (Format21t) instruction;
+        Instruction target = offsetToInstruction.get(condition.getOffset() + condition.BBBB);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Goto) {
+        Goto jump = (Goto) instruction;
+        Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AA);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Goto16) {
+        Goto16 jump = (Goto16) instruction;
+        Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAA);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Goto32) {
+        Goto32 jump = (Goto32) instruction;
+        Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAAAAAA);
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+        Format31t offsetInstruction = (Format31t) instruction;
+        Instruction target = offsetToInstruction.get(
+            offsetInstruction.getOffset() + offsetInstruction.getPayloadOffset());
+        assert target != null;
+        instructionTargets.put(instruction, Lists.newArrayList(target));
+      } else if (instruction instanceof SwitchPayload) {
+        SwitchPayload payload = (SwitchPayload) instruction;
+        int[] targetOffsets = payload.switchTargetOffsets();
+        int switchOffset = payloadToSwitch.get(instruction).getOffset();
+        List<Instruction> targets = new ArrayList<>();
+        for (int i = 0; i < targetOffsets.length; i++) {
+          Instruction target = offsetToInstruction.get(switchOffset + targetOffsets[i]);
+          assert target != null;
+          targets.add(target);
+        }
+        instructionTargets.put(instruction, targets);
+      }
+    }
+  }
+
+  private void recordDebugEventTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+    DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
+    if (debugInfo != null) {
+      int address = 0;
+      for (DexDebugEvent event : debugInfo.events) {
+        if (event instanceof AdvancePC) {
+          AdvancePC advance = (AdvancePC) event;
+          address += advance.delta;
+          Instruction target = offsetToInstruction.get(address);
+          assert target != null;
+          debugEventTargets.put(address, target);
+        } else if (event instanceof Default) {
+          Default defaultEvent = (Default) event;
+          address += defaultEvent.getPCDelta();
+          Instruction target = offsetToInstruction.get(address);
+          assert target != null;
+          debugEventTargets.put(address, target);
+        }
+      }
+    }
+  }
+
+  private void recordTryAndHandlerTargets(
+      Int2ReferenceMap<Instruction> offsetToInstruction,
+      Instruction lastInstruction) {
+    DexCode code = method.getCode().asDexCode();
+    for (Try theTry : code.tries) {
+      Instruction start = offsetToInstruction.get(theTry.startAddress);
+      int endAddress = theTry.startAddress + theTry.instructionCount;
+      TryTargets targets;
+      if (endAddress > lastInstruction.getOffset()) {
+        targets = new TryTargets(start, lastInstruction, true);
+      } else {
+        Instruction end = offsetToInstruction.get(endAddress);
+        targets = new TryTargets(start, end, false);
+      }
+      assert theTry.startAddress == targets.getStartOffset();
+      assert theTry.instructionCount == targets.getStartToEndDelta();
+      tryTargets.put(theTry, targets);
+    }
+    if (code.handlers != null) {
+      for (TryHandler handler : code.handlers) {
+        List<Instruction> targets = new ArrayList<>();
+        if (handler.catchAllAddr != NO_HANDLER) {
+          Instruction target = offsetToInstruction.get(handler.catchAllAddr);
+          assert target != null;
+          targets.add(target);
+        }
+        for (TypeAddrPair pair : handler.pairs) {
+          Instruction target = offsetToInstruction.get(pair.addr);
+          assert target != null;
+          targets.add(target);
+        }
+        handlerTargets.put(handler, targets);
+      }
+    }
+  }
+
+  private void recordTargets() {
+    Int2ReferenceMap<Instruction> offsetToInstruction = new Int2ReferenceOpenHashMap<>();
+    Instruction[] instructions = method.getCode().asDexCode().instructions;
+    boolean containsPayloads = false;
+    for (Instruction instruction : instructions) {
+      offsetToInstruction.put(instruction.getOffset(), instruction);
+      if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+        containsPayloads = true;
+      }
+    }
+    if (containsPayloads) {
+      for (Instruction instruction : instructions) {
+        if (instruction instanceof Format31t) {  // FillArrayData, SparseSwitch, PackedSwitch
+          Instruction payload =
+              offsetToInstruction.get(instruction.getOffset() + instruction.getPayloadOffset());
+          assert payload != null;
+          payloadToSwitch.put(payload, instruction);
+        }
+      }
+    }
+    recordInstructionTargets(offsetToInstruction);
+    recordDebugEventTargets(offsetToInstruction);
+    Instruction lastInstruction = instructions[instructions.length - 1];
+    recordTryAndHandlerTargets(offsetToInstruction, lastInstruction);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
new file mode 100644
index 0000000..4762fd4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2017, the Rex 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.dex;
+
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+
+/**
+ * Abstraction for hidden dex marker intended for the main dex file.
+ */
+public class Marker {
+
+  public enum Tool {D8, R8}
+
+  private static final String kPrefix = "~~";
+  private static final String kD8prefix = kPrefix + Tool.D8 + "{";
+  private static final String kR8prefix = kPrefix + Tool.R8 + "{";
+
+  private final TreeMap<String, Object> content;
+  private final Tool tool;
+
+  public Marker(Tool tool) {
+    this.tool = tool;
+    this.content = new TreeMap<>();
+  }
+
+  private Marker(Tool tool, JSONObject object) {
+    this.tool = tool;
+    content = new TreeMap<>();
+    // This loop is necessary to make the type checker to shut up.
+    for (Object e : object.entrySet()) {
+      Map.Entry entry = (Map.Entry) e;
+      content.put(String.valueOf(entry.getKey()), entry.getValue());
+    }
+  }
+
+  public Marker put(String key, int value) {
+    // value is converted to Long ensuring equals works with the parsed json string.
+    return internalPut(key, new Long(value));
+  }
+
+  public Marker put(String key, String value) {
+    return internalPut(key, value);
+  }
+
+  private Marker internalPut(String key, Object value) {
+    assert (key != null) && (value != null);
+    assert !content.containsKey(key);
+    content.put(key, value);
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    // The JSONObject does not support a predictable sorted serialization of the object.
+    // Therefore, a TreeMap is used and iteration is over the keySet.
+    StringBuffer sb = new StringBuffer(kPrefix + tool);
+    boolean first = true;
+    sb.append('{');
+    for (String key : content.keySet()) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(',');
+      }
+      sb.append(JSONObject.toString(key, content.get(key)));
+    }
+    sb.append('}');
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof Marker) {
+      Marker other = (Marker) obj;
+      return (tool == other.tool) && content.equals(other.content);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return tool.hashCode() + 3 * content.hashCode();
+  }
+
+  // Try to parse str as a marker.
+  // Returns null if parsing fails.
+  public static Marker parse(String str) {
+    if (str.startsWith(kD8prefix)) {
+      return internalParse(Tool.D8, str.substring(kD8prefix.length() - 1));
+    }
+    if (str.startsWith(kR8prefix)) {
+      return internalParse(Tool.R8, str.substring(kR8prefix.length() - 1));
+    }
+    return null;
+  }
+
+  private static Marker internalParse(Tool tool, String str) {
+    try {
+      Object result =  new JSONParser().parse(str);
+      if (result instanceof JSONObject) {
+        return new Marker(tool, (JSONObject) result);
+      }
+    } catch (ParseException e) {
+      // Fall through.
+    }
+    return null;
+  }
+}
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 3432b62..f4fa536 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -318,7 +318,12 @@
       // 1. Place the remaining files based on their packages in sorted order.
 
       // Start with 1 file. The package populator will add more if needed.
-      nameToFileMap.put(0, new VirtualFile(0, writer.namingLens));
+      VirtualFile main = new VirtualFile(0, writer.namingLens);
+      nameToFileMap.put(0, main);
+      if (writer.markerString != null) {
+        main.transaction.addString(writer.markerString);
+        main.commitTransaction();
+      }
 
       // First fill required classes into the main dex file.
       fillForMainDexList(classes);
@@ -925,7 +930,7 @@
         } else {
           assert clazz.superType != null;
           // We don't have a package, add this to a list of classes that we will add last.
-          assert current.transaction.isEmpty();
+          assert current.transaction.classes.isEmpty();
           nonPackageClasses.add(clazz);
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3601ee9..afd923c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -331,6 +331,10 @@
     return null;
   }
 
+  public void registerNewType(DexType newType, DexType superType) {
+    // We do not track subtyping relationships in the basic AppInfo. So do nothing.
+  }
+
   public List<DexClass> getSuperTypeClasses(DexType type) {
     List<DexClass> result = new ArrayList<>();
     do {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 503bc89..9c87b61 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -227,6 +227,12 @@
   }
 
   @Override
+  public void registerNewType(DexType newType, DexType superType) {
+    // Register the relationship between this type and its superType.
+    superType.addDirectSubtype(newType);
+  }
+
+  @Override
   public boolean hasSubtyping() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 04c61a4..d09d395 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -175,12 +175,8 @@
   }
 
   public DexEncodedMethod getClassInitializer() {
-    for (DexEncodedMethod method : directMethods()) {
-      if (method.accessFlags.isConstructor() && method.accessFlags.isStatic()) {
-        return method;
-      }
-    }
-    return null;
+    return Arrays.stream(directMethods()).filter(DexEncodedMethod::isClassInitializer).findAny()
+        .orElse(null);
   }
 
   public Resource.Kind getOrigin() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index dc3ae37..7915df3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -175,6 +175,9 @@
 
   public String toString(DexEncodedMethod method, ClassNameMapper naming) {
     StringBuilder builder = new StringBuilder();
+    if (method != null) {
+      builder.append(method.toSourceString()).append("\n");
+    }
     builder.append("registers: ").append(registerSize);
     builder.append(", inputs: ").append(incomingRegisterSize);
     builder.append(", outputs: ").append(outgoingRegisterSize).append("\n");
@@ -311,7 +314,7 @@
 
     public static final int NO_INDEX = -1;
 
-    private final int handlerOffset;
+    public final int handlerOffset;
     public /* offset */ int startAddress;
     public /* offset */ int instructionCount;
     public int handlerIndex;
@@ -375,7 +378,7 @@
     public static final int NO_HANDLER = -1;
 
     public final TypeAddrPair[] pairs;
-    public /* offset */ int catchAllAddr;
+    public final /* offset */ int catchAllAddr;
 
     public TryHandler(TypeAddrPair[] pairs, int catchAllAddr) {
       this.pairs = pairs;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 39ec7d0..7bfa534 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.ir.code.MoveType;
 import com.google.common.collect.ImmutableMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -44,6 +46,7 @@
   private boolean prologueEnd = false;
   private boolean epilogueBegin = false;
   private final Map<Integer, LocalEntry> locals = new HashMap<>();
+  private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>();
 
   // Delayed construction of an entry. Is finalized once locals information has been collected.
   private DexDebugEntry pending = null;
@@ -65,7 +68,7 @@
     if (!method.accessFlags.isStatic()) {
       DexString name = factory.thisName;
       DexType type = method.method.getHolder();
-      startLocal(argumentRegister, name, type, null);
+      startArgument(argumentRegister, name, type);
       argumentRegister += MoveType.fromDexType(type).requiredRegisters();
     }
     DexType[] types = method.method.proto.parameters.values;
@@ -73,9 +76,9 @@
     for (int i = 0; i < types.length; i++) {
       // If null, the parameter has a parameterized type and the local is introduced in the stream.
       if (names[i] != null) {
-        startLocal(argumentRegister, names[i], types[i], null);
-        argumentRegister += MoveType.fromDexType(types[i]).requiredRegisters();
+        startArgument(argumentRegister, names[i], types[i]);
       }
+      argumentRegister += MoveType.fromDexType(types[i]).requiredRegisters();
     }
     currentLine = info.startLine;
     for (DexDebugEvent event : info.events) {
@@ -83,6 +86,10 @@
     }
   }
 
+  public Int2ReferenceMap<DebugLocalInfo> getArguments() {
+    return arguments;
+  }
+
   public void setFile(DexString file) {
     currentFile = file;
   }
@@ -104,6 +111,12 @@
     epilogueBegin = true;
   }
 
+  public void startArgument(int register, DexString name, DexType type) {
+    DebugLocalInfo argument = canonicalize(name, type, null);
+    arguments.put(register, argument);
+    getEntry(register).set(argument);
+  }
+
   public void startLocal(int register, DexString name, DexType type, DexString signature) {
     getEntry(register).set(canonicalize(name, type, signature));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 572e655..db99359 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -31,10 +31,9 @@
 
   public abstract void addToBuilder(DexDebugEntryBuilder builder);
 
-
   public static class AdvancePC extends DexDebugEvent {
 
-    final int delta;
+    public final int delta;
 
     public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
       writer.putByte(Constants.DBG_ADVANCE_PC);
@@ -350,6 +349,16 @@
       builder.setPosition(address, line);
     }
 
+    public int getPCDelta() {
+      int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+      return adjustedOpcode / Constants.DBG_LINE_RANGE;
+    }
+
+    public int getLineDelta() {
+      int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+      return Constants.DBG_LINE_BASE + (adjustedOpcode % Constants.DBG_LINE_RANGE);
+    }
+
     public String toString() {
       return "DEFAULT " + value;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 947ab44..e66b984 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -98,22 +98,21 @@
     return compilationState != CompilationState.NOT_PROCESSED;
   }
 
-  public boolean cannotInline() {
-    return compilationState == CompilationState.NOT_PROCESSED
-        || compilationState == CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
+  public boolean isInstanceInitializer() {
+    return accessFlags.isConstructor() && !accessFlags.isStatic();
+  }
+
+  public boolean isClassInitializer() {
+    return accessFlags.isConstructor() && accessFlags.isStatic();
   }
 
   public boolean isInliningCandidate(DexEncodedMethod container, boolean alwaysInline,
       AppInfoWithSubtyping appInfo) {
-    if (container.accessFlags.isStatic() && container.accessFlags.isConstructor()) {
+    if (isClassInitializer()) {
       // This will probably never happen but never inline a class initializer.
       return false;
     }
     if (alwaysInline) {
-      // Only inline constructor iff holder classes are equal.
-      if (!accessFlags.isStatic() && accessFlags.isConstructor()) {
-        return container.method.getHolder() == method.getHolder();
-      }
       return true;
     }
     switch (compilationState) {
@@ -172,14 +171,6 @@
     code = builder.build(method.getArity());
   }
 
-  // Replaces the dex code in the method by setting code to result of compiling the IR.
-  public void setCode(IRCode ir, RegisterAllocator registerAllocator,
-      DexItemFactory dexItemFactory, DexString firstJumboString) {
-    final DexBuilder builder =
-        new DexBuilder(ir, registerAllocator, dexItemFactory, firstJumboString);
-    code = builder.build(method.getArity());
-  }
-
   public String toString() {
     return "Encoded method " + method;
   }
@@ -207,6 +198,10 @@
     return code;
   }
 
+  public void setDexCode(DexCode code) {
+    this.code = code;
+  }
+
   public void removeCode() {
     code = null;
   }
@@ -307,10 +302,10 @@
             itemFactory.stringType),
             itemFactory.constructorMethodName);
     DexCode code;
-    if (accessFlags.isConstructor() && !accessFlags.isStatic()) {
+    if (isInstanceInitializer()) {
       // The Java VM Spec requires that a constructor calls an initializer from the super class
       // or another constructor from the current class. For simplicity we do the latter by just
-      // calling outself. This is ok, as the constructor always throws before the recursive call.
+      // calling ourself. This is ok, as the constructor always throws before the recursive call.
       code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag),
           new ConstStringJumbo(1, message),
           new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 6685da7..339f4c0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
@@ -289,6 +290,18 @@
     return canonicalize(strings, new DexString(source));
   }
 
+  // Debugging support to extract marking string.
+  synchronized public Marker extractMarker() {
+    // This is slow but it is not needed for any production code yet.
+    for (DexString dexString : strings.keySet()) {
+      Marker result = Marker.parse(dexString.toString());
+      if (result != null) {
+        return result;
+      }
+    }
+    return null;
+  }
+
   public DexType createType(DexString descriptor) {
     assert !sorted;
     DexType type = new DexType(descriptor);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index d3a5676..60a7319 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -148,7 +148,6 @@
   public void addStaticMethod(DexEncodedMethod staticMethod) {
     assert staticMethod.accessFlags.isStatic();
     assert !staticMethod.accessFlags.isPrivate();
-    assert !staticMethod.accessFlags.isConstructor();
     directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
     directMethods[directMethods.length - 1] = staticMethod;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index e6914ec..75a6d35 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -21,7 +21,7 @@
     this.content = content;
   }
 
-  DexString(String string) {
+  public DexString(String string) {
     this.size = string.length();
     this.content = encode(string);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index e06c0ad..a371684 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -453,4 +453,9 @@
     }
     return getPackageOrName(false);
   }
+
+  public boolean isImmediateSubtypeOf(DexType type) {
+    assert hierarchyLevel != UNKNOWN_LEVEL;
+    return type.directSubtypes.contains(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 74a0800..c8157d6 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -90,8 +90,7 @@
     for (IndexedDexItem item : items) {
       item.assignVirtualFileIndex(virtualFileId, index);
       // For strings collect the first jumbo string (if any).
-      if (index > Constants.MAX_NON_JUMBO_INDEX) {
-        assert item instanceof DexString;
+      if ((index > Constants.MAX_NON_JUMBO_INDEX) && (item instanceof DexString)) {
         if (index == Constants.FIRST_JUMBO_INDEX) {
           firstJumboString = (DexString) item;
         }
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index 3caf8f2..eb130d6 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -13,4 +13,8 @@
   // Layered comparison methods that make use of indices for subpart comparisons. These rely
   // on subparts already being sorted and having indices assigned.
   int layeredCompareTo(T other, NamingLens namingLens);
+
+  static <T extends PresortedComparable<T>> int slowCompare(T a, T b) {
+    return a.slowCompareTo(b);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index c2cd2ea..e787e59 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -12,7 +12,10 @@
 import com.android.tools.r8.code.AgetWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.Arrays;
 
@@ -114,4 +117,9 @@
   public ArrayGet asArrayGet() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.ALWAYS;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 40bd443..88325a4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -5,7 +5,10 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 
 public class ArrayLength extends Instruction {
@@ -79,4 +82,9 @@
     assert other.isArrayLength();
     return 0;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.ALWAYS;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 20ce7e8..11694a3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -12,7 +12,10 @@
 import com.android.tools.r8.code.AputWide;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
@@ -135,4 +138,9 @@
   public ArrayPut asArrayPut() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.ALWAYS;
+  }
 }
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 f93f996..968e560 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
@@ -106,7 +106,7 @@
     if (current == null) {
       throw new IllegalStateException();
     }
-    assert current.outValue() == null || current.outValue().numberOfAllUsers() == 0;
+    assert current.outValue() == null || !current.outValue().isUsed();
     for (int i = 0; i < current.inValues().size(); i++) {
       Value value = current.inValues().get(i);
       value.removeUser(current);
@@ -323,7 +323,7 @@
     while (inlineeIterator.hasNext()) {
       Instruction instruction = inlineeIterator.next();
       if (instruction.isArgument()) {
-        assert instruction.outValue().numberOfAllUsers() == 0;
+        assert !instruction.outValue().isUsed();
         assert instruction.outValue() == arguments.get(index++);
         inlineeIterator.remove();
       }
@@ -349,6 +349,8 @@
     List<Value> arguments = inlinee.collectArguments();
     assert invoke.inValues().size() == arguments.size();
     for (int i = 0; i < invoke.inValues().size(); i++) {
+      // TODO(zerny): Support inlining in --debug mode.
+      assert arguments.get(i).getDebugInfo() == null;
       if ((i == 0) && (downcast != null)) {
         Value invokeValue = invoke.inValues().get(0);
         Value receiverValue = arguments.get(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 573db67..0c0797f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -7,8 +7,10 @@
 import com.android.tools.r8.code.MoveObject;
 import com.android.tools.r8.code.MoveObjectFrom16;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class CheckCast extends Instruction {
 
@@ -94,4 +96,9 @@
   public String toString() {
     return super.toString() + "; " + type;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.classIsVisible(holder, type, info);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 3a3626e..efde56a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -5,8 +5,10 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class ConstClass extends ConstInstruction {
@@ -76,4 +78,9 @@
   public ConstClass asConstClass() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.classIsVisible(holder, clazz, info);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 82db97a..bb1ff32 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.code.ConstStringJumbo;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -30,11 +29,7 @@
   public void buildDex(DexBuilder builder) {
     builder.registerStringReference(value);
     int dest = builder.allocatedRegister(dest(), getNumber());
-    if (builder.isJumboString(value)) {
-      builder.add(this, new ConstStringJumbo(dest, value));
-    } else {
-      builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
-    }
+    builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 0a1cc1b..55bb03d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -82,4 +85,9 @@
     StringUtils.append(builder, starting.int2ReferenceEntrySet());
     return builder.toString();
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.ALWAYS;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 7a5acc5..9a8f8a5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class DebugPosition extends Instruction {
@@ -74,4 +77,9 @@
     }
     builder.append(line);
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.ALWAYS;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 955a6cc..afa18e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -76,10 +75,6 @@
 
   @Override
   public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
-    DexClass targetClass = info.definitionFor(type());
-    if (targetClass == null) {
-      return Constraint.NEVER;
-    }
-    return Constraint.deriveConstraint(holder, type(), targetClass.accessFlags, info);
+    return Constraint.classIsVisible(holder, type, info);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e6ba6c0..e33c796 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -61,7 +61,7 @@
   }
 
   public void setOutValue(Value value) {
-    assert outValue == null || !outValue.hasUsersInfo() || outValue.numberOfAllUsers() == 0;
+    assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed();
     outValue = value;
     if (outValue != null) {
       outValue.definition = this;
@@ -839,7 +839,5 @@
   }
 
   // Returns the inlining constraint for this instruction.
-  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
-    return Constraint.NEVER;
-  }
+  public abstract Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index d0d0739..f597b2c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -7,7 +7,9 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import java.util.List;
 
 public final class InvokeCustom extends Invoke {
@@ -88,4 +90,9 @@
   public InvokeCustom asInvokeCustom() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.NEVER;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 2912c1a..0049baa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import java.util.List;
 
 public class InvokeNewArray extends Invoke {
@@ -93,4 +94,9 @@
   public InvokeNewArray asInvokeNewArray() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.classIsVisible(holder, type, info);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index b3e155b..a3b71e1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -8,7 +8,10 @@
 
 import com.android.tools.r8.code.MonitorEnter;
 import com.android.tools.r8.code.MonitorExit;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class Monitor extends Instruction {
 
@@ -80,4 +83,10 @@
   public Monitor asMonitor() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    // Conservative choice.
+    return Constraint.NEVER;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 023e6f0..3dff4f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class MoveException extends Instruction {
@@ -82,4 +85,10 @@
     }
     return super.toString();
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    // TODO(64432527): Revisit this constraint.
+    return Constraint.NEVER;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index f7b0367..17a08da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -5,8 +5,10 @@
 
 import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class NewArrayEmpty extends Instruction {
 
@@ -68,4 +70,9 @@
   public NewArrayEmpty asNewArrayEmpty() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.classIsVisible(holder, type, info);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 63a1a0e..aed765e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -6,7 +6,10 @@
 import com.android.tools.r8.code.FillArrayData;
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Arrays;
 
@@ -94,4 +97,9 @@
   public NewArrayFilledData asNewArrayFilledData() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.ALWAYS;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 1aa2cbb..8d1671d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 
 public class NewInstance extends Instruction {
 
@@ -69,4 +71,9 @@
   public NewInstance asNewInstance() {
     return this;
   }
+
+  @Override
+  public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+    return Constraint.classIsVisible(holder, clazz, info);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 1abdb1d..7e05dc9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -231,8 +231,11 @@
     StringBuilder builder = new StringBuilder();
     builder.append("v");
     builder.append(number);
+    if (getLocalInfo() != null) {
+      builder.append("(").append(getLocalInfo()).append(")");
+    }
     builder.append(" <- phi");
-    StringUtils.append(builder, ListUtils.map(operands, (Value operand) -> "v" + operand.number));
+    StringUtils.append(builder, ListUtils.map(operands, Value::toString));
     return builder.toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index e0bfdd0..035bf95 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -219,6 +219,12 @@
     return numberOfUsers() + numberOfPhiUsers() + numberOfDebugUsers();
   }
 
+  public boolean isUsed() {
+    return !users.isEmpty()
+        || !phiUsers.isEmpty()
+        || ((debugData != null) && !debugData.debugUsers.isEmpty());
+  }
+
   public void addUser(Instruction user) {
     users.add(user);
     uniqueUsers = null;
@@ -503,7 +509,7 @@
 
   public boolean isDead(InternalOptions options) {
     // Totally unused values are trivially dead.
-    return numberOfAllUsers() == 0 || isDead(new HashSet<>(), options);
+    return !isUsed() || isDead(new HashSet<>(), options);
   }
 
   protected boolean isDead(Set<Value> active, InternalOptions options) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 70e156b..48c8eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -17,15 +17,23 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -45,6 +53,10 @@
  */
 public class CallGraph {
 
+  private CallGraph(InternalOptions options) {
+    this.shuffle = options.testing.irOrdering;
+  }
+
   private static class Node {
 
     public final DexEncodedMethod method;
@@ -121,6 +133,7 @@
 
   private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
   private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>();
+  private final Function<List<DexEncodedMethod>, List<DexEncodedMethod>> shuffle;
 
   // Returns whether the method->callee edge has been removed from the call graph
   // to break a cycle in the call graph.
@@ -133,8 +146,8 @@
   private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
 
   public static CallGraph build(DexApplication application, AppInfoWithSubtyping appInfo,
-      GraphLense graphLense) {
-    CallGraph graph = new CallGraph();
+      GraphLense graphLense, InternalOptions options) {
+    CallGraph graph = new CallGraph(options);
     DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
     Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
     for (DexClass clazz : classes) {
@@ -185,7 +198,9 @@
 
   private static boolean allMethodsExists(DexApplication application, CallGraph graph) {
     for (DexProgramClass clazz : application.classes()) {
-      clazz.forEachMethod(method -> { assert graph.nodes.get(method) != null; });
+      clazz.forEachMethod(method -> {
+        assert graph.nodes.get(method) != null;
+      });
     }
     return true;
   }
@@ -197,19 +212,19 @@
    * Please note that there are no cycles in this graph (see {@link #breakCycles}).
    * <p>
    *
-   * @return  List of {@link DexEncodedMethod}.
+   * @return List of {@link DexEncodedMethod}.
    */
-  List<DexEncodedMethod> extractLeaves() {
+  private List<DexEncodedMethod> extractLeaves() {
     if (isEmpty()) {
-      return null;
+      return Collections.emptyList();
     }
     // First identify all leaves before removing them from the graph.
     List<Node> leaves = nodes.values().stream().filter(Node::isLeaf).collect(Collectors.toList());
-    leaves.forEach( leaf -> {
-      leaf.callers.forEach( caller -> caller.callees.remove(leaf));
+    leaves.forEach(leaf -> {
+      leaf.callers.forEach(caller -> caller.callees.remove(leaf));
       nodes.remove(leaf.method);
     });
-    return leaves.stream().map( leaf -> leaf.method).collect(Collectors.toList());
+    return shuffle.apply(leaves.stream().map(leaf -> leaf.method).collect(Collectors.toList()));
   }
 
   private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
@@ -253,7 +268,7 @@
     int numberOfCycles = 0;
     Set<Node> stack = Sets.newIdentityHashSet();
     Set<Node> marked = Sets.newIdentityHashSet();
-    for(Node node : nodes.values()) {
+    for (Node node : nodes.values()) {
       numberOfCycles += traverse(node, stack, marked);
     }
     return numberOfCycles;
@@ -279,6 +294,21 @@
     return nodes.size() == 0;
   }
 
+  public void forEachMethod(Consumer<DexEncodedMethod> consumer, ExecutorService executorService)
+      throws ExecutionException {
+    while (!isEmpty()) {
+      List<DexEncodedMethod> methods = extractLeaves();
+      assert methods.size() > 0;
+      List<Future<?>> futures = new ArrayList<>();
+      for (DexEncodedMethod method : methods) {
+        futures.add(executorService.submit(() -> {
+          consumer.accept(method);
+        }));
+      }
+      ThreadUtils.awaitFutures(futures);
+    }
+  }
+
   public void dump() {
     nodes.forEach((m, n) -> System.out.println(n + "\n"));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 737dc46..da067d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -85,9 +85,6 @@
   // List of generated FillArrayData dex instructions.
   private final List<FillArrayDataInfo> fillArrayDataInfos = new ArrayList<>();
 
-  // First jumbo string if known.
-  private final DexString firstJumboString;
-
   // Set of if instructions that have offsets that are so large that they cannot be encoded in
   // the if instruction format.
   private Set<BasicBlock> ifsNeedingRewrite = Sets.newIdentityHashSet();
@@ -116,18 +113,6 @@
     this.ir = ir;
     this.registerAllocator = registerAllocator;
     this.dexItemFactory = dexItemFactory;
-    this.firstJumboString = null;
-  }
-
-  public DexBuilder(IRCode ir, RegisterAllocator registerAllocator,
-      DexItemFactory dexItemFactory, DexString firstJumboString) {
-    assert ir != null;
-    assert registerAllocator != null;
-    assert dexItemFactory != null;
-    this.ir = ir;
-    this.registerAllocator = registerAllocator;
-    this.dexItemFactory = dexItemFactory;
-    this.firstJumboString = firstJumboString;
   }
 
   private void reset() {
@@ -143,15 +128,6 @@
     nextBlock = null;
   }
 
-  public boolean isJumboString(DexString string) {
-    if (firstJumboString == null) {
-      return false;
-    }
-    // We have to use compareTo here, as slowCompareTo will return the wrong order when minification
-    // is used.
-    return firstJumboString.compareTo(string) <= 0;
-  }
-
   /**
    * Build the dex instructions added to this builder.
    *
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 8e613c0..6f94b1e 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
@@ -50,7 +50,7 @@
 
 public class IRConverter {
 
-  public static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
+  private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
 
   private final Timing timing;
   public final DexApplication application;
@@ -266,7 +266,7 @@
     removeLambdaDeserializationMethods();
 
     timing.begin("Build call graph");
-    callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense);
+    callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense, options);
     timing.end();
 
     // The process is in two phases.
@@ -278,22 +278,10 @@
     // Process the application identifying outlining candidates.
     timing.begin("IR conversion phase 1");
     OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
-    while (!callGraph.isEmpty()) {
-      List<DexEncodedMethod> methods = callGraph.extractLeaves();
-      assert methods.size() > 0;
-      // For testing we have the option to determine the processing order of the methods.
-      if (options.testing.irOrdering != null) {
-        methods = options.testing.irOrdering.apply(methods);
-      }
-      List<Future<?>> futures = new ArrayList<>();
-      for (DexEncodedMethod method : methods) {
-        futures.add(executorService.submit(() -> {
+    callGraph.forEachMethod(method -> {
           processMethod(method, directFeedback,
               outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
-        }));
-      }
-      ThreadUtils.awaitFutures(futures);
-    }
+    }, executorService);
     timing.end();
 
     // Build a new application with jumbo string info.
@@ -314,13 +302,20 @@
       // add the outline support class IF needed.
       DexProgramClass outlineClass = prepareOutlining();
       if (outlineClass != null) {
-        // Process the selected methods for outlining.
-        for (DexEncodedMethod method : outliner.getMethodsSelectedForOutlining()) {
+        // We need a new call graph to ensure deterministic order and also processing inside out
+        // to get maximal inlining. Use a identity lense, as the code has been rewritten.
+        callGraph = CallGraph
+            .build(application, appInfo.withSubtyping(), GraphLense.getIdentityLense(), options);
+        Set<DexEncodedMethod> outlineMethods = outliner.getMethodsSelectedForOutlining();
+        callGraph.forEachMethod(method -> {
+          if (!outlineMethods.contains(method)) {
+            return;
+          }
           // This is the second time we compile this method first mark it not processed.
           assert !method.getCode().isOutlineCode();
           processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate);
           assert method.isProcessed();
-        }
+        }, executorService);
         builder.addSynthesizedClass(outlineClass, true);
         clearDexMethodCompilationState(outlineClass);
       }
@@ -330,10 +325,6 @@
     return builder.build();
   }
 
-  public void processJumboStrings(DexEncodedMethod method, DexString firstJumboString) {
-    convertMethodJumboStringsOnly(method, firstJumboString);
-  }
-
   private void clearDexMethodCompilationState() {
     application.classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -374,6 +365,8 @@
       count++;
       result = application.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
     } while (application.definitionFor(result) != null);
+    // Register the newly generated type in the subtyping hierarchy, if we have one.
+    appInfo.registerNewType(result, appInfo.dexItemFactory.objectType);
     return result;
   }
 
@@ -461,6 +454,7 @@
       assert !options.debug;
       inliner.performInlining(method, code, callGraph);
     }
+    codeRewriter.removeCastChains(code);
     codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
     codeRewriter.commonSubexpressionElimination(code);
     codeRewriter.simplifyArrayConstruction(code);
@@ -537,44 +531,6 @@
     }
   }
 
-  // Convert a method ensuring that strings sorting equal or higher than the argument
-  // firstJumboString are encoded as jumbo strings.
-  // TODO(sgjesse): Consider replacing this with a direct dex2dex converter instead of going
-  // through IR.
-  private void convertMethodJumboStringsOnly(
-      DexEncodedMethod method, DexString firstJumboString) {
-    // This is only used for methods already converted to Dex, but missing jumbo strings.
-    assert method.getCode() != null && method.getCode().isDexCode();
-    if (options.verbose) {
-      System.out.println("Processing jumbo strings: " + method.toSourceString());
-    }
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Original code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
-    }
-    IRCode code = method.buildIR(options);
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s",
-          method.toSourceString(), code);
-    }
-    // Compilation header if printing CFGs for this method.
-    printC1VisualizerHeader(method);
-    printMethod(code, "Initial IR (SSA)");
-
-    // Methods passed through here should have been through IR processing already and
-    // therefore, we skip most of the IR processing.
-
-    // Perform register allocation.
-    RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, registerAllocator, appInfo.dexItemFactory, firstJumboString);
-
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Resulting dex code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
-    }
-    printMethod(code, "Final IR (non-SSA)");
-  }
-
   private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 55e6296..0a6ea7e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -81,6 +81,9 @@
         : factory.createMethod(lambdaClassType, constructorProto, rewriter.classConstructorName);
     this.instanceField = !stateless ? null
         : factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
+
+    // We have to register this new class as a subtype of object.
+    rewriter.appInfo.registerNewType(type, factory.objectType);
   }
 
   // Generate unique lambda class type for lambda descriptor and instantiation point context.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 7d3bc33..af32cb7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -57,6 +58,7 @@
   static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
 
   final IRConverter converter;
+  final AppInfo appInfo;
   final DexItemFactory factory;
 
   final DexMethod metafactoryMethod;
@@ -78,7 +80,6 @@
   //
   // NOTE: synchronize concurrent access on `knownCallSites`.
   private final Map<DexCallSite, LambdaDescriptor> knownCallSites = new IdentityHashMap<>();
-
   // Maps lambda class type into lambda class representation. Since lambda class
   // type uniquely defines lambda class, effectively canonicalizes lambda classes.
   // NOTE: synchronize concurrent access on `knownLambdaClasses`.
@@ -93,6 +94,7 @@
     assert converter != null;
     this.converter = converter;
     this.factory = converter.application.dexItemFactory;
+    this.appInfo = converter.appInfo;
 
     DexType metafactoryType = factory.createType(METAFACTORY_TYPE_DESCR);
     DexType callSiteType = factory.createType(CALLSITE_TYPE_DESCR);
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 b1cdcbc..981f673 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Binop;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -509,7 +510,7 @@
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
-        if (invoke.outValue() != null) {
+        if (invoke.outValue() != null && invoke.outValue().getLocalInfo() == null) {
           DexEncodedMethod target = invoke.computeSingleTarget(appInfo.withSubtyping());
           // We have a set of library classes with optimization information - consider those
           // as well.
@@ -541,42 +542,45 @@
     assert code.isConsistentGraph();
   }
 
-  // For supporting assert javac adds the static field $assertionsDisabled to all classes which
-  // have methods with assertions. This is used to support the Java VM -ea flag.
-  //
-  //  The class:
-  //
-  //  class A {
-  //    void m() {
-  //      assert xxx;
-  //    }
-  //  }
-  //
-  //  Is compiled into:
-  //
-  //  class A {
-  //    static boolean $assertionsDisabled;
-  //    static {
-  //      $assertionsDisabled = A.class.desiredAssertionStatus();
-  //    }
-  //
-  //    // method with "assert xxx";
-  //    void m() {
-  //      if (!$assertionsDisabled) {
-  //        if (xxx) {
-  //          throw new AssertionError(...);
-  //        }
-  //      }
-  //    }
-  //  }
-  //
-  //  With the rewriting below (and other rewritings) the resulting code is:
-  //
-  //  class A {
-  //    void m() {
-  //    }
-  //  }
-  //
+
+  /**
+   * For supporting assert javac adds the static field $assertionsDisabled to all classes which
+   * have methods with assertions. This is used to support the Java VM -ea flag.
+   *
+   * The class:
+   * <pre>
+   * class A {
+   *   void m() {
+   *     assert xxx;
+   *   }
+   * }
+   * </pre>
+   * Is compiled into:
+   * <pre>
+   * class A {
+   *   static boolean $assertionsDisabled;
+   *   static {
+   *     $assertionsDisabled = A.class.desiredAssertionStatus();
+   *   }
+   *
+   *   // method with "assert xxx";
+   *   void m() {
+   *     if (!$assertionsDisabled) {
+   *       if (xxx) {
+   *         throw new AssertionError(...);
+   *       }
+   *     }
+   *   }
+   * }
+   * </pre>
+   * With the rewriting below (and other rewritings) the resulting code is:
+   * <pre>
+   * class A {
+   *   void m() {
+   *   }
+   * }
+   * </pre>
+   */
   public void disableAssertions(IRCode code) {
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
@@ -600,6 +604,28 @@
     }
   }
 
+  /**
+   * Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
+   * subtype, as that will fail if a cast on a supertype would have failed.
+   */
+  public void removeCastChains(IRCode code) {
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction current = it.next();
+      if (current.isCheckCast()
+          && current.outValue() != null && current.outValue().isUsed()
+          && current.outValue().numberOfPhiUsers() == 0) {
+        CheckCast checkCast = current.asCheckCast();
+        if (checkCast.outValue().uniqueUsers().stream().allMatch(
+            user -> user.isCheckCast()
+                && user.asCheckCast().getType().isSubtypeOf(checkCast.getType(), appInfo))) {
+          checkCast.outValue().replaceUsers(checkCast.inValues().get(0));
+          it.remove();
+        }
+      }
+    }
+  }
+
   private boolean canBeFolded(Instruction instruction) {
     return (instruction.isBinop() && instruction.asBinop().canBeFolded()) ||
         (instruction.isUnop() && instruction.asUnop().canBeFolded());
@@ -1240,7 +1266,7 @@
   }
 
   private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
-    Value value = code.createValue(MoveType.OBJECT);;
+    Value value = code.createValue(MoveType.OBJECT);
     iterator.add(new ConstString(value, dexItemFactory.createString(s)));
     return value;
   }
@@ -1279,7 +1305,7 @@
     iterator.add(new ConstString(value, dexItemFactory.createString("INVOKE ")));
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
 
-    value = code.createValue(MoveType.OBJECT);;
+    value = code.createValue(MoveType.OBJECT);
     iterator.add(
         new ConstString(value, dexItemFactory.createString(method.method.qualifiedName())));
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index b683f3a..3a1902e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -105,7 +105,7 @@
       // Remove unused invoke results.
       if (current.isInvoke()
           && current.outValue() != null
-          && current.outValue().numberOfAllUsers() == 0) {
+          && !current.outValue().isUsed()) {
         current.setOutValue(null);
       }
       // Never remove instructions that can have side effects, except for const-class.
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 f750b09..ce6ad76 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
@@ -183,6 +183,13 @@
       }
     }
 
+    public static Constraint classIsVisible(DexType context, DexType clazz,
+        AppInfoWithSubtyping appInfo) {
+      DexClass definition = appInfo.definitionFor(clazz);
+      return definition == null ? NEVER
+          : deriveConstraint(context, clazz, definition.accessFlags, appInfo);
+    }
+
     public static Constraint min(Constraint one, Constraint other) {
       return one.ordinal() < other.ordinal() ? one : other;
     }
@@ -205,15 +212,15 @@
 
     public final DexEncodedMethod target;
     public final Invoke invoke;
-    public final Reason reason;
+    final Reason reason;
 
-    public InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
+    InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
       this.target = target;
       this.invoke = invoke;
       this.reason = reason;
     }
 
-    public boolean forceInline() {
+    boolean forceInline() {
       return reason != Reason.SIMPLE;
     }
 
@@ -252,12 +259,12 @@
     // Before that method invokes another instance initialization method of myClass or its direct
     // superclass on this, the only operation the method can perform on this is assigning fields
     // declared within myClass.
-    //
 
     // Allow inlining a constructor into a constructor of the same class, as the constructor code
     // is expected to adhere to the VM specification.
-    if (method.accessFlags.isConstructor()
-        && method.method.holder == invoke.getInvokedMethod().holder) {
+    DexType methodHolder = method.method.holder;
+    boolean methodIsConstructor = method.isInstanceInitializer();
+    if (methodIsConstructor && methodHolder == invoke.asInvokeMethod().getInvokedMethod().holder) {
       return true;
     }
 
@@ -265,6 +272,8 @@
     // 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.
     InstructionIterator iterator = code.instructionIterator();
     Instruction instruction = iterator.next();
     // A constructor always has the un-initialized object as the first argument.
@@ -277,12 +286,23 @@
         if (instruction.isInvokeDirect() && !seenSuperInvoke) {
           DexMethod target = instruction.asInvokeDirect().getInvokedMethod();
           seenSuperInvoke = appInfo.dexItemFactory.isConstructor(target);
+          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)) {
+            return false;
+          }
         }
         if (!seenSuperInvoke) {
           return false;
         }
       }
       if (instruction.isInstancePut()) {
+        // Fields may not be initialized outside of a constructor.
+        if (!methodIsConstructor) {
+          return false;
+        }
         DexField field = instruction.asInstancePut().getField();
         DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
         if (target != null && target.accessFlags.isFinal()) {
@@ -293,16 +313,6 @@
     return true;
   }
 
-  /// Computer the receiver value for the holder method.
-  private Value receiverValue(DexEncodedMethod method, IRCode code) {
-    // Ignore static methods.
-    if (method.accessFlags.isStatic()) {
-      return null;
-    }
-    // Find the outValue of the first argument instruction in the first block.
-    return code.collectArguments().get(0);
-  }
-
   public void performInlining(DexEncodedMethod method, IRCode code, CallGraph callGraph) {
     int instruction_allowance = 1500;
     instruction_allowance -= numberOfInstructions(code);
@@ -338,7 +348,7 @@
             IRCode inlinee = result
                 .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
             if (inlinee != null) {
-              // TODO(sgjesse): Get rid of this additional check by improved inlining.
+              // TODO(64432527): Get rid of this additional check by improved inlining.
               if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
                 continue;
               }
@@ -356,8 +366,8 @@
                 performInlining(target, inlinee, callGraph);
               }
               // Make sure constructor inlining is legal.
-              if (target.accessFlags.isConstructor()
-                  && !target.accessFlags.isStatic()
+              assert !target.isClassInitializer();
+              if (target.isInstanceInitializer()
                   && !legalConstructorInline(method, invoke, inlinee)) {
                 continue;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index fb6c2eb..d32f7f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -29,7 +29,7 @@
   private final CallGraph callGraph;
   private final InliningInfo info;
 
-  public InliningOracle(
+  InliningOracle(
       Inliner inliner,
       DexEncodedMethod method,
       CallGraph callGraph) {
@@ -39,7 +39,7 @@
     info = Log.ENABLED ? new InliningInfo(method) : null;
   }
 
-  public void finish() {
+  void finish() {
     if (Log.ENABLED) {
       System.out.println(info.toString());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 8a02435..a34c8cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -137,10 +137,10 @@
         ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
         if (lookup != null) {
           if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
-              && (invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0)) {
+              && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
             iterator.remove();
             invokeReplaced = true;
-          } else {
+          } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
             // Check to see if a constant value can be assumed.
             Instruction replacement =
                 constantReplacementFromProguardRule(lookup.rule, code, invoke);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 8cc8ec4..3857c1f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -400,7 +400,7 @@
 
       // Allow all new-instance instructions in a outline.
       if (instruction.isNewInstance()) {
-        if (instruction.outValue().numberOfAllUsers() > 0) {
+        if (instruction.outValue().isUsed()) {
           // Track the new-instance value to make sure the <init> call is part of the outline.
           pendingNewInstanceIndex = index;
         }
@@ -711,7 +711,7 @@
           }
         }
         assert m.proto.shorty.toString().length() - 1 == in.size();
-        if (returnValue != null && returnValue.numberOfAllUsers() == 0) {
+        if (returnValue != null && !returnValue.isUsed()) {
           returnValue = null;
         }
         Invoke outlineInvoke = new InvokeStatic(m, returnValue, in);
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 2e4a434..38cdb29 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
@@ -432,6 +432,17 @@
       ListIterator<Instruction> instructionIterator,
       Int2ReferenceMap<DebugLocalInfo> initialLocals,
       Int2ReferenceMap<DebugLocalInfo> finalLocals) {
+    if (!options.singleStepDebug) {
+      while (instructionIterator.hasNext()) {
+        if (instructionIterator.next().getNumber() != -1) {
+          break;
+        }
+      }
+      return Int2ReferenceMaps.emptyMap();
+    }
+    // TODO(zerny): Investigate supporting accurate single stepping through spill instructions.
+    // The current code should preferably be updated to account for moving locals and not just
+    // end their scope.
     int spillCount;
     int firstClobberedMove = -1;
     Int2ReferenceMap<DebugLocalInfo> clobberedLocals = Int2ReferenceMaps.emptyMap();
@@ -1944,7 +1955,7 @@
           // For instructions that define values which have no use create a live range covering
           // the instruction. This will typically be instructions that can have side effects even
           // if their output is not used.
-          if (definition.numberOfAllUsers() == 0) {
+          if (!definition.isUsed()) {
             addLiveRange(definition, block, instruction.getNumber() + INSTRUCTION_NUMBER_DELTA);
           }
           live.remove(definition);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d25c4bc..c45a5c1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -31,7 +31,9 @@
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
@@ -39,7 +41,6 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -49,6 +50,7 @@
 import java.util.Map.Entry;
 import java.util.Queue;
 import java.util.Set;
+import java.util.SortedSet;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -886,20 +888,24 @@
         .collect(Collectors.toCollection(Sets::newIdentityHashSet));
   }
 
-  Set<DexField> collectInstanceFieldsRead() {
-    return Collections.unmodifiableSet(collectFields(instanceFieldsRead));
+  SortedSet<DexField> collectInstanceFieldsRead() {
+    return ImmutableSortedSet.copyOf(
+        PresortedComparable::slowCompareTo, collectFields(instanceFieldsRead));
   }
 
-  Set<DexField> collectInstanceFieldsWritten() {
-    return Collections.unmodifiableSet(collectFields(instanceFieldsWritten));
+  SortedSet<DexField> collectInstanceFieldsWritten() {
+    return ImmutableSortedSet.copyOf(
+        PresortedComparable::slowCompareTo, collectFields(instanceFieldsWritten));
   }
 
-  Set<DexField> collectStaticFieldsRead() {
-    return Collections.unmodifiableSet(collectFields(staticFieldsRead));
+  SortedSet<DexField> collectStaticFieldsRead() {
+    return ImmutableSortedSet.copyOf(
+        PresortedComparable::slowCompareTo, collectFields(staticFieldsRead));
   }
 
-  Set<DexField> collectStaticFieldsWritten() {
-    return Collections.unmodifiableSet(collectFields(staticFieldsWritten));
+  SortedSet<DexField> collectStaticFieldsWritten() {
+    return ImmutableSortedSet.copyOf(
+        PresortedComparable::slowCompareTo, collectFields(staticFieldsWritten));
   }
 
   private Set<DexField> collectReachedFields(Map<DexType, Set<DexField>> map,
@@ -918,14 +924,16 @@
     return target == null ? field : target.field;
   }
 
-  Set<DexField> collectFieldsRead() {
-    return Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
-        collectReachedFields(staticFieldsRead, this::tryLookupStaticField));
+  SortedSet<DexField> collectFieldsRead() {
+    return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
+        Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
+        collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
   }
 
-  Set<DexField> collectFieldsWritten() {
-    return Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
-        collectReachedFields(staticFieldsWritten, this::tryLookupStaticField));
+  SortedSet<DexField> collectFieldsWritten() {
+    return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
+        Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
+        collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
   }
 
   private static class Action {
@@ -1009,68 +1017,68 @@
      * Set of types that are mentioned in the program. We at least need an empty abstract classitem
      * for these.
      */
-    public final Set<DexType> liveTypes;
+    public final SortedSet<DexType> liveTypes;
     /**
      * Set of types that are actually instantiated. These cannot be abstract.
      */
-    final Set<DexType> instantiatedTypes;
+    final SortedSet<DexType> instantiatedTypes;
     /**
      * Set of methods that are the immediate target of an invoke. They might not actually be live
      * but are required so that invokes can find the method. If such a method is not live (i.e. not
      * contained in {@link #liveMethods}, it may be marked as abstract and its implementation may be
      * removed.
      */
-    final Set<DexMethod> targetedMethods;
+    final SortedSet<DexMethod> targetedMethods;
     /**
      * Set of methods that belong to live classes and can be reached by invokes. These need to be
      * kept.
      */
-    final Set<DexMethod> liveMethods;
+    final SortedSet<DexMethod> liveMethods;
     /**
      * Set of fields that belong to live classes and can be reached by invokes. These need to be
      * kept.
      */
-    public final Set<DexField> liveFields;
+    public final SortedSet<DexField> liveFields;
     /**
      * Set of all fields which may be touched by a get operation. This is actual field definitions.
      */
-    public final Set<DexField> fieldsRead;
+    public final SortedSet<DexField> fieldsRead;
     /**
      * Set of all fields which may be touched by a put operation. This is actual field definitions.
      */
-    public final Set<DexField> fieldsWritten;
+    public final SortedSet<DexField> fieldsWritten;
     /**
      * Set of all field ids used in instance field reads.
      */
-    public final Set<DexField> instanceFieldReads;
+    public final SortedSet<DexField> instanceFieldReads;
     /**
      * Set of all field ids used in instance field writes.
      */
-    public final Set<DexField> instanceFieldWrites;
+    public final SortedSet<DexField> instanceFieldWrites;
     /**
      * Set of all field ids used in static static field reads.
      */
-    public final Set<DexField> staticFieldReads;
+    public final SortedSet<DexField> staticFieldReads;
     /**
      * Set of all field ids used in static field writes.
      */
-    public final Set<DexField> staticFieldWrites;
+    public final SortedSet<DexField> staticFieldWrites;
     /**
      * Set of all methods referenced in virtual invokes;
      */
-    public final Set<DexMethod> virtualInvokes;
+    public final SortedSet<DexMethod> virtualInvokes;
     /**
      * Set of all methods referenced in super invokes;
      */
-    public final Set<DexMethod> superInvokes;
+    public final SortedSet<DexMethod> superInvokes;
     /**
      * Set of all methods referenced in direct invokes;
      */
-    public final Set<DexMethod> directInvokes;
+    public final SortedSet<DexMethod> directInvokes;
     /**
      * Set of all methods referenced in static invokes;
      */
-    public final Set<DexMethod> staticInvokes;
+    public final SortedSet<DexMethod> staticInvokes;
     /**
      * Set of all items that have to be kept independent of whether they are used.
      */
@@ -1090,8 +1098,10 @@
 
     private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
       super(appInfo);
-      this.liveTypes = Collections.unmodifiableSet(enqueuer.liveTypes);
-      this.instantiatedTypes = enqueuer.instantiatedTypes.getItems();
+      this.liveTypes =
+          ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo, enqueuer.liveTypes);
+      this.instantiatedTypes = ImmutableSortedSet.copyOf(
+          PresortedComparable::slowCompareTo, enqueuer.instantiatedTypes.getItems());
       this.targetedMethods = toDescriptorSet(enqueuer.targetedMethods.getItems());
       this.liveMethods = toDescriptorSet(enqueuer.liveMethods.getItems());
       this.liveFields = toDescriptorSet(enqueuer.liveFields.getItems());
@@ -1101,7 +1111,7 @@
       this.staticFieldWrites = enqueuer.collectStaticFieldsWritten();
       this.fieldsRead = enqueuer.collectFieldsRead();
       this.fieldsWritten = enqueuer.collectFieldsWritten();
-      this.pinnedItems = Collections.unmodifiableSet(enqueuer.pinnedItems);
+      this.pinnedItems = ImmutableSet.copyOf(enqueuer.pinnedItems);
       this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
       this.superInvokes = joinInvokedMethods(enqueuer.superInvokes);
       this.directInvokes = joinInvokedMethods(enqueuer.directInvokes);
@@ -1165,24 +1175,27 @@
       assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
     }
 
-    private Set<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
-      ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+    private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
+      ImmutableSortedSet.Builder<DexMethod> builder =
+          new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
       invokes.values().forEach(builder::addAll);
       return builder.build();
     }
 
-    private <T extends PresortedComparable<T>> Set<T> toDescriptorSet(
+    private <T extends PresortedComparable<T>> SortedSet<T> toDescriptorSet(
         Set<? extends KeyedDexItem<T>> set) {
-      ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+      ImmutableSortedSet.Builder<T> builder =
+          new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo);
       for (KeyedDexItem<T> item : set) {
         builder.add(item.getKey());
       }
       return builder.build();
     }
 
-    private static <T> ImmutableSet<T> rewriteItems(Set<T> original,
-        BiFunction<T, DexEncodedMethod, T> rewrite) {
-      ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+    private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
+        Set<T> original, BiFunction<T, DexEncodedMethod, T> rewrite) {
+      ImmutableSortedSet.Builder<T> builder =
+          new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
       for (T item : original) {
         builder.add(rewrite.apply(item, null));
       }
@@ -1245,11 +1258,11 @@
     }
 
     Set<T> getItems() {
-      return Collections.unmodifiableSet(items);
+      return ImmutableSet.copyOf(items);
     }
 
     Map<T, KeepReason> getReasons() {
-      return Collections.unmodifiableMap(reasons);
+      return ImmutableMap.copyOf(reasons);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
new file mode 100644
index 0000000..54302db
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -0,0 +1,47 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardCheckDiscardRule extends ProguardConfigurationRule {
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    private Builder() {
+    }
+
+    public ProguardCheckDiscardRule build() {
+      return new ProguardCheckDiscardRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules);
+    }
+  }
+
+  private ProguardCheckDiscardRule(
+      ProguardTypeMatcher classAnnotation,
+      DexAccessFlags classAccessFlags,
+      DexAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      List<ProguardTypeMatcher> classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      Set<ProguardMemberRule> memberRules) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "checkdiscard";
+  }
+}
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 17e5128..31fb77d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -137,16 +137,16 @@
       } else if (acceptString("keepattributes")) {
         parseKeepAttributes();
       } else if (acceptString("keeppackagenames")) {
-        ProguardKeepRule rule = parseKeepPackageNamesRule();
+        ProguardKeepPackageNamesRule rule = parseKeepPackageNamesRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("checkdiscard")) {
-        ProguardKeepRule rule = parseCheckDiscardRule();
+        ProguardCheckDiscardRule rule = parseCheckDiscardRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("keep")) {
         ProguardKeepRule rule = parseKeepRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("whyareyoukeeping")) {
-        ProguardKeepRule rule = parseWhyAreYouKeepingRule();
+        ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("dontoptimize")) {
         configurationBuilder.setOptimize(false);
@@ -351,37 +351,36 @@
       ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
       parseRuleTypeAndModifiers(keepRuleBuilder);
       parseClassSpec(keepRuleBuilder, false);
+      if (keepRuleBuilder.getMemberRules().isEmpty()) {
+        // If there are no member rules, a default rule for the parameterless constructor
+        // applies. So we add that here.
+        ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
+        defaultRuleBuilder.setName(Constants.INSTANCE_INITIALIZER_NAME);
+        defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
+        defaultRuleBuilder.setArguments(Collections.emptyList());
+        keepRuleBuilder.getMemberRules().add(defaultRuleBuilder.build());
+      }
       return keepRuleBuilder.build();
     }
 
-    private ProguardKeepRule parseWhyAreYouKeepingRule()
+    private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule()
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
-      keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
-      keepRuleBuilder.getModifiersBuilder().whyAreYouKeeping = true;
-      keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+      ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder();
       parseClassSpec(keepRuleBuilder, false);
       return keepRuleBuilder.build();
     }
 
-    private ProguardKeepRule parseKeepPackageNamesRule()
+    private ProguardKeepPackageNamesRule parseKeepPackageNamesRule()
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
-      keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
-      keepRuleBuilder.getModifiersBuilder().keepPackageNames = true;
-      keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+      ProguardKeepPackageNamesRule.Builder keepRuleBuilder = ProguardKeepPackageNamesRule.builder();
       keepRuleBuilder.setClassNames(parseClassNames());
       return keepRuleBuilder.build();
     }
 
-    private ProguardKeepRule parseCheckDiscardRule()
+    private ProguardCheckDiscardRule parseCheckDiscardRule()
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
-      keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
-      keepRuleBuilder.getModifiersBuilder().checkDiscarded = true;
+      ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder();
       parseClassSpec(keepRuleBuilder, false);
-      keepRuleBuilder.setType(keepRuleBuilder.getMemberRules().isEmpty() ? ProguardKeepRuleType.KEEP
-          : ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
       return keepRuleBuilder.build();
     }
 
@@ -528,14 +527,6 @@
         }
         skipWhitespace();
         expectChar('}');
-      } else {
-        // If there are no member rules, a default rule for the parameterless constructor
-        // applies. So we add that here.
-        ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
-        defaultRuleBuilder.setName(Constants.INSTANCE_INITIALIZER_NAME);
-        defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
-        defaultRuleBuilder.setArguments(Collections.emptyList());
-        classSpecificationBuilder.getMemberRules().add(defaultRuleBuilder.build());
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
new file mode 100644
index 0000000..787a31c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
@@ -0,0 +1,47 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardKeepPackageNamesRule extends ProguardConfigurationRule {
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    private Builder() {
+    }
+
+    public ProguardKeepPackageNamesRule build() {
+      return new ProguardKeepPackageNamesRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules);
+    }
+  }
+
+  private ProguardKeepPackageNamesRule(
+      ProguardTypeMatcher classAnnotation,
+      DexAccessFlags classAccessFlags,
+      DexAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      List<ProguardTypeMatcher> classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      Set<ProguardMemberRule> memberRules) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+  }
+
+  public static ProguardKeepPackageNamesRule.Builder builder() {
+    return new ProguardKeepPackageNamesRule.Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "keeppackagenames";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index e2f53b5..5db35cd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -8,51 +8,30 @@
     public boolean allowsShrinking = false;
     public boolean allowsOptimization = false;
     public boolean allowsObfuscation = false;
-    public boolean whyAreYouKeeping = false;
     public boolean includeDescriptorClasses = false;
-    public boolean keepPackageNames = false;
-    public boolean checkDiscarded = false;
-
-    void setFlagsToHaveNoEffect() {
-      allowsShrinking = true;
-      allowsOptimization = true;
-      allowsObfuscation = true;
-      whyAreYouKeeping = false;
-      includeDescriptorClasses = false;
-      keepPackageNames = false;
-    }
 
     private Builder() {}
 
     ProguardKeepRuleModifiers build() {
       return new ProguardKeepRuleModifiers(allowsShrinking, allowsOptimization, allowsObfuscation,
-          whyAreYouKeeping, includeDescriptorClasses, keepPackageNames, checkDiscarded);
+          includeDescriptorClasses);
     }
   }
 
   public final boolean allowsShrinking;
   public final boolean allowsOptimization;
   public final boolean allowsObfuscation;
-  public final boolean whyAreYouKeeping;
   public final boolean includeDescriptorClasses;
-  public final boolean keepPackageNames;
-  public final boolean checkDiscarded;
 
   private ProguardKeepRuleModifiers(
       boolean allowsShrinking,
       boolean allowsOptimization,
       boolean allowsObfuscation,
-      boolean whyAreYouKeeping,
-      boolean includeDescriptorClasses,
-      boolean keepPackageNames,
-      boolean checkDiscarded) {
+      boolean includeDescriptorClasses) {
     this.allowsShrinking = allowsShrinking;
     this.allowsOptimization = allowsOptimization;
     this.allowsObfuscation = allowsObfuscation;
-    this.whyAreYouKeeping = whyAreYouKeeping;
     this.includeDescriptorClasses = includeDescriptorClasses;
-    this.keepPackageNames = keepPackageNames;
-    this.checkDiscarded = checkDiscarded;
   }
   /**
    * Create a new empty builder.
@@ -71,8 +50,7 @@
     return allowsShrinking == that.allowsShrinking
         && allowsOptimization == that.allowsOptimization
         && allowsObfuscation == that.allowsObfuscation
-        && includeDescriptorClasses == that.includeDescriptorClasses
-        && keepPackageNames == that.keepPackageNames;
+        && includeDescriptorClasses == that.includeDescriptorClasses;
   }
 
   @Override
@@ -80,9 +58,7 @@
     return (allowsShrinking ? 1 : 0)
         | (allowsOptimization ? 2 : 0)
         | (allowsObfuscation ? 4 : 0)
-        | (whyAreYouKeeping ? 8 : 0)
-        | (includeDescriptorClasses ? 16 : 0)
-        | (keepPackageNames ? 32 : 0);
+        | (includeDescriptorClasses ? 8 : 0);
   }
 
   @Override
@@ -91,9 +67,7 @@
     appendWithComma(builder, allowsObfuscation, "allowobfuscation");
     appendWithComma(builder, allowsShrinking, "allowshrinking");
     appendWithComma(builder, allowsOptimization, "allowoptimization");
-    appendWithComma(builder, whyAreYouKeeping, "whyareyoukeeping");
     appendWithComma(builder, includeDescriptorClasses, "includedescriptorclasses");
-    appendWithComma(builder, keepPackageNames, "keeppackagenames");
     return builder.toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 1e2d6d4..f3e6c6f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -200,7 +200,7 @@
   public boolean matches(DexEncodedMethod method, RootSetBuilder builder) {
     switch (getRuleType()) {
       case ALL_METHODS:
-        if (method.accessFlags.isConstructor() && method.accessFlags.isStatic()) {
+        if (method.isClassInitializer()) {
           break;
         }
       case ALL:
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
new file mode 100644
index 0000000..581a0db
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -0,0 +1,47 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
+
+  public static class Builder extends ProguardConfigurationRule.Builder {
+
+    private Builder() {
+    }
+
+    public ProguardWhyAreYouKeepingRule build() {
+      return new ProguardWhyAreYouKeepingRule(classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules);
+    }
+  }
+
+  private ProguardWhyAreYouKeepingRule(
+      ProguardTypeMatcher classAnnotation,
+      DexAccessFlags classAccessFlags,
+      DexAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      List<ProguardTypeMatcher> classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      Set<ProguardMemberRule> memberRules) {
+    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+  }
+
+  public static ProguardWhyAreYouKeepingRule.Builder builder() {
+    return new ProguardWhyAreYouKeepingRule.Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "whyareyoukeeping";
+  }
+}
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 6ac5694..1ddd7d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,9 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Sets;
-
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -24,9 +21,11 @@
 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.ThreadUtils;
-
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -185,12 +184,23 @@
             }
             case KEEP: {
               markClass(clazz, rule);
-              markClass(clazz, rule);
               markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
               markMatchingFields(clazz, memberKeepRules, rule, null);
               break;
             }
           }
+        } else if (rule instanceof ProguardCheckDiscardRule) {
+          if (memberKeepRules.isEmpty()) {
+            markClass(clazz, rule);
+          } else {
+            markMatchingFields(clazz, memberKeepRules, rule, clazz.type);
+            markMatchingMethods(clazz, memberKeepRules, rule, clazz.type);
+          }
+        } else if (rule instanceof ProguardWhyAreYouKeepingRule
+            || rule instanceof ProguardKeepPackageNamesRule) {
+          markClass(clazz, rule);
+          markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
+          markMatchingFields(clazz, memberKeepRules, rule, null);
         } else if (rule instanceof ProguardAssumeNoSideEffectRule) {
           markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
           markMatchingFields(clazz, memberKeepRules, rule, null);
@@ -247,13 +257,24 @@
       Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
       DexType onlyIfClassKept) {
     Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>();
+    Arrays.stream(clazz.directMethods()).forEach(method ->
+        markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
     while (clazz != null) {
-      clazz.forEachMethod(method ->
+      Arrays.stream(clazz.virtualMethods()).forEach(method ->
           markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
       clazz = application.definitionFor(clazz.superType);
     }
   }
 
+  private void markMatchingMethods(DexClass clazz,
+      Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+      DexType onlyIfClassKept) {
+    Arrays.stream(clazz.directMethods()).forEach(method ->
+        markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+    Arrays.stream(clazz.virtualMethods()).forEach(method ->
+        markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+  }
+
   private void markMatchingFields(DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
       DexType onlyIfClassKept) {
@@ -375,7 +396,8 @@
   private void markMethod(DexEncodedMethod method, Collection<ProguardMemberRule> rules,
       ProguardConfigurationRule context, Set<Wrapper<DexMethod>> methodsMarked,
       DexType onlyIfClassKept) {
-    if (methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
+    if ((methodsMarked != null)
+        && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
       return;
     }
     for (ProguardMemberRule rule : rules) {
@@ -384,6 +406,9 @@
           Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
               rule);
         }
+        if (methodsMarked != null) {
+          methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
+        }
         addItemToSets(method, context, rule, onlyIfClassKept);
       }
     }
@@ -461,24 +486,19 @@
       if (!modifiers.allowsObfuscation) {
         noObfuscation.add(item);
       }
-      if (modifiers.whyAreYouKeeping) {
-        assert onlyIfClassKept == null;
-        reasonAsked.add(item);
-      }
-      if (modifiers.keepPackageNames) {
-        assert onlyIfClassKept == null;
-        keepPackageName.add(item);
-      }
       if (modifiers.includeDescriptorClasses) {
         includeDescriptorClasses(item, keepRule);
       }
-      if (modifiers.checkDiscarded) {
-        checkDiscarded.add(item);
-      }
     } else if (context instanceof ProguardAssumeNoSideEffectRule) {
       noSideEffects.put(item, rule);
+    } else if (context instanceof ProguardWhyAreYouKeepingRule) {
+      reasonAsked.add(item);
+    } else if (context instanceof ProguardKeepPackageNamesRule) {
+      keepPackageName.add(item);
     } else if (context instanceof ProguardAssumeValuesRule) {
       assumedValues.put(item, rule);
+    } else if (context instanceof ProguardCheckDiscardRule) {
+      checkDiscarded.add(item);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index e01439d..d3306d9 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -379,7 +379,7 @@
 
     private DexEncodedMethod renameConstructors(DexEncodedMethod method) {
       // Only rename instance initializers.
-      if (!method.accessFlags.isConstructor() || method.accessFlags.isStatic()) {
+      if (!method.isInstanceInitializer()) {
         return method;
       }
       DexType holder = method.method.holder;
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 2a5c9e0..d8fd6c2 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -113,7 +113,7 @@
   }
 
   private boolean isDefaultConstructor(DexEncodedMethod method) {
-    return method.accessFlags.isConstructor() && !method.accessFlags.isStatic()
+    return method.isInstanceInitializer()
         && method.method.proto.parameters.isEmpty();
   }
 
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 8b0ac9e..4610f41 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
@@ -196,7 +196,7 @@
           // Remove now unneeded constructor calls.
           InvokeStatic invokeStatic = insn.asInvokeStatic();
           DexMethod invokedMethod = invokeStatic.getInvokedMethod();
-          if ((invokeStatic.outValue().numberOfAllUsers() == 0)
+          if ((!invokeStatic.outValue().isUsed())
               && invokedMethod.proto.returnType.isSubtypeOf(protobufListType, appInfo)) {
             it.remove();
           }
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 b328895..2480417 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -52,7 +52,9 @@
   private final Resource proguardMap;
   private final Resource proguardSeeds;
   private final Resource packageDistribution;
-  private final Resource mainDexList;
+  private final List<Resource> mainDexListResources;
+  private final List<String> mainDexClasses;
+  private final Resource mainDexListOutput;
 
   // See factory methods and AndroidApp.Builder below.
   private AndroidApp(
@@ -64,7 +66,9 @@
       Resource proguardMap,
       Resource proguardSeeds,
       Resource packageDistribution,
-      Resource mainDexList) {
+      List<Resource> mainDexListResources,
+      List<String> mainDexClasses,
+      Resource mainDexListOutput) {
     this.programResources = programResources;
     this.programFileArchiveReaders = programFileArchiveReaders;
     this.classpathResourceProviders = classpathResourceProviders;
@@ -73,7 +77,9 @@
     this.proguardMap = proguardMap;
     this.proguardSeeds = proguardSeeds;
     this.packageDistribution = packageDistribution;
-    this.mainDexList = mainDexList;
+    this.mainDexListResources = mainDexListResources;
+    this.mainDexClasses = mainDexClasses;
+    this.mainDexListOutput = mainDexListOutput;
   }
 
   /**
@@ -239,17 +245,45 @@
   }
 
   /**
-   * True if the main dex list resource exists.
+   * True if the main dex list resources exists.
    */
   public boolean hasMainDexList() {
-    return mainDexList != null;
+    return !(mainDexListResources.isEmpty() && mainDexClasses.isEmpty());
   }
 
   /**
-   * Get the input stream of the main dex list resource if it exists.
+   * True if the main dex list resources exists.
    */
-  public InputStream getMainDexList(Closer closer) throws IOException {
-    return mainDexList == null ? null : closer.register(mainDexList.getStream());
+  public boolean hasMainDexListResources() {
+    return !mainDexListResources.isEmpty();
+  }
+
+  /**
+   * Get the main dex list resources if any.
+   */
+  public List<Resource> getMainDexListResources() {
+    return mainDexListResources;
+  }
+
+  /**
+   * Get the main dex classes if any.
+   */
+  public List<String> getMainDexClasses() {
+    return mainDexClasses;
+  }
+
+  /**
+   * True if the main dex list resource exists.
+   */
+  public boolean hasMainDexListOutput() {
+    return mainDexListOutput != null;
+  }
+
+  /**
+   * Get the main dex list output resources if any.
+   */
+  public InputStream getMainDexListOutput(Closer closer) throws IOException {
+    return mainDexListOutput == null ? null : closer.register(mainDexListOutput.getStream());
   }
 
   /**
@@ -361,7 +395,7 @@
   }
 
   public void writeMainDexList(Closer closer, OutputStream out) throws IOException {
-    InputStream input = getMainDexList(closer);
+    InputStream input = getMainDexListOutput(closer);
     assert input != null;
     out.write(ByteStreams.toByteArray(input));
   }
@@ -385,7 +419,9 @@
     private Resource proguardMap;
     private Resource proguardSeeds;
     private Resource packageDistribution;
-    private Resource mainDexList;
+    private List<Resource> mainDexListResources = new ArrayList<>();
+    private List<String> mainDexListClasses = new ArrayList<>();
+    private Resource mainDexListOutput;
 
     // See AndroidApp::builder().
     private Builder() {
@@ -401,7 +437,9 @@
       proguardMap = app.proguardMap;
       proguardSeeds = app.proguardSeeds;
       packageDistribution = app.packageDistribution;
-      mainDexList = app.mainDexList;
+      mainDexListResources = app.mainDexListResources;
+      mainDexListClasses = app.mainDexClasses;
+      mainDexListOutput = app.mainDexListOutput;
     }
 
     /**
@@ -585,22 +623,49 @@
     }
 
     /**
-     * Set the main-dex list file.
+     * Add a main-dex list file.
      */
-    public Builder setMainDexListFile(Path file) {
-      mainDexList = file == null ? null : Resource.fromFile(null, file);
+    public Builder addMainDexListFiles(Path... files) throws IOException {
+      return addMainDexListFiles(Arrays.asList(files));
+    }
+
+    public Builder addMainDexListFiles(Collection<Path> files) throws IOException {
+      for (Path file : files) {
+        if (!Files.exists(file)) {
+          throw new FileNotFoundException("Non-existent input file: " + file);
+        }
+        // TODO(sgjesse): Should we just read the file here? This will sacrifice the parallelism
+        // in ApplicationReader where all input resources are read in parallel.
+        mainDexListResources.add(Resource.fromFile(null, file));
+      }
+      return this;
+    }
+
+
+    /**
+     * Add main-dex classes.
+     */
+    public Builder addMainDexClasses(String... classes) {
+      return addMainDexClasses(Arrays.asList(classes));
+    }
+
+    /**
+     * Add main-dex classes.
+     */
+    public Builder addMainDexClasses(Collection<String> classes) {
+      mainDexListClasses.addAll(classes);
       return this;
     }
 
     public boolean hasMainDexList() {
-      return mainDexList != null;
+      return !(mainDexListResources.isEmpty() && mainDexListClasses.isEmpty());
     }
 
     /**
-     * Set the main-dex list data.
+     * Set the main-dex list output data.
      */
-    public Builder setMainDexListData(byte[] content) {
-      mainDexList = content == null ? null : Resource.fromBytes(null, content);
+    public Builder setMainDexListOutputData(byte[] content) {
+      mainDexListOutput = content == null ? null : Resource.fromBytes(null, content);
       return this;
     }
 
@@ -617,7 +682,9 @@
           proguardMap,
           proguardSeeds,
           packageDistribution,
-          mainDexList);
+          mainDexListResources,
+          mainDexListClasses,
+          mainDexListOutput);
     }
 
     private void addProgramFile(Path file) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 5c5a776..90c22f0 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -13,7 +13,6 @@
 import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
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 eb746d7..9fea848 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -45,6 +46,9 @@
   // Silencing output.
   public boolean quiet = false;
 
+  // Hidden marker for classes.dex
+  public Marker customizedMarker;
+
   public List<String> methodsFilter = ImmutableList.of();
   public int minApiLevel = Constants.DEFAULT_ANDROID_API;
   // Skipping min_api check and compiling an intermediate result intended for later merging.
@@ -69,7 +73,6 @@
   public Path seedsFile;
   public boolean printMapping;
   public Path printMappingFile;
-  public boolean printMainDexList;
   public Path printMainDexListFile;
   public boolean ignoreMissingClasses = false;
   public boolean skipMinification = false;
@@ -84,6 +87,7 @@
   public boolean allowParameterName = false;
 
   public boolean debug = false;
+  public boolean singleStepDebug = false;
   public final TestingOptions testing = new TestingOptions();
 
   // TODO(zerny): These stateful dictionaries do not belong here.
@@ -141,7 +145,9 @@
   }
 
   public static class TestingOptions {
-    public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering;
+
+    public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering
+        = Function.identity();
   }
 
   public static class AttributeRemovalOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/MainDexList.java b/src/main/java/com/android/tools/r8/utils/MainDexList.java
index b219314..826901a 100644
--- a/src/main/java/com/android/tools/r8/utils/MainDexList.java
+++ b/src/main/java/com/android/tools/r8/utils/MainDexList.java
@@ -28,6 +28,18 @@
     }
   }
 
+  public static DexType parse(String clazz, DexItemFactory itemFactory) {
+    if (!clazz.endsWith(CLASS_EXTENSION)) {
+      throw new CompilationError("Illegal main-dex-list entry '" + clazz + "'.");
+    }
+    String name = clazz.substring(0, clazz.length() - CLASS_EXTENSION.length());
+    if (name.contains("" + JAVA_PACKAGE_SEPARATOR)) {
+      throw new CompilationError("Illegal main-dex-list entry '" + clazz + "'.");
+    }
+    String descriptor = "L" + name + ";";
+    return itemFactory.createType(descriptor);
+  }
+
   public static Set<DexType> parse(InputStream input, DexItemFactory itemFactory) {
     Set<DexType> result = Sets.newIdentityHashSet();
     try {
@@ -35,15 +47,7 @@
           new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
       String line;
       while ((line = file.readLine()) != null) {
-        if (!line.endsWith(CLASS_EXTENSION)) {
-          throw new CompilationError("Illegal main-dex-list entry '" + line + "'.");
-        }
-        String name = line.substring(0, line.length() - CLASS_EXTENSION.length());
-        if (name.contains("" + JAVA_PACKAGE_SEPARATOR)) {
-          throw new CompilationError("Illegal main-dex-list entry '" + line + "'.");
-        }
-        String descriptor = "L" + name + ";";
-        result.add(itemFactory.createType(descriptor));
+        result.add(parse(line, itemFactory));
       }
     } catch (IOException e) {
       throw new CompilationError("Cannot load main-dex-list.");
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 3ad6596..3ab5d7b 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -188,6 +188,29 @@
     }
   }
 
+  public static int stepEmptyForLoopBody1(int n) {
+    int i;
+    for (i = 0; i < n; i++) ;
+    return i;
+  }
+
+  public static int stepEmptyForLoopBody2(int n) {
+    int i;
+    for (i = 0; i < n; i++) {
+      // has a line but still empty...
+    }
+    return i;
+  }
+
+  public static int stepNonEmptyForLoopBody(int n) {
+    int i;
+    for (i = 0; i < n; i++)
+      nop();
+    return i;
+  }
+
+  public static void nop() {}
+
   public static void main(String[] args) {
     noLocals();
     unusedLocals();
@@ -198,6 +221,9 @@
     reverseRange(1,2,3,4,5,6,7);
     new Locals().lotsOfArrayLength();
     new Locals().foo(21);
+    stepEmptyForLoopBody1(3);
+    stepEmptyForLoopBody2(3);
+    stepNonEmptyForLoopBody(3);
   }
 
 }
diff --git a/src/test/examples/inlining/Inlining.java b/src/test/examples/inlining/Inlining.java
index bd8870f..f89a0e1 100644
--- a/src/test/examples/inlining/Inlining.java
+++ b/src/test/examples/inlining/Inlining.java
@@ -52,14 +52,39 @@
     this.a = a;
   }
 
-  @CheckDiscarded
   InlineConstructor(long a) {
     this((int) a);
   }
 
+  InlineConstructor(int a, int loopy) {
+    this.a = a;
+    // Make this too big to inline.
+    if (loopy > 10) {
+      throw new RuntimeException("Too big!");
+    }
+    for (int i = 1; i < loopy; i++) {
+      this.a = this.a * i;
+    }
+  }
+
+  @CheckDiscarded
+  InlineConstructor() {
+    this(42, 9);
+  }
+
   static InlineConstructor create() {
     return new InlineConstructor(10L);
   }
+
+  static InlineConstructor createMore() {
+    new InlineConstructor(0, 0);
+    new InlineConstructor(0, 0);
+    new InlineConstructor(0, 0);
+    new InlineConstructor(0, 0);
+    new InlineConstructor(0, 0);
+    new InlineConstructor(0, 0);
+    return new InlineConstructor();
+  }
 }
 
 class InlineConstructorOfInner {
@@ -152,6 +177,8 @@
 
     InlineConstructor ic = InlineConstructor.create();
     Assert(ic != null);
+    InlineConstructor ic2 = InlineConstructor.createMore();
+    Assert(ic2 != null);
     InlineConstructorOfInner icoi = new InlineConstructorOfInner();
     Assert(icoi != null);
 
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 055a880..063d5fc 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4850,7 +4850,7 @@
     if (testMatch(failuresToTriage, name, compilerUnderTest, dexVm, compilationMode)) {
       outcome = Outcome.FAILS_WITH_ART;
     }
-    if (testMatch(requiresInliningDisabled, name, compilerUnderTest, dexVm, compilationMode)) {
+    if (testMatch(timeoutsWithArt, name, compilerUnderTest, dexVm, compilationMode)) {
       assert outcome == null;
       outcome = Outcome.TIMEOUTS_WITH_ART;
     }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index fabf8e7..da561f8 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -752,12 +752,19 @@
       "488-checker-inline-recursive-calls",
       "487-checker-inline-calls",
       "122-npe",
+      "141-class-unload",
 
       // Calls some internal art methods that cannot tolerate inlining.
       "466-get-live-vreg",
 
       // Requires a certain call pattern to surface an Art bug.
-      "534-checker-bce-deoptimization"
+      "534-checker-bce-deoptimization",
+
+      // Requires something to be allocated in a method so that it goes out of scope.
+      "059-finalizer-throw",
+
+      // Has tests in submethods, which we should not inline.
+      "625-checker-licm-regressions"
   );
 
   private static List<String> failuresToTriage = ImmutableList.of(
@@ -1087,49 +1094,47 @@
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
     assert mode != null;
     switch (compilerUnderTest) {
-      case D8:
-        {
-          assert keepRulesFile == null : "Keep-rules file specified for D8.";
-          D8Command.Builder builder =
-              D8Command.builder()
-                  .setMode(mode)
-                  .addProgramFiles(ListUtils.map(fileNames, Paths::get));
-          Integer minSdkVersion = needMinSdkVersion.get(name);
-          if (minSdkVersion != null) {
-            builder.setMinApiLevel(minSdkVersion);
-          }
-          D8Output output = D8.run(builder.build());
-          output.write(Paths.get(resultPath));
-          break;
+      case D8: {
+        assert keepRulesFile == null : "Keep-rules file specified for D8.";
+        D8Command.Builder builder =
+            D8Command.builder()
+                .setMode(mode)
+                .addProgramFiles(ListUtils.map(fileNames, Paths::get));
+        Integer minSdkVersion = needMinSdkVersion.get(name);
+        if (minSdkVersion != null) {
+          builder.setMinApiLevel(minSdkVersion);
         }
-      case R8:
-        {
-          R8Command.Builder builder =
-              R8Command.builder()
-                  .setMode(mode)
-                  .setOutputPath(Paths.get(resultPath))
-                  .addProgramFiles(ListUtils.map(fileNames, Paths::get))
-                  .setIgnoreMissingClasses(true);
-          Integer minSdkVersion = needMinSdkVersion.get(name);
-          if (minSdkVersion != null) {
-            builder.setMinApiLevel(minSdkVersion);
-          }
-          if (keepRulesFile != null) {
-            builder.addProguardConfigurationFiles(Paths.get(keepRulesFile));
-          }
-          // Add internal flags for testing purposes.
-          ToolHelper.runR8(
-              builder.build(),
-              options -> {
-                if (enableInterfaceMethodDesugaring.contains(name)) {
-                  options.interfaceMethodDesugaring = OffOrAuto.Auto;
-                }
-                if (disableInlining) {
-                  options.inlineAccessors = false;
-                }
-              });
-          break;
+        D8Output output = D8.run(builder.build());
+        output.write(Paths.get(resultPath));
+        break;
+      }
+      case R8: {
+        R8Command.Builder builder =
+            R8Command.builder()
+                .setMode(mode)
+                .setOutputPath(Paths.get(resultPath))
+                .addProgramFiles(ListUtils.map(fileNames, Paths::get))
+                .setIgnoreMissingClasses(true);
+        Integer minSdkVersion = needMinSdkVersion.get(name);
+        if (minSdkVersion != null) {
+          builder.setMinApiLevel(minSdkVersion);
         }
+        if (keepRulesFile != null) {
+          builder.addProguardConfigurationFiles(Paths.get(keepRulesFile));
+        }
+        // Add internal flags for testing purposes.
+        ToolHelper.runR8(
+            builder.build(),
+            options -> {
+              if (enableInterfaceMethodDesugaring.contains(name)) {
+                options.interfaceMethodDesugaring = OffOrAuto.Auto;
+              }
+              if (disableInlining) {
+                options.inlineAccessors = false;
+              }
+            });
+        break;
+      }
       default:
         assert false : compilerUnderTest;
     }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e4b37f2..562518f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -658,18 +658,36 @@
       Consumer<ArtCommandBuilder> extras,
       DexVm version)
       throws IOException {
-    // Run art on original.
-    for (String file : files1) {
-      assertTrue("file1 " + file + " must exists", Files.exists(Paths.get(file)));
+    return checkArtOutputIdentical(
+        version,
+        mainClass,
+        extras,
+        ImmutableList.of(ListUtils.map(files1, Paths::get), ListUtils.map(files2, Paths::get)));
+  }
+
+  public static String checkArtOutputIdentical(
+      DexVm version,
+      String mainClass,
+      Consumer<ArtCommandBuilder> extras,
+      Collection<Collection<Path>> programs)
+      throws IOException {
+    for (Collection<Path> program : programs) {
+      for (Path path : program) {
+        assertTrue("File " + path + " must exist", Files.exists(path));
+      }
     }
-    String output1 = ToolHelper.runArtNoVerificationErrors(files1, mainClass, extras, version);
-    // Run art on R8 processed version.
-    for (String file : files2) {
-      assertTrue("file2 " + file + " must exists", Files.exists(Paths.get(file)));
+    String output = null;
+    for (Collection<Path> program : programs) {
+      String result =
+          ToolHelper.runArtNoVerificationErrors(
+              ListUtils.map(program, Path::toString), mainClass, extras, version);
+      if (output != null) {
+        assertEquals(output, result);
+      } else {
+        output = result;
+      }
     }
-    String output2 = ToolHelper.runArtNoVerificationErrors(files2, mainClass, extras, version);
-    assertEquals(output1, output2);
-    return output1;
+    return output;
   }
 
   public static void runDex2Oat(Path file, Path outFile) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
similarity index 87%
rename from src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
rename to src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index c33d5b4..6e3c114 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -2,11 +2,13 @@
 // 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.bridgeremoval.bridgestoremove;
+package com.android.tools.r8.bridgeremoval;
 
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.bridgeremoval.bridgestoremove.Main;
+import com.android.tools.r8.bridgeremoval.bridgestoremove.Outer;
 import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
new file mode 100644
index 0000000..a56c0c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -0,0 +1,74 @@
+// 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.checkdiscarded;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.checkdiscarded.testclasses.Main;
+import com.android.tools.r8.checkdiscarded.testclasses.UnusedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.UsedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.WillBeGone;
+import com.android.tools.r8.checkdiscarded.testclasses.WillStay;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CheckDiscardedTest extends TestBase {
+
+  private void run(boolean obfuscate, Class annotation, boolean checkMembers, boolean shouldFail)
+      throws Exception {
+    List<Class> classes = ImmutableList.of(
+        UnusedClass.class,
+        UsedClass.class,
+        Main.class);
+    String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate)
+        + checkDiscardRule(checkMembers, annotation);
+    try {
+      compileWithR8(classes, proguardConfig, this::noInlining);
+    } catch (CompilationError e) {
+      Assert.assertTrue(shouldFail);
+      return;
+    }
+    Assert.assertFalse(shouldFail);
+  }
+
+  private void noInlining(InternalOptions options) {
+    options.inlineAccessors = false;
+  }
+
+  private String checkDiscardRule(boolean member, Class annotation) {
+    if (member) {
+      return "-checkdiscard class * { @" + annotation.getCanonicalName() + " *; }";
+    } else {
+      return "-checkdiscard @" + annotation.getCanonicalName() + " class *";
+    }
+  }
+
+  @Test
+  public void classesAreGone() throws Exception {
+    run(false, WillBeGone.class, false, false);
+    run(true, WillBeGone.class, false, false);
+  }
+
+  @Test
+  public void classesAreNotGone() throws Exception {
+    run(false, WillStay.class, false, true);
+    run(true, WillStay.class, false, true);
+  }
+
+  @Test
+  public void membersAreGone() throws Exception {
+    run(false, WillBeGone.class, true, false);
+    run(true, WillBeGone.class, true, false);
+  }
+
+  @Test
+  public void membersAreNotGone() throws Exception {
+    run(false, WillStay.class, true, true);
+    run(true, WillStay.class, true, true);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java
new file mode 100644
index 0000000..677330a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java
@@ -0,0 +1,13 @@
+// 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.checkdiscarded.testclasses;
+
+@WillStay
+public class Main {
+
+  public static void main(String... args) {
+    UsedClass usedClass = new UsedClass();
+    System.out.println(usedClass.hello());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java
new file mode 100644
index 0000000..1b1689b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java
@@ -0,0 +1,9 @@
+// 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.checkdiscarded.testclasses;
+
+@WillBeGone
+public class UnusedClass {
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
new file mode 100644
index 0000000..253c114
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
@@ -0,0 +1,18 @@
+// 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.checkdiscarded.testclasses;
+
+@WillStay
+public class UsedClass {
+
+  @WillStay
+  public String hello() {
+    return "hello";
+  }
+
+  @WillBeGone
+  public String world() {
+    return "world";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java
new file mode 100644
index 0000000..9176911
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java
@@ -0,0 +1,8 @@
+// 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.checkdiscarded.testclasses;
+
+public @interface WillBeGone {
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java
new file mode 100644
index 0000000..e1925c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java
@@ -0,0 +1,8 @@
+// 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.checkdiscarded.testclasses;
+
+public @interface WillStay {
+
+}
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
new file mode 100644
index 0000000..2878ed0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2017, the Rex 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.d8;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
+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.Timing;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Simple test that compiles framework.jar with D8 a number of times with
+ * various number of threads available to the compiler.
+ * This test also tests the hidden marker inserted into classes.dex.
+ */
+@RunWith( Parameterized.class )
+public class D8FrameworkTest {
+
+  private static final Path FRAMEWORK_JAR =
+      Paths.get("tools/linux/art-5.1.1/product/mako/system/framework/framework.jar");
+
+  @Rule
+  public TemporaryFolder output = ToolHelper.getTemporaryFolderForTest();
+
+  @Parameters(name = "Number of threads = {0}")
+  public static Collection<Object[]> data() {
+    return Arrays.asList(new Object[][] { {1}, {2}, {4}, {8}, {16} });
+  }
+
+  private final int threads;
+
+  public D8FrameworkTest(int threads) {
+    this.threads = threads;
+  }
+
+  @Test
+  public void compile() throws CompilationException, IOException, ExecutionException {
+    D8Command command = D8Command.builder()
+        .setMinApiLevel(Constants.ANDROID_N_API)
+        .addProgramFiles(FRAMEWORK_JAR)
+        .build();
+    Marker marker = new Marker(Tool.D8)
+        .put("revision", "1.0.0")
+        .put("threads", threads);
+    Marker selfie = Marker.parse(marker.toString());
+    assert marker.equals(selfie);
+    AndroidApp app = ToolHelper.runD8(command, options -> {
+      options.customizedMarker = marker;
+      options.numberOfThreads = threads;
+    });
+    DexApplication dexApp =
+        new ApplicationReader(app, new InternalOptions(), new Timing("D8FrameworkTest")).read();
+    Marker readMarker = dexApp.dexItemFactory.extractMarker();
+    assertEquals(marker, readMarker);
+  }
+}
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 6a5f738..57a1ce8 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -623,15 +623,24 @@
               ));
         }
 
+        private void failNoLocal(String localName) {
+          Assert.fail(
+              "line " + getLineNumber() + ": Expected local '" + localName + "' not present");
+        }
+
         public void checkLocal(String localName) {
           Optional<Variable> localVar = JUnit3Wrapper
               .getVariableAt(mirror, getLocation(), localName);
-          Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
+          if (!localVar.isPresent()) {
+            failNoLocal(localName);
+          }
         }
 
         public void checkLocal(String localName, Value expectedValue) {
           Optional<Variable> localVar = getVariableAt(mirror, getLocation(), localName);
-          Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
+          if (!localVar.isPresent()) {
+            failNoLocal(localName);
+          }
 
           // Get value
           CommandPacket commandPacket = new CommandPacket(
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 1fd4bc9..16d1a60 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -348,4 +348,79 @@
         run());
   }
 
+  @Test
+  public void testStepEmptyForLoopBody1() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "stepEmptyForLoopBody1"),
+        run(),
+        checkLocal("n", Value.createInt(3)),
+        checkNoLocal("i"),
+        stepOver(),
+        checkLocal("n", Value.createInt(3)),
+        checkLocal("i", Value.createInt(3)),
+        run());
+  }
+
+  @Test
+  public void testStepEmptyForLoopBody2() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "stepEmptyForLoopBody2"),
+        run(),
+        checkLocal("n", Value.createInt(3)),
+        checkNoLocal("i"),
+        stepOver(),
+        checkLocal("n", Value.createInt(3)),
+        checkLocal("i", Value.createInt(3)),
+        run());
+  }
+
+  @Test
+  public void testStepNonEmptyForLoopBody() throws Throwable {
+    final int LOOP_HEADER_LINE = 207;
+    final int LOOP_BODY_LINE = 208;
+    final int RETURN_LINE = 209;
+    final Value N = Value.createInt(3);
+    final Value I0 = Value.createInt(0);
+    final Value I1 = Value.createInt(1);
+    final Value I2 = Value.createInt(2);
+    final Value I3 = Value.createInt(3);
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "stepNonEmptyForLoopBody"),
+        run(),
+        checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+        checkLocal("n", N),
+        checkNoLocal("i"),
+        stepOver(),
+        checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+        checkLocal("n", N),
+        checkLocal("i", I0),
+        stepOver(),
+        checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+        checkLocal("n", N),
+        checkLocal("i", I0),
+        stepOver(),
+        checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+        checkLocal("n", N),
+        checkLocal("i", I1),
+        stepOver(),
+        checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+        checkLocal("n", N),
+        checkLocal("i", I1),
+        stepOver(),
+        checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+        checkLocal("n", N),
+        checkLocal("i", I2),
+        stepOver(),
+        checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+        checkLocal("n", N),
+        checkLocal("i", I2),
+        stepOver(),
+        checkLine(SOURCE_FILE, RETURN_LINE),
+        checkLocal("n", N),
+        checkLocal("i", I3),
+        run());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java
new file mode 100644
index 0000000..5a83545
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java
@@ -0,0 +1,21 @@
+// 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.debuginfo;
+
+public class ConditionalLocalTest {
+
+  public void foo(int x) {
+    if (x % 2 != 0) {
+      Integer obj = new Integer(x + x);
+      long l = obj.longValue();
+      x = (int) l;
+      System.out.print(obj);
+    }
+    return;
+  }
+
+  public static void main(String[] args) {
+    new ConditionalLocalTest().foo(21);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
new file mode 100644
index 0000000..8348c0b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
@@ -0,0 +1,41 @@
+// 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.debuginfo;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Test;
+
+public class ConditionalLocalTestRunner extends DebugInfoTestBase {
+
+  @Test
+  public void testConditionalLocal() throws Exception {
+    Class clazz = ConditionalLocalTest.class;
+
+    AndroidApp d8App = compileWithD8(clazz);
+    AndroidApp dxApp = getDxCompiledSources();
+
+    String expected = "42";
+    assertEquals(expected, runOnJava(clazz));
+    assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
+    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
+
+    checkConditonalLocal(inspectMethod(d8App, clazz, "void", "foo", "int"));
+    checkConditonalLocal(inspectMethod(dxApp, clazz, "void", "foo", "int"));
+  }
+
+  private void checkConditonalLocal(DebugInfoInspector info) {
+    String self = ConditionalLocalTest.class.getCanonicalName();
+    String Integer = "java.lang.Integer";
+    info.checkStartLine(9);
+    info.checkLineHasExactLocals(9, "this", self, "x", "int");
+    info.checkLineHasExactLocals(10, "this", self, "x", "int");
+    info.checkLineHasExactLocals(11, "this", self, "x", "int", "obj", Integer);
+    info.checkLineHasExactLocals(12, "this", self, "x", "int", "obj", Integer, "l", "long");
+    info.checkLineHasExactLocals(13, "this", self, "x", "int", "obj", Integer, "l", "long");
+    info.checkNoLine(14);
+    info.checkLineHasExactLocals(15, "this", self, "x", "int");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
index 8df6f79..8a5b77d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
@@ -22,11 +22,11 @@
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
     assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
-    checkBackBranchToSelf(inspectMethod(d8App, clazz, "int", "localsAtThrow", "int"));
-    checkBackBranchToSelf(inspectMethod(dxApp, clazz, "int", "localsAtThrow", "int"));
+    checkLocalsAtThrow(inspectMethod(d8App, clazz, "int", "localsAtThrow", "int"));
+    checkLocalsAtThrow(inspectMethod(dxApp, clazz, "int", "localsAtThrow", "int"));
   }
 
-  private void checkBackBranchToSelf(DebugInfoInspector info) {
+  private void checkLocalsAtThrow(DebugInfoInspector info) {
     info.checkStartLine(9);
     info.checkLineHasExactLocals(9, "x", "int");
     info.checkLineHasExactLocals(10, "x", "int", "a", "int");
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
new file mode 100644
index 0000000..98f1071
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -0,0 +1,108 @@
+// 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.dex;
+
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.DexAccessFlags;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class JumboStringProcessing {
+
+  @Test
+  public void branching() {
+    DexItemFactory factory = new DexItemFactory();
+    DexString string = factory.createString("turn into jumbo");
+    factory.sort(NamingLens.getIdentityLens());
+    Instruction[] instructions = buildInstructions(string, false);
+    DexCode code = jumboStringProcess(factory, string, instructions);
+    Instruction[] rewrittenInstructions = code.instructions;
+    assert rewrittenInstructions[1] instanceof IfEq;
+    IfEq condition = (IfEq) rewrittenInstructions[1];
+    assert condition.getOffset() + condition.CCCC == rewrittenInstructions[3].getOffset();
+    assert rewrittenInstructions[2] instanceof Goto32;
+    Goto32 jump = (Goto32) rewrittenInstructions[2];
+    Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+    assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+  }
+
+  @Test
+  public void branching2() {
+    DexItemFactory factory = new DexItemFactory();
+    DexString string = factory.createString("turn into jumbo");
+    factory.sort(NamingLens.getIdentityLens());
+    Instruction[] instructions = buildInstructions(string, true);
+    DexCode code = jumboStringProcess(factory, string, instructions);
+    Instruction[] rewrittenInstructions = code.instructions;
+    assert rewrittenInstructions[1] instanceof IfEqz;
+    IfEqz condition = (IfEqz) rewrittenInstructions[1];
+    assert condition.getOffset() + condition.BBBB == rewrittenInstructions[3].getOffset();
+    assert rewrittenInstructions[2] instanceof Goto32;
+    Goto32 jump = (Goto32) rewrittenInstructions[2];
+    Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+    assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+  }
+
+  private Instruction[] buildInstructions(DexString string, boolean zeroCondition) {
+    List<Instruction> instructions = new ArrayList<>();
+    int offset = 0;
+    Instruction instr = new Const4(0, 0);
+    instr.setOffset(offset);
+    instructions.add(instr);
+    offset += instr.getSize();
+    int lastInstructionOffset = 15000 * 2 + 2 + offset;
+    if (zeroCondition) {
+      instr = new IfNez(0, lastInstructionOffset - offset);
+    } else {
+      instr = new IfNe(0, 0, lastInstructionOffset - offset);
+    }
+    instr.setOffset(offset);
+    instructions.add(instr);
+    offset += instr.getSize();
+    for (int i = 0; i < 15000; i++) {
+      instr = new ConstString(0, string);
+      instr.setOffset(offset);
+      instructions.add(instr);
+      offset += instr.getSize();
+    }
+    instr = new ReturnVoid();
+    instr.setOffset(offset);
+    instructions.add(instr);
+    assert instr.getOffset() == lastInstructionOffset;
+    return instructions.toArray(new Instruction[instructions.size()]);
+  }
+
+  private DexCode jumboStringProcess(
+      DexItemFactory factory, DexString string, Instruction[] instructions) {
+    DexCode code = new DexCode(
+        1,
+        0,
+        0,
+        instructions,
+        new Try[0],
+        null,
+        null,
+        null);
+    DexAccessFlags flags = new DexAccessFlags(0);
+    flags.setPublic();
+    DexEncodedMethod method = new DexEncodedMethod(null, flags, null, null, code);
+    new JumboStringRewriter(method, string, factory).rewrite();
+    return method.getCode().asDexCode();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index f7a9caa..10947a4 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -157,8 +157,8 @@
       throws IOException {
     try (Closer closer = Closer.create()) {
       if (write) {
-        app1.writeToDirectory(Paths.get("app1"), OutputMode.Indexed);
-        app2.writeToDirectory(Paths.get("app2"), OutputMode.Indexed);
+        app1.writeToDirectory(temp.newFolder("app1").toPath(), OutputMode.Indexed);
+        app2.writeToDirectory(temp.newFolder("app2").toPath(), OutputMode.Indexed);
       }
       List<Resource> files1 = app1.getDexProgramResources();
       List<Resource> files2 = app2.getDexProgramResources();
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
new file mode 100644
index 0000000..61b624e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
@@ -0,0 +1,104 @@
+// 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.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import org.junit.Test;
+
+public class JumboStringTests extends JasminTestBase {
+
+  // String constants are split into several class files to ensure both the constant-pool and
+  // instruction count are below the class-file limits.
+  private static int CLASSES_COUNT = 10;
+  private static int MIN_STRING_COUNT = Constants.FIRST_JUMBO_INDEX + 1;
+  private static int EXTRA_STRINGS_PER_CLASSES_COUNT = MIN_STRING_COUNT % CLASSES_COUNT;
+  private static int STRINGS_PER_CLASSES_COUNT =
+      EXTRA_STRINGS_PER_CLASSES_COUNT + MIN_STRING_COUNT / CLASSES_COUNT;
+
+  @Test
+  public void test() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    LinkedHashMap<String, MethodSignature> classes = new LinkedHashMap<>(CLASSES_COUNT);
+    for (int i = 0; i < CLASSES_COUNT; i++) {
+      JasminBuilder.ClassBuilder clazz = builder.addClass("Test" + i);
+      List<String> lines = new ArrayList<>(STRINGS_PER_CLASSES_COUNT + 100);
+      lines.addAll(
+          ImmutableList.of(
+              ".limit locals 3",
+              ".limit stack 4",
+              ".var 0 is this LTest; from L0 to L2",
+              ".var 1 is i I from L0 to L2",
+              ".var 2 is strings [Ljava/lang/String; from L1 to L2",
+              "L0:",
+              ".line 1",
+              "  ldc " + STRINGS_PER_CLASSES_COUNT,
+              "  anewarray java/lang/String",
+              "  astore 2",
+              "L1:",
+              ".line 2"));
+      for (int j = 0; j < STRINGS_PER_CLASSES_COUNT; j++) {
+        lines.add("  aload 2");
+        lines.add("  ldc " + j);
+        lines.add("  ldc \"string" + i + "_" + j + "\"");
+        lines.add("  aastore");
+      }
+      lines.addAll(
+          ImmutableList.of(
+              "L2:",
+              "  .line 3",
+              "  aload 2",
+              "  iload 1",
+              "  aaload",
+              "  checkcast java/lang/String",
+              "  areturn"));
+      MethodSignature foo =
+          clazz.addVirtualMethod(
+              "foo", ImmutableList.of("I"), "Ljava/lang/String;", lines.toArray(new String[0]));
+      classes.put(clazz.name, foo);
+    }
+
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+    clazz.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 1",
+        "  new Test0",
+        "  dup",
+        "  invokespecial Test0/<init>()V",
+        "  ldc 42",
+        "  invokevirtual Test0/foo(I)Ljava/lang/String;",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  swap",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  return");
+
+    String expected = "string0_42";
+    assertEquals(expected, runOnJava(builder, clazz.name));
+
+    AndroidApp jasminApp = builder.build();
+    AndroidApp d8App = ToolHelper.runD8(jasminApp);
+    assertEquals(expected, runOnArt(d8App, clazz.name));
+
+    DexInspector inspector = new DexInspector(d8App);
+    for (Entry<String, MethodSignature> entry : classes.entrySet()) {
+      DebugInfoInspector info = new DebugInfoInspector(inspector, entry.getKey(), entry.getValue());
+      info.checkStartLine(1);
+      // If jumbo-string processing fails to keep debug info, some methods will have lost 'i' here.
+      info.checkLineHasExactLocals(1, "this", entry.getKey(), "i", "int");
+      info.checkLineHasExactLocals(
+          2, "this", entry.getKey(), "i", "int", "strings", "java.lang.String[]");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index f8ca2a8..dfc20f9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -6,43 +6,45 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 public class MainDexListOutputTest extends TestBase {
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   @Test
   public void testNoMainDex() throws Exception {
-    Path mainDexList = temp.newFile().toPath();
-    compileWithR8(ImmutableList.of(HelloWorldMain.class),
-        options -> {
-          options.printMainDexList = true;
-          options.printMainDexListFile = mainDexList;
-        });
-    // Empty main dex list.
-    assertEquals(0, FileUtils.readTextFile(mainDexList).size());
+    thrown.expect(CompilationException.class);
+    Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
+            .setMainDexListOutputPath(mainDexListOutput)
+            .build();
+    ToolHelper.runR8(command);
   }
 
   @Test
   public void testWithMainDex() throws Exception {
     Path mainDexRules = writeTextToTempFile(keepMainProguardConfiguration(HelloWorldMain.class));
+    Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
     R8Command command =
         ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
             .addMainDexRules(mainDexRules)
+            .setMainDexListOutputPath(mainDexListOutput)
             .build();
-    Path mainDexList = temp.newFile().toPath();
-    ToolHelper.runR8(command,
-        options -> {
-          options.printMainDexList = true;
-          options.printMainDexListFile = mainDexList;
-        });
+    ToolHelper.runR8(command);
     // Main dex list with the single class.
     assertEquals(
         ImmutableList.of(HelloWorldMain.class.getTypeName().replace('.', '/') + ".class"),
-        FileUtils.readTextFile(mainDexList));
+        FileUtils.readTextFile(mainDexListOutput));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 714792d..a87985e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -4,11 +4,14 @@
 package com.android.tools.r8.maindexlist;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
@@ -63,6 +66,8 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Rule;
@@ -216,7 +221,9 @@
     FileUtils.writeTextFile(mainDexList, list);
     Set<DexType> types = MainDexList.parse(mainDexList, factory);
     for (String entry : list) {
-      assertTrue(types.contains(factory.createType("L" + entry.replace(".class", "") + ";")));
+      DexType type = factory.createType("L" + entry.replace(".class", "") + ";");
+      assertTrue(types.contains(type));
+      assertSame(type, MainDexList.parse(entry, factory));
     }
   }
 
@@ -229,34 +236,108 @@
     MainDexList.parse(mainDexList, factory);
   }
 
+  private Path runD8WithMainDexList(
+      CompilationMode mode, Path input, List<String> mainDexClasses, boolean useFile)
+      throws Exception {
+    Path testDir = temp.newFolder().toPath();
+    Path listFile = testDir.resolve("main-dex-list.txt");
+    if (mainDexClasses != null && useFile) {
+      FileUtils.writeTextFile(
+          listFile,
+          mainDexClasses
+              .stream()
+              .map(clazz -> clazz.replace('.', '/') + ".class")
+              .collect(Collectors.toList()));
+    }
+
+    D8Command.Builder builder = D8Command.builder()
+        .addProgramFiles(input)
+        .setMode(mode)
+        .setOutputPath(testDir);
+    if (mainDexClasses != null) {
+      if (useFile) {
+        builder.addMainDexListFiles(listFile);
+      } else {
+        builder.addMainDexClasses(mainDexClasses);
+      }
+    }
+    ToolHelper.runD8(builder.build());
+    return testDir;
+  }
+
+  private void runDeterministicTest(Path input, List<String> mainDexClasses, boolean allClasses)
+      throws Exception {
+    // Run test in debug and release mode for minimal vs. non-minimal main-dex.
+    for (CompilationMode mode : CompilationMode.values()) {
+
+      // Build with all different main dex lists.
+      List<Path> testDirs = new ArrayList<>();
+      if (allClasses) {
+        // If all classes are passed add a run without a main-dex list as well.
+        testDirs.add(runD8WithMainDexList(mode, input, null, true));
+      }
+      testDirs.add(runD8WithMainDexList(mode, input, mainDexClasses, true));
+      testDirs.add(runD8WithMainDexList(mode, input, mainDexClasses, false));
+      if (mainDexClasses != null) {
+        testDirs.add(runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), true));
+        testDirs.add(runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), false));
+      }
+
+      byte[] ref = null;
+      byte[] ref2 = null;
+      for (Path testDir : testDirs) {
+        Path primaryDexFile = testDir.resolve(FileUtils.DEFAULT_DEX_FILENAME);
+        Path secondaryDexFile = testDir.resolve("classes2.dex");
+        assertTrue(Files.exists(primaryDexFile));
+        boolean hasSecondaryDexFile = !allClasses && mode == CompilationMode.DEBUG;
+        assertEquals(hasSecondaryDexFile, Files.exists(testDir.resolve(secondaryDexFile)));
+        byte[] content = Files.readAllBytes(primaryDexFile);
+        if (ref == null) {
+          ref = content;
+        } else {
+          assertArrayEquals(ref, content);
+        }
+        if (hasSecondaryDexFile) {
+          content = Files.readAllBytes(primaryDexFile);
+          if (ref2 == null) {
+            ref2 = content;
+          } else {
+            assertArrayEquals(ref2, content);
+          }
+        }
+      }
+    }
+  }
+
   @Test
-  public void checkDeterminism() throws Exception {
+  public void deterministicTest() throws Exception {
     // Synthesize a dex containing a few empty classes including some in the default package.
-    // Everything can fit easaly in a single dex file.
-    String[] classes = {
-        "A",
-        "B",
-        "C",
-        "D",
-        "E",
-        "F",
-        "A1",
-        "A2",
-        "A3",
-        "A4",
-        "A5",
-        "maindexlist/A",
-        "maindexlist/B",
-        "maindexlist/C",
-        "maindexlist/D",
-        "maindexlist/E",
-        "maindexlist/F",
-        "maindexlist/A1",
-        "maindexlist/A2",
-        "maindexlist/A3",
-        "maindexlist/A4",
-        "maindexlist/A5"
-    };
+    // Everything can fit easily in a single dex file.
+    ImmutableList<String> classes = new ImmutableList.Builder<String>()
+        .add("A")
+        .add("B")
+        .add("C")
+        .add("D")
+        .add("E")
+        .add("F")
+        .add("A1")
+        .add("A2")
+        .add("A3")
+        .add("A4")
+        .add("A5")
+        .add("maindexlist.A")
+        .add("maindexlist.B")
+        .add("maindexlist.C")
+        .add("maindexlist.D")
+        .add("maindexlist.E")
+        .add("maindexlist.F")
+        .add("maindexlist.A1")
+        .add("maindexlist.A2")
+        .add("maindexlist.A3")
+        .add("maindexlist.A4")
+        .add("maindexlist.A5")
+        .build();
+
     JasminBuilder jasminBuilder = new JasminBuilder();
     for (String name : classes) {
       jasminBuilder.addClass(name);
@@ -264,69 +345,28 @@
     Path input = temp.newFolder().toPath().resolve("input.zip");
     ToolHelper.runR8(jasminBuilder.build()).writeToZip(input, OutputMode.Indexed);
 
-    // Prepare different main dex lists.
-    ArrayList<Path> mainLists = new ArrayList<>();
-    // Lets first without a main dex list.
-    mainLists.add(null);
+    // Test with empty main dex list.
+    runDeterministicTest(input, null, true);
 
-    // List with all classes.
-    List<String> mainList = new ArrayList<>();
-    for (int i = 0; i < classes.length; i++) {
-      mainList.add(classes[i] + ".class");
-    }
-    addMainListFile(mainLists, mainList);
+    // Test with main-dex list with all classes.
+    runDeterministicTest(input, classes, true);
 
-    // Full list in reverse order
-    addMainListFile(mainLists, Lists.reverse(mainList));
+    // Test with main-dex list with first and second half of the classes.
+    List<List<String>> partitions = Lists.partition(classes, classes.size() / 2);
+    runDeterministicTest(input, partitions.get(0), false);
+    runDeterministicTest(input, partitions.get(1), false);
 
-    // Partial list without first entries (those in default package).
-    mainList.clear();
-    for (int i = classes.length / 2; i < classes.length; i++) {
-      mainList.add(classes[i] + ".class");
-    }
-    addMainListFile(mainLists, mainList);
-
-    // Same in reverse order
-    addMainListFile(mainLists, Lists.reverse(mainList));
-
-    // Mixed partial list.
-    mainList.clear();
-    for (int i = 0; i < classes.length; i += 2) {
-      mainList.add(classes[i] + ".class");
-    }
-    addMainListFile(mainLists, mainList);
-
-    // Another different mixed partial list.
-    mainList.clear();
-    for (int i = 1; i < classes.length; i += 2) {
-      mainList.add(classes[i] + ".class");
-    }
-    addMainListFile(mainLists, mainList);
-
-    // Build with all main dex lists.
-    Path tmp = temp.getRoot().toPath();
-    for (int i = 0; i < mainLists.size(); i++) {
-      Path out = tmp.resolve(String.valueOf(i));
-      Files.createDirectories(out);
-      D8Command.Builder builder = D8Command.builder()
-          .addProgramFiles(input)
-          .setOutputPath(out);
-      if (mainLists.get(i) != null) {
-        builder.setMainDexListFile(mainLists.get(i));
-      }
-      ToolHelper.runD8(builder.build());
-    }
-
-    // Check: no secondary dex and resulting dex is always the same.
-    assertFalse(Files.exists(tmp.resolve(String.valueOf(0)).resolve("classes2.dex")));
-    byte[] ref = Files.readAllBytes(
-        tmp.resolve(String.valueOf(0)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
-    for (int i = 1; i < mainLists.size(); i++) {
-      assertFalse(Files.exists(tmp.resolve(String.valueOf(i)).resolve("classes2.dex")));
-      byte[] checked = Files.readAllBytes(
-          tmp.resolve(String.valueOf(i)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
-      assertArrayEquals(ref, checked);
-    }
+    // Test with main-dex list with every second of the classes.
+    runDeterministicTest(input,
+        IntStream.range(0, classes.size())
+            .filter(n -> n % 2 == 0)
+            .mapToObj(classes::get)
+            .collect(Collectors.toList()), false);
+    runDeterministicTest(input,
+        IntStream.range(0, classes.size())
+            .filter(n -> n % 2 == 1)
+            .mapToObj(classes::get)
+            .collect(Collectors.toList()), false);
   }
 
   @Test
@@ -359,13 +399,6 @@
     }
   }
 
-  private void addMainListFile(ArrayList<Path> mainLists, List<String> content)
-      throws IOException {
-    Path listFile = temp.newFile().toPath();
-    FileUtils.writeTextFile(listFile, content);
-    mainLists.add(listFile);
-  }
-
   private static String typeToEntry(String type) {
     return type.replace(".", "/") + FileUtils.CLASS_EXTENSION;
   }
@@ -398,8 +431,16 @@
     }
   }
 
+  private enum MultiDexTestMode {
+    SINGLE_FILE,
+    MULTIPLE_FILES,
+    STRINGS,
+    FILES_AND_STRINGS
+  }
+
   private void doVerifyMainDexContains(
-      List<String> mainDex, Path app, boolean singleDexApp, boolean minimalMainDex)
+      List<String> mainDex, Path app, boolean singleDexApp, boolean minimalMainDex,
+      MultiDexTestMode testMode)
       throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
     AndroidApp originalApp = AndroidApp.fromProgramFiles(app);
     DexInspector originalInspector = new DexInspector(originalApp);
@@ -408,18 +449,58 @@
           originalInspector.clazz(clazz).isPresent());
     }
     Path outDir = temp.newFolder().toPath();
-    Path mainDexList = temp.newFile().toPath();
-    FileUtils.writeTextFile(mainDexList, ListUtils.map(mainDex, MainDexListTests::typeToEntry));
-    R8Command command =
+    R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(app)
-            .setMainDexListFile(mainDexList)
-            .setMinimalMainDex(minimalMainDex && mainDex.size() > 0)
+            .setMode(minimalMainDex && mainDex.size() > 0
+                ? CompilationMode.DEBUG : CompilationMode.RELEASE)
             .setOutputPath(outDir)
             .setTreeShaking(false)
-            .setMinification(false)
-            .build();
-    ToolHelper.runR8(command);
+            .setMinification(false);
+
+    switch (testMode) {
+      case SINGLE_FILE:
+        Path mainDexList = temp.newFile().toPath();
+        FileUtils.writeTextFile(mainDexList, ListUtils.map(mainDex, MainDexListTests::typeToEntry));
+        builder.addMainDexListFiles(mainDexList);
+        break;
+      case MULTIPLE_FILES: {
+        // Partion the main dex list into several files.
+        List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
+        List<Path> mainDexListFiles = new ArrayList<>();
+        for (List<String> partition : partitions) {
+          Path partialMainDexList = temp.newFile().toPath();
+          FileUtils.writeTextFile(partialMainDexList,
+              ListUtils.map(partition, MainDexListTests::typeToEntry));
+          mainDexListFiles.add(partialMainDexList);
+        }
+        builder.addMainDexListFiles(mainDexListFiles);
+        break;
+      }
+      case STRINGS:
+        builder.addMainDexClasses(mainDex);
+        break;
+      case FILES_AND_STRINGS: {
+        // Partion the main dex list add some parts through files and the other parts using strings.
+        List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
+        List<Path> mainDexListFiles = new ArrayList<>();
+        for (int i = 0; i < partitions.size(); i++) {
+          List<String> partition = partitions.get(i);
+          if (i % 2 == 0) {
+            Path partialMainDexList = temp.newFile().toPath();
+            FileUtils.writeTextFile(partialMainDexList,
+                ListUtils.map(partition, MainDexListTests::typeToEntry));
+            mainDexListFiles.add(partialMainDexList);
+          } else {
+            builder.addMainDexClasses(mainDex);
+          }
+        }
+        builder.addMainDexListFiles(mainDexListFiles);
+        break;
+      }
+    }
+
+    ToolHelper.runR8(builder.build());
     if (!singleDexApp && !minimalMainDex) {
       assertTrue("Output run only produced one dex file.",
           1 < Files.list(outDir).filter(FileUtils::isDexFile).count());
@@ -431,15 +512,17 @@
         failedToFindClassInExpectedFile(outDir, clazz);
       }
     }
-    if (minimalMainDex) {
+    if (minimalMainDex && mainDex.size() > 0) {
       inspector.forAllClasses(clazz -> assertMainDexClass(clazz, mainDex));
     }
   }
 
   private void verifyMainDexContains(List<String> mainDex, Path app, boolean singleDexApp)
       throws Throwable {
-    doVerifyMainDexContains(mainDex, app, singleDexApp, false);
-    doVerifyMainDexContains(mainDex, app, singleDexApp, true);
+    for (MultiDexTestMode multiDexTestMode : MultiDexTestMode.values()) {
+      doVerifyMainDexContains(mainDex, app, singleDexApp, false, multiDexTestMode);
+      doVerifyMainDexContains(mainDex, app, singleDexApp, true, multiDexTestMode);
+    }
   }
 
   public static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
@@ -498,7 +581,7 @@
     DexApplication application = builder.build();
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
     ApplicationWriter writer = new ApplicationWriter(
-        application, appInfo, options, null, NamingLens.getIdentityLens(), null);
+        application, appInfo, options, null, null, NamingLens.getIdentityLens(), null);
     ExecutorService executor = ThreadUtils.getExecutorService(options);
     try {
       return writer.write(null, executor);
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
index 70d4efc..8d0e667 100644
--- a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -7,9 +7,9 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.OffOrAuto;
 import java.nio.file.Path;
 import org.junit.Assert;
 import org.junit.Test;
@@ -17,17 +17,15 @@
 public class Regress63935662 extends TestBase {
 
   void run(AndroidApp app, Class mainClass) throws Exception {
-    if (!ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1)) {
-      return;
-    }
-    Path proguardConfig = writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
+    Path proguardConfig =
+        writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
     R8Command command =
         ToolHelper.prepareR8CommandBuilder(app)
             .addProguardConfigurationFiles(proguardConfig)
-            .setMinApiLevel(Constants.ANDROID_N_API)
+            .setMinApiLevel(Constants.ANDROID_L_API)
             .build();
     String resultFromJava = runOnJava(mainClass);
-    app = ToolHelper.runR8(command);
+    app = ToolHelper.runR8(command, options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
     String resultFromArt = runOnArt(app, mainClass);
     Assert.assertEquals(resultFromJava, resultFromArt);
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index c9b476a..89f0f3b 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -74,6 +74,8 @@
             .addLibraryFiles(Paths.get(ANDROID_JAR))
             .build();
     ToolHelper.runR8(command, options -> {
+      // Disable inlining to make this test not depend on inlining decisions.
+      options.inlineAccessors = false;
       options.printUsage = true;
       options.printUsageFile = out.resolve(test + PRINT_USAGE_FILE_SUFFIX);
     });
@@ -152,12 +154,11 @@
 
   private static void inspectShaking9(PrintUsageInspector inspector) {
     Optional<ClassSubject> superClass = inspector.clazz("shaking9.Superclass");
-    assertTrue(superClass.isPresent());
-    assertTrue(superClass.get().method("void", "<init>", Collections.emptyList()));
+    assertFalse(superClass.isPresent());
     Optional<ClassSubject> subClass = inspector.clazz("shaking9.Subclass");
     assertTrue(subClass.isPresent());
     assertTrue(subClass.get().method("void", "aMethod", Collections.emptyList()));
-    assertTrue(subClass.get().method("void", "<init>", Collections.emptyList()));
+    assertFalse(subClass.get().method("void", "<init>", Collections.emptyList()));
   }
 
   private static void inspectShaking12(PrintUsageInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 8f46572..795c020 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -92,7 +92,11 @@
             .addProgramFiles(originalDex)
             .setOutputPath(out)
             .addProguardConfigurationFiles(keepRules, printMapping)
-            .build());
+            .build(),
+        options -> {
+          // Turn off inlining, as we want the mapping that is printed to be stable.
+          options.inlineAccessors = false;
+        });
     Path outputmapping = out.resolve("mapping.txt");
     String actualMapping;
     actualMapping = new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8);
diff --git a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
index 443e257..1e8555b 100644
--- a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -49,7 +50,7 @@
   private void verifyEmptyCommand(D8Command command) throws IOException {
     assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
     assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
-    assertFalse(ToolHelper.getApp(command).hasMainDexList());
+    assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
     assertFalse(ToolHelper.getApp(command).hasProguardMap());
     assertFalse(ToolHelper.getApp(command).hasProguardSeeds());
     assertFalse(ToolHelper.getApp(command).hasPackageDistribution());
@@ -157,33 +158,38 @@
 
   @Test
   public void mainDexList() throws Throwable {
-    Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
-    D8Command command = parse("--main-dex-list", mailDexList.toString());
-    assertTrue(ToolHelper.getApp(command).hasMainDexList());
+    Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+    Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+
+    D8Command command = parse("--main-dex-list", mainDexList1.toString());
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
+
+    command = parse(
+        "--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
   @Test
-  public void multipleMainDexList() throws Throwable {
-    thrown.expect(CompilationException.class);
-    Path mailDexList1 = temp.getRoot().toPath().resolve("main-dex-list-1.txt");
-    Path mailDexList2 = temp.getRoot().toPath().resolve("main-dex-list-2.txt");
-    parse("--main-dex-list", mailDexList1.toString(), "--main-dex-list", mailDexList2.toString());
+  public void nonExistingMainDexList() throws Throwable {
+    thrown.expect(FileNotFoundException.class);
+    Path mainDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
+    parse("--main-dex-list", mainDexList.toString());
   }
 
   @Test
   public void mainDexListWithFilePerClass() throws Throwable {
     thrown.expect(CompilationException.class);
-    Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
-    D8Command command = parse("--main-dex-list", mailDexList.toString(), "--file-per-class");
-    assertTrue(ToolHelper.getApp(command).hasMainDexList());
+    Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+    D8Command command = parse("--main-dex-list", mainDexList.toString(), "--file-per-class");
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
   @Test
   public void mainDexListWithIntermediate() throws Throwable {
     thrown.expect(CompilationException.class);
-    Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
-    D8Command command = parse("--main-dex-list", mailDexList.toString(), "--intermediate");
-    assertTrue(ToolHelper.getApp(command).hasMainDexList());
+    Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+    D8Command command = parse("--main-dex-list", mainDexList.toString(), "--intermediate");
+    assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
index a1e57ac..fdeb2dc 100644
--- a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
@@ -16,8 +16,10 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
@@ -49,7 +51,7 @@
   private void verifyEmptyCommand(R8Command command) throws IOException {
     assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
     assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
-    assertFalse(ToolHelper.getApp(command).hasMainDexList());
+    assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
     assertFalse(ToolHelper.getApp(command).hasProguardMap());
     assertFalse(ToolHelper.getApp(command).hasProguardSeeds());
     assertFalse(ToolHelper.getApp(command).hasPackageDistribution());
@@ -90,20 +92,50 @@
 
   @Test
   public void mainDexRules() throws Throwable {
-    Path mailDexRules = temp.newFile("main-dex.rules").toPath();
-    parse("--main-dex-rules", mailDexRules.toString());
+    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 minimalMainDex() throws Throwable {
+  public void nonExistingMainDexRules() throws Throwable {
+    thrown.expect(NoSuchFileException.class);
+    Path mainDexRules = temp.getRoot().toPath().resolve("main-dex.rules");
+    parse("--main-dex-rules", mainDexRules.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());
+  }
+
+  @Test
+  public void nonExistingMainDexList() throws Throwable {
+    thrown.expect(FileNotFoundException.class);
+    Path mainDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
+    parse("--main-dex-list", mainDexList.toString());
+  }
+
+  @Test
+  public void mainDexListOutput() throws Throwable {
+    Path mainDexRules = temp.newFile("main-dex.rules").toPath();
+    Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+    Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
+    parse("--main-dex-rules", mainDexRules.toString(),
+        "--main-dex-list-output", mainDexListOutput.toString());
+    parse("--main-dex-list", mainDexList.toString(),
+        "--main-dex-list-output", mainDexListOutput.toString());
+  }
+
+  @Test
+  public void mainDexListOutputWithoutAnyMainDexSpecification() throws Throwable {
     thrown.expect(CompilationException.class);
-    parse("--minimal-main-dex");
-  }
-
-  @Test
-  public void mainDexRulesWithMinimalMainDex() throws Throwable {
-    Path mailDexRules = temp.newFile("main-dex.rules").toPath();
-    parse("--main-dex-rules", mailDexRules.toString(), "--minimal-main-dex");
+    Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
+    parse("--main-dex-list-output", mainDexListOutput.toString());
   }
 
   @Test
diff --git a/third_party/goyt.tar.gz.sha1 b/third_party/goyt.tar.gz.sha1
index 873f829..0630836 100644
--- a/third_party/goyt.tar.gz.sha1
+++ b/third_party/goyt.tar.gz.sha1
@@ -1 +1 @@
-9ce39f3b3b0db4661946863cbb8fb073df174410
\ No newline at end of file
+9237df6f97a6ef87cab77de8e53abf457793df9a
\ No newline at end of file
diff --git a/tools/test_framework.py b/tools/test_framework.py
index b328375..1ade73d 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -32,7 +32,7 @@
     'dx.jar')
 D8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'd8.jar')
 GOYT_EXE = os.path.join('third_party', 'goyt',
-    'goyt_160525751')
+    'goyt_164843480')
 FRAMEWORK_JAR = os.path.join('third_party', 'framework',
     'framework_160115954.jar')
 MIN_SDK_VERSION = '24'
@@ -49,9 +49,10 @@
   parser.add_argument('--name',
       required = True,
       help = 'Results will be printed using the specified benchmark name (e.g.'
-          ' <NAME>-<segment>(CodeSize): <bytes>)')
+          ' <NAME>-<segment>(CodeSize): <bytes>), the full size is reported'
+          ' with <NAME>-Total(CodeSize)')
   parser.add_argument('--print-memoryuse',
-      help = 'Prints the line \'<NAME>(MemoryUse):' +
+      help = 'Prints the line \'<NAME>-Total(MemoryUse):' +
              ' <mem>\' at the end where <mem> is the peak' +
              ' peak resident set size (VmHWM) in bytes.',
       default = False,
@@ -99,7 +100,7 @@
     dt = time.time() - t0
 
     if args.print_memoryuse:
-      print('{}(MemoryUse): {}'
+      print('{}-Total(MemoryUse): {}'
           .format(args.name, utils.grep_memoryuse(track_memory_file)))
 
     dex_files = [f for f in glob(os.path.join(temp_dir, '*.dex'))]