Merge "Implement package name obfuscation (w/ access modification)."
diff --git a/build.gradle b/build.gradle
index e2c21b6..a00c516 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,11 +6,15 @@
 
 apply plugin: 'java'
 apply plugin: 'idea'
-apply plugin: 'jacoco'
 apply plugin: 'com.google.protobuf'
 
 apply from: 'copyAdditionalJctfCommonFiles.gradle'
 
+
+if (project.hasProperty('with_code_coverage')) {
+    apply plugin: 'jacoco'
+}
+
 repositories {
     maven { url 'https://maven.google.com' }
     mavenCentral()
@@ -918,6 +922,9 @@
         println "JCTF: compiling only"
         systemProperty 'jctf_compile_only', '1'
     }
+    if (project.hasProperty('test_dir')) {
+        systemProperty 'test_dir', project.property('test_dir')
+    }
 
     if (OperatingSystem.current().isLinux()
             || OperatingSystem.current().isMacOsX()
diff --git a/src/main/java/com/android/tools/r8/BSPatch.java b/src/main/java/com/android/tools/r8/BSPatch.java
index eda5f23..8c1d208 100644
--- a/src/main/java/com/android/tools/r8/BSPatch.java
+++ b/src/main/java/com/android/tools/r8/BSPatch.java
@@ -202,7 +202,6 @@
 
     @Override
     public void copyOld(int blockSize) throws IOException {
-      assert mergeBuffer != null;
       assert mergeBuffer.length == blockSize;
       byte[] data = new byte[blockSize];
       oldInput.get(data);
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 430624b..debe8a1 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -95,6 +95,9 @@
     private CompilationMode mode;
     private int minApiLevel = Constants.DEFAULT_ANDROID_API;
 
+    // Internal flag used by CompatDx to ignore dex files in archives.
+    protected boolean ignoreDexInArchive = false;
+
     protected Builder(CompilationMode mode) {
       this(AndroidApp.builder(), mode);
     }
@@ -108,6 +111,7 @@
       assert mode != null;
       this.app = builder;
       this.mode = mode;
+      app.setIgnoreDexInArchive(ignoreDexInArchive);
     }
 
     abstract B self();
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 4c8cdd5..ed992a3 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@
  */
 public final class D8 {
 
-  private static final String kVersion = "v0.0.1";
+  private static final String VERSION = "v0.1.0";
   private static final int STATUS_ERROR = 1;
 
   private D8() {}
@@ -110,7 +110,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("D8 " + kVersion);
+      System.out.println("D8 " + VERSION);
       return;
     }
     run(command);
@@ -161,7 +161,7 @@
       return options.getMarker();
     }
     return new Marker(Tool.D8)
-        .put("version", kVersion)
+        .put("version", VERSION)
         .put("min-api", options.minApiLevel);
   }
 
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 64a1a0e..cb8c170 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -36,7 +36,7 @@
 
     private boolean intermediate = false;
 
-    private Builder() {
+    protected Builder() {
       super(CompilationMode.DEBUG);
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index acbbe0a..da90e82 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@
 
 public class R8 {
 
-  private static final String kVersion = "v0.0.1";
+  private static final String VERSION = "v0.1.0";
   private final Timing timing = new Timing("R8");
   private final InternalOptions options;
 
@@ -86,7 +86,7 @@
       return options.getMarker();
     }
     return new Marker(Tool.R8)
-        .put("version", kVersion)
+        .put("version", VERSION)
         .put("min-api", options.minApiLevel);
   }
 
@@ -434,7 +434,7 @@
             options.proguardConfiguration.getPrintMappingFile(),
             System.out,
             StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-        outputApp.writeProguardMap(closer, mapOut);
+        outputApp.writeProguardMap(mapOut);
       }
     }
     if (options.proguardConfiguration.isPrintSeeds()) {
@@ -502,7 +502,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("R8 " + kVersion);
+      System.out.println("R8 " + VERSION);
       return;
     }
     run(command);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 928b28f..ec040a5 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -34,17 +34,14 @@
     private Optional<Boolean> treeShaking = Optional.empty();
     private Optional<Boolean> minification = Optional.empty();
     private boolean ignoreMissingClasses = false;
+    private Path packageDistributionFile = null;
 
     private Builder() {
       super(CompilationMode.RELEASE);
-      // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
-      proguardConfigurationConsumer = builder -> builder.setAllowAccessModification(true);
     }
 
     private Builder(AndroidApp app) {
       super(app, CompilationMode.RELEASE);
-      // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
-      proguardConfigurationConsumer = builder -> builder.setAllowAccessModification(true);
     }
 
     @Override
@@ -131,7 +128,7 @@
      * Set a package distribution file resource.
      */
     public Builder setPackageDistributionFile(Path path) {
-      getAppBuilder().setPackageDistributionFile(path);
+      packageDistributionFile = path;
       return self();
     }
 
@@ -151,6 +148,10 @@
         throw new CompilationException(
             "Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
       }
+      if (getMode() == CompilationMode.DEBUG && packageDistributionFile != null) {
+        throw new CompilationException(
+            "Package distribution file is not supported in debug mode");
+      }
     }
 
     @Override
@@ -193,6 +194,10 @@
         addLibraryFiles(configuration.getLibraryjars());
       }
 
+      if (packageDistributionFile != null) {
+        getAppBuilder().setPackageDistributionFile(packageDistributionFile);
+      }
+
       boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking());
       boolean useMinification = minification.orElse(configuration.isObfuscating());
 
diff --git a/src/main/java/com/android/tools/r8/code/Const.java b/src/main/java/com/android/tools/r8/code/Const.java
index 301f982..2f63777 100644
--- a/src/main/java/com/android/tools/r8/code/Const.java
+++ b/src/main/java/com/android/tools/r8/code/Const.java
@@ -41,12 +41,12 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+    return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
         " (" + decodedValue() + ")");
   }
 
   public String toSmaliString(ClassNameMapper naming) {
-    return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+    return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
         "  # " + decodedValue());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Const16.java b/src/main/java/com/android/tools/r8/code/Const16.java
index aa00341..baf2ca7 100644
--- a/src/main/java/com/android/tools/r8/code/Const16.java
+++ b/src/main/java/com/android/tools/r8/code/Const16.java
@@ -42,7 +42,7 @@
 
   @Override
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 4) +
+    return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 4) +
         " (" + decodedValue() + ")");
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Const4.java b/src/main/java/com/android/tools/r8/code/Const4.java
index 26da02a..b907e78 100644
--- a/src/main/java/com/android/tools/r8/code/Const4.java
+++ b/src/main/java/com/android/tools/r8/code/Const4.java
@@ -41,12 +41,12 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + A + ", 0x" + StringUtils.hexString(decodedValue(), 1) +
+    return formatString("v" + A + ", " + StringUtils.hexString(decodedValue(), 1) +
         " (" + decodedValue() + ")");
   }
 
   public String toSmaliString(ClassNameMapper naming) {
-    return formatSmaliString("v" + A + ", 0x" + StringUtils.hexString(decodedValue(), 2) +
+    return formatSmaliString("v" + A + ", " + StringUtils.hexString(decodedValue(), 2) +
         "  # " + decodedValue());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/ConstHigh16.java b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
index 3245fa9..d3a4264 100644
--- a/src/main/java/com/android/tools/r8/code/ConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
@@ -41,12 +41,12 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+    return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
         " (" + decodedValue() + ")");
   }
 
   public String toSmaliString(ClassNameMapper naming) {
-    return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+    return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
         "  # " + decodedValue());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide.java b/src/main/java/com/android/tools/r8/code/ConstWide.java
index 7323f9b..002a377 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide.java
@@ -41,12 +41,12 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+    return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
         " (" + decodedValue() + ")");
   }
 
   public String toSmaliString(ClassNameMapper naming) {
-    return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+    return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
         "L  # " + decodedValue());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide16.java b/src/main/java/com/android/tools/r8/code/ConstWide16.java
index edd93fc..b593491 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide16.java
@@ -41,13 +41,13 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+    return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
         " (" + decodedValue() + ")");
   }
 
   public String toSmaliString(ClassNameMapper naming) {
     return formatSmaliString(
-        "v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) + "L  # " + decodedValue());
+        "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + "L  # " + decodedValue());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide32.java b/src/main/java/com/android/tools/r8/code/ConstWide32.java
index 540e840..5c4fc7b 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide32.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide32.java
@@ -41,13 +41,13 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+    return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
         " (" + decodedValue() + ")");
   }
 
   public String toSmaliString(ClassNameMapper naming) {
     return formatSmaliString(
-        "v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) + "  # " + decodedValue());
+        "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + "  # " + decodedValue());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
index 864a079..182b39a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
@@ -40,12 +40,12 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+    return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
         " (" + decodedValue() + ")");
   }
 
   public String toSmaliString(ClassNameMapper naming) {
-    return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+    return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
         "L  # " + decodedValue());
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
index 1f69d9c..885a8af 100644
--- a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
+++ b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
@@ -80,8 +80,7 @@
   public String toSmaliString(ClassNameMapper naming) {
     StringBuilder builder = new StringBuilder();
     builder.append("    ");
-    builder.append(".array-data");
-    builder.append(" 0x");
+    builder.append(".array-data ");
     builder.append(StringUtils.hexString(element_width, 1));
     builder.append("  # ");
     builder.append(element_width);
@@ -93,7 +92,6 @@
           int value = (data[i] >> j * 8) & 0xff;
           if (i * 2 + j < size) {
             builder.append("      ");
-            builder.append("0x");
             builder.append(StringUtils.hexString(value, 2));
             builder.append("  # ");
             builder.append(value);
@@ -109,7 +107,6 @@
         value = (Short.toUnsignedLong(data[i]) << (16 * (i % (element_width / 2)))) | value;
         if ((((i + 1) * 2) % element_width) == 0) {
           builder.append("      ");
-          builder.append("0x");
           builder.append(StringUtils.hexString(value, element_width * 2));
           builder.append("  # ");
           builder.append(value);
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 e5d2952..8d65d2b 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -40,8 +40,7 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    String relative = AA >= 0 ? ("+" + AA) : Integer.toString(AA);
-    return formatString(relative + " (" + (getOffset() + AA) + ")");
+    return formatString(formatRelativeOffset(AA));
   }
 
   public String toSmaliString(ClassNameMapper naming) {
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 b3e3627..75721a3 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -40,7 +40,7 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("" + AAAA + " (" + (getOffset() + AAAA) + ")");
+    return formatString("" + AAAA + " " + formatRelativeOffset(AAAA));
   }
 
   public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format21s.java b/src/main/java/com/android/tools/r8/code/Format21s.java
index db148bd..cc5d6c0 100644
--- a/src/main/java/com/android/tools/r8/code/Format21s.java
+++ b/src/main/java/com/android/tools/r8/code/Format21s.java
@@ -51,7 +51,7 @@
   }
 
   public String toSmaliString(ClassNameMapper naming) {
-    return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(BBBB, 4) + "  # " + BBBB);
+    return formatSmaliString("v" + AA + ", " + StringUtils.hexString(BBBB, 4) + "  # " + BBBB);
   }
 
   @Override
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 26f7650..6e91554 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -62,7 +62,7 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", +" + BBBB + " (" + (getOffset() + BBBB) + ")");
+    return formatString("v" + AA + ", " + formatRelativeOffset(BBBB));
   }
 
   public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22b.java b/src/main/java/com/android/tools/r8/code/Format22b.java
index 8aeae42..1164b03 100644
--- a/src/main/java/com/android/tools/r8/code/Format22b.java
+++ b/src/main/java/com/android/tools/r8/code/Format22b.java
@@ -56,7 +56,7 @@
 
   public String toSmaliString(ClassNameMapper naming) {
     return formatSmaliString(
-        "v" + AA + ", v" + BB + ", 0x" + StringUtils.hexString(CC, 2) + "  # " + CC);
+        "v" + AA + ", v" + BB + ", " + StringUtils.hexString(CC, 2) + "  # " + CC);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format22s.java b/src/main/java/com/android/tools/r8/code/Format22s.java
index eef5fa5..85fbeab 100644
--- a/src/main/java/com/android/tools/r8/code/Format22s.java
+++ b/src/main/java/com/android/tools/r8/code/Format22s.java
@@ -56,7 +56,7 @@
 
   public String toSmaliString(ClassNameMapper naming) {
     return formatSmaliString(
-        "v" + A + ", v" + B + ", 0x" + StringUtils.hexString(CCCC, 4) + "  # " + CCCC);
+        "v" + A + ", v" + B + ", " + StringUtils.hexString(CCCC, 4) + "  # " + CCCC);
   }
 
   @Override
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 3ff5190..0a4d664 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -66,7 +66,7 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + A + ", v" + B + ", +" + CCCC + " (" + (getOffset() + CCCC) + ")");
+    return formatString("v" + A + ", v" + B + ", " + formatRelativeOffset(CCCC));
   }
 
   public String toSmaliString(ClassNameMapper naming) {
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 77cb49d..098863c 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -39,7 +39,7 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("" + AAAAAAAA);
+    return formatString(formatOffset(AAAAAAAA));
   }
 
   public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format31t.java b/src/main/java/com/android/tools/r8/code/Format31t.java
index ec41ed4..87106f0 100644
--- a/src/main/java/com/android/tools/r8/code/Format31t.java
+++ b/src/main/java/com/android/tools/r8/code/Format31t.java
@@ -58,7 +58,7 @@
   }
 
   public String toString(ClassNameMapper naming) {
-    return formatString("v" + AA + ", +" + BBBBBBBB + " (" + (getOffset() + BBBBBBBB) + ")");
+    return formatString("v" + AA + ", " + formatRelativeOffset(BBBBBBBB));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 6b61845..617642c 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -142,9 +142,18 @@
     return 0;
   }
 
+  static String formatOffset(int offset) {
+    return StringUtils.hexString(offset, 2);
+  }
+
+  String formatRelativeOffset(int offset) {
+    String relativeString = offset >= 0 ? ("+" + offset) : Integer.toString(offset);
+    return formatOffset(getOffset() + offset) + " (" + relativeString + ")";
+  }
+
   String formatString(String left) {
     StringBuilder builder = new StringBuilder();
-    StringUtils.appendLeftPadded(builder, Integer.toString(getOffset()), 6);
+    StringUtils.appendLeftPadded(builder, formatOffset(getOffset()), 6);
     builder.append(": ");
     StringUtils.appendRightPadded(builder, getName(), 20);
     builder.append(left == null ? "" : left);
diff --git a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
index a3e2d15..5032ab0 100644
--- a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
@@ -94,7 +94,6 @@
     StringBuilder builder = new StringBuilder();
     builder.append("    ");
     builder.append(".packed-switch ");
-    builder.append("0x");
     builder.append(StringUtils.hexString(first_key, 8));
     builder.append("  # ");
     builder.append(first_key);
diff --git a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
index 328e6d2..e0e1280 100644
--- a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
@@ -103,7 +103,6 @@
     builder.append("\n");
     for (int i = 0; i < keys.length; i++) {
       builder.append("      ");
-      builder.append("0x");
       builder.append(StringUtils.hexString(keys[i], 8));
       builder.append(" -> :label_");
       builder.append(payloadUser.getOffset() + targets[i]);
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 6750f4c..9bcf510 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -450,7 +450,7 @@
     ExecutorService executor = ThreadUtils.getExecutorService(numberOfThreads);
     D8Output result;
     try {
-      D8Command.Builder builder = D8Command.builder();
+      D8Command.Builder builder = new CompatDxCommandBuilder();
       builder
           .addProgramFiles(inputs)
           .setMode(mode)
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
new file mode 100644
index 0000000..ba9daec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.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.compatdx;
+
+import com.android.tools.r8.D8Command;
+
+public class CompatDxCommandBuilder extends D8Command.Builder {
+  CompatDxCommandBuilder() {
+    ignoreDexInArchive = true;
+  }
+}
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 2553650..cca3520 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -75,7 +75,7 @@
       throws IOException, ExecutionException {
     timing.begin("DexApplication.read");
     final DexApplication.Builder builder = new DexApplication.Builder(itemFactory, timing);
-    try (Closer closer = Closer.create()) {
+    try {
       List<Future<?>> futures = new ArrayList<>();
       // Still preload some of the classes, primarily for two reasons:
       // (a) class lazy loading is not supported for DEX files
@@ -84,9 +84,9 @@
       // (b) some of the class file resources don't provide information
       //     about class descriptor.
       // TODO: try and preload less classes.
-      readProguardMap(builder, executorService, futures, closer);
-      readMainDexList(builder, executorService, futures, closer);
-      ClassReader classReader = new ClassReader(executorService, futures, closer);
+      readProguardMap(builder, executorService, futures);
+      readMainDexList(builder, executorService, futures);
+      ClassReader classReader = new ClassReader(executorService, futures);
       classReader.readSources();
       ThreadUtils.awaitFutures(futures);
       classReader.initializeLazyClassCollection(builder);
@@ -130,13 +130,12 @@
   }
 
   private void readProguardMap(DexApplication.Builder builder, ExecutorService executorService,
-      List<Future<?>> futures, Closer closer)
+      List<Future<?>> futures)
       throws IOException {
     // Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
     if (inputApp.hasProguardMap()) {
       futures.add(executorService.submit(() -> {
-        try {
-          InputStream map = inputApp.getProguardMap(closer);
+        try (InputStream map = inputApp.getProguardMap()) {
           builder.setProguardMap(ProguardMapReader.mapperFromInputStream(map));
         } catch (IOException e) {
           throw new RuntimeException(e);
@@ -146,18 +145,18 @@
   }
 
   private void readMainDexList(DexApplication.Builder builder, ExecutorService executorService,
-      List<Future<?>> futures, Closer closer)
+      List<Future<?>> futures)
       throws IOException {
     if (inputApp.hasMainDexList()) {
       futures.add(executorService.submit(() -> {
-        try {
-          for (Resource resource : inputApp.getMainDexListResources()) {
-            InputStream input = closer.register(resource.getStream());
+        for (Resource resource : inputApp.getMainDexListResources()) {
+          try (InputStream input = resource.getStream()) {
             builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+          } catch (IOException e) {
+            throw new RuntimeException(e);
           }
-        } catch (IOException e) {
-          throw new RuntimeException(e);
         }
+
         builder.addToMainDexList(
             inputApp.getMainDexClasses()
                 .stream()
@@ -170,7 +169,6 @@
   private final class ClassReader {
     private final ExecutorService executorService;
     private final List<Future<?>> futures;
-    private final Closer closer;
 
     // We use concurrent queues to collect classes
     // since the classes can be collected concurrently.
@@ -180,10 +178,9 @@
     // Jar application reader to share across all class readers.
     private final JarApplicationReader application = new JarApplicationReader(options);
 
-    ClassReader(ExecutorService executorService, List<Future<?>> futures, Closer closer) {
+    ClassReader(ExecutorService executorService, List<Future<?>> futures) {
       this.executorService = executorService;
       this.futures = futures;
-      this.closer = closer;
     }
 
     private <T extends DexClass> void readDexSources(List<Resource> dexSources,
@@ -192,9 +189,11 @@
         List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
         int computedMinApiLevel = options.minApiLevel;
         for (Resource input : dexSources) {
-          DexFile file = new DexFile(closer.register(input.getStream()));
-          computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
-          fileReaders.add(new DexFileReader(file, classKind, itemFactory));
+          try (InputStream is = input.getStream()) {
+            DexFile file = new DexFile(is);
+            computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
+            fileReaders.add(new DexFileReader(file, classKind, itemFactory));
+          }
         }
         options.minApiLevel = computedMinApiLevel;
         for (DexFileReader reader : fileReaders) {
@@ -215,8 +214,16 @@
         ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
       JarClassFileReader reader = new JarClassFileReader(
           application, classKind.bridgeConsumer(classes::add));
+      // Read classes in parallel.
       for (Resource input : classSources) {
-        reader.read(DEFAULT_DEX_FILENAME, classKind, closer.register(input.getStream()));
+        futures.add(executorService.submit(() -> {
+          try (InputStream is = input.getStream()) {
+            reader.read(DEFAULT_DEX_FILENAME, classKind, is);
+          }
+          // No other way to have a void callable, but we want the IOException from the previous
+          // line to be wrapped into an ExecutionException.
+          return null;
+        }));
       }
     }
 
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 a8f56a7..f1162a9 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -253,10 +253,10 @@
 
   private void sortClassData(Collection<DexProgramClass> classesWithData) {
     for (DexProgramClass clazz : classesWithData) {
-      sortEncodedFields(clazz.instanceFields);
-      sortEncodedFields(clazz.staticFields);
-      sortEncodedMethods(clazz.directMethods);
-      sortEncodedMethods(clazz.virtualMethods);
+      sortEncodedFields(clazz.instanceFields());
+      sortEncodedFields(clazz.staticFields());
+      sortEncodedMethods(clazz.directMethods());
+      sortEncodedMethods(clazz.virtualMethods());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/CanonicalizedDexItem.java b/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/graph/CanonicalizedDexItem.java
rename to src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java
index 7872801..8f110d5 100644
--- a/src/main/java/com/android/tools/r8/graph/CanonicalizedDexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java
@@ -4,9 +4,9 @@
 package com.android.tools.r8.graph;
 
 /**
- * DexItems of this kind have to be canonicalized for the whole application.
+ * DexItems of this kind have cached hash values and quick equals check.
  */
-public abstract class CanonicalizedDexItem extends DexItem {
+public abstract class CachedHashValueDexItem extends DexItem {
 
   private static final int NOT_COMPUTED_HASH_VALUE = -1;
   private static final int SENTINEL_HASH_VALUE = 0;
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 54f9470..3845507 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
 
-public abstract class Code extends CanonicalizedDexItem {
+public abstract class Code extends CachedHashValueDexItem {
 
   public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options);
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index 4277324..db55cd6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -17,9 +17,11 @@
   private final List<DexEncodedMethod> methodAnnotations;
   private final List<DexEncodedMethod> parameterAnnotations;
   private final List<DexEncodedField> fieldAnnotations;
+  private final boolean classHasOnlyInternalizableAnnotations;
 
   public DexAnnotationDirectory(DexProgramClass clazz) {
     this.clazz = clazz;
+    this.classHasOnlyInternalizableAnnotations = clazz.hasOnlyInternalizableAnnotations();
     assert isSorted(clazz.directMethods());
     assert isSorted(clazz.virtualMethods());
     OrderedMergingIterator<DexEncodedMethod, DexMethod> methods =
@@ -77,7 +79,7 @@
     if (!(obj instanceof DexAnnotationDirectory)) {
       return false;
     }
-    if (clazz.hasOnlyInternalizableAnnotations()) {
+    if (classHasOnlyInternalizableAnnotations) {
       DexAnnotationDirectory other = (DexAnnotationDirectory) obj;
       if (!other.clazz.hasOnlyInternalizableAnnotations()) {
         return false;
@@ -89,7 +91,7 @@
 
   @Override
   public final int hashCode() {
-    if (clazz.hasOnlyInternalizableAnnotations()) {
+    if (classHasOnlyInternalizableAnnotations) {
       return clazz.annotations.hashCode();
     }
     return super.hashCode();
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index e91e1f0..00b99fa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import java.util.Arrays;
 
-public class DexAnnotationSet extends DexItem {
+public class DexAnnotationSet extends CachedHashValueDexItem {
 
   private static final int UNSORTED = 0;
   private static final DexAnnotationSet THE_EMPTY_ANNOTATIONS_SET =
@@ -25,15 +25,12 @@
   }
 
   @Override
-  public int hashCode() {
+  public int computeHashCode() {
     return Arrays.hashCode(annotations);
   }
 
   @Override
-  public boolean equals(Object other) {
-    if (this == other) {
-      return true;
-    }
+  public boolean computeEquals(Object other) {
     if (other instanceof DexAnnotationSet) {
       DexAnnotationSet o = (DexAnnotationSet) other;
       return Arrays.equals(annotations, o.annotations);
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 d09d395..3f492b5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -22,10 +22,10 @@
   public DexType superType;
   public DexTypeList interfaces;
   public final DexString sourceFile;
-  public DexEncodedField[] staticFields;
-  public DexEncodedField[] instanceFields;
-  public DexEncodedMethod[] directMethods;
-  public DexEncodedMethod[] virtualMethods;
+  protected DexEncodedField[] staticFields;
+  protected DexEncodedField[] instanceFields;
+  protected DexEncodedMethod[] directMethods;
+  protected DexEncodedMethod[] virtualMethods;
   public DexAnnotationSet annotations;
 
   public DexClass(
@@ -39,10 +39,10 @@
     this.accessFlags = accessFlags;
     this.superType = superType;
     this.type = type;
-    this.staticFields = staticFields;
-    this.instanceFields = instanceFields;
-    this.directMethods = directMethods;
-    this.virtualMethods = virtualMethods;
+    setStaticFields(staticFields);
+    setInstanceFields(instanceFields);
+    setDirectMethods(directMethods);
+    setVirtualMethods(virtualMethods);
     this.annotations = annotations;
     if (type == superType) {
       throw new CompilationError("Class " + type.toString() + " cannot extend itself");
@@ -66,13 +66,22 @@
   }
 
   public DexEncodedMethod[] directMethods() {
-    return MoreObjects.firstNonNull(directMethods, NO_METHODS);
+    return directMethods;
+  }
+
+  public void setDirectMethods(DexEncodedMethod[] values) {
+    directMethods = MoreObjects.firstNonNull(values, NO_METHODS);
   }
 
   public DexEncodedMethod[] virtualMethods() {
-    return MoreObjects.firstNonNull(virtualMethods, NO_METHODS);
+    return virtualMethods;
   }
 
+  public void setVirtualMethods(DexEncodedMethod[] values) {
+    virtualMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+  }
+
+
   public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
     for (DexEncodedMethod method : directMethods()) {
       consumer.accept(method);
@@ -103,11 +112,19 @@
   }
 
   public DexEncodedField[] staticFields() {
-    return MoreObjects.firstNonNull(staticFields, NO_FIELDS);
+    return staticFields;
+  }
+
+  public void setStaticFields(DexEncodedField[] values) {
+    staticFields = MoreObjects.firstNonNull(values, NO_FIELDS);
   }
 
   public DexEncodedField[] instanceFields() {
-    return MoreObjects.firstNonNull(instanceFields, NO_FIELDS);
+    return instanceFields;
+  }
+
+  public void setInstanceFields(DexEncodedField[] values) {
+    instanceFields = MoreObjects.firstNonNull(values, NO_FIELDS);
   }
 
   /**
@@ -187,6 +204,14 @@
     return getClassInitializer() != null;
   }
 
+  public boolean hasTrivialClassInitializer() {
+    DexEncodedMethod clinit = getClassInitializer();
+    return clinit != null
+        && clinit.getCode() != null
+        && clinit.getCode().asDexCode().isEmptyVoidMethod();
+  }
+
+
   public boolean hasNonTrivialClassInitializer() {
     DexEncodedMethod clinit = getClassInitializer();
     if (clinit == null || clinit.getCode() == null) {
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 7915df3..8bd13f0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -193,7 +193,7 @@
     int instructionNumber = 0;
     for (Instruction insn : instructions) {
       while (debugInfo != null && debugInfo.address == insn.getOffset()) {
-        builder.append("      ").append(debugInfo).append("\n");
+        builder.append("         ").append(debugInfo.toString(false)).append("\n");
         debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
       }
       StringUtils.appendLeftPadded(builder, Integer.toString(instructionNumber++), 5);
@@ -353,9 +353,9 @@
 
     public String toString() {
       return "["
-          + startAddress
+          + StringUtils.hexString(startAddress, 2)
           + " .. "
-          + (startAddress + instructionCount - 1)
+          + StringUtils.hexString(startAddress + instructionCount - 1, 2)
           + "] -> "
           + handlerIndex;
     }
@@ -420,12 +420,12 @@
         builder.append("       ");
         builder.append(pair.type);
         builder.append(" -> ");
-        builder.append(pair.addr);
+        builder.append(StringUtils.hexString(pair.addr, 2));
         builder.append("\n");
       }
       if (catchAllAddr != NO_HANDLER) {
         builder.append("       default -> ");
-        builder.append(catchAllAddr);
+        builder.append(StringUtils.hexString(catchAllAddr, 2));
         builder.append("\n");
       }
       builder.append("     ]");
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index de7b544..f083794 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -33,8 +33,15 @@
 
   @Override
   public String toString() {
+    return toString(true);
+  }
+
+  public String toString(boolean withPcPrefix) {
     StringBuilder builder = new StringBuilder();
-    builder.append("pc 0x").append(StringUtils.hexString(address, 2));
+    if (withPcPrefix) {
+      builder.append("pc ");
+    }
+    builder.append(StringUtils.hexString(address, 2));
     builder.append(", line ").append(line);
     if (sourceFile != null) {
       builder.append(", file ").append(sourceFile);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index f82fe2a..d2c9b3b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -8,7 +8,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class DexDebugInfo extends CanonicalizedDexItem {
+public class DexDebugInfo extends CachedHashValueDexItem {
 
   public final int startLine;
   public final DexString[] parameters;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c94fc3c..1ba7755 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -15,7 +15,7 @@
   public final DexField field;
   public final DexAccessFlags accessFlags;
   public DexAnnotationSet annotations;
-  public final DexValue staticValue;
+  public DexValue staticValue;
 
   public DexEncodedField(DexField field, DexAccessFlags accessFlags, DexAnnotationSet annotations,
       DexValue staticValue) {
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 339f4c0..80dea00 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -24,17 +24,19 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
 public class DexItemFactory {
 
-  private final Map<DexString, DexString> strings = new HashMap<>();
-  private final Map<DexType, DexType> types = new HashMap<>();
-  private final Map<DexField, DexField> fields = new HashMap<>();
-  private final Map<DexProto, DexProto> protos = new HashMap<>();
-  private final Map<DexMethod, DexMethod> methods = new HashMap<>();
-  private final Map<DexCallSite, DexCallSite> callSites = new HashMap<>();
-  private final Map<DexMethodHandle, DexMethodHandle> methodHandles = new HashMap<>();
+  private final ConcurrentHashMap<DexString, DexString> strings = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexString, DexType> types = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexField, DexField> fields = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexProto, DexProto> protos = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexMethod, DexMethod> methods = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexCallSite, DexCallSite> callSites = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexMethodHandle, DexMethodHandle> methodHandles =
+      new ConcurrentHashMap<>();
 
   // DexDebugEvent Canonicalization.
   private final Int2ObjectMap<AdvanceLine> advanceLines = new Int2ObjectOpenHashMap<>();
@@ -85,6 +87,8 @@
   public final DexString getClassMethodName = createString("getClass");
   public final DexString ordinalMethodName = createString("ordinal");
   public final DexString desiredAssertionStatusMethodName = createString("desiredAssertionStatus");
+  public final DexString getNameName = createString("getName");
+  public final DexString getSimpleNameName = createString("getSimpleName");
   public final DexString assertionsDisabled = createString("$assertionsDisabled");
 
   public final DexString stringDescriptor = createString("Ljava/lang/String;");
@@ -208,10 +212,15 @@
   public class ClassMethods {
 
     public DexMethod desiredAssertionStatus;
+    public DexMethod getName;
+    public DexMethod getSimpleName;
 
     private ClassMethods() {
       desiredAssertionStatus = createMethod(classDescriptor,
           desiredAssertionStatusMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
+      getName = createMethod(classDescriptor, getNameName, stringDescriptor, DexString.EMPTY_ARRAY);
+      getSimpleName = createMethod(classDescriptor,
+          getSimpleNameName, stringDescriptor, DexString.EMPTY_ARRAY);
     }
   }
 
@@ -273,7 +282,7 @@
     }
   }
 
-  synchronized private static <T extends DexItem> T canonicalize(Map<T, T> map, T item) {
+  private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
     assert !internalSentinels.contains(item);
     T previous = map.putIfAbsent(item, item);
@@ -302,10 +311,16 @@
     return null;
   }
 
-  public DexType createType(DexString descriptor) {
+  synchronized public DexType createType(DexString descriptor) {
     assert !sorted;
-    DexType type = new DexType(descriptor);
-    return canonicalize(types, type);
+    assert descriptor != null;
+    DexType result = types.get(descriptor);
+    if (result == null) {
+      result = new DexType(descriptor);
+      assert !internalSentinels.contains(result);
+      types.put(descriptor, result);
+    }
+    return result;
   }
 
   public DexType createType(String 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 60a7319..1bdc56c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -152,6 +152,18 @@
     directMethods[directMethods.length - 1] = staticMethod;
   }
 
+  public void removeStaticMethod(DexEncodedMethod staticMethod) {
+    assert staticMethod.accessFlags.isStatic();
+    DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[directMethods.length - 1];
+    int toIndex = 0;
+    for (int fromIndex = 0; fromIndex < directMethods.length; fromIndex++) {
+      if (directMethods[fromIndex] != staticMethod) {
+        newDirectMethods[toIndex++] = directMethods[fromIndex];
+      }
+    }
+    directMethods = newDirectMethods;
+  }
+
   @Override
   public DexProgramClass get() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
index d3d36e5..5c8f439 100644
--- a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
@@ -10,7 +10,7 @@
 /**
  * Subset of dex items that are referenced by some table index.
  */
-public abstract class IndexedDexItem extends CanonicalizedDexItem implements Presorted {
+public abstract class IndexedDexItem extends CachedHashValueDexItem implements Presorted {
 
   private static final int SORTED_INDEX_UNKNOWN = -1;
   private int sortedIndex = SORTED_INDEX_UNKNOWN; // assigned globally after reading.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index d53c577..1880668 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -48,6 +48,10 @@
     return outValue;
   }
 
+  public boolean getBooleanValue() {
+    return !isZero();
+  }
+
   public int getIntValue() {
     assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
     return (int) value;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 9f28eea..c8a17dc2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -110,6 +110,39 @@
     };
   }
 
+  /**
+   * Returns an iterator over all dominator blocks of <code>dominated</code>.
+   *
+   * Iteration order is always the immediate dominator of the previously returned block. The
+   * iteration starts by returning <code>dominated</code>.
+   */
+  public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+    return () -> new Iterator<BasicBlock>() {
+      private BasicBlock current = dominated;
+
+      @Override
+      public boolean hasNext() {
+        return current != null;
+      }
+
+      @Override
+      public BasicBlock next() {
+        if (!hasNext()) {
+          return null;
+        } else {
+          BasicBlock result = current;
+          if (current.getNumber() == 0) {
+            current = null;
+          } else {
+            current = immediateDominator(current);
+            assert current != result;
+          }
+          return result;
+        }
+      }
+    };
+  }
+
   public BasicBlock[] getSortedBlocks() {
     return sorted;
   }
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 2ffb5a1..bf93530 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
@@ -426,6 +426,10 @@
     return isConstant() && getConstInstruction().isConstNumber();
   }
 
+  public boolean isConstString() {
+    return isConstant() && getConstInstruction().isConstString();
+  }
+
   public boolean isConstant() {
     return definition.isOutConstant() && getLocalInfo() == null;
   }
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 48c8eb0..dad58d8 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
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
-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;
@@ -328,16 +327,57 @@
       this.graph = graph;
     }
 
-    private void processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method) {
+    private void addClassInitializerTarget(DexClass clazz) {
+      assert clazz != null;
+      if (clazz.hasClassInitializer() && !clazz.isLibraryClass()) {
+        DexEncodedMethod possibleTarget = clazz.getClassInitializer();
+        addTarget(possibleTarget);
+      }
+    }
+
+    private void addClassInitializerTarget(DexType type) {
+      if (type.isArrayType()) {
+        type = type.toBaseType(appInfo.dexItemFactory);
+      }
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz != null) {
+        addClassInitializerTarget(clazz);
+      }
+    }
+
+    private void addTarget(DexEncodedMethod target) {
+      Node callee = graph.ensureMethodNode(target);
+      graph.addCall(caller, callee);
+    }
+
+    private void addPossibleTarget(DexEncodedMethod possibleTarget) {
+      DexClass possibleTargetClass =
+          appInfo.definitionFor(possibleTarget.method.getHolder());
+      if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
+        addTarget(possibleTarget);
+      }
+    }
+
+    private void addPossibleTargets(
+        DexEncodedMethod definition, Set<DexEncodedMethod> possibleTargets) {
+      for (DexEncodedMethod possibleTarget : possibleTargets) {
+        if (possibleTarget != definition) {
+          addPossibleTarget(possibleTarget);
+        }
+      }
+    }
+
+    private void processInvoke(Type type, DexMethod method) {
+      DexEncodedMethod source = caller.method;
       method = graphLense.lookupMethod(method, source);
       DexEncodedMethod definition = appInfo.lookup(type, method);
       if (definition != null) {
         assert !source.accessFlags.isBridge() || definition != caller.method;
-        DexType definitionHolder = definition.method.getHolder();
-        assert definitionHolder.isClassType();
-        if (!appInfo.definitionFor(definitionHolder).isLibraryClass()) {
-          Node callee = graph.ensureMethodNode(definition);
-          graph.addCall(caller, callee);
+        DexClass definitionHolder = appInfo.definitionFor(definition.method.getHolder());
+        assert definitionHolder != null;
+        if (!definitionHolder.isLibraryClass()) {
+          addClassInitializerTarget(definitionHolder);
+          addTarget(definition);
           // For virtual and interface calls add all potential targets that could be called.
           if (type == Type.VIRTUAL || type == Type.INTERFACE) {
             Set<DexEncodedMethod> possibleTargets;
@@ -346,73 +386,74 @@
             } else {
               possibleTargets = appInfo.lookupVirtualTargets(definition.method);
             }
-            for (DexEncodedMethod possibleTarget : possibleTargets) {
-              if (possibleTarget != definition) {
-                DexClass possibleTargetClass =
-                    appInfo.definitionFor(possibleTarget.method.getHolder());
-                if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
-                  callee = graph.ensureMethodNode(possibleTarget);
-                  graph.addCall(caller, callee);
-                }
-              }
-            }
+            addPossibleTargets(definition, possibleTargets);
           }
         }
       }
     }
 
+    private void processFieldAccess(DexField field) {
+      // Any field access implicitly calls the class initializer.
+      addClassInitializerTarget(field.getHolder());
+    }
+
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
-      processInvoke(caller.method, Type.VIRTUAL, method);
+      processInvoke(Type.VIRTUAL, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeDirect(DexMethod method) {
-      processInvoke(caller.method, Type.DIRECT, method);
+      processInvoke(Type.DIRECT, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      processInvoke(caller.method, Type.STATIC, method);
+      processInvoke(Type.STATIC, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeInterface(DexMethod method) {
-      processInvoke(caller.method, Type.INTERFACE, method);
+      processInvoke(Type.INTERFACE, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeSuper(DexMethod method) {
-      processInvoke(caller.method, Type.SUPER, method);
+      processInvoke(Type.SUPER, method);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldWrite(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldRead(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerNewInstance(DexType type) {
+      addClassInitializerTarget(type);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
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 23b6b70..fad378e 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
@@ -38,8 +38,8 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -89,8 +89,7 @@
     this.graphLense = graphLense != null ? graphLense : GraphLense.getIdentityLense();
     this.options = options;
     this.printer = printer;
-    Set<DexType> libraryClassesWithOptimizationInfo = markLibraryMethodsReturningReceiver();
-    this.codeRewriter = new CodeRewriter(appInfo, libraryClassesWithOptimizationInfo);
+    this.codeRewriter = new CodeRewriter(appInfo, libraryMethodsReturningReceiver());
     this.lambdaRewriter = enableDesugaring ? new LambdaRewriter(this) : null;
     this.interfaceMethodRewriter =
         (enableDesugaring && enableInterfaceMethodDesugaring())
@@ -183,18 +182,12 @@
     throw new Unreachable();
   }
 
-  private Set<DexType> markLibraryMethodsReturningReceiver() {
+  private Set<DexMethod> libraryMethodsReturningReceiver() {
+    Set<DexMethod> methods = new HashSet<>();
     DexItemFactory dexItemFactory = appInfo.dexItemFactory;
-    dexItemFactory.stringBuilderMethods.forEachAppendMethod(this::markReturnsReceiver);
-    dexItemFactory.stringBufferMethods.forEachAppendMethod(this::markReturnsReceiver);
-    return ImmutableSet.of(dexItemFactory.stringBuilderType, dexItemFactory.stringBufferType);
-  }
-
-  private void markReturnsReceiver(DexMethod method) {
-    DexEncodedMethod definition = appInfo.definitionFor(method);
-    if (definition != null) {
-      definition.markReturnsArgument(0);
-    }
+    dexItemFactory.stringBufferMethods.forEachAppendMethod(methods::add);
+    dexItemFactory.stringBuilderMethods.forEachAppendMethod(methods::add);
+    return methods;
   }
 
   private void removeLambdaDeserializationMethods() {
@@ -238,7 +231,11 @@
     for (DexProgramClass clazz : classes) {
       futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
     }
+
     ThreadUtils.awaitFutures(futures);
+
+    // Get rid of <clinit> methods with no code.
+    removeEmptyClassInitializers();
   }
 
   private void convertMethodToDex(DexEncodedMethod method) {
@@ -284,6 +281,9 @@
     }, executorService);
     timing.end();
 
+    // Get rid of <clinit> methods with no code.
+    removeEmptyClassInitializers();
+
     // Build a new application with jumbo string info.
     Builder builder = new Builder(application);
     builder.setHighestSortingString(highestSortingString);
@@ -325,6 +325,16 @@
     return builder.build();
   }
 
+  private void removeEmptyClassInitializers() {
+    application.classes().forEach(this::removeEmptyClassInitializer);
+  }
+
+  private void removeEmptyClassInitializer(DexProgramClass clazz) {
+    if (clazz.hasTrivialClassInitializer()) {
+      clazz.removeStaticMethod(clazz.getClassInitializer());
+    }
+  }
+
   private void clearDexMethodCompilationState() {
     application.classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -463,6 +473,7 @@
     codeRewriter.foldConstants(code);
     codeRewriter.rewriteSwitch(code);
     codeRewriter.simplifyIf(code);
+    codeRewriter.collectClassInitializerDefaults(method, code);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
           method.toSourceString(), code);
@@ -511,7 +522,7 @@
     }
     printMethod(code, "Final IR (non-SSA)");
 
-    // After all the optimizations have take place, we compute whether method should be inlined.
+    // After all the optimizations have take place, we compute whether method should be inlinedex.
     Constraint state;
     if (!options.inlineAccessors || inliner == null) {
       state = Constraint.NEVER;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 484354a..8d36ef7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -258,23 +258,15 @@
   @Override
   public void buildPrelude(IRBuilder builder) {
     Map<Integer, MoveType> initializedLocals = new HashMap<>(node.localVariables.size());
+    // Record types for arguments.
+    recordArgumentTypes(initializedLocals);
+    // Add debug information for all locals at the initial label.
     if (initialLabel != null) {
       state.openLocals(initialLabel);
     }
-    int argumentRegister = 0;
-    if (!isStatic()) {
-      Type thisType = Type.getType(clazz.descriptor.toString());
-      int register = state.writeLocal(argumentRegister++, thisType);
-      builder.addThisArgument(register);
-      initializedLocals.put(register, moveType(thisType));
-    }
-    for (Type type : parameterTypes) {
-      MoveType moveType = moveType(type);
-      int register = state.writeLocal(argumentRegister, type);
-      builder.addNonThisArgument(register, moveType);
-      argumentRegister += moveType.requiredRegisters();
-      initializedLocals.put(register, moveType);
-    }
+    // Build the actual argument instructions now that type and debug information is known
+    // for arguments.
+    buildArgumentInstructions(builder);
     if (isSynchronized()) {
       generatingMethodSynchronization = true;
       Type clazzType = Type.getType(clazz.toDescriptorString());
@@ -297,6 +289,23 @@
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
       Type localType = Type.getType(local.desc);
+      int sort = localType.getSort();
+      switch (sort) {
+        case Type.OBJECT:
+        case Type.ARRAY:
+          localType = JarState.NULL_TYPE;
+          break;
+        case Type.DOUBLE:
+        case Type.LONG:
+        case Type.FLOAT:
+          break;
+        case Type.VOID:
+        case Type.METHOD:
+          throw new Unreachable("Invalid local variable type: " + localType);
+        default:
+          localType = Type.INT_TYPE;
+          break;
+      }
       int localRegister = state.getLocalRegister(local.index, localType);
       MoveType exitingLocalType = initializedLocals.get(localRegister);
       assert exitingLocalType == null || exitingLocalType == moveType(localType);
@@ -311,6 +320,36 @@
     state.setBuilding();
   }
 
+  private void buildArgumentInstructions(IRBuilder builder) {
+    int argumentRegister = 0;
+    if (!isStatic()) {
+      Type thisType = Type.getType(clazz.descriptor.toString());
+      Slot slot = state.readLocal(argumentRegister++, thisType);
+      builder.addThisArgument(slot.register);
+    }
+    for (Type type : parameterTypes) {
+      MoveType moveType = moveType(type);
+      Slot slot = state.readLocal(argumentRegister, type);
+      builder.addNonThisArgument(slot.register, moveType);
+      argumentRegister += moveType.requiredRegisters();
+    }
+  }
+
+  private void recordArgumentTypes(Map<Integer, MoveType> initializedLocals) {
+    int argumentRegister = 0;
+    if (!isStatic()) {
+      Type thisType = Type.getType(clazz.descriptor.toString());
+      int register = state.writeLocal(argumentRegister++, thisType);
+      initializedLocals.put(register, moveType(thisType));
+    }
+    for (Type type : parameterTypes) {
+      MoveType moveType = moveType(type);
+      int register = state.writeLocal(argumentRegister, type);
+      argumentRegister += moveType.requiredRegisters();
+      initializedLocals.put(register, moveType);
+    }
+  }
+
   private void computeBlockEntryJarStates(IRBuilder builder) {
     Int2ReferenceSortedMap<BlockInfo> CFG = builder.getCFG();
     Queue<JarStateWorklistItem> worklist = new LinkedList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 5009d5e..2a3e431 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -87,6 +87,9 @@
       if (type == BYTE_OR_BOOL_TYPE) {
         type = Type.BYTE_TYPE;
       }
+      if (other == BYTE_OR_BOOL_TYPE) {
+        other = Type.BYTE_TYPE;
+      }
       int sort = type.getSort();
       int otherSort = other.getSort();
       if (isReferenceCompatible(type, other)) {
@@ -256,7 +259,7 @@
     Collection<LocalVariableNode> nodes = localVariableStartPoints.get(label);
     ArrayList<Local> locals = new ArrayList<>(nodes.size());
     for (LocalVariableNode node : nodes) {
-      locals.add(setLocal(node.index, Type.getType(node.desc), localVariables.get(node)));
+      locals.add(setLocalInfo(node.index, Type.getType(node.desc), localVariables.get(node)));
     }
     // Sort to ensure deterministic instruction ordering (correctness is unaffected).
     locals.sort(Comparator.comparingInt(local -> local.slot.register));
@@ -335,6 +338,25 @@
     return local;
   }
 
+  private Local setLocalInfo(int index, Type type, DebugLocalInfo info) {
+    return setLocalInfoForRegister(getLocalRegister(index, type), info);
+  }
+
+  private Local setLocalInfoForRegister(int register, DebugLocalInfo info) {
+    Local existingLocal = getLocalForRegister(register);
+    // TODO(ager, zerny): Kotlin debug information contains locals that are not referenced.
+    // That seems broken and we currently do not retain that debug information because
+    // we do not let locals debug information influence code generation. Debug information can
+    // be completely malformed, so we shouldn't let it influence code generation. However, we
+    // need to deal with these unused locals in the debug information. For now we
+    // use a null type for the slot, but we should reconsider that.
+    Slot slot = existingLocal != null ? existingLocal.slot : new Slot(register, null);
+    Local local = new Local(slot, info);
+    locals[register] = local;
+    return local;
+  }
+
+
   public int writeLocal(int index, Type type) {
     assert nonNullType(type);
     Local local = getLocal(index, type);
@@ -344,8 +366,9 @@
     }
     // We cannot assume consistency for writes because we do not have complete information about the
     // scopes of locals. We assume the program to be verified and overwrite if the types mismatch.
-    if (local == null || (local.info == null && !typeEquals(local.slot.type, type))) {
-      local = setLocal(index, type, null);
+    if (local == null || !typeEquals(local.slot.type, type)) {
+      DebugLocalInfo info = local == null ? null : local.info;
+      local = setLocal(index, type, info);
     }
     return local.slot.register;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 236c9e5..b7036f5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -80,15 +80,15 @@
     }
 
     // Add the methods.
-    DexEncodedMethod[] existing = clazz.virtualMethods;
-    clazz.virtualMethods = new DexEncodedMethod[existing.length + methodsToImplement.size()];
-    System.arraycopy(existing, 0, clazz.virtualMethods, 0, existing.length);
+    DexEncodedMethod[] existing = clazz.virtualMethods();
+    clazz.setVirtualMethods(new DexEncodedMethod[existing.length + methodsToImplement.size()]);
+    System.arraycopy(existing, 0, clazz.virtualMethods(), 0, existing.length);
 
     for (int i = 0; i < methodsToImplement.size(); i++) {
       DexEncodedMethod method = methodsToImplement.get(i);
       assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
       DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
-      clazz.virtualMethods[existing.length + i] = newMethod;
+      clazz.virtualMethods()[existing.length + i] = newMethod;
       createdMethods.put(newMethod, method);
     }
   }
@@ -142,7 +142,7 @@
     current = clazz;
     while (true) {
       // Hide candidates by virtual method of the class.
-      hideCandidates(current.virtualMethods, candidates, toBeImplemented);
+      hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
       if (candidates.isEmpty()) {
         return toBeImplemented;
       }
@@ -218,12 +218,12 @@
     }
 
     // Hide by virtual methods of this interface.
-    for (DexEncodedMethod virtual : clazz.virtualMethods) {
+    for (DexEncodedMethod virtual : clazz.virtualMethods()) {
       helper.hideMatches(virtual.method);
     }
 
     // Add all default methods of this interface.
-    for (DexEncodedMethod encoded : clazz.virtualMethods) {
+    for (DexEncodedMethod encoded : clazz.virtualMethods()) {
       if (rewriter.isDefaultMethod(encoded)) {
         helper.addDefaultMethod(encoded);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index a84e082..2d06abe 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -44,7 +44,7 @@
 
     // Process virtual interface methods first.
     List<DexEncodedMethod> remainingMethods = new ArrayList<>();
-    for (DexEncodedMethod virtual : iface.virtualMethods) {
+    for (DexEncodedMethod virtual : iface.virtualMethods()) {
       if (rewriter.isDefaultMethod(virtual)) {
         // Create a new method in a companion class to represent default method implementation.
         DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(virtual.method);
@@ -79,14 +79,14 @@
     }
 
     // If at least one bridge methods was removed update the table.
-    if (remainingMethods.size() < iface.virtualMethods.length) {
-      iface.virtualMethods = remainingMethods.toArray(
-          new DexEncodedMethod[remainingMethods.size()]);
+    if (remainingMethods.size() < iface.virtualMethods().length) {
+      iface.setVirtualMethods(remainingMethods.toArray(
+          new DexEncodedMethod[remainingMethods.size()]));
     }
     remainingMethods.clear();
 
     // Process static methods, move them into companion class as well.
-    for (DexEncodedMethod direct : iface.directMethods) {
+    for (DexEncodedMethod direct : iface.directMethods()) {
       if (direct.accessFlags.isPrivate()) {
         // We only expect to see private methods which are lambda$ methods,
         // and they are supposed to be relaxed to package private static methods
@@ -105,9 +105,9 @@
         remainingMethods.add(direct);
       }
     }
-    if (remainingMethods.size() < iface.directMethods.length) {
-      iface.directMethods = remainingMethods.toArray(
-          new DexEncodedMethod[remainingMethods.size()]);
+    if (remainingMethods.size() < iface.directMethods().length) {
+      iface.setDirectMethods(remainingMethods.toArray(
+          new DexEncodedMethod[remainingMethods.size()]));
     }
 
     if (companionMethods.isEmpty()) {
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 0a6ea7e..be6252a 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
@@ -444,7 +444,7 @@
       DexMethod implMethod = descriptor.implHandle.asMethod();
       DexClass implMethodHolder = definitionFor(implMethod.holder);
 
-      DexEncodedMethod[] directMethods = implMethodHolder.directMethods;
+      DexEncodedMethod[] directMethods = implMethodHolder.directMethods();
       for (int i = 0; i < directMethods.length; i++) {
         DexEncodedMethod encodedMethod = directMethods[i];
         if (implMethod.match(encodedMethod)) {
@@ -491,8 +491,8 @@
       DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
           callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
           new SynthesizedCode(new AccessorMethodSourceCode(LambdaClass.this)));
-      accessorClass.directMethods = appendMethod(
-          accessorClass.directMethods, accessorEncodedMethod);
+      accessorClass.setDirectMethods(appendMethod(
+          accessorClass.directMethods(), accessorEncodedMethod));
       rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
       return true;
     }
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 af32cb7..f6f8f2c 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
@@ -162,7 +162,7 @@
   public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
     for (DexProgramClass clazz : classes) {
       // Search for a lambda deserialization method and remove it if found.
-      DexEncodedMethod[] directMethods = clazz.directMethods;
+      DexEncodedMethod[] directMethods = clazz.directMethods();
       if (directMethods != null) {
         int methodCount = directMethods.length;
         for (int i = 0; i < methodCount; i++) {
@@ -177,7 +177,7 @@
             DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
             System.arraycopy(directMethods, 0, newMethods, 0, i);
             System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
-            clazz.directMethods = newMethods;
+            clazz.setDirectMethods(newMethods);
 
             // We assume there is only one such method in the class.
             break;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 27cf89a..9f833f0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -5,19 +5,25 @@
 
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
 
 class BasicBlockInstructionsEquivalence extends Equivalence<BasicBlock> {
-
+  private static final int UNKNOW_HASH = -1;
   private static final int MAX_HASH_INSTRUCTIONS = 5;
   private final RegisterAllocator allocator;
+  private final int[] hashes;
 
-  BasicBlockInstructionsEquivalence(RegisterAllocator allocator) {
+  BasicBlockInstructionsEquivalence(IRCode code, RegisterAllocator allocator) {
     this.allocator = allocator;
+    hashes = new int[code.getHighestBlockNumber() + 1];
+    Arrays.fill(hashes, UNKNOW_HASH);
   }
 
   private boolean hasIdenticalInstructions(BasicBlock first, BasicBlock second) {
@@ -60,8 +66,23 @@
     return hasIdenticalInstructions(a, b);
   }
 
+  void clearComputedHash(BasicBlock basicBlock) {
+    hashes[basicBlock.getNumber()] = UNKNOW_HASH;
+  }
+
   @Override
   protected int doHash(BasicBlock basicBlock) {
+    int hash = hashes[basicBlock.getNumber()];
+    if (hash != UNKNOW_HASH) {
+      assert hash == computeHash(basicBlock);
+      return hash;
+    }
+    hash = computeHash(basicBlock);
+    hashes[basicBlock.getNumber()] = hash;
+    return hash;
+  }
+
+  private int computeHash(BasicBlock basicBlock) {
     List<Instruction> instructions = basicBlock.getInstructions();
     int hash = instructions.size();
     for (int i = 0; i < instructions.size() && i < MAX_HASH_INSTRUCTIONS; i++) {
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 48a64b8..78d8369 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
@@ -6,13 +6,27 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Binop;
@@ -55,6 +69,7 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -82,12 +97,12 @@
 
   private final AppInfo appInfo;
   private final DexItemFactory dexItemFactory;
-  private final Set<DexType> libraryClassesWithOptimizationInfo;
+  private final Set<DexMethod> libraryMethodsReturningReceiver;
 
-  public CodeRewriter(AppInfo appInfo, Set<DexType> libraryClassesWithOptimizationInfo) {
+  public CodeRewriter(AppInfo appInfo, Set<DexMethod> libraryMethodsReturningReceiver) {
     this.appInfo = appInfo;
     this.dexItemFactory = appInfo.dexItemFactory;
-    this.libraryClassesWithOptimizationInfo = libraryClassesWithOptimizationInfo;
+    this.libraryMethodsReturningReceiver = libraryMethodsReturningReceiver;
   }
 
   /**
@@ -503,37 +518,39 @@
 
   // Replace result uses for methods where something is known about what is returned.
   public void rewriteMoveResult(IRCode code) {
-    if (!appInfo.hasSubtyping()) {
-      return;
-    }
+    AppInfoWithSubtyping appInfoWithSubtyping = appInfo.withSubtyping();
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         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.
-          if ((target == null) &&
-              libraryClassesWithOptimizationInfo.contains(invoke.getInvokedMethod().getHolder())) {
-            target = appInfo.definitionFor(invoke.getInvokedMethod());
-          }
-          if (target != null) {
-            DexMethod invokedMethod = target.method;
-            // Check if the invoked method is known to return one of its arguments.
-            DexEncodedMethod definition = appInfo.definitionFor(invokedMethod);
-            if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
-              int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
-              // Replace the out value of the invoke with the argument and ignore the out value.
-              if (argumentIndex != -1 && checkArgumentType(invoke, target.method, argumentIndex)) {
-                Value argument = invoke.arguments().get(argumentIndex);
-                assert (invoke.outType() == argument.outType()) ||
-                    (invoke.outType() == MoveType.OBJECT
-                        && argument.outType() == MoveType.SINGLE
-                        && argument.getConstInstruction().asConstNumber().isZero());
-                invoke.outValue().replaceUsers(argument);
-                invoke.setOutValue(null);
+          boolean isLibraryMethodReturningReceiver =
+              libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod());
+          if (isLibraryMethodReturningReceiver) {
+            if (checkArgumentType(invoke, invoke.getInvokedMethod(), 0)) {
+              invoke.outValue().replaceUsers(invoke.arguments().get(0));
+              invoke.setOutValue(null);
+            }
+          } else if (appInfoWithSubtyping != null) {
+            DexEncodedMethod target = invoke.computeSingleTarget(appInfoWithSubtyping);
+            if (target != null) {
+              DexMethod invokedMethod = target.method;
+              // Check if the invoked method is known to return one of its arguments.
+              DexEncodedMethod definition = appInfo.definitionFor(invokedMethod);
+              if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
+                int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
+                // Replace the out value of the invoke with the argument and ignore the out value.
+                if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
+                    argumentIndex)) {
+                  Value argument = invoke.arguments().get(argumentIndex);
+                  assert (invoke.outType() == argument.outType()) ||
+                      (invoke.outType() == MoveType.OBJECT
+                          && argument.outType() == MoveType.SINGLE
+                          && argument.getConstInstruction().asConstNumber().isZero());
+                  invoke.outValue().replaceUsers(argument);
+                  invoke.setOutValue(null);
+                }
               }
             }
           }
@@ -605,6 +622,171 @@
     }
   }
 
+  // Check if the static put is a constant derived from the class holding the method.
+  // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
+  private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
+    if (put.getField().type != dexItemFactory.stringType) {
+      return false;
+    }
+    if (put.inValue().definition != null && put.inValue().definition.isInvokeVirtual()) {
+      InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
+      if ((invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName
+          || invoke.getInvokedMethod() == dexItemFactory.classMethods.getName)
+          && invoke.inValues().get(0).definition.isConstClass()
+          && invoke.inValues().get(0).definition.asConstClass().getValue()
+          == method.method.getHolder()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public void collectClassInitializerDefaults(DexEncodedMethod method, IRCode code) {
+    if (!method.isClassInitializer()) {
+      return;
+    }
+
+    // Collect relevant static put which are dominated by the exit block, and not dominated by a
+    // static get.
+    // This does not check for instructions that can throw. However, as classes which throw in the
+    // class initializer are never visible to the program (see Java Virtual Machine Specification
+    // section 5.5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5), this
+    // does not matter (except maybe for removal of const-string instructions, but that is
+    // acceptable).
+    DominatorTree dominatorTree = new DominatorTree(code);
+    BasicBlock exit = code.getNormalExitBlock();
+    if (exit == null) {
+      return;
+    }
+    Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+    for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+      InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
+      while (iterator.hasPrevious()) {
+        Instruction current = iterator.previous();
+        if (current.isStaticPut()) {
+          StaticPut put = current.asStaticPut();
+          DexField field = put.getField();
+          if (field.getHolder() == method.method.getHolder()) {
+            if (put.inValue().isConstant()) {
+              if ((field.type.isClassType() || field.type.isArrayType())
+                  && put.inValue().getConstInstruction().isConstNumber() &&
+                  put.inValue().getConstInstruction().asConstNumber().isZero()) {
+                // Collect put of zero as a potential default value.
+                puts.put(put.getField(), put);
+              } else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
+                // Collect put as a potential default value.
+                puts.put(put.getField(), put);
+              }
+            } else if (isClassNameConstant(method, put)) {
+              // Collect put of class name constant as a potential default value.
+              puts.put(put.getField(), put);
+            }
+          }
+        }
+        if (current.isStaticGet()) {
+          // If a static field is read, any collected potential default value cannot be a
+          // default value.
+          if (puts.containsKey(current.asStaticGet().getField())) {
+            puts.remove(current.asStaticGet().getField());
+          }
+        }
+      }
+    }
+
+    if (!puts.isEmpty()) {
+      // Set initial values for static fields from the definitive static put instructions collected.
+      for (StaticPut put : puts.values()) {
+        DexField field = put.getField();
+        DexEncodedField encodedField = appInfo.definitionFor(field);
+        if (field.type == dexItemFactory.stringType) {
+          if (put.inValue().isConstant()) {
+            if (put.inValue().getConstInstruction().isConstNumber()) {
+              assert put.inValue().getConstInstruction().asConstNumber().isZero();
+              encodedField.staticValue = DexValueNull.NULL;
+            } else {
+              ConstString cnst = put.inValue().getConstInstruction().asConstString();
+              encodedField.staticValue = new DexValueString(cnst.getValue());
+            }
+          } else {
+            InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
+            String name = method.method.getHolder().toSourceString();
+            if (invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName) {
+              String simpleName = name.substring(name.lastIndexOf('.') + 1);
+              encodedField.staticValue =
+                  new DexValueString(dexItemFactory.createString(simpleName));
+            } else {
+              assert invoke.getInvokedMethod() == dexItemFactory.classMethods.getName;
+              encodedField.staticValue = new DexValueString(dexItemFactory.createString(name));
+            }
+          }
+        } else if (field.type.isClassType() || field.type.isArrayType()) {
+          if (put.inValue().getConstInstruction().isConstNumber()
+              && put.inValue().getConstInstruction().asConstNumber().isZero()) {
+            encodedField.staticValue = DexValueNull.NULL;
+          } else {
+            throw new Unreachable("Unexpected default value for field type " + field.type + ".");
+          }
+        } else {
+          ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
+          if (field.type == dexItemFactory.booleanType) {
+            encodedField.staticValue = DexValueBoolean.create(cnst.getBooleanValue());
+          } else if (field.type == dexItemFactory.byteType) {
+            encodedField.staticValue = DexValueByte.create((byte) cnst.getIntValue());
+          } else if (field.type == dexItemFactory.shortType) {
+            encodedField.staticValue = DexValueShort.create((short) cnst.getIntValue());
+          } else if (field.type == dexItemFactory.intType) {
+            encodedField.staticValue = DexValueInt.create(cnst.getIntValue());
+          } else if (field.type == dexItemFactory.longType) {
+            encodedField.staticValue = DexValueLong.create(cnst.getLongValue());
+          } else if (field.type == dexItemFactory.floatType) {
+            encodedField.staticValue = DexValueFloat.create(cnst.getFloatValue());
+          } else if (field.type == dexItemFactory.doubleType) {
+            encodedField.staticValue = DexValueDouble.create(cnst.getDoubleValue());
+          } else if (field.type == dexItemFactory.charType) {
+            encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
+          } else {
+            throw new Unreachable("Unexpected field type " + field.type + ".");
+          }
+        }
+      }
+
+      // Remove the static put instructions now replaced by static filed initial values.
+      List<Instruction> toRemove = new ArrayList<>();
+      for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+        InstructionListIterator iterator = block.listIterator();
+        while (iterator.hasNext()) {
+          Instruction current = iterator.next();
+          if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+            iterator.remove();
+            // Collect, for removal, the instruction that created the value for the static put,
+            // if all users are gone. This is done even if these instructions can throw as for
+            // the current patterns matched these exceptions are not detectable.
+            StaticPut put = current.asStaticPut();
+            if (put.inValue().uniqueUsers().size() == 0) {
+              if (put.inValue().isConstString()) {
+                toRemove.add(put.inValue().definition);
+              } else if (put.inValue().definition.isInvokeVirtual()) {
+                toRemove.add(put.inValue().definition);
+              }
+            }
+          }
+        }
+      }
+
+      // Remove the instructions collected for removal.
+      if (toRemove.size() > 0) {
+        for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+          InstructionListIterator iterator = block.listIterator();
+          while (iterator.hasNext()) {
+            if (toRemove.contains(iterator.next())) {
+              iterator.remove();
+            }
+          }
+        }
+      }
+    }
+  }
+
   /**
    * 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.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java
index 0d93561..a228b20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Move;
+import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.HashSet;
@@ -30,21 +31,32 @@
             allocator.getRegisterForValue(activeMove.src(), activeMove.getNumber());
         int activeMoveDstRegister =
             allocator.getRegisterForValue(activeMove.dest(), activeMove.getNumber());
-        if (activeMoveSrcRegister == moveSrcRegister
-            && activeMoveDstRegister == moveDstRegister) {
+        if (activeMoveSrcRegister == moveSrcRegister && activeMoveDstRegister == moveDstRegister) {
           return true;
         }
+        if (activeMoveDstRegister == moveSrcRegister && activeMoveSrcRegister == moveDstRegister) {
+          if (move.outType() != MoveType.WIDE) {
+            return true;
+          }
+          // If the move is wide make sure the register pair is non-overlapping.
+          if (moveSrcRegister != moveDstRegister + 1 && moveSrcRegister + 1 != moveDstRegister) {
+            return true;
+          }
+        }
       }
     }
     if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
       Value defined = instruction.outValue();
       int definedRegister = allocator.getRegisterForValue(defined, instruction.getNumber());
       activeMoves.removeIf((m) -> {
-        int moveSrcRegister = allocator.getRegisterForValue(m.inValues().get(0), m.getNumber());
-        int moveDstRegister = allocator.getRegisterForValue(m.outValue(), m.getNumber());
+        int moveSrcRegister = allocator.getRegisterForValue(m.src(), m.getNumber());
+        int moveDstRegister = allocator.getRegisterForValue(m.dest(), m.getNumber());
         for (int i = 0; i < defined.requiredRegisters(); i++) {
-          if (definedRegister + i == moveDstRegister || definedRegister + i == moveSrcRegister) {
-            return true;
+          for (int j = 0; j < m.outValue().requiredRegisters(); j++) {
+            if (definedRegister + i == moveDstRegister + j
+                || definedRegister + i == moveSrcRegister + j) {
+              return true;
+            }
           }
         }
         return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index 4ad05cc..a7d6e8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -173,7 +173,7 @@
    */
   private static void removeIdenticalPredecessorBlocks(IRCode code, RegisterAllocator allocator) {
     BasicBlockInstructionsEquivalence equivalence =
-        new BasicBlockInstructionsEquivalence(allocator);
+        new BasicBlockInstructionsEquivalence(code, allocator);
     // Locate one block at a time that has identical predecessors. Rewrite those predecessors and
     // then start over. Restarting when one blocks predecessors have been rewritten simplifies
     // the rewriting and reduces the size of the data structures.
@@ -194,6 +194,7 @@
             BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
             pred.clearCatchHandlers();
             pred.getInstructions().clear();
+            equivalence.clearComputedHash(pred);
             for (BasicBlock succ : pred.getSuccessors()) {
               succ.removePredecessor(pred);
             }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index f42af78..2011530 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.graph.CanonicalizedDexItem;
+import com.android.tools.r8.graph.CachedHashValueDexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.StringUtils;
@@ -16,14 +16,14 @@
 import java.util.Map;
 import java.util.Set;
 
-class NamingState<T extends CanonicalizedDexItem> {
+class NamingState<T extends CachedHashValueDexItem> {
 
   private final NamingState<T> parent;
   private final Map<T, InternalState> usedNames = new IdentityHashMap<>();
   private final DexItemFactory itemFactory;
   private final ImmutableList<String> dictionary;
 
-  static <T extends CanonicalizedDexItem> NamingState<T> createRoot(
+  static <T extends CachedHashValueDexItem> NamingState<T> createRoot(
       DexItemFactory itemFactory, ImmutableList<String> dictionary) {
     return new NamingState<>(null, itemFactory, dictionary);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 31c7f1f..300f18e 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -60,7 +60,7 @@
         .collect(Collectors.toSet());
     for (DexType type : classes) {
       DexClass clazz = appInfo.definitionFor(type);
-      clazz.virtualMethods = removeMethods(clazz.virtualMethods, unneededVisibilityBridges);
+      clazz.setVirtualMethods(removeMethods(clazz.virtualMethods(), unneededVisibilityBridges));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index 2850790..3fd5470 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -35,7 +35,7 @@
     DexClass holder = appInfo.definitionFor(type);
     scope = new ScopedDexItemSet(scope);
     if (holder != null) {
-      holder.virtualMethods = processMethods(holder.virtualMethods);
+      holder.setVirtualMethods(processMethods(holder.virtualMethods()));
     }
     type.forAllExtendsSubtypes(this::processClass);
     scope = scope.getParent();
@@ -74,10 +74,6 @@
     private final ScopedDexItemSet parent;
     private final Set<Wrapper<DexMethod>> items = new HashSet<>();
 
-    private ScopedDexItemSet() {
-      this(null);
-    }
-
     private ScopedDexItemSet(ScopedDexItemSet parent) {
       this.parent = parent;
     }
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 d3306d9..c04b29a 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -257,20 +257,20 @@
           ? DexTypeList.empty()
           : new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
       // Step 2: replace fields and methods.
-      target.directMethods = mergedDirectMethods
-          .toArray(new DexEncodedMethod[mergedDirectMethods.size()]);
-      target.virtualMethods = mergedVirtualMethods
-          .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]);
-      target.staticFields = mergedStaticFields
-          .toArray(new DexEncodedField[mergedStaticFields.size()]);
-      target.instanceFields = mergedInstanceFields
-          .toArray(new DexEncodedField[mergedInstanceFields.size()]);
+      target.setDirectMethods(mergedDirectMethods
+          .toArray(new DexEncodedMethod[mergedDirectMethods.size()]));
+      target.setVirtualMethods(mergedVirtualMethods
+          .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]));
+      target.setStaticFields(mergedStaticFields
+          .toArray(new DexEncodedField[mergedStaticFields.size()]));
+      target.setInstanceFields(mergedInstanceFields
+          .toArray(new DexEncodedField[mergedInstanceFields.size()]));
       // Step 3: Unlink old class to ease tree shaking.
       source.superType = application.dexItemFactory.objectType;
-      source.directMethods = null;
-      source.virtualMethods = null;
-      source.instanceFields = null;
-      source.staticFields = null;
+      source.setDirectMethods(null);
+      source.setVirtualMethods(null);
+      source.setInstanceFields(null);
+      source.setStaticFields(null);
       source.interfaces = DexTypeList.empty();
       // Step 4: Record merging.
       mergedClasses.put(source.type, target.type);
@@ -427,11 +427,11 @@
     private GraphLense fixupTypeReferences(GraphLense graphLense) {
       // Globally substitute merged class types in protos and holders.
       for (DexProgramClass clazz : appInfo.classes()) {
-        clazz.directMethods = substituteTypesIn(clazz.directMethods);
-        clazz.virtualMethods = substituteTypesIn(clazz.virtualMethods);
-        clazz.virtualMethods = removeDupes(clazz.virtualMethods);
-        clazz.staticFields = substituteTypesIn(clazz.staticFields);
-        clazz.instanceFields = substituteTypesIn(clazz.instanceFields);
+        clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
+        clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+        clazz.setVirtualMethods(removeDupes(clazz.virtualMethods()));
+        clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
+        clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
       }
       // Record type renamings so instanceof and checkcast checks are also fixed.
       for (DexType type : mergedClasses.keySet()) {
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 58646a8..64294bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -84,10 +84,10 @@
         // The class is used and must be kept. Remove the unused fields and methods from
         // the class.
         usagePrinter.visiting(clazz);
-        clazz.directMethods = reachableMethods(clazz.directMethods(), clazz);
-        clazz.virtualMethods = reachableMethods(clazz.virtualMethods(), clazz);
-        clazz.instanceFields = reachableFields(clazz.instanceFields());
-        clazz.staticFields = reachableFields(clazz.staticFields());
+        clazz.setDirectMethods(reachableMethods(clazz.directMethods(), clazz));
+        clazz.setVirtualMethods(reachableMethods(clazz.virtualMethods(), clazz));
+        clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
+        clazz.setStaticFields(reachableFields(clazz.staticFields()));
         usagePrinter.visited();
       }
     }
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 2480417..97a3679 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -212,8 +212,8 @@
   /**
    * Get the input stream of the proguard-map resource if it exists.
    */
-  public InputStream getProguardMap(Closer closer) throws IOException {
-    return proguardMap == null ? null : closer.register(proguardMap.getStream());
+  public InputStream getProguardMap() throws IOException {
+    return proguardMap == null ? null : proguardMap.getStream();
   }
 
   /**
@@ -382,10 +382,11 @@
     }
   }
 
-  public void writeProguardMap(Closer closer, OutputStream out) throws IOException {
-    InputStream input = getProguardMap(closer);
-    assert input != null;
-    out.write(ByteStreams.toByteArray(input));
+  public void writeProguardMap(OutputStream out) throws IOException {
+    try (InputStream input = getProguardMap()) {
+      assert input != null;
+      out.write(ByteStreams.toByteArray(input));
+    }
   }
 
   public void writeProguardSeeds(Closer closer, OutputStream out) throws IOException {
@@ -422,6 +423,7 @@
     private List<Resource> mainDexListResources = new ArrayList<>();
     private List<String> mainDexListClasses = new ArrayList<>();
     private Resource mainDexListOutput;
+    private boolean ignoreDexInArchive = false;
 
     // See AndroidApp::builder().
     private Builder() {
@@ -670,6 +672,17 @@
     }
 
     /**
+     * Ignore dex resources in input archives.
+     *
+     * In some situations (e.g. AOSP framework build) the input archives include both class and
+     * dex resources. Setting this flag ignores the dex resources and reads the class resources
+     * only.
+     */
+    public void setIgnoreDexInArchive(boolean value) {
+      ignoreDexInArchive = value;
+    }
+
+    /**
      * Build final AndroidApp.
      */
     public AndroidApp build() {
@@ -696,7 +709,7 @@
       } else if (isClassFile(file)) {
         programResources.add(Resource.fromFile(Resource.Kind.CLASSFILE, file));
       } else if (isArchive(file)) {
-        programFileArchiveReaders.add(new ProgramFileArchiveReader(file));
+        programFileArchiveReaders.add(new ProgramFileArchiveReader(file, ignoreDexInArchive));
       } else {
         throw new CompilationError("Unsupported source file type for file: " + file);
       }
diff --git a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
index 8903525..d6848a4 100644
--- a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
@@ -13,10 +13,11 @@
     long result = 0;
     int shift = 0;
     for (int i = 1; i < numberOfBytes; i++) {
-      result |= ((long) ((file.get() & 0xFF))) << shift;
+      result |= ((long) (file.get() & 0xFF)) << shift;
       shift += 8;
     }
-    return result | (file.get() << shift);
+    // Let the last byte sign-extend into any remaining bytes.
+    return result | (((long) file.get()) << shift);
   }
 
   // Inspired by com.android.dex.EncodedValueCodec
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 814ffd1..8341921 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -25,11 +25,13 @@
 class ProgramFileArchiveReader {
 
   private final Path archive;
+  private boolean ignoreDexInArchive;
   private List<Resource> dexResources = null;
   private List<Resource> classResources = null;
 
-  ProgramFileArchiveReader(Path archive) {
+  ProgramFileArchiveReader(Path archive, boolean ignoreDexInArchive) {
     this.archive = archive;
+    this.ignoreDexInArchive = ignoreDexInArchive;
   }
 
   private void readArchive() throws IOException {
@@ -41,9 +43,11 @@
       while ((entry = stream.getNextEntry()) != null) {
         Path name = Paths.get(entry.getName());
         if (isDexFile(name)) {
-          Resource resource =
-              new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
-          dexResources.add(resource);
+          if (!ignoreDexInArchive) {
+            Resource resource =
+                new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
+            dexResources.add(resource);
+          }
         } else if (isClassFile(name)) {
           String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
           Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 8404703..8712b7a 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -109,7 +109,7 @@
     return join(collection, separator, BraceType.NONE);
   }
 
-  public static <T> String join(String separator, T... strings) {
+  public static String join(String separator, String... strings) {
     return join(Arrays.asList(strings), separator, BraceType.NONE);
   }
 
@@ -124,15 +124,15 @@
     return builder.toString();
   }
 
-  public static <T> String lines(T... lines) {
+  public static String lines(String... lines) {
     StringBuilder builder = new StringBuilder();
-    for (T line : lines) {
+    for (String line : lines) {
       builder.append(line).append(LINE_SEPARATOR);
     }
     return builder.toString();
   }
 
-  public static <T> String joinLines(T... lines) {
+  public static String joinLines(String... lines) {
     return join(LINE_SEPARATOR, lines);
   }
 
@@ -158,11 +158,11 @@
     assert(0 <= width && width <= 8);
     String hex = Integer.toHexString(value);
     if (value >= 0) {
-      return zeroPrefixString(hex, width);
+      return "0x" + zeroPrefixString(hex, width);
     } else {
       // Negative ints are always formatted as 8 characters.
       assert(hex.length() == 8);
-      return hex;
+      return "0x" + hex;
     }
   }
 
@@ -170,11 +170,11 @@
     assert(0 <= width && width <= 16);
     String hex = Long.toHexString(value);
     if (value >= 0) {
-      return zeroPrefixString(hex, width);
+      return "0x" + zeroPrefixString(hex, width);
     } else {
       // Negative longs are always formatted as 16 characters.
       assert(hex.length() == 16);
-      return hex;
+      return "0x" + hex;
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 063d5fc..beba10e 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4656,14 +4656,6 @@
           // 1) t04
           // java.lang.AssertionError
 
-          .put("lang.reflect.Field.getLjava_lang_Object.Field_get_A04", match(R8_AFTER_D8_COMPILER))
-          // 1) t02
-          // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
-
-          .put("lang.reflect.Field.getLongLjava_lang_Object.Field_getLong_A04", match(R8_AFTER_D8_COMPILER))
-          // 1)
-          // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
-
           .build(); // end of failuresToTriage
 
   public static final Multimap<String, TestCondition> flakyWithArt =
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index fd4cdccb..eee820f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -314,10 +314,22 @@
     }
   }
 
+  static class RetainedTemporaryFolder extends TemporaryFolder {
+    RetainedTemporaryFolder(java.io.File parentFolder) {
+      super(parentFolder);
+    }
+    protected void after() {} // instead of remove, do nothing
+  }
+
   // For non-Linux platforms create the temporary directory in the repository root to simplify
   // running Art in a docker container
   public static TemporaryFolder getTemporaryFolderForTest() {
-    return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
+    String tmpDir = System.getProperty("test_dir");
+    if (tmpDir == null) {
+      return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
+    } else {
+      return new RetainedTemporaryFolder(new java.io.File(tmpDir));
+    }
   }
 
   public static String getArtBinary() {
diff --git a/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java b/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
index d1e46f3..6114f61 100644
--- a/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
+++ b/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -18,6 +19,7 @@
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 
 public class ExtraFileTest {
@@ -32,8 +34,11 @@
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   @Test
-  public void splitMemberRebindingTwoFiles()
+  public void splitMemberRebindingTwoFilesRelease()
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
     if (!ToolHelper.artSupported()) {
       return;
@@ -46,6 +51,7 @@
     R8Command command =
         R8Command.builder()
             .addProgramFiles(original)
+            .setMode(CompilationMode.RELEASE)
             .setOutputPath(out)
             .setMinApiLevel(Constants.ANDROID_L_API) // Allow native multidex.
             .setProguardMapFile(proguardMap)
@@ -66,4 +72,24 @@
         null,
         null);
   }
+
+  @Test
+  public void splitMemberRebindingTwoFilesDebug()
+      throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+    thrown.expect(CompilationException.class);
+    Path out = temp.getRoot().toPath();
+    Path original = Paths.get(EXAMPLE_DIR, EXAMPLE_DEX);
+    Path packageMap = Paths.get(ToolHelper.EXAMPLES_DIR, EXAMPLE_PACKAGE_MAP);
+    Path proguardMap = Paths.get(ToolHelper.EXAMPLES_DIR, EXAMPLE_PROGUARD_MAP);
+    R8Command command =
+        R8Command.builder()
+            .addProgramFiles(original)
+            .setMode(CompilationMode.DEBUG)
+            .setOutputPath(out)
+            .setMinApiLevel(Constants.ANDROID_L_API) // Allow native multidex.
+            .setProguardMapFile(proguardMap)
+            .setPackageDistributionFile(packageMap)
+            .build();
+    ToolHelper.runR8(command);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 72bbaed..2782c91 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -183,9 +183,9 @@
         "MethodStart:",
         ".line 1",
 
-        "LabelXStart:",
         "  ldc 0",
         "  istore 1",
+        "LabelXStart:",
         ".line 2",
         "  invokestatic Test/ensureLine()V",
         "LabelXEnd:",
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 1812fe1..671698b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.fail;
 
@@ -10,6 +11,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,13 +36,17 @@
     String artResult = null;
     try {
       artResult = runOnArt(builder, main);
-    } catch (CompilationError t) {
-      // Ignore.
+      fail();
+    } catch (ExecutionException t) {
+      if (!(t.getCause() instanceof CompilationError)) {
+        t.printStackTrace(System.out);
+        fail("Invalid dex class names should be compilation errors.");
+      }
     } catch (Throwable t) {
       t.printStackTrace(System.out);
       fail("Invalid dex class names should be compilation errors.");
     }
-    assert artResult == null : "Invalid dex class names should be rejected.";
+    assertNull("Invalid dex class names should be rejected.", artResult);
   }
 
   @Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 791ecca..87d350c 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,11 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.fail;
 
 import com.android.tools.r8.errors.CompilationError;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,10 +35,14 @@
     String artResult = null;
     try {
       artResult = runOnArt(builder, main);
-    } catch (CompilationError t) {
-      // Ignore.
+      fail();
+    } catch (ExecutionException t) {
+      if (!(t.getCause() instanceof CompilationError)) {
+        t.printStackTrace(System.out);
+        fail("Invalid dex field names should be compilation errors.");
+      }
     }
-    assert artResult == null : "Invalid dex class names should be rejected.";
+    assertNull("Invalid dex field names should be rejected.", artResult);
   }
 
   @Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index f8b7615..e1ace71 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,13 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.fail;
 
+import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,13 +37,17 @@
     String artResult = null;
     try {
       artResult = runOnArt(builder, main);
-    } catch (CompilationError t) {
-      // Ignore.
+      fail();
+    } catch (ExecutionException t) {
+      if (!(t.getCause() instanceof CompilationError)) {
+        t.printStackTrace(System.out);
+        fail("Invalid dex method names should be compilation errors.");
+      }
     } catch (Throwable t) {
       t.printStackTrace(System.out);
-      fail("Invalid dex class names should be compilation errors.");
+      fail("Invalid dex method names should be compilation errors.");
     }
-    assert artResult == null : "Invalid dex class names should be rejected.";
+    assertNull("Invalid dex method names should be rejected.", artResult);
   }
 
   @Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
new file mode 100644
index 0000000..8c1df56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress64658224 extends JasminTestBase {
+
+  @Test
+  public void testInvalidTypeInfoFromLocals() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
+        ".limit stack 2",
+        ".limit locals 2",
+        ".var 1 is x Ljava/lang/Object; from L1 to L2",
+        "  aconst_null",
+        "  astore 1",
+        "L1:",
+        "  iload 0",
+        "  ifeq L3",
+        "L2:",
+        "  goto L5",
+        "L3:",
+        "  aload 1",
+        "  iconst_0",
+        "  aaload",
+        "  pop",
+        "L5:",
+        "  return");
+
+    clazz.addMainMethod(
+        ".limit stack 1",
+        ".limit locals 1",
+        "  ldc 2",
+        "  invokestatic Test/foo(I)V",
+        "  return");
+
+    String expected = "";
+    String javaResult = runOnJava(builder, clazz.name);
+    assertEquals(expected, javaResult);
+    String artResult = runOnArtD8(builder, clazz.name);
+    assertEquals(expected, artResult);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index 6ee0b66..a71291d 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -205,7 +205,7 @@
       String pkg = "org.apache.harmony.jpda.tests.jdwp";
       command = Arrays.asList(
           ToolHelper.getJavaExecutable(),
-          "-cp", System.getProperty("java.class.path") + ":" + lib,
+          "-cp", System.getProperty("java.class.path") + File.pathSeparator + lib,
           run, pkg + "." + test);
     } else {
       command = Arrays.asList(
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 66137df..d5f9c6a 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -70,10 +70,8 @@
                 .addProguardConfigurationFiles(keepRulesPath)
                 .build());
     if (androidApp.hasProguardMap()) {
-      try (Closer closer = Closer.create()) {
-        androidApp.writeProguardMap(closer, new FileOutputStream(
+        androidApp.writeProguardMap(new FileOutputStream(
             Paths.get(tmpOutputDir.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME).toFile()));
-      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java b/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
index 72b452f..1426a0f 100644
--- a/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
@@ -30,6 +30,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.io.Closer;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashMap;
@@ -115,8 +116,8 @@
         ToolHelper.runR8(command, (options) -> options.skipDebugLineNumberOpt = !compressRanges);
 
     ClassNameMapper classNameMapper;
-    try (Closer closer = Closer.create()) {
-      classNameMapper = ProguardMapReader.mapperFromInputStream(result.getProguardMap(closer));
+    try (InputStream is = result.getProguardMap()) {
+      classNameMapper = ProguardMapReader.mapperFromInputStream(is);
     }
     if (compressRanges) {
       classNameMapper.forAllClassNamings(this::ensureRangesAreUniquePerClass);
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
new file mode 100644
index 0000000..195c3aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -0,0 +1,359 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.staticvalues;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+
+public class StaticValuesTest extends SmaliTestBase {
+
+  @Test
+  public void testAllTypes() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("booleanField", "Z");
+    builder.addStaticField("byteField", "B");
+    builder.addStaticField("shortField", "S");
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("longField", "J");
+    builder.addStaticField("floatField", "F");
+    builder.addStaticField("doubleField", "D");
+    builder.addStaticField("charField", "C");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+
+    builder.addStaticInitializer(
+        2,
+        "const               v0, 1",
+        "sput-byte           v0, LTest;->booleanField:Z",
+        "sput-byte           v0, LTest;->byteField:B",
+        "const               v0, 2",
+        "sput-short          v0, LTest;->shortField:S",
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "const-wide          v0, 4",
+        "sput-wide           v0, LTest;->longField:J",
+        "const               v0, 0x40a00000",  // 5.0.
+        "sput                v0, LTest;->floatField:F",
+        "const-wide          v0, 0x4018000000000000L",  // 6.0.
+        "sput-wide           v0, LTest;->doubleField:D",
+        "const               v0, 0x37",  // ASCII 7.
+        "sput-char           v0, LTest;->charField:C",
+        "const-string        v0, \"8\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-boolean        v1, LTest;->booleanField:Z",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Z)V",
+        "sget-byte           v1, LTest;->byteField:B",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-short          v1, LTest;->shortField:S",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-wide           v1, LTest;->longField:J",
+        "invoke-virtual      { v0, v1, v2 }, Ljava/io/PrintStream;->println(J)V",
+        "sget                v1, LTest;->floatField:F",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(F)V",
+        "sget-wide           v1, LTest;->doubleField:D",
+        "invoke-virtual      { v0, v1, v2 }, Ljava/io/PrintStream;->println(D)V",
+        "sget-char           v1, LTest;->charField:C",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(C)V",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("true\n1\n2\n3\n4\n5.0\n6.0\n7\n8\n", result);
+  }
+
+  @Test
+  public void getBeforePut() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("field1", "I", "1");
+    builder.addStaticField("field2", "I", "2");
+
+    builder.addStaticInitializer(
+        1,
+        "sget                v0, LTest;->field1:I",
+        "sput                v0, LTest;->field2:I",
+        "const               v0, 0",
+        "sput                v0, LTest;->field1:I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->field1:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget                v1, LTest;->field2:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // Nothing changed in the class initializer.
+    assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("0\n1\n", result);
+  }
+
+  @Test
+  public void testNull() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
+    builder.addStaticField("arrayField", "[I");
+    builder.addStaticField("arrayField2", "[[[[I");
+
+    builder.addStaticInitializer(
+        2,
+        "const               v0, 0",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "sput-object         v0, LTest;->arrayField:[I",
+        "sput-object         v0, LTest;->arrayField2:[[[[I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, LTest;->arrayField:[I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
+        "sget-object         v1, LTest;->arrayField2:[[[[I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("null\nnull\nnull\n", result);
+  }
+
+  @Test
+  public void testString() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("stringField1", "Ljava/lang/String;", "Hello");
+    builder.addStaticField("stringField2", "Ljava/lang/String;", "Hello");
+    builder.addStaticField("stringField3", "Ljava/lang/String;", "Hello");
+
+    builder.addStaticInitializer(
+        2,
+        "const-string        v0, \"Value1\"",
+        "sput-object         v0, LTest;->stringField1:Ljava/lang/String;",
+        "const-string        v0, \"Value2\"",
+        "sput-object         v0, LTest;->stringField2:Ljava/lang/String;",
+        "sput-object         v0, LTest;->stringField3:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-object         v1, LTest;->stringField1:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, LTest;->stringField2:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, LTest;->stringField3:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("Value1\nValue2\nValue2\n", result);
+  }
+
+  @Test
+  public void testInitializationToOwnClassName() {
+    String className = "org.example.Test";
+    SmaliBuilder builder = new SmaliBuilder(className);
+
+    builder.addStaticField("name1", "Ljava/lang/String;");
+    builder.addStaticField("name2", "Ljava/lang/String;");
+    builder.addStaticField("name3", "Ljava/lang/String;");
+    builder.addStaticField("simpleName1", "Ljava/lang/String;");
+    builder.addStaticField("simpleName2", "Ljava/lang/String;");
+    builder.addStaticField("simpleName3", "Ljava/lang/String;");
+
+    String descriptor = builder.getCurrentClassDescriptor();
+
+    builder.addStaticInitializer(
+        3,
+        "const-class         v0, " + descriptor,
+        "invoke-virtual      { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+        "move-result-object  v0",
+        "sput-object         v0, " + descriptor + "->simpleName1:Ljava/lang/String;",
+        "const-class         v0, " + descriptor,
+        "invoke-virtual      { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+        "move-result-object  v0",
+        "sput-object         v0, " + descriptor + "->name1:Ljava/lang/String;",
+        "const-class         v0, " + descriptor,
+        "invoke-virtual      { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+        "move-result-object  v1",
+        "invoke-virtual      { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+        "move-result-object  v2",
+        "sput-object         v1, " + descriptor + "->simpleName2:Ljava/lang/String;",
+        "sput-object         v1, " + descriptor + "->simpleName3:Ljava/lang/String;",
+        "sput-object         v2, " + descriptor + "->name2:Ljava/lang/String;",
+        "sput-object         v2, " + descriptor + "->name3:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-object         v1, " + descriptor + "->simpleName1:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, " + descriptor + "->name1:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, " + descriptor + "->simpleName2:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, " + descriptor + "->name2:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, " + descriptor + "->simpleName3:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, " + descriptor + "->name3:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    assertTrue(inspector.clazz(className).isPresent());
+    assertFalse(inspector.clazz(className).clinit().isPresent());
+
+    String result = runArt(processedApplication, options, className);
+
+    assertEquals(
+        "Test\n" + className + "\nTest\n" + className + "\nTest\n"  + className + "\n", result);
+  }
+
+  @Test
+  public void testInitializationToOtherClassName() {
+    String className = "org.example.Test";
+    SmaliBuilder builder = new SmaliBuilder(className);
+
+    builder.addStaticField("simpleName", "Ljava/lang/String;");
+    builder.addStaticField("name", "Ljava/lang/String;");
+
+    String descriptor = builder.getCurrentClassDescriptor();
+
+    builder.addStaticInitializer(
+        3,
+        "const-class         v0, Lorg/example/Test2;",
+        "invoke-virtual      { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+        "move-result-object  v0",
+        "sput-object         v0, " + descriptor + "->simpleName:Ljava/lang/String;",
+        "const-class         v0, Lorg/example/Test2;",
+        "invoke-virtual      { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+        "move-result-object  v0",
+        "sput-object         v0, " + descriptor + "->name:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-object         v1, " + descriptor + "->simpleName:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "sget-object         v1, " + descriptor + "->name:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    builder.addClass("org.example.Test2");
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    assertTrue(inspector.clazz(className).isPresent());
+    assertTrue(inspector.clazz(className).clinit().isPresent());
+
+    String result = runArt(processedApplication, options, className);
+
+    assertEquals("Test2\norg.example.Test2\n", result);
+  }
+
+  @Test
+  public void fieldOnOtherClass() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 2",
+        "sput                v0, LOther;->field:I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LOther;->field:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "return-void"
+    );
+
+    builder.addClass("Other");
+    builder.addStaticField("field", "I", "1");
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // Nothing changed in the class initializer.
+    assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("2\n", result);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 4c1fc69..51b6ab5 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -584,9 +584,12 @@
 
   private static void checkSameStructure(FoundMethodSubject refMethod, ClassSubject clazz) {
     MethodSignature signature = refMethod.getOriginalSignature();
-    Assert.assertTrue("Missing Method: " + clazz.getDexClass().toSourceString() + "."
-            + signature.toString(),
-        clazz.method(signature).isPresent());
+    // Don't check for existence of class initializers, as the code optimization can remove them.
+    if (!refMethod.isClassInitializer()) {
+      Assert.assertTrue("Missing Method: " + clazz.getDexClass().toSourceString() + "."
+              + signature.toString(),
+          clazz.method(signature).isPresent());
+    }
   }
 
   private static void checkSameStructure(FoundFieldSubject refField, ClassSubject clazz) {
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 79ef0b9..1d256e0 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -53,6 +53,9 @@
     );
 
     InternalOptions options = new InternalOptions();
+    // (b/64777953) Disable outliner optimization for this test because it increases time
+    // from 1 minute to 7 minutes.
+    options.outline.enabled = false;
     DexApplication originalApplication = buildApplication(smaliBuilder, options);
     DexApplication processedApplication = processApplication(originalApplication, options);
     String result = runArt(processedApplication, options);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 4656cd6..c726aad 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -828,13 +828,13 @@
     DexInspector inspector = new DexInspector(processedApplication);
     ClassSubject clazz = inspector.clazz(options.outline.className);
     assertTrue(clazz.isPresent());
-    assertEquals(3, clazz.getDexClass().directMethods.length);
+    assertEquals(3, clazz.getDexClass().directMethods().length);
     // Collect the return types of the putlines for the body of method1 and method2.
     List<DexType> r = new ArrayList<>();
-    for (int i = 0; i < clazz.getDexClass().directMethods.length; i++) {
-      if (clazz.getDexClass().directMethods[i].getCode().asDexCode().instructions[0]
+    for (int i = 0; i < clazz.getDexClass().directMethods().length; i++) {
+      if (clazz.getDexClass().directMethods()[i].getCode().asDexCode().instructions[0]
           instanceof InvokeVirtual) {
-        r.add(clazz.getDexClass().directMethods[i].method.proto.returnType);
+        r.add(clazz.getDexClass().directMethods()[i].method.proto.returnType);
       }
     }
     assert r.size() == 2;
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index f070130..4253395 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -71,6 +71,10 @@
       this.returnType = returnType;
       this.parameterTypes = parameterTypes;
     }
+
+    public static MethodSignature staticInitializer(String clazz) {
+      return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+    }
   }
 
   public static class SmaliBuilder {
@@ -210,6 +214,29 @@
       );
     }
 
+    public void addStaticField(String name, String type, String defaultValue) {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".field static ");
+      builder.append(name);
+      builder.append(":");
+      builder.append(type);
+      if (defaultValue != null) {
+        builder.append(" = ");
+        if (type.equals("Ljava/lang/String;")) {
+          builder.append('"');
+          builder.append(defaultValue);
+          builder.append('"');
+        } else {
+          builder.append(defaultValue);
+        }
+      }
+      getSource(currentClassName).add(builder.toString());
+    }
+
+    public void addStaticField(String name, String type) {
+      addStaticField(name, type, null);
+    }
+
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String... instructions) {
       StringBuilder builder = new StringBuilder();
@@ -222,8 +249,30 @@
 
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String code) {
+      return addStaticMethod("", returnType, name, parameters, locals, code);
+    }
+
+    public MethodSignature addStaticInitializer(int locals, String... instructions) {
+      StringBuilder builder = new StringBuilder();
+      for (String instruction : instructions) {
+        builder.append(instruction);
+        builder.append("\n");
+      }
+      return addStaticInitializer(locals, builder.toString());
+    }
+
+    public MethodSignature addStaticInitializer(int locals, String code) {
+      return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+    }
+
+    private MethodSignature addStaticMethod(String flags, String returnType, String name,
+        List<String> parameters, int locals, String code) {
       StringBuilder builder = new StringBuilder();
       builder.append(".method public static ");
+      if (flags != null && flags.length() > 0) {
+        builder.append(flags);
+        builder.append(" ");
+      }
       builder.append(name);
       builder.append("(");
       for (String parameter : parameters) {
@@ -469,12 +518,16 @@
   }
 
   public String runArt(DexApplication application, InternalOptions options) {
+    return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
+  }
+
+  public String runArt(DexApplication application, InternalOptions options, String mainClass) {
     try {
       AndroidApp app = writeDex(application, options);
       Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
       // TODO(sgjesse): Pass in a unique temp directory for each run.
       app.writeToZip(out, OutputMode.Indexed);
-      return ToolHelper.runArtNoVerificationErrors(out.toString(), DEFAULT_MAIN_CLASS_NAME);
+      return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 61c3c14..0ad240c 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -267,6 +267,10 @@
 
     public abstract MethodSubject method(String returnType, String name, List<String> parameters);
 
+    public MethodSubject clinit() {
+      return method("void", "<clinit>", ImmutableList.of());
+    }
+
     public MethodSubject method(MethodSignature signature) {
       return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
     }
@@ -517,6 +521,8 @@
 
     public abstract boolean isBridge();
 
+    public abstract boolean isClassInitializer();
+
     public abstract DexEncodedMethod getMethod();
 
     public Iterator<InstructionSubject> iterateInstructions() {
@@ -574,6 +580,11 @@
     }
 
     @Override
+    public boolean isClassInitializer() {
+      return false;
+    }
+
+    @Override
     public DexEncodedMethod getMethod() {
       return null;
     }
@@ -640,6 +651,11 @@
     }
 
     @Override
+    public boolean isClassInitializer() {
+      return dexMethod.isClassInitializer();
+    }
+
+    @Override
     public DexEncodedMethod getMethod() {
       return dexMethod;
     }
diff --git a/tools/test.py b/tools/test.py
index 8e016c7..1da077b 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -44,7 +44,8 @@
       help='Print a line before a tests starts and after it ends to stdout.',
       default=False, action='store_true')
   result.add_option('--tool',
-      help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if "--all_tests" enabled.',
+      help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if'
+          ' "--all_tests" enabled.',
       default=None, choices=["r8", "d8"])
   result.add_option('--jctf',
       help='Run JCTF tests with: "r8" (default) or "d8".',
@@ -56,8 +57,15 @@
       help="Don't run, only compile JCTF tests.",
       default=False, action='store_true')
   result.add_option('--disable_assertions',
-      help="Disable assertions when running tests.",
+      help='Disable assertions when running tests.',
       default=False, action='store_true')
+  result.add_option('--with_code_coverage',
+      help='Enable code coverage with Jacoco.',
+      default=False, action='store_true')
+  result.add_option('--test_dir',
+      help='Use a custom directory for the test artifacts instead of a'
+          ' temporary (which is automatically removed after the test).'
+          ' Note that the directory will not be cleared before the test.')
 
   return result.parse_args()
 
@@ -72,10 +80,12 @@
 
 def Main():
   (options, args) = ParseOptions()
-  gradle_args = ['cleanTest', 'test']
   if len(args) > 1:
     print("test.py takes at most one argument, the pattern for tests to run")
     return -1
+
+  gradle_args = []
+  # Set all necessary Gradle properties and options first.
   if options.verbose:
     gradle_args.append('-Pprint_test_stdout')
   if options.no_internal:
@@ -96,9 +106,8 @@
     gradle_args.append('-Pjctf_compile_only')
   if options.disable_assertions:
     gradle_args.append('-Pdisable_assertions')
-  if len(args) > 0:
-    gradle_args.append('--tests')
-    gradle_args.append(args[0])
+  if options.with_code_coverage:
+    gradle_args.append('-Pwith_code_coverage')
   if os.name == 'nt':
     # temporary hack
     gradle_args.append('-Pno_internal')
@@ -108,6 +117,23 @@
     gradle_args.append('jctfCommonJar')
     gradle_args.append('-x')
     gradle_args.append('jctfTestsClasses')
+  if options.test_dir:
+    gradle_args.append('-Ptest_dir=' + options.test_dir)
+    if not os.path.exists(options.test_dir):
+      os.makedirs(options.test_dir)
+
+  # Add Gradle tasks
+  gradle_args.append('cleanTest')
+  gradle_args.append('test')
+  if len(args) > 0:
+    # Test filtering. Must always follow the 'test' task.
+    gradle_args.append('--tests')
+    gradle_args.append(args[0])
+  if options.with_code_coverage:
+    # Create Jacoco report after tests.
+    gradle_args.append('jacocoTestReport')
+
+  # Now run tests on selected runtime(s).
   vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
   for art_vm in vms_to_test:
     return_code = gradle.RunGradle(gradle_args + ['-Pdex_vm=%s' % art_vm],
diff --git a/tools/test_framework.py b/tools/test_framework.py
index b6f5a19..9e5f63f 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -43,7 +43,7 @@
           ' third_party/framework/framework*.jar.'
           ' Report Golem-compatible CodeSize and RunTimeRaw values.')
   parser.add_argument('--tool',
-      choices = ['dx', 'd8', 'd8-release', 'goyt'],
+      choices = ['dx', 'd8', 'd8-release', 'goyt', 'goyt-release'],
       required = True,
       help = 'Compiler tool to use.')
   parser.add_argument('--name',
@@ -65,21 +65,25 @@
 
   with utils.TempDir() as temp_dir:
 
-    if args.tool in ['dx', 'goyt']:
+    if args.tool in ['dx', 'goyt', 'goyt-release']:
       tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
           '--min-sdk-version=' + MIN_SDK_VERSION]
 
-    if args.tool == 'goyt':
+    xmx = None
+    if args.tool.startswith('goyt'):
       tool_file = GOYT_EXE
       tool_args = ['--num-threads=8'] + tool_args
+      if args.tool == 'goyt-release':
+        tool_args.append('--no-locals')
     elif args.tool == 'dx':
       tool_file = DX_JAR
+      xmx = '-Xmx1600m'
     else:
       tool_file = D8_JAR
       tool_args = ['--output', temp_dir, '--min-api', MIN_SDK_VERSION]
       if args.tool == 'd8-release':
         tool_args.append('--release')
-
+      xmx = '-Xmx600m'
 
     cmd = []
 
@@ -89,7 +93,8 @@
       cmd.extend(['tools/track_memory.sh', track_memory_file])
 
     if tool_file.endswith('.jar'):
-      cmd.extend(['java', '-jar'])
+      assert xmx is not None
+      cmd.extend(['java', xmx, '-jar'])
 
     cmd.extend([tool_file] + tool_args + [FRAMEWORK_JAR])