Merge "Add support for -if rule with back references."
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index eb2d0fe..fbc6ca3 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -66,6 +66,34 @@
   // Internal access to the internal options.
   abstract InternalOptions getInternalOptions();
 
+  abstract static class InputFileOrigin extends PathOrigin {
+    private final String inputType;
+
+    public InputFileOrigin(String inputType, Path file) {
+      super(file);
+      this.inputType = inputType;
+    }
+
+    @Override
+    public String part() {
+      return inputType + " '" + super.part() + "'";
+    }
+  }
+
+  private static class ProgramInputOrigin extends InputFileOrigin {
+
+    public ProgramInputOrigin(Path file) {
+      super("program input", file);
+    }
+  }
+
+  private static class LibraryInputOrigin extends InputFileOrigin {
+
+    public LibraryInputOrigin(Path file) {
+      super("library input", file);
+    }
+  }
+
   /**
    * Base builder for commands.
    *
@@ -83,20 +111,16 @@
     List<Path> programFiles = new ArrayList<>();
 
     Builder() {
-      this(AndroidApp.builder(), new DefaultDiagnosticsHandler());
+      this(AndroidApp.builder());
     }
 
     Builder(DiagnosticsHandler handler) {
-      this(AndroidApp.builder(), handler);
+      this(AndroidApp.builder(new Reporter(handler)));
     }
 
     Builder(AndroidApp.Builder builder) {
-      this(builder, new DefaultDiagnosticsHandler());
-    }
-
-    Builder(AndroidApp.Builder builder, DiagnosticsHandler handler) {
       this.app = builder;
-      this.reporter = new Reporter(handler);
+      this.reporter = builder.getReporter();
     }
 
     abstract B self();
@@ -147,7 +171,7 @@
                     app.addProgramFile(path);
                     programFiles.add(path);
                   } catch (IOException | CompilationError e) {
-                    error("Error with input file: ", path, e);
+                    error(new ProgramInputOrigin(path), e);
                   }
                 });
           });
@@ -181,7 +205,7 @@
                   try {
                     app.addLibraryFile(path);
                   } catch (IOException | CompilationError e) {
-                    error("Error with library file: ", path, e);
+                    error(new LibraryInputOrigin(path), e);
                   }
                 });
           });
@@ -289,9 +313,8 @@
     void validate() {}
 
     // Helper to signify an error.
-    void error(String baseMessage, Path path, Throwable throwable) {
-      reporter.error(new StringDiagnostic(
-          baseMessage + throwable.getMessage(), new PathOrigin(path)), throwable);
+    void error(Origin origin, Throwable throwable) {
+      reporter.error(new ExceptionDiagnostic(throwable, origin));
     }
 
     // Helper to guard and handle exceptions.
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 1bf77b1..e393f32 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -119,20 +119,21 @@
     private boolean disableDesugaring = false;
     private boolean optimizeMultidexForLinearAlloc = false;
 
-    Builder() {}
+    abstract CompilationMode defaultCompilationMode();
+
+    Builder() {
+      mode = defaultCompilationMode();
+    }
 
     Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
+      mode = defaultCompilationMode();
     }
 
     // Internal constructor for testing.
     Builder(AndroidApp app) {
       super(AndroidApp.builder(app));
-    }
-
-    // Internal constructor for testing.
-    Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
-      super(AndroidApp.builder(app), diagnosticsHandler);
+      mode = defaultCompilationMode();
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 0394441..6d7ad87 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.utils.ArchiveBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DirectoryBuilder;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
@@ -135,7 +135,7 @@
       try {
         outputBuilder.close();
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, outputBuilder.getOrigin()));
+        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index f2f59a2..a6923ba 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -13,12 +13,17 @@
 
   public CompatProguardCommandBuilder(
       boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
-    super(forceProguardCompatibility, diagnosticsHandler);
+    super(diagnosticsHandler);
+    if (forceProguardCompatibility) {
+      internalForceProguardCompatibility();
+    }
     setIgnoreDexInArchive(true);
   }
 
   public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
-    super(forceProguardCompatibility);
+    if (forceProguardCompatibility) {
+      internalForceProguardCompatibility();
+    }
     setIgnoreDexInArchive(true);
   }
 
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c300bbf..bb7c37f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -32,6 +32,13 @@
  */
 public class D8Command extends BaseCompilerCommand {
 
+  private static class ClasspathInputOrigin extends InputFileOrigin {
+
+    public ClasspathInputOrigin(Path file) {
+      super("classpath input", file);
+    }
+  }
+
   /**
    * Builder for constructing a D8Command.
    *
@@ -41,18 +48,14 @@
 
     private boolean intermediate = false;
 
-    Builder() {
-      setMode(CompilationMode.DEBUG);
-    }
+    private Builder() {}
 
-    Builder(DiagnosticsHandler diagnosticsHandler) {
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
-      setMode(CompilationMode.DEBUG);
     }
 
     private Builder(AndroidApp app) {
       super(app);
-      setMode(CompilationMode.DEBUG);
     }
 
     /** Add dex program-data. */
@@ -78,7 +81,7 @@
         try {
           getAppBuilder().addClasspathFile(file);
         } catch (IOException e) {
-          error("Error with classpath entry: ", file, e);
+          error(new ClasspathInputOrigin(file), e);
         }
       });
     }
@@ -105,6 +108,11 @@
     }
 
     @Override
+    CompilationMode defaultCompilationMode() {
+      return CompilationMode.DEBUG;
+    }
+
+    @Override
     void validate() {
       Reporter reporter = getReporter();
       if (getProgramConsumer() instanceof ClassFileConsumer) {
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
index f93c338..ca86c70 100644
--- a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.utils.ArchiveBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DirectoryBuilder;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
@@ -156,7 +156,7 @@
       try {
         outputBuilder.close();
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, outputBuilder.getOrigin()));
+        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 0f932d5..f26ab73 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -3,11 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.ArchiveBuilder;
 import com.android.tools.r8.utils.DirectoryBuilder;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
@@ -126,6 +127,10 @@
       }
     }
 
+    public Origin getOrigin() {
+      return outputBuilder.getOrigin();
+    }
+
     @Override
     public DataResourceConsumer getDataResourceConsumer() {
       return consumeDataResources ? this : null;
@@ -154,7 +159,7 @@
       try {
         outputBuilder.close();
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, outputBuilder.getOrigin()));
+        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
       }
     }
 
@@ -219,7 +224,7 @@
       try {
         prepareDirectory();
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, new PathOrigin(directory)));
+        handler.error(new ExceptionDiagnostic(e, new PathOrigin(directory)));
       }
       outputBuilder.addFile(getDexFileName(fileIndex), data, handler);
     }
@@ -240,7 +245,7 @@
       try {
         outputBuilder.close();
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, outputBuilder.getOrigin()));
+        handler.error(new ExceptionDiagnostic(e, outputBuilder.getOrigin()));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index ab86367..1fcd70a 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FeatureClassMapping;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
 import com.android.tools.r8.utils.InternalOptions;
@@ -33,14 +34,14 @@
 public class DexSplitterHelper {
 
   public static void run(
-      D8Command command,
-      FeatureClassMapping featureClassMapping,
-      String output,
-      String proguardMap)
-      throws IOException, CompilationException, ExecutionException {
+      D8Command command, FeatureClassMapping featureClassMapping, String output, String proguardMap)
+      throws CompilationFailedException {
     ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
     try {
-      run(command, featureClassMapping, output, proguardMap, executor);
+      ExceptionUtils.withCompilationHandler(
+          command.getReporter(),
+          () -> run(command, featureClassMapping, output, proguardMap, executor),
+          CompilationException::getMessage);
     } finally {
       executor.shutdown();
     }
@@ -52,7 +53,7 @@
       String output,
       String proguardMap,
       ExecutorService executor)
-      throws IOException, CompilationException, ExecutionException {
+      throws IOException, CompilationException {
     InternalOptions options = command.getInternalOptions();
     options.enableDesugaring = false;
     options.enableMainDexListCheck = false;
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index e51e4d4..4f64416 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -32,6 +32,7 @@
       private Path outputPath = null;
       private Path proguardMapFile = null;
       private boolean useSmali = false;
+      private boolean allInfo = false;
 
       @Override
       Builder self() {
@@ -52,6 +53,11 @@
         return this;
       }
 
+      public Builder setAllInfo(boolean allInfo) {
+        this.allInfo = allInfo;
+        return this;
+      }
+
       public Builder setUseSmali(boolean useSmali) {
         this.useSmali = useSmali;
         return this;
@@ -67,6 +73,7 @@
             getAppBuilder().build(),
             getOutputPath(),
             proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
+            allInfo,
             useSmali);
       }
     }
@@ -75,6 +82,7 @@
         "Usage: disasm [options] <input-files>",
         " where <input-files> are dex files",
         " and options are:",
+        "  --all                   # Include all information in disassembly.",
         "  --smali                 # Disassemble using smali syntax.",
         "  --pg-map <file>         # Proguard map <file> for mapping names.",
         "  --output                # Specify a file or directory to write to.",
@@ -82,6 +90,7 @@
         "  --help                  # Print this message."));
 
 
+    private final boolean allInfo;
     private final boolean useSmali;
 
     public static Builder builder() {
@@ -103,6 +112,8 @@
           builder.setPrintHelp(true);
         } else if (arg.equals("--version")) {
           builder.setPrintVersion(true);
+          } else if (arg.equals("--all")) {
+          builder.setAllInfo(true);
         } else if (arg.equals("--smali")) {
           builder.setUseSmali(true);
         } else if (arg.equals("--pg-map")) {
@@ -121,10 +132,12 @@
     }
 
     private DisassembleCommand(
-        AndroidApp inputApp, Path outputPath, StringResource proguardMap, boolean useSmali) {
+        AndroidApp inputApp, Path outputPath, StringResource proguardMap,
+        boolean allInfo, boolean useSmali) {
       super(inputApp);
       this.outputPath = outputPath;
       this.proguardMap = proguardMap;
+      this.allInfo = allInfo;
       this.useSmali = useSmali;
     }
 
@@ -132,6 +145,7 @@
       super(printHelp, printVersion);
       outputPath = null;
       proguardMap = null;
+      allInfo = false;
       useSmali = false;
     }
 
@@ -177,7 +191,7 @@
           new ApplicationReader(app, options, timing).read(command.proguardMap, executor);
       DexByteCodeWriter writer = command.useSmali()
           ? new SmaliWriter(application, options)
-          : new AssemblyWriter(application, options);
+          : new AssemblyWriter(application, options, command.allInfo);
       if (command.getOutputPath() != null) {
         writer.write(command.getOutputPath());
       } else {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 83f9dc2..919e982 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.Reporter;
@@ -65,35 +64,21 @@
 
     private StringConsumer mainDexListConsumer = null;
 
-    private Builder() {
-      setMode(CompilationMode.RELEASE);
-    }
+    // TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
+    Builder() {}
 
-    // Internal compatibility mode for use from CompatProguard tool.
-    Builder(boolean forceProguardCompatibility) {
-      setMode(CompilationMode.RELEASE);
-      this.forceProguardCompatibility = forceProguardCompatibility;
-    }
-
-    Builder(boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
+    Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
-      setMode(CompilationMode.RELEASE);
-      this.forceProguardCompatibility = forceProguardCompatibility;
-    }
-
-    private Builder(DiagnosticsHandler diagnosticsHandler) {
-      super(diagnosticsHandler);
-      setMode(CompilationMode.DEBUG);
     }
 
     private Builder(AndroidApp app) {
       super(app);
-      setMode(CompilationMode.RELEASE);
     }
 
-    private Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
-      super(app, diagnosticsHandler);
-      setMode(CompilationMode.RELEASE);
+    // Internal
+
+    void internalForceProguardCompatibility() {
+      this.forceProguardCompatibility = true;
     }
 
     @Override
@@ -101,6 +86,11 @@
       return this;
     }
 
+    @Override
+    CompilationMode defaultCompilationMode() {
+      return CompilationMode.RELEASE;
+    }
+
     /**
      * Disable tree shaking.
      *
@@ -278,22 +268,14 @@
 
     @Override
     R8Command makeCommand() {
-      try {
-        // If printing versions ignore everything else.
-        if (isPrintHelp() || isPrintVersion()) {
-          return new R8Command(isPrintHelp(), isPrintVersion());
-        }
-
-        return makeR8Command();
-      } catch (IOException e) {
-        throw getReporter().fatalError(new IOExceptionDiagnostic(e), e);
-      } catch (CompilationException e) {
-        throw getReporter().fatalError(new StringDiagnostic(e.getMessage()), e);
+      // If printing versions ignore everything else.
+      if (isPrintHelp() || isPrintVersion()) {
+        return new R8Command(isPrintHelp(), isPrintVersion());
       }
+      return makeR8Command();
     }
 
-    private R8Command makeR8Command()
-        throws IOException, CompilationException {
+    private R8Command makeR8Command() {
       Reporter reporter = getReporter();
       DexItemFactory factory = new DexItemFactory();
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
@@ -429,7 +411,6 @@
       "  --help                   # Print this message."));
 
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
-  private DataResourceConsumer dataResourceConsumer;
   private final StringConsumer mainDexListConsumer;
   private final ProguardConfiguration proguardConfiguration;
   private final boolean enableTreeShaking;
@@ -453,11 +434,6 @@
     return new Builder(app);
   }
 
-  // Internal builder to start from an existing AndroidApp.
-  static Builder builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
-    return new Builder(app, diagnosticsHandler);
-  }
-
   /**
    * Parse the R8 command-line.
    *
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 23883e9..cccb72e 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -102,7 +102,8 @@
       try {
         Files.write(outputPath, string.getBytes(encoding));
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
+        Origin origin = new PathOrigin(outputPath);
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
   }
@@ -149,7 +150,7 @@
         writer.write(string);
         writer.flush();
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, origin));
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/UsageInformationConsumer.java b/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
index e949276..a572908 100644
--- a/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
+++ b/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.file.Path;
@@ -89,7 +89,8 @@
       try {
         FileUtils.writeToFile(outputPath, null, data);
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
+        Origin origin = new PathOrigin(outputPath);
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
   }
@@ -125,7 +126,7 @@
       try {
         outputStream.write(data);
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, origin));
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index d7f1fa8..0712db2 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "v1.2.15-dev";
+  public static final String LABEL = "1.2.15-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 300335a..6d7ae19 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -3,11 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfArrayLength;
 import com.android.tools.r8.cf.code.CfArrayLoad;
 import com.android.tools.r8.cf.code.CfArrayStore;
-import com.android.tools.r8.cf.code.CfBinop;
 import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.cf.code.CfConstMethodType;
@@ -27,11 +28,14 @@
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.cf.code.CfMonitor;
 import com.android.tools.r8.cf.code.CfMultiANewArray;
+import com.android.tools.r8.cf.code.CfNeg;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
@@ -41,7 +45,6 @@
 import com.android.tools.r8.cf.code.CfSwitch.Kind;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
-import com.android.tools.r8.cf.code.CfUnop;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
@@ -230,12 +233,24 @@
     print(monitor.getType() == Monitor.Type.ENTER ? "monitorenter" : "monitorexit");
   }
 
-  public void print(CfBinop binop) {
-    print(opcodeName(binop.getOpcode()));
+  public void print(CfArithmeticBinop arithmeticBinop) {
+    print(opcodeName(arithmeticBinop.getAsmOpcode()));
   }
 
-  public void print(CfUnop unop) {
-    print(opcodeName(unop.getOpcode()));
+  public void print(CfCmp cmp) {
+    print(opcodeName(cmp.getAsmOpcode()));
+  }
+
+  public void print(CfLogicalBinop logicalBinop) {
+    print(opcodeName(logicalBinop.getAsmOpcode()));
+  }
+
+  public void print(CfNeg neg) {
+    print(opcodeName(neg.getAsmOpcode()));
+  }
+
+  public void print(CfNumberConversion numberConversion) {
+    print(opcodeName(numberConversion.getAsmOpcode()));
   }
 
   public void print(CfConstString constString) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
new file mode 100644
index 0000000..423bba2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2018, 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfArithmeticBinop extends CfInstruction {
+
+  public enum Opcode {
+    Add,
+    Sub,
+    Mul,
+    Div,
+    Rem,
+  }
+
+  private final Opcode opcode;
+  private final NumericType type;
+
+  public CfArithmeticBinop(Opcode opcode, NumericType type) {
+    assert opcode != null;
+    assert type != null;
+    this.opcode = opcode;
+    this.type = type;
+  }
+
+  public static CfArithmeticBinop fromAsm(int opcode) {
+    switch (opcode) {
+      case Opcodes.IADD:
+        return new CfArithmeticBinop(Opcode.Add, NumericType.INT);
+      case Opcodes.LADD:
+        return new CfArithmeticBinop(Opcode.Add, NumericType.LONG);
+      case Opcodes.FADD:
+        return new CfArithmeticBinop(Opcode.Add, NumericType.FLOAT);
+      case Opcodes.DADD:
+        return new CfArithmeticBinop(Opcode.Add, NumericType.DOUBLE);
+      case Opcodes.ISUB:
+        return new CfArithmeticBinop(Opcode.Sub, NumericType.INT);
+      case Opcodes.LSUB:
+        return new CfArithmeticBinop(Opcode.Sub, NumericType.LONG);
+      case Opcodes.FSUB:
+        return new CfArithmeticBinop(Opcode.Sub, NumericType.FLOAT);
+      case Opcodes.DSUB:
+        return new CfArithmeticBinop(Opcode.Sub, NumericType.DOUBLE);
+      case Opcodes.IMUL:
+        return new CfArithmeticBinop(Opcode.Mul, NumericType.INT);
+      case Opcodes.LMUL:
+        return new CfArithmeticBinop(Opcode.Mul, NumericType.LONG);
+      case Opcodes.FMUL:
+        return new CfArithmeticBinop(Opcode.Mul, NumericType.FLOAT);
+      case Opcodes.DMUL:
+        return new CfArithmeticBinop(Opcode.Mul, NumericType.DOUBLE);
+      case Opcodes.IDIV:
+        return new CfArithmeticBinop(Opcode.Div, NumericType.INT);
+      case Opcodes.LDIV:
+        return new CfArithmeticBinop(Opcode.Div, NumericType.LONG);
+      case Opcodes.FDIV:
+        return new CfArithmeticBinop(Opcode.Div, NumericType.FLOAT);
+      case Opcodes.DDIV:
+        return new CfArithmeticBinop(Opcode.Div, NumericType.DOUBLE);
+      case Opcodes.IREM:
+        return new CfArithmeticBinop(Opcode.Rem, NumericType.INT);
+      case Opcodes.LREM:
+        return new CfArithmeticBinop(Opcode.Rem, NumericType.LONG);
+      case Opcodes.FREM:
+        return new CfArithmeticBinop(Opcode.Rem, NumericType.FLOAT);
+      case Opcodes.DREM:
+        return new CfArithmeticBinop(Opcode.Rem, NumericType.DOUBLE);
+      default:
+        throw new Unreachable("Wrong ASM opcode for CfArithmeticBinop " + opcode);
+    }
+  }
+
+  public int getAsmOpcode() {
+    switch (opcode) {
+      case Add:
+        return Opcodes.IADD + getAsmOpcodeTypeOffset();
+      case Sub:
+        return Opcodes.ISUB + getAsmOpcodeTypeOffset();
+      case Mul:
+        return Opcodes.IMUL + getAsmOpcodeTypeOffset();
+      case Div:
+        return Opcodes.IDIV + getAsmOpcodeTypeOffset();
+      case Rem:
+        return Opcodes.IREM + getAsmOpcodeTypeOffset();
+      default:
+        throw new Unreachable("CfArithmeticBinop has unknown opcode " + opcode);
+    }
+  }
+
+  private int getAsmOpcodeTypeOffset() {
+    switch (type) {
+      case LONG:
+        return Opcodes.LADD - Opcodes.IADD;
+      case FLOAT:
+        return Opcodes.FADD - Opcodes.IADD;
+      case DOUBLE:
+        return Opcodes.DADD - Opcodes.IADD;
+      default:
+        return 0;
+    }
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitInsn(getAsmOpcode());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfBinop.java
deleted file mode 100644
index 6f8e868..0000000
--- a/src/main/java/com/android/tools/r8/cf/code/CfBinop.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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.cf.code;
-
-import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.naming.NamingLens;
-import org.objectweb.asm.MethodVisitor;
-
-public class CfBinop extends CfInstruction {
-
-  private final int opcode;
-
-  public CfBinop(int opcode) {
-    this.opcode = opcode;
-  }
-
-  public int getOpcode() {
-    return opcode;
-  }
-
-  @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
-    visitor.visitInsn(opcode);
-  }
-
-  @Override
-  public void print(CfPrinter printer) {
-    printer.print(this);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
new file mode 100644
index 0000000..9c6d3d7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2018, 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.Cmp;
+import com.android.tools.r8.ir.code.Cmp.Bias;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfCmp extends CfInstruction {
+
+  private final Bias bias;
+  private final NumericType type;
+
+  public CfCmp(Bias bias, NumericType type) {
+    assert bias != null;
+    assert type != null;
+    assert type == NumericType.LONG || type == NumericType.FLOAT || type == NumericType.DOUBLE;
+    assert type != NumericType.LONG || bias == Cmp.Bias.NONE;
+    assert type == NumericType.LONG || bias != Cmp.Bias.NONE;
+    this.bias = bias;
+    this.type = type;
+  }
+
+  public static CfCmp fromAsm(int opcode) {
+    switch (opcode) {
+      case Opcodes.LCMP:
+        return new CfCmp(Bias.NONE, NumericType.LONG);
+      case Opcodes.FCMPL:
+        return new CfCmp(Bias.LT, NumericType.FLOAT);
+      case Opcodes.FCMPG:
+        return new CfCmp(Bias.GT, NumericType.FLOAT);
+      case Opcodes.DCMPL:
+        return new CfCmp(Bias.LT, NumericType.DOUBLE);
+      case Opcodes.DCMPG:
+        return new CfCmp(Bias.GT, NumericType.DOUBLE);
+      default:
+        throw new Unreachable("Wrong ASM opcode for CfCmp " + opcode);
+    }
+  }
+
+  public int getAsmOpcode() {
+    switch (type) {
+      case LONG:
+        return Opcodes.LCMP;
+      case FLOAT:
+        return bias == Cmp.Bias.LT ? Opcodes.FCMPL : Opcodes.FCMPG;
+      case DOUBLE:
+        return bias == Cmp.Bias.LT ? Opcodes.DCMPL : Opcodes.DCMPG;
+      default:
+        throw new Unreachable("CfCmp has unknown type " + type);
+    }
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitInsn(getAsmOpcode());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
new file mode 100644
index 0000000..2aab288
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2018, 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfLogicalBinop extends CfInstruction {
+
+  public enum Opcode {
+    Shl,
+    Shr,
+    Ushr,
+    And,
+    Or,
+    Xor,
+  }
+
+  private final Opcode opcode;
+  private final NumericType type;
+
+  public CfLogicalBinop(Opcode opcode, NumericType type) {
+    assert opcode != null;
+    assert type != null;
+    assert type != NumericType.FLOAT && type != NumericType.DOUBLE;
+    this.opcode = opcode;
+    this.type = type;
+  }
+
+  public static CfLogicalBinop fromAsm(int opcode) {
+    switch (opcode) {
+      case Opcodes.ISHL:
+        return new CfLogicalBinop(Opcode.Shl, NumericType.INT);
+      case Opcodes.LSHL:
+        return new CfLogicalBinop(Opcode.Shl, NumericType.LONG);
+      case Opcodes.ISHR:
+        return new CfLogicalBinop(Opcode.Shr, NumericType.INT);
+      case Opcodes.LSHR:
+        return new CfLogicalBinop(Opcode.Shr, NumericType.LONG);
+      case Opcodes.IUSHR:
+        return new CfLogicalBinop(Opcode.Ushr, NumericType.INT);
+      case Opcodes.LUSHR:
+        return new CfLogicalBinop(Opcode.Ushr, NumericType.LONG);
+      case Opcodes.IAND:
+        return new CfLogicalBinop(Opcode.And, NumericType.INT);
+      case Opcodes.LAND:
+        return new CfLogicalBinop(Opcode.And, NumericType.LONG);
+      case Opcodes.IOR:
+        return new CfLogicalBinop(Opcode.Or, NumericType.INT);
+      case Opcodes.LOR:
+        return new CfLogicalBinop(Opcode.Or, NumericType.LONG);
+      case Opcodes.IXOR:
+        return new CfLogicalBinop(Opcode.Xor, NumericType.INT);
+      case Opcodes.LXOR:
+        return new CfLogicalBinop(Opcode.Xor, NumericType.LONG);
+      default:
+        throw new Unreachable("Wrong ASM opcode for CfLogicalBinop " + opcode);
+    }
+  }
+
+  public int getAsmOpcode() {
+    switch (opcode) {
+      case Shl:
+        return type.isWide() ? Opcodes.LSHL : Opcodes.ISHL;
+      case Shr:
+        return type.isWide() ? Opcodes.LSHR : Opcodes.ISHR;
+      case Ushr:
+        return type.isWide() ? Opcodes.LUSHR : Opcodes.IUSHR;
+      case And:
+        return type.isWide() ? Opcodes.LAND : Opcodes.IAND;
+      case Or:
+        return type.isWide() ? Opcodes.LOR : Opcodes.IOR;
+      case Xor:
+        return type.isWide() ? Opcodes.LXOR : Opcodes.IXOR;
+      default:
+        throw new Unreachable("CfLogicalBinop has unknown opcode " + opcode);
+    }
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitInsn(getAsmOpcode());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
new file mode 100644
index 0000000..db0019a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -0,0 +1,63 @@
+// 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfNeg extends CfInstruction {
+
+  private final NumericType type;
+
+  public CfNeg(NumericType type) {
+    this.type = type;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitInsn(getAsmOpcode());
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public int getAsmOpcode() {
+    switch (type) {
+      case BYTE:
+      case CHAR:
+      case SHORT:
+      case INT:
+        return Opcodes.INEG;
+      case LONG:
+        return Opcodes.LNEG;
+      case FLOAT:
+        return Opcodes.FNEG;
+      case DOUBLE:
+        return Opcodes.DNEG;
+      default:
+        throw new Unreachable("Invalid type for CfNeg " + type);
+    }
+  }
+
+  public static CfNeg fromAsm(int opcode) {
+    switch (opcode) {
+      case Opcodes.INEG:
+        return new CfNeg(NumericType.INT);
+      case Opcodes.LNEG:
+        return new CfNeg(NumericType.LONG);
+      case Opcodes.FNEG:
+        return new CfNeg(NumericType.FLOAT);
+      case Opcodes.DNEG:
+        return new CfNeg(NumericType.DOUBLE);
+      default:
+        throw new Unreachable("Invalid opcode for CfNeg " + opcode);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
new file mode 100644
index 0000000..b585cb6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2018, 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.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfNumberConversion extends CfInstruction {
+
+  private final NumericType from;
+  private final NumericType to;
+
+  public CfNumberConversion(NumericType from, NumericType to) {
+    assert from != to;
+    assert from != NumericType.BYTE && from != NumericType.SHORT && from != NumericType.CHAR;
+    assert (to != NumericType.BYTE && to != NumericType.SHORT && to != NumericType.CHAR)
+        || from == NumericType.INT;
+    this.from = from;
+    this.to = to;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor, NamingLens lens) {
+    visitor.visitInsn(this.getAsmOpcode());
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public int getAsmOpcode() {
+    switch (from) {
+      case INT:
+        switch (to) {
+          case BYTE:
+            return Opcodes.I2B;
+          case CHAR:
+            return Opcodes.I2C;
+          case SHORT:
+            return Opcodes.I2S;
+          case LONG:
+            return Opcodes.I2L;
+          case FLOAT:
+            return Opcodes.I2F;
+          case DOUBLE:
+            return Opcodes.I2D;
+          default:
+            throw new Unreachable("Invalid CfNumberConversion from " + from + " to " + to);
+        }
+      case LONG:
+        switch (to) {
+          case INT:
+            return Opcodes.L2I;
+          case FLOAT:
+            return Opcodes.L2F;
+          case DOUBLE:
+            return Opcodes.L2D;
+          default:
+            throw new Unreachable("Invalid CfNumberConversion from " + from + " to " + to);
+        }
+      case FLOAT:
+        switch (to) {
+          case INT:
+            return Opcodes.F2I;
+          case LONG:
+            return Opcodes.F2L;
+          case DOUBLE:
+            return Opcodes.F2D;
+          default:
+            throw new Unreachable("Invalid CfNumberConversion from " + from + " to " + to);
+        }
+      case DOUBLE:
+        switch (to) {
+          case INT:
+            return Opcodes.D2I;
+          case LONG:
+            return Opcodes.D2L;
+          case FLOAT:
+            return Opcodes.D2F;
+          default:
+            throw new Unreachable("Invalid CfNumberConversion from " + from + " to " + to);
+        }
+      default:
+        throw new Unreachable("Invalid CfNumberConversion from " + from + " to " + to);
+    }
+  }
+
+  public static CfNumberConversion fromAsm(int opcode) {
+    switch (opcode) {
+      case Opcodes.I2L:
+        return new CfNumberConversion(NumericType.INT, NumericType.LONG);
+      case Opcodes.I2F:
+        return new CfNumberConversion(NumericType.INT, NumericType.FLOAT);
+      case Opcodes.I2D:
+        return new CfNumberConversion(NumericType.INT, NumericType.DOUBLE);
+      case Opcodes.L2I:
+        return new CfNumberConversion(NumericType.LONG, NumericType.INT);
+      case Opcodes.L2F:
+        return new CfNumberConversion(NumericType.LONG, NumericType.FLOAT);
+      case Opcodes.L2D:
+        return new CfNumberConversion(NumericType.LONG, NumericType.DOUBLE);
+      case Opcodes.F2I:
+        return new CfNumberConversion(NumericType.FLOAT, NumericType.INT);
+      case Opcodes.F2L:
+        return new CfNumberConversion(NumericType.FLOAT, NumericType.LONG);
+      case Opcodes.F2D:
+        return new CfNumberConversion(NumericType.FLOAT, NumericType.DOUBLE);
+      case Opcodes.D2I:
+        return new CfNumberConversion(NumericType.DOUBLE, NumericType.INT);
+      case Opcodes.D2L:
+        return new CfNumberConversion(NumericType.DOUBLE, NumericType.LONG);
+      case Opcodes.D2F:
+        return new CfNumberConversion(NumericType.DOUBLE, NumericType.FLOAT);
+      case Opcodes.I2B:
+        return new CfNumberConversion(NumericType.INT, NumericType.BYTE);
+      case Opcodes.I2C:
+        return new CfNumberConversion(NumericType.INT, NumericType.CHAR);
+      case Opcodes.I2S:
+        return new CfNumberConversion(NumericType.INT, NumericType.SHORT);
+      default:
+        throw new Unreachable("Unexpected CfNumberConversion opcode " + opcode);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfUnop.java b/src/main/java/com/android/tools/r8/cf/code/CfUnop.java
deleted file mode 100644
index 6b4bf76..0000000
--- a/src/main/java/com/android/tools/r8/cf/code/CfUnop.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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.cf.code;
-
-import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.naming.NamingLens;
-import org.objectweb.asm.MethodVisitor;
-
-public class CfUnop extends CfInstruction {
-
-  private final int opcode;
-
-  public CfUnop(int opcode) {
-    this.opcode = opcode;
-  }
-
-  @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
-    visitor.visitInsn(this.opcode);
-  }
-
-  @Override
-  public void print(CfPrinter printer) {
-    printer.print(this);
-  }
-
-  public int getOpcode() {
-    return opcode;
-  }
-}
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 1de9606..b9131e5 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -24,9 +24,10 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
@@ -547,7 +548,7 @@
             StandardOpenOption.TRUNCATE_EXISTING,
             StandardOpenOption.WRITE);
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e));
+        handler.error(new ExceptionDiagnostic(e, new PathOrigin(output)));
       }
     }
   }
@@ -566,7 +567,7 @@
       try {
         writeZipWithClasses(handler);
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e));
+        handler.error(new ExceptionDiagnostic(e, getOrigin()));
       }
       super.finished(handler);
     }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 4524505..8042a1a 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -649,7 +649,7 @@
       ClassAccessFlags flags = ClassAccessFlags.fromDexAccessFlags(accessFlags[i]);
       // Check if constraints from
       // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
-      if (!flags.areValid(Constants.CORRESPONDING_CLASS_FILE_VERSION)) {
+      if (!flags.areValid(Constants.CORRESPONDING_CLASS_FILE_VERSION, false)) {
         throw new CompilationError("Class " + type.toSourceString()
             + " has illegal access flags. Found: " + flags, origin);
       }
diff --git a/src/main/java/com/android/tools/r8/dex/DexReader.java b/src/main/java/com/android/tools/r8/dex/DexReader.java
index 44bacc6..d8997f7 100644
--- a/src/main/java/com/android/tools/r8/dex/DexReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexReader.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DexVersion;
 import java.io.IOException;
+import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
@@ -38,6 +39,12 @@
 
   // Parse the magic header and determine the dex file version.
   private int parseMagic(ByteBuffer buffer) {
+    try {
+      buffer.get();
+      buffer.rewind();
+    } catch (BufferUnderflowException e) {
+      throw new CompilationError("Dex file is empty", origin);
+    }
     int index = 0;
     for (byte prefixByte : DEX_FILE_MAGIC_PREFIX) {
       if (buffer.get(index++) != prefixByte) {
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index 59aecfe..ba576e7 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -233,7 +233,7 @@
                   Files.newOutputStream(
                       path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
         } catch (IOException e) {
-          handler.error(new IOExceptionDiagnostic(e, origin));
+          handler.error(new ExceptionDiagnostic(e, origin));
         }
       }
       return stream;
@@ -246,7 +246,7 @@
             getStream(handler), getDexFileName(fileIndex), data, ZipEntry.DEFLATED, true);
         hasWrittenSomething = true;
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, origin));
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
 
@@ -264,7 +264,7 @@
           stream = null;
         }
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, origin));
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index b2fe1bc..f452cf1 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -4,23 +4,25 @@
 
 package com.android.tools.r8.dexsplitter;
 
-import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.utils.AbortException;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FeatureClassMapping;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 public class DexSplitter {
 
@@ -29,6 +31,37 @@
 
   private static final boolean PRINT_ARGS = false;
 
+  public static class Reporter implements DiagnosticsHandler {
+
+    private final DiagnosticsHandler handler = new DefaultDiagnosticsHandler();
+    private boolean reportedErrors = false;
+
+    public RuntimeException fatal(Diagnostic error) {
+      error(error);
+      throw new AbortException();
+    }
+
+    public boolean hasReportedErrors() {
+      return reportedErrors;
+    }
+
+    @Override
+    public void error(Diagnostic error) {
+      handler.error(error);
+      reportedErrors = true;
+    }
+
+    @Override
+    public void warning(Diagnostic warning) {
+      handler.warning(warning);
+    }
+
+    @Override
+    public void info(Diagnostic info) {
+      handler.info(info);
+    }
+  }
+
   public static class FeatureJar {
     private String jar;
     private String outputName;
@@ -62,6 +95,7 @@
   }
 
   public static class Options {
+    private final Reporter reporter = new Reporter();
     private List<String> inputArchives = new ArrayList<>();
     private List<FeatureJar> featureJars = new ArrayList<>();
     private String baseOutputName = DEFAULT_BASE_NAME;
@@ -69,6 +103,10 @@
     private String featureSplitMapping;
     private String proguardMap;
 
+    public Reporter getReporter() {
+      return reporter;
+    }
+
     public String getOutput() {
       return output;
     }
@@ -124,6 +162,11 @@
     public ImmutableList<FeatureJar> getFeatureJars() {
       return ImmutableList.copyOf(featureJars);
     }
+
+    // Shorthand error messages.
+    public void error(String msg) {
+      reporter.error(new StringDiagnostic(msg));
+    }
   }
 
   /**
@@ -144,7 +187,7 @@
     return new FeatureJar(argument);
   }
 
-  private static Options parseArguments(String[] args) throws IOException {
+  private static Options parseArguments(String[] args) {
     Options options = new Options();
     ParseContext context = new ParseContext(args);
     while (context.head() != null) {
@@ -185,32 +228,35 @@
   }
 
   private static FeatureClassMapping createFeatureClassMapping(Options options)
-      throws IOException, FeatureMappingException, ResourceException {
+      throws FeatureMappingException {
     if (options.getFeatureSplitMapping() != null) {
-      return FeatureClassMapping.fromSpecification(Paths.get(options.getFeatureSplitMapping()));
+      return FeatureClassMapping.fromSpecification(
+          Paths.get(options.getFeatureSplitMapping()), options.getReporter());
     }
     assert !options.getFeatureJars().isEmpty();
-    return FeatureClassMapping.fromJarFiles(options.getFeatureJars(), options.getBaseOutputName());
+    return FeatureClassMapping.fromJarFiles(
+        options.getFeatureJars(), options.getBaseOutputName(), options.getReporter());
   }
 
   private static void run(String[] args)
-      throws CompilationFailedException, IOException, CompilationException, ExecutionException,
-          ResourceException, FeatureMappingException {
+      throws CompilationFailedException, FeatureMappingException {
     Options options = parseArguments(args);
     run(options);
   }
 
   public static void run(Options options)
-      throws IOException, FeatureMappingException, ResourceException, CompilationException,
-      ExecutionException, CompilationFailedException {
+      throws FeatureMappingException, CompilationFailedException {
     if (options.getInputArchives().isEmpty()) {
-      throw new RuntimeException("Need at least one --input");
+      options.error("Need at least one --input");
     }
     if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
-      throw new RuntimeException("You must supply a feature split mapping or feature jars");
+      options.error("You must supply a feature split mapping or feature jars");
     }
     if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
-      throw new RuntimeException("You can't supply both a feature split mapping and feature jars");
+      options.error("You can't supply both a feature split mapping and feature jars");
+    }
+    if (options.getReporter().hasReportedErrors()) {
+      throw new AbortException();
     }
 
     D8Command.Builder builder = D8Command.builder();
@@ -228,20 +274,19 @@
   }
 
   public static void main(String[] args) {
-    try {
-      if (PRINT_ARGS) {
-        printArgs(args);
-      }
-      run(args);
-    } catch (CompilationFailedException
-        | IOException
-        | CompilationException
-        | ExecutionException
-        | ResourceException
-        | FeatureMappingException e) {
-      System.err.println("Splitting failed: " + e.getMessage());
-      System.exit(1);
+    if (PRINT_ARGS) {
+      printArgs(args);
     }
+    ExceptionUtils.withMainProgramHandler(
+        () -> {
+          try {
+            run(args);
+          } catch (FeatureMappingException e) {
+            // TODO(ricow): Report feature mapping errors via the reporter.
+            System.err.println("Splitting failed: " + e.getMessage());
+            System.exit(1);
+          }
+        });
   }
 
   private static void printArgs(String[] args) {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 50ff578..63a2958 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -4,13 +4,21 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintStream;
 
 public class AssemblyWriter extends DexByteCodeWriter {
 
-  public AssemblyWriter(DexApplication application, InternalOptions options) {
+  private final boolean writeAllClassInfo;
+  private final boolean writeFields;
+  private final boolean writeAnnotations;
+
+  public AssemblyWriter(DexApplication application, InternalOptions options, boolean allInfo) {
     super(application, options);
+    this.writeAllClassInfo = allInfo;
+    this.writeFields = allInfo;
+    this.writeAnnotations = allInfo;
   }
 
   @Override
@@ -28,12 +36,43 @@
     }
     ps.println("# Bytecode for");
     ps.println("# Class: '" + clazzName + "'");
+    if (writeAllClassInfo) {
+      writeAnnotations(clazz.annotations, ps);
+      ps.println("# Flags: '" + clazz.accessFlags + "'");
+      if (clazz.superType != application.dexItemFactory.objectType) {
+        ps.println("# Extends: '" + clazz.superType.toSourceString() + "'");
+      }
+      for (DexType value : clazz.interfaces.values) {
+        ps.println("# Implements: '" + value.toSourceString() + "'");
+      }
+    }
     ps.println();
   }
 
   @Override
+  void writeFieldsHeader(DexProgramClass clazz, PrintStream ps) {
+    if (writeFields) {
+      ps.println("#");
+      ps.println("# Fields:");
+      ps.println("#");
+    }
+  }
+
+  @Override
   void writeField(DexEncodedField field, PrintStream ps) {
-    // Not implemented, yet.
+    if (writeFields) {
+      ClassNameMapper naming = application.getProguardMap();
+      FieldSignature fieldSignature = naming != null
+          ? naming.originalSignatureOf(field.field)
+          : FieldSignature.fromDexField(field.field);
+      writeAnnotations(field.annotations, ps);
+      ps.println(fieldSignature);
+    }
+  }
+
+  @Override
+  void writeFieldsFooter(DexProgramClass clazz, PrintStream ps) {
+    ps.println();
   }
 
   @Override
@@ -44,6 +83,7 @@
         : method.method.name.toString();
     ps.println("#");
     ps.println("# Method: '" + methodName + "':");
+    writeAnnotations(method.annotations, ps);
     ps.println("#");
     ps.println();
     Code code = method.getCode();
@@ -52,6 +92,18 @@
     }
   }
 
+  private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
+    if (writeAnnotations) {
+      if (!annotations.isEmpty()) {
+        ps.println("# Annotations:");
+        for (DexAnnotation annotation : annotations.annotations) {
+          ps.print("#   ");
+          ps.println(annotation);
+        }
+      }
+    }
+  }
+
   @Override
   void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
 
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 0111aa1..44d6726 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -92,15 +92,17 @@
    * Checks whether the constraints from
    * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
    */
-  public boolean areValid(int majorVersion) {
+  public boolean areValid(int majorVersion, boolean isPackageInfo) {
     if (isInterface()) {
       // We ignore the super flags prior to JDK 9, as so did the VM.
       if ((majorVersion >= 53) && isSuper()) {
         return false;
       }
-      // We require interfaces to be abstract from JDK 7 onwards. Old versions of javac seem to
-      // have produced package-info classes that are interfaces but not abstract.
-      if ((majorVersion >= 51) && (!isAbstract())) {
+      // When not coming from DEX input we require interfaces to be abstract - except for
+      // package-info classes - as both old versions of javac and other tools can produce
+      // package-info classes that are interfaces but not abstract.
+      if (((majorVersion > Constants.CORRESPONDING_CLASS_FILE_VERSION) && !isAbstract())
+          && !isPackageInfo) {
         return false;
       }
       return !isFinal() && !isEnum();
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 52217a3..08d65f0 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -60,6 +60,10 @@
     throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
   }
 
+  public LazyCfCode asLazyCfCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asLazyCfCode()");
+  }
+
   public DexCode asDexCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asDexCode()");
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 025a502..21900a01 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -82,8 +82,12 @@
 
   private void writeClass(DexProgramClass clazz, PrintStream ps) {
     writeClassHeader(clazz, ps);
+    writeFieldsHeader(clazz, ps);
     clazz.forEachField(field -> writeField(field, ps));
+    writeFieldsFooter(clazz, ps);
+    writeMethodsHeader(clazz, ps);
     clazz.forEachMethod(method -> writeMethod(method, ps));
+    writeMethodsFooter(clazz, ps);
     writeClassFooter(clazz, ps);
   }
 
@@ -91,9 +95,25 @@
 
   abstract void writeClassHeader(DexProgramClass clazz, PrintStream ps);
 
+  void writeFieldsHeader(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
   abstract void writeField(DexEncodedField field, PrintStream ps);
 
+  void writeFieldsFooter(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
+  void writeMethodsHeader(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
   abstract void writeMethod(DexEncodedMethod method, PrintStream ps);
 
+  void writeMethodsFooter(DexProgramClass clazz, PrintStream ps) {
+    // Do nothing.
+  }
+
   abstract void writeClassFooter(DexProgramClass clazz, PrintStream ps);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index bef2f57..2f1d3fd 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -3,54 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static org.objectweb.asm.ClassReader.EXPAND_FRAMES;
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
 import static org.objectweb.asm.Opcodes.ASM6;
 
 import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.cf.code.CfArrayLength;
-import com.android.tools.r8.cf.code.CfArrayLoad;
-import com.android.tools.r8.cf.code.CfArrayStore;
-import com.android.tools.r8.cf.code.CfBinop;
-import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfConstClass;
-import com.android.tools.r8.cf.code.CfConstMethodHandle;
-import com.android.tools.r8.cf.code.CfConstMethodType;
-import com.android.tools.r8.cf.code.CfConstNull;
-import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
-import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.cf.code.CfGoto;
-import com.android.tools.r8.cf.code.CfIf;
-import com.android.tools.r8.cf.code.CfIfCmp;
-import com.android.tools.r8.cf.code.CfIinc;
-import com.android.tools.r8.cf.code.CfInstanceOf;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfInvokeDynamic;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfMonitor;
-import com.android.tools.r8.cf.code.CfMultiANewArray;
-import com.android.tools.r8.cf.code.CfNew;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfNop;
-import com.android.tools.r8.cf.code.CfPosition;
-import com.android.tools.r8.cf.code.CfReturn;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStore;
-import com.android.tools.r8.cf.code.CfSwitch;
-import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.cf.code.CfTryCatch;
-import com.android.tools.r8.cf.code.CfUnop;
 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.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -65,23 +25,15 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.JarCode.ReparseContext;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.Monitor;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.IdentityHashMap;
+import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
@@ -90,10 +42,8 @@
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Handle;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.TypePath;
 
@@ -102,6 +52,8 @@
  */
 public class JarClassFileReader {
 
+  private static final byte[] CLASSFILE_HEADER = ByteBuffer.allocate(4).putInt(0xCAFEBABE).array();
+
   // Hidden ASM "synthetic attribute" bit we need to clear.
   private static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
   // Descriptor used by ASM for missing annotations.
@@ -117,13 +69,28 @@
   }
 
   public void read(Origin origin, ClassKind classKind, InputStream input) throws IOException {
-    ClassReader reader = new ClassReader(input);
-    int flags = SKIP_FRAMES;
-    if (application.options.enableCfFrontend) {
-      flags = EXPAND_FRAMES;
+    if (!input.markSupported()) {
+      input = new BufferedInputStream(input);
     }
+    byte[] header = new byte[CLASSFILE_HEADER.length];
+    input.mark(header.length);
+    int size = 0;
+    while (size < header.length) {
+      int read = input.read(header, size, header.length - size);
+      if (read < 0) {
+        throw new CompilationError("Invalid empty classfile", origin);
+      }
+      size += read;
+    }
+    if (!Arrays.equals(CLASSFILE_HEADER, header)) {
+      throw new CompilationError("Invalid classfile header", origin);
+    }
+    input.reset();
+
+    ClassReader reader = new ClassReader(input);
     reader.accept(
-        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer), flags);
+        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
+        SKIP_FRAMES);
   }
 
   private static int cleanAccessFlags(int access) {
@@ -218,6 +185,24 @@
               : new EnclosingMethodAttribute(application.getMethod(ownerType, name, desc));
     }
 
+    private String illegalClassFilePrefix(ClassAccessFlags accessFlags, String name) {
+      return "Illegal class file: "
+          + (accessFlags.isInterface() ? "Interface" : "Class")
+          + " "
+          + name;
+    }
+
+    private String illegalClassFilePostfix(int version) {
+      return "Class file version " + version;
+    }
+
+    private String illegalClassFileMessage(
+        ClassAccessFlags accessFlags, String name, int version, String message) {
+      return illegalClassFilePrefix(accessFlags, name)
+          + " " + message
+          + ". " + illegalClassFilePostfix(version) + ".";
+    }
+
     @Override
     public void visit(int version, int access, String name, String signature, String superName,
         String[] interfaces) {
@@ -226,18 +211,22 @@
       type = application.getTypeFromName(name);
       // Check if constraints from
       // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
-      if (!accessFlags.areValid(getMajorVersion())) {
-        throw new CompilationError("Illegal class file: Class " + name
-            + " has invalid access flags. Found: " + accessFlags.toString(), origin);
+      if (!accessFlags.areValid(getMajorVersion(), name.endsWith("/package-info"))) {
+        throw new CompilationError(
+            illegalClassFileMessage(accessFlags, name, version,
+                "has invalid access flags. Found: " + accessFlags.toString()), origin);
       }
       if (superName == null && !name.equals(Constants.JAVA_LANG_OBJECT_NAME)) {
-        throw new CompilationError("Illegal class file: Class " + name
-            + " is missing a super type.", origin);
+        throw new CompilationError(
+            illegalClassFileMessage(accessFlags, name, version,
+                "is missing a super type"), origin);
       }
       if (accessFlags.isInterface()
           && !Objects.equals(superName, Constants.JAVA_LANG_OBJECT_NAME)) {
-        throw new CompilationError("Illegal class file: Interface " + name
-            + " must extend class java.lang.Object. Found: " + Objects.toString(superName), origin);
+        throw new CompilationError(
+            illegalClassFileMessage(accessFlags, name, version,
+                "must extend class java.lang.Object. Found: " + Objects.toString(superName)),
+            origin);
       }
       assert superName != null || name.equals(Constants.JAVA_LANG_OBJECT_NAME);
       superType = superName == null ? null : application.getTypeFromName(superName);
@@ -268,9 +257,6 @@
     @Override
     public MethodVisitor visitMethod(
         int access, String name, String desc, String signature, String[] exceptions) {
-      if (application.options.enableCfFrontend) {
-        return new CfCreateMethodVisitor(access, name, desc, signature, exceptions, this);
-      }
       return new CreateMethodVisitor(access, name, desc, signature, exceptions, this);
     }
 
@@ -580,7 +566,11 @@
     public void visitCode() {
       assert !flags.isAbstract() && !flags.isNative();
       if (parent.classKind == ClassKind.PROGRAM) {
-        code = new JarCode(method, parent.origin, parent.context, parent.application);
+        if (parent.application.options.enableCfFrontend) {
+          code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
+        } else {
+          code = new JarCode(method, parent.origin, parent.context, parent.application);
+        }
       }
     }
 
@@ -634,646 +624,6 @@
     }
   }
 
-  private static class CfCreateMethodVisitor extends CreateMethodVisitor {
-
-    private final JarApplicationReader application;
-    private final DexItemFactory factory;
-    private int maxStack;
-    private int maxLocals;
-    private List<CfInstruction> instructions;
-    private List<CfTryCatch> tryCatchRanges;
-    private List<LocalVariableInfo> localVariables;
-    private Map<Label, CfLabel> labelMap;
-
-    public CfCreateMethodVisitor(
-        int access,
-        String name,
-        String desc,
-        String signature,
-        String[] exceptions,
-        CreateDexClassVisitor parent) {
-      super(access, name, desc, signature, exceptions, parent);
-      this.application = parent.application;
-      this.factory = application.getFactory();
-    }
-
-    @Override
-    public void visitCode() {
-      assert !flags.isAbstract() && !flags.isNative();
-      maxStack = 0;
-      maxLocals = 0;
-      instructions = new ArrayList<>();
-      tryCatchRanges = new ArrayList<>();
-      localVariables = new ArrayList<>();
-      labelMap = new IdentityHashMap<>();
-    }
-
-    @Override
-    public void visitEnd() {
-      if (!flags.isAbstract() && !flags.isNative() && parent.classKind == ClassKind.PROGRAM) {
-        code =
-            new CfCode(method, maxStack, maxLocals, instructions, tryCatchRanges, localVariables);
-      }
-      super.visitEnd();
-    }
-
-    @Override
-    public void visitFrame(
-        int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
-      assert frameType == Opcodes.F_NEW;
-      Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
-      List<FrameType> parsedStack = parseStack(nStack, stackTypes);
-      instructions.add(new CfFrame(parsedLocals, parsedStack));
-    }
-
-    private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) {
-      Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>();
-      int i = 0;
-      for (int j = 0; j < typeCount; j++) {
-        Object localType = asmTypes[j];
-        FrameType value = getFrameType(localType);
-        types.put(i++, value);
-        if (value.isWide()) {
-          i++;
-        }
-      }
-      return types;
-    }
-
-    private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
-      List<FrameType> dexStack = new ArrayList<>(nStack);
-      for (int i = 0; i < nStack; i++) {
-        dexStack.add(getFrameType(stackTypes[i]));
-      }
-      return dexStack;
-    }
-
-    private FrameType getFrameType(Object localType) {
-      if (localType instanceof Label) {
-        return FrameType.uninitializedNew(getLabel((Label) localType));
-      } else if (localType == Opcodes.UNINITIALIZED_THIS) {
-        return FrameType.uninitializedThis();
-      } else if (localType == null || localType == Opcodes.TOP) {
-        return FrameType.top();
-      } else {
-        return FrameType.initialized(parseAsmType(localType));
-      }
-    }
-
-    private CfLabel getLabel(Label label) {
-      return labelMap.computeIfAbsent(label, l -> new CfLabel());
-    }
-
-    private DexType parseAsmType(Object local) {
-      assert local != null && local != Opcodes.TOP;
-      if (local == Opcodes.INTEGER) {
-        return factory.intType;
-      } else if (local == Opcodes.FLOAT) {
-        return factory.floatType;
-      } else if (local == Opcodes.LONG) {
-        return factory.longType;
-      } else if (local == Opcodes.DOUBLE) {
-        return factory.doubleType;
-      } else if (local == Opcodes.NULL) {
-        return DexItemFactory.nullValueType;
-      } else if (local instanceof String) {
-        return createTypeFromInternalType((String) local);
-      } else {
-        throw new Unreachable("Unexpected ASM type: " + local);
-      }
-    }
-
-    private DexType createTypeFromInternalType(String local) {
-      assert local.indexOf('.') == -1;
-      return factory.createType("L" + local + ";");
-    }
-
-    @Override
-    public void visitInsn(int opcode) {
-      switch (opcode) {
-        case Opcodes.NOP:
-          instructions.add(new CfNop());
-          break;
-        case Opcodes.ACONST_NULL:
-          instructions.add(new CfConstNull());
-          break;
-        case Opcodes.ICONST_M1:
-        case Opcodes.ICONST_0:
-        case Opcodes.ICONST_1:
-        case Opcodes.ICONST_2:
-        case Opcodes.ICONST_3:
-        case Opcodes.ICONST_4:
-        case Opcodes.ICONST_5:
-          instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT));
-          break;
-        case Opcodes.LCONST_0:
-        case Opcodes.LCONST_1:
-          instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG));
-          break;
-        case Opcodes.FCONST_0:
-        case Opcodes.FCONST_1:
-        case Opcodes.FCONST_2:
-          instructions.add(
-              new CfConstNumber(
-                  Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT));
-          break;
-        case Opcodes.DCONST_0:
-        case Opcodes.DCONST_1:
-          instructions.add(
-              new CfConstNumber(
-                  Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE));
-          break;
-        case Opcodes.IALOAD:
-        case Opcodes.LALOAD:
-        case Opcodes.FALOAD:
-        case Opcodes.DALOAD:
-        case Opcodes.AALOAD:
-        case Opcodes.BALOAD:
-        case Opcodes.CALOAD:
-        case Opcodes.SALOAD:
-          instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode)));
-          break;
-        case Opcodes.IASTORE:
-        case Opcodes.LASTORE:
-        case Opcodes.FASTORE:
-        case Opcodes.DASTORE:
-        case Opcodes.AASTORE:
-        case Opcodes.BASTORE:
-        case Opcodes.CASTORE:
-        case Opcodes.SASTORE:
-          instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode)));
-          break;
-        case Opcodes.POP:
-        case Opcodes.POP2:
-        case Opcodes.DUP:
-        case Opcodes.DUP_X1:
-        case Opcodes.DUP_X2:
-        case Opcodes.DUP2:
-        case Opcodes.DUP2_X1:
-        case Opcodes.DUP2_X2:
-        case Opcodes.SWAP:
-          instructions.add(CfStackInstruction.fromAsm(opcode));
-          break;
-        case Opcodes.IADD:
-        case Opcodes.LADD:
-        case Opcodes.FADD:
-        case Opcodes.DADD:
-        case Opcodes.ISUB:
-        case Opcodes.LSUB:
-        case Opcodes.FSUB:
-        case Opcodes.DSUB:
-        case Opcodes.IMUL:
-        case Opcodes.LMUL:
-        case Opcodes.FMUL:
-        case Opcodes.DMUL:
-        case Opcodes.IDIV:
-        case Opcodes.LDIV:
-        case Opcodes.FDIV:
-        case Opcodes.DDIV:
-        case Opcodes.IREM:
-        case Opcodes.LREM:
-        case Opcodes.FREM:
-        case Opcodes.DREM:
-          instructions.add(new CfBinop(opcode));
-          break;
-        case Opcodes.INEG:
-        case Opcodes.LNEG:
-        case Opcodes.FNEG:
-        case Opcodes.DNEG:
-          instructions.add(new CfUnop(opcode));
-          break;
-        case Opcodes.ISHL:
-        case Opcodes.LSHL:
-        case Opcodes.ISHR:
-        case Opcodes.LSHR:
-        case Opcodes.IUSHR:
-        case Opcodes.LUSHR:
-        case Opcodes.IAND:
-        case Opcodes.LAND:
-        case Opcodes.IOR:
-        case Opcodes.LOR:
-        case Opcodes.IXOR:
-        case Opcodes.LXOR:
-          instructions.add(new CfBinop(opcode));
-          break;
-        case Opcodes.I2L:
-        case Opcodes.I2F:
-        case Opcodes.I2D:
-        case Opcodes.L2I:
-        case Opcodes.L2F:
-        case Opcodes.L2D:
-        case Opcodes.F2I:
-        case Opcodes.F2L:
-        case Opcodes.F2D:
-        case Opcodes.D2I:
-        case Opcodes.D2L:
-        case Opcodes.D2F:
-        case Opcodes.I2B:
-        case Opcodes.I2C:
-        case Opcodes.I2S:
-          instructions.add(new CfUnop(opcode));
-          break;
-        case Opcodes.LCMP:
-        case Opcodes.FCMPL:
-        case Opcodes.FCMPG:
-        case Opcodes.DCMPL:
-        case Opcodes.DCMPG:
-          instructions.add(new CfBinop(opcode));
-          break;
-        case Opcodes.IRETURN:
-          instructions.add(new CfReturn(ValueType.INT));
-          break;
-        case Opcodes.LRETURN:
-          instructions.add(new CfReturn(ValueType.LONG));
-          break;
-        case Opcodes.FRETURN:
-          instructions.add(new CfReturn(ValueType.FLOAT));
-          break;
-        case Opcodes.DRETURN:
-          instructions.add(new CfReturn(ValueType.DOUBLE));
-          break;
-        case Opcodes.ARETURN:
-          instructions.add(new CfReturn(ValueType.OBJECT));
-          break;
-        case Opcodes.RETURN:
-          instructions.add(new CfReturnVoid());
-          break;
-        case Opcodes.ARRAYLENGTH:
-          instructions.add(new CfArrayLength());
-          break;
-        case Opcodes.ATHROW:
-          instructions.add(new CfThrow());
-          break;
-        case Opcodes.MONITORENTER:
-          instructions.add(new CfMonitor(Monitor.Type.ENTER));
-          break;
-        case Opcodes.MONITOREXIT:
-          instructions.add(new CfMonitor(Monitor.Type.EXIT));
-          break;
-        default:
-          throw new Unreachable("Unknown instruction");
-      }
-    }
-
-    private DexType opType(int opcode, DexItemFactory factory) {
-      switch (opcode) {
-        case Opcodes.IADD:
-        case Opcodes.ISUB:
-        case Opcodes.IMUL:
-        case Opcodes.IDIV:
-        case Opcodes.IREM:
-        case Opcodes.INEG:
-        case Opcodes.ISHL:
-        case Opcodes.ISHR:
-        case Opcodes.IUSHR:
-          return factory.intType;
-        case Opcodes.LADD:
-        case Opcodes.LSUB:
-        case Opcodes.LMUL:
-        case Opcodes.LDIV:
-        case Opcodes.LREM:
-        case Opcodes.LNEG:
-        case Opcodes.LSHL:
-        case Opcodes.LSHR:
-        case Opcodes.LUSHR:
-          return factory.longType;
-        case Opcodes.FADD:
-        case Opcodes.FSUB:
-        case Opcodes.FMUL:
-        case Opcodes.FDIV:
-        case Opcodes.FREM:
-        case Opcodes.FNEG:
-          return factory.floatType;
-        case Opcodes.DADD:
-        case Opcodes.DSUB:
-        case Opcodes.DMUL:
-        case Opcodes.DDIV:
-        case Opcodes.DREM:
-        case Opcodes.DNEG:
-          return factory.doubleType;
-        default:
-          throw new Unreachable("Unexpected opcode " + opcode);
-      }
-    }
-
-    private static MemberType getMemberTypeForOpcode(int opcode) {
-      switch (opcode) {
-        case Opcodes.IALOAD:
-        case Opcodes.IASTORE:
-          return MemberType.INT;
-        case Opcodes.FALOAD:
-        case Opcodes.FASTORE:
-          return MemberType.FLOAT;
-        case Opcodes.LALOAD:
-        case Opcodes.LASTORE:
-          return MemberType.LONG;
-        case Opcodes.DALOAD:
-        case Opcodes.DASTORE:
-          return MemberType.DOUBLE;
-        case Opcodes.AALOAD:
-        case Opcodes.AASTORE:
-          return MemberType.OBJECT;
-        case Opcodes.BALOAD:
-        case Opcodes.BASTORE:
-          return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
-        case Opcodes.CALOAD:
-        case Opcodes.CASTORE:
-          return MemberType.CHAR;
-        case Opcodes.SALOAD:
-        case Opcodes.SASTORE:
-          return MemberType.SHORT;
-        default:
-          throw new Unreachable("Unexpected array opcode " + opcode);
-      }
-    }
-
-    @Override
-    public void visitIntInsn(int opcode, int operand) {
-      switch (opcode) {
-        case Opcodes.SIPUSH:
-        case Opcodes.BIPUSH:
-          instructions.add(new CfConstNumber(operand, ValueType.INT));
-          break;
-        case Opcodes.NEWARRAY:
-          instructions.add(
-              new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
-          break;
-        default:
-          throw new Unreachable("Unexpected int opcode " + opcode);
-      }
-    }
-
-    private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) {
-      switch (arrayTypeCode) {
-        case Opcodes.T_BOOLEAN:
-          return factory.booleanType;
-        case Opcodes.T_CHAR:
-          return factory.charType;
-        case Opcodes.T_FLOAT:
-          return factory.floatType;
-        case Opcodes.T_DOUBLE:
-          return factory.doubleType;
-        case Opcodes.T_BYTE:
-          return factory.byteType;
-        case Opcodes.T_SHORT:
-          return factory.shortType;
-        case Opcodes.T_INT:
-          return factory.intType;
-        case Opcodes.T_LONG:
-          return factory.longType;
-        default:
-          throw new Unreachable("Unexpected array-type code " + arrayTypeCode);
-      }
-    }
-
-    @Override
-    public void visitVarInsn(int opcode, int var) {
-      ValueType type;
-      switch (opcode) {
-        case Opcodes.ILOAD:
-        case Opcodes.ISTORE:
-          type = ValueType.INT;
-          break;
-        case Opcodes.FLOAD:
-        case Opcodes.FSTORE:
-          type = ValueType.FLOAT;
-          break;
-        case Opcodes.LLOAD:
-        case Opcodes.LSTORE:
-          type = ValueType.LONG;
-          break;
-        case Opcodes.DLOAD:
-        case Opcodes.DSTORE:
-          type = ValueType.DOUBLE;
-          break;
-        case Opcodes.ALOAD:
-        case Opcodes.ASTORE:
-          type = ValueType.OBJECT;
-          break;
-        case Opcodes.RET:
-          throw new Unreachable("RET should be handled by the ASM jsr inliner");
-        default:
-          throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
-      }
-      if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
-        instructions.add(new CfLoad(type, var));
-      } else {
-        instructions.add(new CfStore(type, var));
-      }
-    }
-
-    @Override
-    public void visitTypeInsn(int opcode, String typeName) {
-      DexType type = createTypeFromInternalType(typeName);
-      switch (opcode) {
-        case Opcodes.NEW:
-          instructions.add(new CfNew(type));
-          break;
-        case Opcodes.ANEWARRAY:
-          instructions.add(new CfNewArray(factory.createArrayType(1, type)));
-          break;
-        case Opcodes.CHECKCAST:
-          instructions.add(new CfCheckCast(type));
-          break;
-        case Opcodes.INSTANCEOF:
-          instructions.add(new CfInstanceOf(type));
-          break;
-        default:
-          throw new Unreachable("Unexpected TypeInsn opcode: " + opcode);
-      }
-    }
-
-    @Override
-    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
-      DexField field =
-          factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
-      // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
-      // renaming in the backend, but it is not available here in the frontend.
-      instructions.add(new CfFieldInstruction(opcode, field, field));
-    }
-
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
-      visitMethodInsn(opcode, owner, name, desc, false);
-    }
-
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
-      DexMethod method = application.getMethod(owner, name, desc);
-      instructions.add(new CfInvoke(opcode, method, itf));
-    }
-
-    @Override
-    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
-      DexCallSite callSite =
-          DexCallSite.fromAsmInvokeDynamic(application, parent.type, name, desc, bsm, bsmArgs);
-      instructions.add(new CfInvokeDynamic(callSite));
-    }
-
-    @Override
-    public void visitJumpInsn(int opcode, Label label) {
-      CfLabel target = getLabel(label);
-      if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
-        if (opcode <= Opcodes.IFLE) {
-          // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE.
-          instructions.add(new CfIf(ifType(opcode), ValueType.INT, target));
-        } else {
-          // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or
-          // IF_ACMPNE.
-          ValueType valueType;
-          if (opcode <= Opcodes.IF_ICMPLE) {
-            valueType = ValueType.INT;
-          } else {
-            valueType = ValueType.OBJECT;
-          }
-          instructions.add(new CfIfCmp(ifType(opcode), valueType, target));
-        }
-      } else {
-        // GOTO, JSR, IFNULL or IFNONNULL.
-        switch (opcode) {
-          case Opcodes.GOTO:
-            instructions.add(new CfGoto(target));
-            break;
-          case Opcodes.IFNULL:
-          case Opcodes.IFNONNULL:
-            If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE;
-            instructions.add(new CfIf(type, ValueType.OBJECT, target));
-            break;
-          case Opcodes.JSR:
-            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
-          default:
-            throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
-        }
-      }
-    }
-
-    private static If.Type ifType(int opcode) {
-      switch (opcode) {
-        case Opcodes.IFEQ:
-        case Opcodes.IF_ICMPEQ:
-        case Opcodes.IF_ACMPEQ:
-          return If.Type.EQ;
-        case Opcodes.IFNE:
-        case Opcodes.IF_ICMPNE:
-        case Opcodes.IF_ACMPNE:
-          return If.Type.NE;
-        case Opcodes.IFLT:
-        case Opcodes.IF_ICMPLT:
-          return If.Type.LT;
-        case Opcodes.IFGE:
-        case Opcodes.IF_ICMPGE:
-          return If.Type.GE;
-        case Opcodes.IFGT:
-        case Opcodes.IF_ICMPGT:
-          return If.Type.GT;
-        case Opcodes.IFLE:
-        case Opcodes.IF_ICMPLE:
-          return If.Type.LE;
-        default:
-          throw new Unreachable("Unexpected If instruction opcode: " + opcode);
-      }
-    }
-
-    @Override
-    public void visitLabel(Label label) {
-      instructions.add(getLabel(label));
-    }
-
-    @Override
-    public void visitLdcInsn(Object cst) {
-      if (cst instanceof Type) {
-        Type type = (Type) cst;
-        if (type.getSort() == Type.METHOD) {
-          DexProto proto = application.getProto(type.getDescriptor());
-          instructions.add(new CfConstMethodType(proto));
-        } else {
-          instructions.add(new CfConstClass(factory.createType(type.getDescriptor())));
-        }
-      } else if (cst instanceof String) {
-        instructions.add(new CfConstString(factory.createString((String) cst)));
-      } else if (cst instanceof Long) {
-        instructions.add(new CfConstNumber((Long) cst, ValueType.LONG));
-      } else if (cst instanceof Double) {
-        long l = Double.doubleToRawLongBits((Double) cst);
-        instructions.add(new CfConstNumber(l, ValueType.DOUBLE));
-      } else if (cst instanceof Integer) {
-        instructions.add(new CfConstNumber((Integer) cst, ValueType.INT));
-      } else if (cst instanceof Float) {
-        long i = Float.floatToRawIntBits((Float) cst);
-        instructions.add(new CfConstNumber(i, ValueType.FLOAT));
-      } else if (cst instanceof Handle) {
-        instructions.add(
-            new CfConstMethodHandle(
-                DexMethodHandle.fromAsmHandle((Handle) cst, application, parent.type)));
-      } else {
-        throw new CompilationError("Unsupported constant: " + cst.toString());
-      }
-    }
-
-    @Override
-    public void visitIincInsn(int var, int increment) {
-      instructions.add(new CfIinc(var, increment));
-    }
-
-    @Override
-    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
-      assert max == min + labels.length - 1;
-      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
-      for (Label label : labels) {
-        targets.add(getLabel(label));
-      }
-      instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets));
-    }
-
-    @Override
-    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
-      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
-      for (Label label : labels) {
-        targets.add(getLabel(label));
-      }
-      instructions.add(new CfSwitch(CfSwitch.Kind.LOOKUP, getLabel(dflt), keys, targets));
-    }
-
-    @Override
-    public void visitMultiANewArrayInsn(String desc, int dims) {
-      instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
-    }
-
-    @Override
-    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
-      List<DexType> guards =
-          Collections.singletonList(
-              type == null ? DexItemFactory.catchAllType : createTypeFromInternalType(type));
-      List<CfLabel> targets = Collections.singletonList(getLabel(handler));
-      tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets));
-    }
-
-    @Override
-    public void visitLocalVariable(
-        String name, String desc, String signature, Label start, Label end, int index) {
-      DebugLocalInfo debugLocalInfo =
-          new DebugLocalInfo(
-              factory.createString(name),
-              factory.createType(desc),
-              signature == null ? null : factory.createString(signature));
-      localVariables.add(
-          new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
-    }
-
-    @Override
-    public void visitLineNumber(int line, Label start) {
-      instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
-    }
-
-    @Override
-    public void visitMaxs(int maxStack, int maxLocals) {
-      assert maxStack >= 0;
-      assert maxLocals >= 0;
-      this.maxStack = maxStack;
-      this.maxLocals = maxLocals;
-    }
-  }
-
   private static class CreateAnnotationVisitor extends AnnotationVisitor {
 
     private final JarApplicationReader application;
@@ -1418,4 +768,14 @@
       return getDexValueArray(value);
     }
   }
+
+  public static class ReparseContext {
+
+    // This will hold the content of the whole class. Once all the methods of the class are swapped
+    // from this to the actual JarCode, no other references would be left and the content can be
+    // GC'd.
+    public byte[] classCache;
+    public DexProgramClass owner;
+    public final List<Code> codeList = new ArrayList<>();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 55ae757..dc75307 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -17,9 +18,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.List;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
@@ -39,16 +38,6 @@
     node.accept(visitor);
   }
 
-  public static class ReparseContext {
-
-    // This will hold the content of the whole class. Once all the methods of the class are swapped
-    // from this to the actual JarCode, no other references would be left and the content can be
-    // GC'd.
-    public byte[] classCache;
-    public DexProgramClass owner;
-    private final List<JarCode> codeList = new ArrayList<>();
-  }
-
   private final DexMethod method;
   private final Origin origin;
   private MethodNode node;
@@ -227,7 +216,7 @@
       JarCode code = null;
       MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
       if (!flags.isAbstract() && !flags.isNative()) {
-        code = context.codeList.get(methodIndex++);
+        code = context.codeList.get(methodIndex++).asJarCode();
         assert code.method == application.getMethod(context.owner.type, name, desc);
       }
       if (code != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
new file mode 100644
index 0000000..a864aba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -0,0 +1,804 @@
+// Copyright (c) 2018, 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.graph;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfCmp;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
+import com.android.tools.r8.cf.code.CfConstMethodType;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.cf.code.CfMonitor;
+import com.android.tools.r8.cf.code.CfMultiANewArray;
+import com.android.tools.r8.cf.code.CfNeg;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfNumberConversion;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfSwitch;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.JSRInlinerAdapter;
+
+public class LazyCfCode extends Code {
+
+  public LazyCfCode(
+      DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
+
+    this.method = method;
+    this.origin = origin;
+    this.context = context;
+    this.application = application;
+    context.codeList.add(this);
+  }
+
+  private final DexMethod method;
+  private final Origin origin;
+  private final JarApplicationReader application;
+  private CfCode code;
+  private ReparseContext context;
+
+  @Override
+  public boolean isCfCode() {
+    return true;
+  }
+
+  @Override
+  public LazyCfCode asLazyCfCode() {
+    return this;
+  }
+
+  @Override
+  public CfCode asCfCode() {
+    if (code == null) {
+      assert context != null;
+      // The SecondVistor is in charge of setting the context to null.
+      DexProgramClass owner = context.owner;
+      ClassReader classReader = new ClassReader(context.classCache);
+      classReader.accept(new ClassCodeVisitor(context, application), ClassReader.EXPAND_FRAMES);
+      assert verifyNoReparseContext(owner);
+    }
+    assert code != null;
+    return code;
+  }
+
+  private void setCode(CfCode code) {
+    assert this.code == null;
+    assert this.context != null;
+    this.code = code;
+    this.context = null;
+  }
+
+  @Override
+  protected int computeHashCode() {
+    throw new Unimplemented();
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    return asCfCode().isEmptyVoidMethod();
+  }
+
+  @Override
+  public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+      throws ApiLevelException {
+    return asCfCode().buildIR(encodedMethod, options);
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition)
+      throws ApiLevelException {
+    return asCfCode().buildInliningIR(encodedMethod, options, valueNumberGenerator, callerPosition);
+  }
+
+  @Override
+  public void registerCodeReferences(UseRegistry registry) {
+    asCfCode().registerCodeReferences(registry);
+  }
+
+  @Override
+  public String toString() {
+    return asCfCode().toString();
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return null;
+  }
+
+  private static class ClassCodeVisitor extends ClassVisitor {
+
+    private final ReparseContext context;
+    private final JarApplicationReader application;
+    private int methodIndex = 0;
+
+    ClassCodeVisitor(ReparseContext context, JarApplicationReader application) {
+      super(Opcodes.ASM6);
+      this.context = context;
+      this.application = application;
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String desc, String signature, String[] exceptions) {
+      MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access);
+      if (!flags.isAbstract() && !flags.isNative()) {
+        LazyCfCode code = context.codeList.get(methodIndex++).asLazyCfCode();
+        DexMethod method = application.getMethod(context.owner.type, name, desc);
+        assert code.method == method;
+        MethodCodeVisitor methodVisitor = new MethodCodeVisitor(application, code);
+        return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions);
+      }
+      return null;
+    }
+  }
+
+  private static class MethodCodeVisitor extends MethodVisitor {
+    private final JarApplicationReader application;
+    private final DexItemFactory factory;
+    private int maxStack;
+    private int maxLocals;
+    private List<CfInstruction> instructions;
+    private List<CfTryCatch> tryCatchRanges;
+    private List<LocalVariableInfo> localVariables;
+    private Map<Label, CfLabel> labelMap;
+    private final LazyCfCode code;
+    private DexMethod method;
+
+    MethodCodeVisitor(JarApplicationReader application, LazyCfCode code) {
+      super(Opcodes.ASM6);
+      this.application = application;
+      this.factory = application.getFactory();
+      this.method = code.method;
+      this.code = code;
+    }
+
+    @Override
+    public void visitCode() {
+      maxStack = 0;
+      maxLocals = 0;
+      instructions = new ArrayList<>();
+      tryCatchRanges = new ArrayList<>();
+      localVariables = new ArrayList<>();
+      labelMap = new IdentityHashMap<>();
+    }
+
+    @Override
+    public void visitEnd() {
+      code.setCode(
+          new CfCode(method, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
+    }
+
+    @Override
+    public void visitFrame(
+        int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
+      assert frameType == Opcodes.F_NEW;
+      Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
+      List<FrameType> parsedStack = parseStack(nStack, stackTypes);
+      instructions.add(new CfFrame(parsedLocals, parsedStack));
+    }
+
+    private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) {
+      Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>();
+      int i = 0;
+      for (int j = 0; j < typeCount; j++) {
+        Object localType = asmTypes[j];
+        FrameType value = getFrameType(localType);
+        types.put(i++, value);
+        if (value.isWide()) {
+          i++;
+        }
+      }
+      return types;
+    }
+
+    private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
+      List<FrameType> dexStack = new ArrayList<>(nStack);
+      for (int i = 0; i < nStack; i++) {
+        dexStack.add(getFrameType(stackTypes[i]));
+      }
+      return dexStack;
+    }
+
+    private FrameType getFrameType(Object localType) {
+      if (localType instanceof Label) {
+        return FrameType.uninitializedNew(getLabel((Label) localType));
+      } else if (localType == Opcodes.UNINITIALIZED_THIS) {
+        return FrameType.uninitializedThis();
+      } else if (localType == null || localType == Opcodes.TOP) {
+        return FrameType.top();
+      } else {
+        return FrameType.initialized(parseAsmType(localType));
+      }
+    }
+
+    private CfLabel getLabel(Label label) {
+      return labelMap.computeIfAbsent(label, l -> new CfLabel());
+    }
+
+    private DexType parseAsmType(Object local) {
+      assert local != null && local != Opcodes.TOP;
+      if (local == Opcodes.INTEGER) {
+        return factory.intType;
+      } else if (local == Opcodes.FLOAT) {
+        return factory.floatType;
+      } else if (local == Opcodes.LONG) {
+        return factory.longType;
+      } else if (local == Opcodes.DOUBLE) {
+        return factory.doubleType;
+      } else if (local == Opcodes.NULL) {
+        return DexItemFactory.nullValueType;
+      } else if (local instanceof String) {
+        return createTypeFromInternalType((String) local);
+      } else {
+        throw new Unreachable("Unexpected ASM type: " + local);
+      }
+    }
+
+    private DexType createTypeFromInternalType(String local) {
+      assert local.indexOf('.') == -1;
+      return factory.createType("L" + local + ";");
+    }
+
+    @Override
+    public void visitInsn(int opcode) {
+      switch (opcode) {
+        case Opcodes.NOP:
+          instructions.add(new CfNop());
+          break;
+        case Opcodes.ACONST_NULL:
+          instructions.add(new CfConstNull());
+          break;
+        case Opcodes.ICONST_M1:
+        case Opcodes.ICONST_0:
+        case Opcodes.ICONST_1:
+        case Opcodes.ICONST_2:
+        case Opcodes.ICONST_3:
+        case Opcodes.ICONST_4:
+        case Opcodes.ICONST_5:
+          instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT));
+          break;
+        case Opcodes.LCONST_0:
+        case Opcodes.LCONST_1:
+          instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG));
+          break;
+        case Opcodes.FCONST_0:
+        case Opcodes.FCONST_1:
+        case Opcodes.FCONST_2:
+          instructions.add(
+              new CfConstNumber(
+                  Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT));
+          break;
+        case Opcodes.DCONST_0:
+        case Opcodes.DCONST_1:
+          instructions.add(
+              new CfConstNumber(
+                  Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE));
+          break;
+        case Opcodes.IALOAD:
+        case Opcodes.LALOAD:
+        case Opcodes.FALOAD:
+        case Opcodes.DALOAD:
+        case Opcodes.AALOAD:
+        case Opcodes.BALOAD:
+        case Opcodes.CALOAD:
+        case Opcodes.SALOAD:
+          instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode)));
+          break;
+        case Opcodes.IASTORE:
+        case Opcodes.LASTORE:
+        case Opcodes.FASTORE:
+        case Opcodes.DASTORE:
+        case Opcodes.AASTORE:
+        case Opcodes.BASTORE:
+        case Opcodes.CASTORE:
+        case Opcodes.SASTORE:
+          instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode)));
+          break;
+        case Opcodes.POP:
+        case Opcodes.POP2:
+        case Opcodes.DUP:
+        case Opcodes.DUP_X1:
+        case Opcodes.DUP_X2:
+        case Opcodes.DUP2:
+        case Opcodes.DUP2_X1:
+        case Opcodes.DUP2_X2:
+        case Opcodes.SWAP:
+          instructions.add(CfStackInstruction.fromAsm(opcode));
+          break;
+        case Opcodes.IADD:
+        case Opcodes.LADD:
+        case Opcodes.FADD:
+        case Opcodes.DADD:
+        case Opcodes.ISUB:
+        case Opcodes.LSUB:
+        case Opcodes.FSUB:
+        case Opcodes.DSUB:
+        case Opcodes.IMUL:
+        case Opcodes.LMUL:
+        case Opcodes.FMUL:
+        case Opcodes.DMUL:
+        case Opcodes.IDIV:
+        case Opcodes.LDIV:
+        case Opcodes.FDIV:
+        case Opcodes.DDIV:
+        case Opcodes.IREM:
+        case Opcodes.LREM:
+        case Opcodes.FREM:
+        case Opcodes.DREM:
+          instructions.add(CfArithmeticBinop.fromAsm(opcode));
+          break;
+        case Opcodes.INEG:
+        case Opcodes.LNEG:
+        case Opcodes.FNEG:
+        case Opcodes.DNEG:
+          instructions.add(CfNeg.fromAsm(opcode));
+          break;
+        case Opcodes.ISHL:
+        case Opcodes.LSHL:
+        case Opcodes.ISHR:
+        case Opcodes.LSHR:
+        case Opcodes.IUSHR:
+        case Opcodes.LUSHR:
+        case Opcodes.IAND:
+        case Opcodes.LAND:
+        case Opcodes.IOR:
+        case Opcodes.LOR:
+        case Opcodes.IXOR:
+        case Opcodes.LXOR:
+          instructions.add(CfLogicalBinop.fromAsm(opcode));
+          break;
+        case Opcodes.I2L:
+        case Opcodes.I2F:
+        case Opcodes.I2D:
+        case Opcodes.L2I:
+        case Opcodes.L2F:
+        case Opcodes.L2D:
+        case Opcodes.F2I:
+        case Opcodes.F2L:
+        case Opcodes.F2D:
+        case Opcodes.D2I:
+        case Opcodes.D2L:
+        case Opcodes.D2F:
+        case Opcodes.I2B:
+        case Opcodes.I2C:
+        case Opcodes.I2S:
+          instructions.add(CfNumberConversion.fromAsm(opcode));
+          break;
+        case Opcodes.LCMP:
+        case Opcodes.FCMPL:
+        case Opcodes.FCMPG:
+        case Opcodes.DCMPL:
+        case Opcodes.DCMPG:
+          instructions.add(CfCmp.fromAsm(opcode));
+          break;
+        case Opcodes.IRETURN:
+          instructions.add(new CfReturn(ValueType.INT));
+          break;
+        case Opcodes.LRETURN:
+          instructions.add(new CfReturn(ValueType.LONG));
+          break;
+        case Opcodes.FRETURN:
+          instructions.add(new CfReturn(ValueType.FLOAT));
+          break;
+        case Opcodes.DRETURN:
+          instructions.add(new CfReturn(ValueType.DOUBLE));
+          break;
+        case Opcodes.ARETURN:
+          instructions.add(new CfReturn(ValueType.OBJECT));
+          break;
+        case Opcodes.RETURN:
+          instructions.add(new CfReturnVoid());
+          break;
+        case Opcodes.ARRAYLENGTH:
+          instructions.add(new CfArrayLength());
+          break;
+        case Opcodes.ATHROW:
+          instructions.add(new CfThrow());
+          break;
+        case Opcodes.MONITORENTER:
+          instructions.add(new CfMonitor(Monitor.Type.ENTER));
+          break;
+        case Opcodes.MONITOREXIT:
+          instructions.add(new CfMonitor(Monitor.Type.EXIT));
+          break;
+        default:
+          throw new Unreachable("Unknown instruction");
+      }
+    }
+
+    private static MemberType getMemberTypeForOpcode(int opcode) {
+      switch (opcode) {
+        case Opcodes.IALOAD:
+        case Opcodes.IASTORE:
+          return MemberType.INT;
+        case Opcodes.FALOAD:
+        case Opcodes.FASTORE:
+          return MemberType.FLOAT;
+        case Opcodes.LALOAD:
+        case Opcodes.LASTORE:
+          return MemberType.LONG;
+        case Opcodes.DALOAD:
+        case Opcodes.DASTORE:
+          return MemberType.DOUBLE;
+        case Opcodes.AALOAD:
+        case Opcodes.AASTORE:
+          return MemberType.OBJECT;
+        case Opcodes.BALOAD:
+        case Opcodes.BASTORE:
+          return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
+        case Opcodes.CALOAD:
+        case Opcodes.CASTORE:
+          return MemberType.CHAR;
+        case Opcodes.SALOAD:
+        case Opcodes.SASTORE:
+          return MemberType.SHORT;
+        default:
+          throw new Unreachable("Unexpected array opcode " + opcode);
+      }
+    }
+
+    @Override
+    public void visitIntInsn(int opcode, int operand) {
+      switch (opcode) {
+        case Opcodes.SIPUSH:
+        case Opcodes.BIPUSH:
+          instructions.add(new CfConstNumber(operand, ValueType.INT));
+          break;
+        case Opcodes.NEWARRAY:
+          instructions.add(
+              new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
+          break;
+        default:
+          throw new Unreachable("Unexpected int opcode " + opcode);
+      }
+    }
+
+    private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) {
+      switch (arrayTypeCode) {
+        case Opcodes.T_BOOLEAN:
+          return factory.booleanType;
+        case Opcodes.T_CHAR:
+          return factory.charType;
+        case Opcodes.T_FLOAT:
+          return factory.floatType;
+        case Opcodes.T_DOUBLE:
+          return factory.doubleType;
+        case Opcodes.T_BYTE:
+          return factory.byteType;
+        case Opcodes.T_SHORT:
+          return factory.shortType;
+        case Opcodes.T_INT:
+          return factory.intType;
+        case Opcodes.T_LONG:
+          return factory.longType;
+        default:
+          throw new Unreachable("Unexpected array-type code " + arrayTypeCode);
+      }
+    }
+
+    @Override
+    public void visitVarInsn(int opcode, int var) {
+      ValueType type;
+      switch (opcode) {
+        case Opcodes.ILOAD:
+        case Opcodes.ISTORE:
+          type = ValueType.INT;
+          break;
+        case Opcodes.FLOAD:
+        case Opcodes.FSTORE:
+          type = ValueType.FLOAT;
+          break;
+        case Opcodes.LLOAD:
+        case Opcodes.LSTORE:
+          type = ValueType.LONG;
+          break;
+        case Opcodes.DLOAD:
+        case Opcodes.DSTORE:
+          type = ValueType.DOUBLE;
+          break;
+        case Opcodes.ALOAD:
+        case Opcodes.ASTORE:
+          type = ValueType.OBJECT;
+          break;
+        case Opcodes.RET:
+          throw new Unreachable("RET should be handled by the ASM jsr inliner");
+        default:
+          throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
+      }
+      if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
+        instructions.add(new CfLoad(type, var));
+      } else {
+        instructions.add(new CfStore(type, var));
+      }
+    }
+
+    @Override
+    public void visitTypeInsn(int opcode, String typeName) {
+      DexType type = factory.createType(Type.getObjectType(typeName).getDescriptor());
+      switch (opcode) {
+        case Opcodes.NEW:
+          instructions.add(new CfNew(type));
+          break;
+        case Opcodes.ANEWARRAY:
+          instructions.add(new CfNewArray(factory.createArrayType(1, type)));
+          break;
+        case Opcodes.CHECKCAST:
+          instructions.add(new CfCheckCast(type));
+          break;
+        case Opcodes.INSTANCEOF:
+          instructions.add(new CfInstanceOf(type));
+          break;
+        default:
+          throw new Unreachable("Unexpected TypeInsn opcode: " + opcode);
+      }
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+      DexField field =
+          factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
+      // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
+      // renaming in the backend, but it is not available here in the frontend.
+      instructions.add(new CfFieldInstruction(opcode, field, field));
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+      visitMethodInsn(opcode, owner, name, desc, false);
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+      DexMethod method = application.getMethod(owner, name, desc);
+      instructions.add(new CfInvoke(opcode, method, itf));
+    }
+
+    @Override
+    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+      DexCallSite callSite =
+          DexCallSite.fromAsmInvokeDynamic(application, method.holder, name, desc, bsm, bsmArgs);
+      instructions.add(new CfInvokeDynamic(callSite));
+    }
+
+    @Override
+    public void visitJumpInsn(int opcode, Label label) {
+      CfLabel target = getLabel(label);
+      if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
+        if (opcode <= Opcodes.IFLE) {
+          // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE.
+          instructions.add(new CfIf(ifType(opcode), ValueType.INT, target));
+        } else {
+          // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or
+          // IF_ACMPNE.
+          ValueType valueType;
+          if (opcode <= Opcodes.IF_ICMPLE) {
+            valueType = ValueType.INT;
+          } else {
+            valueType = ValueType.OBJECT;
+          }
+          instructions.add(new CfIfCmp(ifType(opcode), valueType, target));
+        }
+      } else {
+        // GOTO, JSR, IFNULL or IFNONNULL.
+        switch (opcode) {
+          case Opcodes.GOTO:
+            instructions.add(new CfGoto(target));
+            break;
+          case Opcodes.IFNULL:
+          case Opcodes.IFNONNULL:
+            If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE;
+            instructions.add(new CfIf(type, ValueType.OBJECT, target));
+            break;
+          case Opcodes.JSR:
+            throw new Unreachable("JSR should be handled by the ASM jsr inliner");
+          default:
+            throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
+        }
+      }
+    }
+
+    private static If.Type ifType(int opcode) {
+      switch (opcode) {
+        case Opcodes.IFEQ:
+        case Opcodes.IF_ICMPEQ:
+        case Opcodes.IF_ACMPEQ:
+          return If.Type.EQ;
+        case Opcodes.IFNE:
+        case Opcodes.IF_ICMPNE:
+        case Opcodes.IF_ACMPNE:
+          return If.Type.NE;
+        case Opcodes.IFLT:
+        case Opcodes.IF_ICMPLT:
+          return If.Type.LT;
+        case Opcodes.IFGE:
+        case Opcodes.IF_ICMPGE:
+          return If.Type.GE;
+        case Opcodes.IFGT:
+        case Opcodes.IF_ICMPGT:
+          return If.Type.GT;
+        case Opcodes.IFLE:
+        case Opcodes.IF_ICMPLE:
+          return If.Type.LE;
+        default:
+          throw new Unreachable("Unexpected If instruction opcode: " + opcode);
+      }
+    }
+
+    @Override
+    public void visitLabel(Label label) {
+      instructions.add(getLabel(label));
+    }
+
+    @Override
+    public void visitLdcInsn(Object cst) {
+      if (cst instanceof Type) {
+        Type type = (Type) cst;
+        if (type.getSort() == Type.METHOD) {
+          DexProto proto = application.getProto(type.getDescriptor());
+          instructions.add(new CfConstMethodType(proto));
+        } else {
+          instructions.add(new CfConstClass(factory.createType(type.getDescriptor())));
+        }
+      } else if (cst instanceof String) {
+        instructions.add(new CfConstString(factory.createString((String) cst)));
+      } else if (cst instanceof Long) {
+        instructions.add(new CfConstNumber((Long) cst, ValueType.LONG));
+      } else if (cst instanceof Double) {
+        long l = Double.doubleToRawLongBits((Double) cst);
+        instructions.add(new CfConstNumber(l, ValueType.DOUBLE));
+      } else if (cst instanceof Integer) {
+        instructions.add(new CfConstNumber((Integer) cst, ValueType.INT));
+      } else if (cst instanceof Float) {
+        long i = Float.floatToRawIntBits((Float) cst);
+        instructions.add(new CfConstNumber(i, ValueType.FLOAT));
+      } else if (cst instanceof Handle) {
+        instructions.add(
+            new CfConstMethodHandle(
+                DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
+      } else {
+        throw new CompilationError("Unsupported constant: " + cst.toString());
+      }
+    }
+
+    @Override
+    public void visitIincInsn(int var, int increment) {
+      instructions.add(new CfIinc(var, increment));
+    }
+
+    @Override
+    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+      assert max == min + labels.length - 1;
+      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+      for (Label label : labels) {
+        targets.add(getLabel(label));
+      }
+      instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets));
+    }
+
+    @Override
+    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+      ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+      for (Label label : labels) {
+        targets.add(getLabel(label));
+      }
+      instructions.add(new CfSwitch(CfSwitch.Kind.LOOKUP, getLabel(dflt), keys, targets));
+    }
+
+    @Override
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+      instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
+    }
+
+    @Override
+    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+      List<DexType> guards =
+          Collections.singletonList(
+              type == null ? DexItemFactory.catchAllType : createTypeFromInternalType(type));
+      List<CfLabel> targets = Collections.singletonList(getLabel(handler));
+      tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets));
+    }
+
+    @Override
+    public void visitLocalVariable(
+        String name, String desc, String signature, Label start, Label end, int index) {
+      DebugLocalInfo debugLocalInfo =
+          new DebugLocalInfo(
+              factory.createString(name),
+              factory.createType(desc),
+              signature == null ? null : factory.createString(signature));
+      localVariables.add(
+          new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
+    }
+
+    @Override
+    public void visitLineNumber(int line, Label start) {
+      instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
+    }
+
+    @Override
+    public void visitMaxs(int maxStack, int maxLocals) {
+      assert maxStack >= 0;
+      assert maxLocals >= 0;
+      this.maxStack = maxStack;
+      this.maxLocals = maxLocals;
+    }
+  }
+
+  private static boolean verifyNoReparseContext(DexProgramClass owner) {
+    for (DexEncodedMethod method : owner.virtualMethods()) {
+      Code code = method.getCode();
+      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+    }
+    for (DexEncodedMethod method : owner.directMethods()) {
+      Code code = method.getCode();
+      assert code == null || !(code instanceof LazyCfCode) || ((LazyCfCode) code).context == null;
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Add.java b/src/main/java/com/android/tools/r8/ir/code/Add.java
index a147d21..d5896da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Add.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Add.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.code.AddDouble;
 import com.android.tools.r8.code.AddDouble2Addr;
 import com.android.tools.r8.code.AddFloat;
@@ -14,8 +15,6 @@
 import com.android.tools.r8.code.AddIntLit8;
 import com.android.tools.r8.code.AddLong;
 import com.android.tools.r8.code.AddLong2Addr;
-import com.android.tools.r8.errors.Unreachable;
-import org.objectweb.asm.Opcodes;
 
 public class Add extends ArithmeticBinop {
 
@@ -119,21 +118,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.IADD;
-      case FLOAT:
-        return Opcodes.FADD;
-      case LONG:
-        return Opcodes.LADD;
-      case DOUBLE:
-        return Opcodes.DADD;
-      default:
-        throw new Unreachable("Unexpected numeric type in add: " + type);
-    }
+  CfArithmeticBinop.Opcode getCfOpcode() {
+    return CfArithmeticBinop.Opcode.Add;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/And.java b/src/main/java/com/android/tools/r8/ir/code/And.java
index 461b1ec..283f369 100644
--- a/src/main/java/com/android/tools/r8/ir/code/And.java
+++ b/src/main/java/com/android/tools/r8/ir/code/And.java
@@ -4,14 +4,13 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.AndInt;
 import com.android.tools.r8.code.AndInt2Addr;
 import com.android.tools.r8.code.AndIntLit16;
 import com.android.tools.r8.code.AndIntLit8;
 import com.android.tools.r8.code.AndLong;
 import com.android.tools.r8.code.AndLong2Addr;
-import com.android.tools.r8.errors.Unreachable;
-import org.objectweb.asm.Opcodes;
 
 public class And extends LogicalBinop {
 
@@ -85,19 +84,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-      case FLOAT:
-        return Opcodes.IAND;
-      case LONG:
-      case DOUBLE:
-        return Opcodes.LAND;
-      default:
-        throw new Unreachable("Unexpected numeric type in logical and: " + type);
-    }
+  CfLogicalBinop.Opcode getCfOpcode() {
+    return CfLogicalBinop.Opcode.And;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index b0b6b9e..296fba5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
 
@@ -157,4 +159,11 @@
     }
     return Bottom.getInstance();
   }
+
+  abstract CfArithmeticBinop.Opcode getCfOpcode();
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfArithmeticBinop(getCfOpcode(), type));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index c2f21ac..9e6738a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -7,13 +7,11 @@
 import static com.android.tools.r8.dex.Constants.U8BIT_MAX;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfBinop;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -136,13 +134,6 @@
     helper.storeOutValue(this, it);
   }
 
-  abstract int getCfOpcode();
-
-  @Override
-  public void buildCf(CfBuilder builder) {
-    builder.add(new CfBinop(getCfOpcode()));
-  }
-
   @Override
   public TypeLatticeElement evaluate(
       AppInfo appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 2e84e4e..239e404 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.code.CmpLong;
 import com.android.tools.r8.code.CmpgDouble;
 import com.android.tools.r8.code.CmpgFloat;
@@ -13,12 +14,12 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import java.util.function.Function;
-import org.objectweb.asm.Opcodes;
 
 public class Cmp extends Binop {
 
@@ -222,17 +223,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case LONG:
-        assert bias == Bias.NONE;
-        return Opcodes.LCMP;
-      case FLOAT:
-        return bias == Bias.GT ? Opcodes.FCMPG : Opcodes.FCMPL;
-      case DOUBLE:
-        return bias == Bias.GT ? Opcodes.DCMPG : Opcodes.DCMPL;
-      default:
-        throw new Unreachable("Unexpected cmp type: " + type);
-    }
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfCmp(bias, type));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index 0add78c..6c9537a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.code.DivDouble;
 import com.android.tools.r8.code.DivDouble2Addr;
 import com.android.tools.r8.code.DivFloat;
@@ -13,11 +14,9 @@
 import com.android.tools.r8.code.DivIntLit8;
 import com.android.tools.r8.code.DivLong;
 import com.android.tools.r8.code.DivLong2Addr;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
 import java.util.function.Function;
-import org.objectweb.asm.Opcodes;
 
 public class Div extends ArithmeticBinop {
 
@@ -140,21 +139,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.IDIV;
-      case FLOAT:
-        return Opcodes.FDIV;
-      case LONG:
-        return Opcodes.LDIV;
-      case DOUBLE:
-        return Opcodes.DDIV;
-      default:
-        throw new Unreachable("Unexpected numeric type: " + type);
-    }
+  CfArithmeticBinop.Opcode getCfOpcode() {
+    return CfArithmeticBinop.Opcode.Div;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index e9504cd..819bb4b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -119,8 +119,19 @@
     assert !needsRangedInvoke(builder);
     int i = 0;
     for (Value value : arguments()) {
-      int register = builder.allocatedRegister(value, getNumber());
-      assert register <= Constants.U4BIT_MAX;
+      // If one of the arguments to the invoke instruction is an argument of the enclosing method
+      // that has been spilled at this location, then we need to take the argument from its
+      // original input register (because the register allocator never inserts moves from an
+      // argument register to a spill register). Note that this is only a problem if an argument
+      // has been spilled to a register that is not the argument's original register.
+      //
+      // For simplicity, we just use the original input register for all arguments if the register
+      // fits in 4 bits.
+      int register = builder.argumentOrAllocateRegister(value, getNumber());
+      if (register + value.requiredRegisters() - 1 > Constants.U4BIT_MAX) {
+        register = builder.allocatedRegister(value, getNumber());
+      }
+      assert register + value.requiredRegisters() - 1 <= Constants.U4BIT_MAX;
       for (int j = 0; j < value.requiredRegisters(); j++) {
         assert i < 5;
         registers[i++] = register++;
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 8314e3f..4b34ca1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
 
@@ -133,4 +135,11 @@
     }
     return Bottom.getInstance();
   }
+
+  abstract CfLogicalBinop.Opcode getCfOpcode();
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfLogicalBinop(getCfOpcode(), type));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Mul.java b/src/main/java/com/android/tools/r8/ir/code/Mul.java
index b78d288..7407f2e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Mul.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Mul.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.code.MulDouble;
 import com.android.tools.r8.code.MulDouble2Addr;
 import com.android.tools.r8.code.MulFloat;
@@ -14,8 +15,6 @@
 import com.android.tools.r8.code.MulIntLit8;
 import com.android.tools.r8.code.MulLong;
 import com.android.tools.r8.code.MulLong2Addr;
-import com.android.tools.r8.errors.Unreachable;
-import org.objectweb.asm.Opcodes;
 
 public class Mul extends ArithmeticBinop {
 
@@ -131,21 +130,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.IMUL;
-      case FLOAT:
-        return Opcodes.FMUL;
-      case LONG:
-        return Opcodes.LMUL;
-      case DOUBLE:
-        return Opcodes.DMUL;
-      default:
-        throw new Unreachable("Unexpected numeric type: " + type);
-    }
+  CfArithmeticBinop.Opcode getCfOpcode() {
+    return CfArithmeticBinop.Opcode.Mul;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index f6e91a4..16b2922 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfNeg;
 import com.android.tools.r8.code.NegDouble;
 import com.android.tools.r8.code.NegFloat;
 import com.android.tools.r8.code.NegInt;
@@ -11,9 +12,9 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
-import org.objectweb.asm.Opcodes;
 
 public class Neg extends Unop {
 
@@ -99,18 +100,7 @@
   }
 
   @Override
-  public int getCfOpcode() {
-    switch (type) {
-      case INT:
-        return Opcodes.INEG;
-      case FLOAT:
-        return Opcodes.FNEG;
-      case LONG:
-        return Opcodes.LNEG;
-      case DOUBLE:
-        return Opcodes.DNEG;
-      default:
-        throw new Unreachable("Unexpected type: " + type);
-    }
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfNeg(type));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 8b35b02..1037c34 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfBinop;
 import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.NotInt;
 import com.android.tools.r8.code.NotLong;
 import com.android.tools.r8.errors.Unreachable;
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
-import org.objectweb.asm.Opcodes;
 
 public class Not extends Unop {
 
@@ -96,13 +95,8 @@
   }
 
   @Override
-  public int getCfOpcode() {
-    throw new Unreachable("Unexpected request for 'not' opcode which is translated to 'xor -1'");
-  }
-
-  @Override
   public void buildCf(CfBuilder builder) {
     builder.add(new CfConstNumber(-1, ValueType.fromNumericType(type)));
-    builder.add(new CfBinop(type.isWide() ? Opcodes.LXOR : Opcodes.IXOR));
+    builder.add(new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, type));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index bc3be95..f1c0049 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.code.DoubleToFloat;
 import com.android.tools.r8.code.DoubleToInt;
 import com.android.tools.r8.code.DoubleToLong;
@@ -19,8 +20,8 @@
 import com.android.tools.r8.code.LongToFloat;
 import com.android.tools.r8.code.LongToInt;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import org.objectweb.asm.Opcodes;
 
 public class NumberConversion extends Unop {
 
@@ -145,60 +146,7 @@
   }
 
   @Override
-  public int getCfOpcode() {
-    switch (from) {
-      case INT:
-        switch (to) {
-          case BYTE:
-            return Opcodes.I2B;
-          case CHAR:
-            return Opcodes.I2C;
-          case SHORT:
-            return Opcodes.I2S;
-          case FLOAT:
-            return Opcodes.I2F;
-          case LONG:
-            return Opcodes.I2L;
-          case DOUBLE:
-            return Opcodes.I2D;
-          default:
-            throw new Unreachable("Unexpected type conversion: " + from + " -> " + to);
-        }
-      case FLOAT:
-        switch (to) {
-          case INT:
-            return Opcodes.F2I;
-          case LONG:
-            return Opcodes.F2L;
-          case DOUBLE:
-            return Opcodes.F2D;
-          default:
-            throw new Unreachable("Unexpected type conversion: " + from + " -> " + to);
-        }
-      case LONG:
-        switch (to) {
-          case INT:
-            return Opcodes.L2I;
-          case FLOAT:
-            return Opcodes.L2F;
-          case DOUBLE:
-            return Opcodes.L2D;
-          default:
-            throw new Unreachable("Unexpected type conversion: " + from + " -> " + to);
-        }
-      case DOUBLE:
-        switch (to) {
-          case INT:
-            return Opcodes.D2I;
-          case LONG:
-            return Opcodes.D2L;
-          case FLOAT:
-            return Opcodes.D2F;
-          default:
-            throw new Unreachable("Unexpected type conversion: " + from + " -> " + to);
-        }
-      default:
-        throw new Unreachable("Unexpected type conversion: " + from + " -> " + to);
-    }
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfNumberConversion(from, to));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Or.java b/src/main/java/com/android/tools/r8/ir/code/Or.java
index 4e5dc9c..9136be3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Or.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Or.java
@@ -3,14 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.OrInt;
 import com.android.tools.r8.code.OrInt2Addr;
 import com.android.tools.r8.code.OrIntLit16;
 import com.android.tools.r8.code.OrIntLit8;
 import com.android.tools.r8.code.OrLong;
 import com.android.tools.r8.code.OrLong2Addr;
-import com.android.tools.r8.errors.Unreachable;
-import org.objectweb.asm.Opcodes;
 
 public class Or extends LogicalBinop {
 
@@ -84,17 +83,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.IOR;
-      case LONG:
-        return Opcodes.LOR;
-      default:
-        throw new Unreachable("Unexpected numeric type for or: " + type);
-    }
+  CfLogicalBinop.Opcode getCfOpcode() {
+    return CfLogicalBinop.Opcode.Or;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Rem.java b/src/main/java/com/android/tools/r8/ir/code/Rem.java
index d584eed..02eb712 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Rem.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Rem.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.code.RemDouble;
 import com.android.tools.r8.code.RemDouble2Addr;
 import com.android.tools.r8.code.RemFloat;
@@ -13,11 +14,9 @@
 import com.android.tools.r8.code.RemIntLit8;
 import com.android.tools.r8.code.RemLong;
 import com.android.tools.r8.code.RemLong2Addr;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
 import java.util.function.Function;
-import org.objectweb.asm.Opcodes;
 
 public class Rem extends ArithmeticBinop {
 
@@ -140,21 +139,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.IREM;
-      case FLOAT:
-        return Opcodes.FREM;
-      case LONG:
-        return Opcodes.LREM;
-      case DOUBLE:
-        return Opcodes.DREM;
-      default:
-        throw new Unreachable("Unexpected numeric type: " + type);
-    }
+  CfArithmeticBinop.Opcode getCfOpcode() {
+    return CfArithmeticBinop.Opcode.Rem;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shl.java b/src/main/java/com/android/tools/r8/ir/code/Shl.java
index c959740..57b3190 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shl.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shl.java
@@ -3,13 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.ShlInt;
 import com.android.tools.r8.code.ShlInt2Addr;
 import com.android.tools.r8.code.ShlIntLit8;
 import com.android.tools.r8.code.ShlLong;
 import com.android.tools.r8.code.ShlLong2Addr;
 import com.android.tools.r8.errors.Unreachable;
-import org.objectweb.asm.Opcodes;
 
 public class Shl extends LogicalBinop {
 
@@ -89,17 +89,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.ISHL;
-      case LONG:
-        return Opcodes.LSHL;
-      default:
-        throw new Unreachable("Unexpected numeric type in shift: " + type);
-    }
+  CfLogicalBinop.Opcode getCfOpcode() {
+    return CfLogicalBinop.Opcode.Shl;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shr.java b/src/main/java/com/android/tools/r8/ir/code/Shr.java
index 35f8f61..18c38ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shr.java
@@ -3,13 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.ShrInt;
 import com.android.tools.r8.code.ShrInt2Addr;
 import com.android.tools.r8.code.ShrIntLit8;
 import com.android.tools.r8.code.ShrLong;
 import com.android.tools.r8.code.ShrLong2Addr;
 import com.android.tools.r8.errors.Unreachable;
-import org.objectweb.asm.Opcodes;
 
 public class Shr extends LogicalBinop {
 
@@ -89,17 +89,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.ISHR;
-      case LONG:
-        return Opcodes.LSHR;
-      default:
-        throw new Unreachable("Unexpected numeric type in shift: " + type);
-    }
+  CfLogicalBinop.Opcode getCfOpcode() {
+    return CfLogicalBinop.Opcode.Shr;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Sub.java b/src/main/java/com/android/tools/r8/ir/code/Sub.java
index abd6c60..b263e63 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Sub.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Sub.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.code.AddIntLit16;
 import com.android.tools.r8.code.AddIntLit8;
 import com.android.tools.r8.code.RsubInt;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import org.objectweb.asm.Opcodes;
 
 public class Sub extends ArithmeticBinop {
 
@@ -208,21 +208,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.ISUB;
-      case FLOAT:
-        return Opcodes.FSUB;
-      case LONG:
-        return Opcodes.LSUB;
-      case DOUBLE:
-        return Opcodes.DSUB;
-      default:
-        throw new Unreachable("Unexpected numeric type: " + type);
-    }
+  CfArithmeticBinop.Opcode getCfOpcode() {
+    return CfArithmeticBinop.Opcode.Sub;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 94758fb..6db3ad5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -4,13 +4,11 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfUnop;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.function.Function;
@@ -60,13 +58,6 @@
     helper.storeOutValue(this, it);
   }
 
-  public abstract int getCfOpcode();
-
-  @Override
-  public void buildCf(CfBuilder builder) {
-    builder.add(new CfUnop(getCfOpcode()));
-  }
-
   @Override
   public TypeLatticeElement evaluate(
       AppInfo appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Ushr.java b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
index e0fb715..d1e5a69 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Ushr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
@@ -3,13 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.UshrInt;
 import com.android.tools.r8.code.UshrInt2Addr;
 import com.android.tools.r8.code.UshrIntLit8;
 import com.android.tools.r8.code.UshrLong;
 import com.android.tools.r8.code.UshrLong2Addr;
 import com.android.tools.r8.errors.Unreachable;
-import org.objectweb.asm.Opcodes;
 
 public class Ushr extends LogicalBinop {
 
@@ -89,17 +89,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    switch (type) {
-      case BYTE:
-      case CHAR:
-      case SHORT:
-      case INT:
-        return Opcodes.IUSHR;
-      case LONG:
-        return Opcodes.LUSHR;
-      default:
-        throw new Unreachable("Unexpected numeric type in shift: " + type);
-    }
+  CfLogicalBinop.Opcode getCfOpcode() {
+    return CfLogicalBinop.Opcode.Ushr;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Xor.java b/src/main/java/com/android/tools/r8/ir/code/Xor.java
index 3050f70..2e9c497 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Xor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Xor.java
@@ -3,13 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.code.XorInt;
 import com.android.tools.r8.code.XorInt2Addr;
 import com.android.tools.r8.code.XorIntLit16;
 import com.android.tools.r8.code.XorIntLit8;
 import com.android.tools.r8.code.XorLong;
 import com.android.tools.r8.code.XorLong2Addr;
-import org.objectweb.asm.Opcodes;
 
 public class Xor extends LogicalBinop {
 
@@ -83,7 +83,7 @@
   }
 
   @Override
-  int getCfOpcode() {
-    return type.isWide() ? Opcodes.LXOR : Opcodes.IXOR;
+  CfLogicalBinop.Opcode getCfOpcode() {
+    return CfLogicalBinop.Opcode.Xor;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 8af4486..1a4b97d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -128,7 +128,7 @@
         // propagated through dominance.
         Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
         Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
-        Map<Phi, IntList> dominatedPhiUsersWithPotisions = new IdentityHashMap<>();
+        Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
         DominatorTree dominatorTree = new DominatorTree(code);
         Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
         for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
@@ -151,11 +151,11 @@
           IntList dominatedPredecessorIndexes =
               findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
           if (!dominatedPredecessorIndexes.isEmpty()) {
-            dominatedPhiUsersWithPotisions.put(user, dominatedPredecessorIndexes);
+            dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
           }
         }
         knownToBeNonNullValue.replaceSelectiveUsers(
-            nonNullValue, dominatedUsers, dominatedPhiUsersWithPotisions);
+            nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
       }
 
       // Add non-null on top of the successor block if the current block ends with a null check.
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index de85da5..2f055ce 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1144,6 +1144,10 @@
   }
 
   private int getSpillRegister(LiveIntervals intervals) {
+    if (intervals.isArgumentInterval()) {
+      return intervals.getSplitParent().getRegister();
+    }
+
     int register = maxRegisterNumber + 1;
     increaseCapacity(maxRegisterNumber + intervals.requiredRegisters());
     assert registersAreFree(register, intervals.getType().isWide());
@@ -1279,9 +1283,10 @@
     return false;
   }
 
-  private boolean longOverlappingLong(int register1, int register2) {
-    return register1 == register2 || register1 == (register2 + 1)
-        || (register1 + 1) == register2 || (register1 + 1) == (register2 + 1);
+  // Check if the two longs are half-overlapping, that is first register of one is the second
+  // register of the other.
+  private boolean longHalfOverlappingLong(int register1, int register2) {
+    return register1 == (register2 + 1) || (register1 + 1) == register2;
   }
 
   private boolean isLongResultOverlappingLongOperands(
@@ -1297,7 +1302,8 @@
     // The dalvik bug is actually only for overlap with the second operand, For now we
     // make sure that there is no overlap with either register of either operand. Some vendor
     // optimization have bees seen to need this more conservative check.
-    return longOverlappingLong(register, leftReg) || longOverlappingLong(register, rightReg);
+    return longHalfOverlappingLong(register, leftReg)
+        || longHalfOverlappingLong(register, rightReg);
   }
 
   // Intervals overlap a move exception interval if one of the splits of the intervals does.
@@ -1474,14 +1480,7 @@
       // of finding another candidate to spill via allocateBlockedRegister.
       if (!unhandledInterval.getUses().first().hasConstraint()) {
         int nextConstrainedPosition = unhandledInterval.firstUseWithConstraint().getPosition();
-        int register;
-        // Arguments are always in the argument registers, so for arguments just use that register
-        // for the unconstrained prefix. For everything else, get a spill register.
-        if (unhandledInterval.isArgumentInterval()) {
-          register = unhandledInterval.getSplitParent().getRegister();
-        } else {
-          register = getSpillRegister(unhandledInterval);
-        }
+        int register = getSpillRegister(unhandledInterval);
         LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
         assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
         unhandled.add(split);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 7e398e6..74600f4 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -88,6 +88,9 @@
   }
 
   public void setSpilled(boolean value) {
+    // Check that we always spill arguments to their original register.
+    assert getRegister() != NO_REGISTER;
+    assert !(value && isArgumentInterval()) || getRegister() == getSplitParent().getRegister();
     spilled = value;
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 30a5418..5b908af 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -110,9 +110,9 @@
     return (MethodSignature) canonicalizeSignature(signature);
   }
 
-  public Signature getRenamedFieldSignature(DexField field) {
+  public FieldSignature getRenamedFieldSignature(DexField field) {
     String type = deobfuscateType(field.type.toDescriptorString());
-    return canonicalizeSignature(new FieldSignature(field.name.toString(), type));
+    return (FieldSignature) canonicalizeSignature(new FieldSignature(field.name.toString(), type));
   }
 
   /**
@@ -231,6 +231,20 @@
     return memberNaming.signature;
   }
 
+  public FieldSignature originalSignatureOf(DexField field) {
+    String decoded = descriptorToJavaType(field.clazz.descriptor.toString());
+    FieldSignature memberSignature = getRenamedFieldSignature(field);
+    ClassNaming classNaming = getClassNaming(decoded);
+    if (classNaming == null) {
+      return memberSignature;
+    }
+    MemberNaming memberNaming = classNaming.lookup(memberSignature);
+    if (memberNaming == null) {
+      return memberSignature;
+    }
+    return (FieldSignature) memberNaming.signature;
+  }
+
   public String originalNameOf(DexType clazz) {
     return deobfuscateType(clazz.descriptor.toString());
   }
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 d6ddf5d..1bf193b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -10,11 +10,11 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
 import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
@@ -78,18 +78,32 @@
     this.mainDexClasses = mainDexClasses;
   }
 
+  static Reporter defaultReporter() {
+    return new Reporter(new DefaultDiagnosticsHandler());
+  }
+
   /**
    * Create a new empty builder.
    */
   public static Builder builder() {
-    return new Builder();
+    return builder(defaultReporter());
+  }
+
+  /** Create a new empty builder. */
+  public static Builder builder(Reporter reporter) {
+    return new Builder(reporter);
   }
 
   /**
    * Create a new builder initialized with the resources from @code{app}.
    */
   public static Builder builder(AndroidApp app) {
-    return new Builder(app);
+    return builder(app, defaultReporter());
+  }
+
+  /** Create a new builder initialized with the resources from @code{app}. */
+  public static Builder builder(AndroidApp app, Reporter reporter) {
+    return new Builder(reporter, app);
   }
 
   /** Get full collection of all program resources from all program providers. */
@@ -273,12 +287,16 @@
     // Proguard map data is output only data. This should never be used as input to a compilation.
     private StringResource proguardMapOutputData;
 
+    private final Reporter reporter;
+
     // See AndroidApp::builder().
-    private Builder() {
+    private Builder(Reporter reporter) {
+      this.reporter = reporter;
     }
 
     // See AndroidApp::builder(AndroidApp).
-    private Builder(AndroidApp app) {
+    private Builder(Reporter reporter, AndroidApp app) {
+      this(reporter);
       programResourceProviders.addAll(app.programResourceProviders);
       classpathResourceProviders.addAll(app.classpathResourceProviders);
       libraryResourceProviders.addAll(app.libraryResourceProviders);
@@ -287,6 +305,10 @@
       mainDexListClasses = app.mainDexClasses;
     }
 
+    public Reporter getReporter() {
+      return reporter;
+    }
+
     /** Add program file resources. */
     public Builder addProgramFiles(Path... files) throws NoSuchFileException {
       return addProgramFiles(Arrays.asList(files));
@@ -301,8 +323,7 @@
     }
 
     /** Add filtered archives of program resources. */
-    public Builder addFilteredProgramArchives(Collection<FilteredClassPath> filteredArchives)
-        throws NoSuchFileException {
+    public Builder addFilteredProgramArchives(Collection<FilteredClassPath> filteredArchives) {
       for (FilteredClassPath archive : filteredArchives) {
         assert isArchive(archive.getPath());
         ArchiveResourceProvider archiveResourceProvider =
@@ -379,11 +400,14 @@
     }
 
     /** Add library file resources. */
-    public Builder addFilteredLibraryArchives(Collection<FilteredClassPath> filteredArchives)
-        throws IOException {
+    public Builder addFilteredLibraryArchives(Collection<FilteredClassPath> filteredArchives) {
       for (FilteredClassPath archive : filteredArchives) {
         assert isArchive(archive.getPath());
-        libraryResourceProviders.add(new FilteredArchiveClassFileProvider(archive));
+        try {
+          libraryResourceProviders.add(new FilteredArchiveClassFileProvider(archive));
+        } catch (IOException e) {
+          reporter.error(new ExceptionDiagnostic(e, new PathOrigin(archive.getPath())));
+        }
       }
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 4a7abc3..cd21d17 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -61,7 +61,7 @@
                 Files.newOutputStream(
                     archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
       } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, origin));
+        handler.error(new ExceptionDiagnostic(e, origin));
       }
     }
     return stream;
@@ -71,9 +71,9 @@
     if (e instanceof ZipException && e.getMessage().startsWith("duplicate entry")) {
       // For now we stick to the Proguard behaviour, see section "Warning: can't write resource ...
       // Duplicate zip entry" on https://www.guardsquare.com/en/proguard/manual/troubleshooting.
-      handler.warning(new IOExceptionDiagnostic(e, origin));
+      handler.warning(new ExceptionDiagnostic(e, origin));
     } else {
-      handler.error(new IOExceptionDiagnostic(e, origin));
+      handler.error(new ExceptionDiagnostic(e, origin));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java b/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java
index da439fe..b169297 100644
--- a/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java
+++ b/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java
@@ -14,4 +14,8 @@
     assert throwable != null;
     this.throwable = throwable;
   }
+
+  public Throwable getThrowable() {
+    return throwable;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
index b793f19..2dc4a2d 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
@@ -39,7 +39,7 @@
     try {
       Files.createDirectories(target.getParent());
     } catch (IOException e) {
-      handler.error(new IOExceptionDiagnostic(e, new PathOrigin(target)));
+      handler.error(new ExceptionDiagnostic(e, new PathOrigin(target)));
     }
   }
 
@@ -48,7 +48,7 @@
     try (InputStream in = content.getByteStream()) {
       addFile(name, ByteStreams.toByteArray(in), handler);
     } catch (IOException e) {
-      handler.error(new IOExceptionDiagnostic(e, content.getOrigin()));
+      handler.error(new ExceptionDiagnostic(e, content.getOrigin()));
     } catch (ResourceException e) {
       handler.error(new StringDiagnostic("Failed to open input: " + e.getMessage(),
           content.getOrigin()));
@@ -62,7 +62,7 @@
       Files.createDirectories(target.getParent());
       FileUtils.writeToFile(target, null, content);
     } catch (IOException e) {
-      handler.error(new IOExceptionDiagnostic(e, new PathOrigin(target)));
+      handler.error(new ExceptionDiagnostic(e, new PathOrigin(target)));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
new file mode 100644
index 0000000..9fd91d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.io.FileNotFoundException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.NoSuchFileException;
+
+public class ExceptionDiagnostic extends DiagnosticWithThrowable {
+
+  private final Origin origin;
+
+  public ExceptionDiagnostic(Throwable e, Origin origin) {
+    super(e);
+    this.origin = origin;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    Throwable e = getThrowable();
+    if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
+      return "File not found: " + e.getMessage();
+    }
+    if (e instanceof FileAlreadyExistsException) {
+      return "File already exists: " + e.getMessage();
+    }
+    return e.getMessage();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index f4d3ae7..9c7f525 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -9,7 +9,11 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import java.io.IOException;
+import java.nio.file.FileSystemException;
+import java.nio.file.Paths;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -55,16 +59,13 @@
       try {
         action.run();
       } catch (IOException e) {
-        throw reporter.fatalError(new IOExceptionDiagnostic(e));
+        throw reporter.fatalError(new ExceptionDiagnostic(e, extractIOExceptionOrigin(e)));
       } catch (CompilationException e) {
         throw reporter.fatalError(new StringDiagnostic(compilerMessage.apply(e)), e);
       } catch (CompilationError e) {
         throw reporter.fatalError(e);
       } catch (ResourceException e) {
-        throw reporter.fatalError(
-            e.getCause() instanceof IOException
-                ? new IOExceptionDiagnostic((IOException) e.getCause(), e.getOrigin())
-                : new StringDiagnostic(e.getMessage(), e.getOrigin()));
+        throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
       }
       reporter.failIfPendingErrors();
     } catch (AbortException e) {
@@ -89,6 +90,19 @@
       cause.printStackTrace();
       System.exit(STATUS_ERROR);
     }
-
   }
+
+  // We should try to avoid the use of this extraction as it signifies a point where we don't have
+  // enough context to associate a specific origin with an IOException. Concretely, we should move
+  // towards always catching IOException and rethrowing CompilationError with proper origins.
+  public static Origin extractIOExceptionOrigin(IOException e) {
+    if (e instanceof FileSystemException) {
+      FileSystemException fse = (FileSystemException) e;
+      if (fse.getFile() != null && !fse.getFile().isEmpty()) {
+        return new PathOrigin(Paths.get(fse.getFile()));
+      }
+    }
+    return Origin.unknown();
+  }
+
 }
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 1c969f6..e8c6df9 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
+import com.android.tools.r8.origin.PathOrigin;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -51,11 +53,39 @@
   static final String COMMENT = "#";
   static final String SEPARATOR = ":";
 
+  private static class SpecificationOrigin extends PathOrigin {
 
-  public static FeatureClassMapping fromSpecification(Path file)
-      throws FeatureMappingException, IOException {
+    public SpecificationOrigin(Path path) {
+      super(path);
+    }
+
+    @Override
+    public String part() {
+      return "specification file '" + super.part() + "'";
+    }
+  }
+
+  private static class JarFileOrigin extends PathOrigin {
+
+    public JarFileOrigin(Path path) {
+      super(path);
+    }
+
+    @Override
+    public String part() {
+      return "jar file '" + super.part() + "'";
+    }
+  }
+
+  public static FeatureClassMapping fromSpecification(Path file, DexSplitter.Reporter reporter)
+      throws FeatureMappingException {
     FeatureClassMapping mapping = new FeatureClassMapping();
-    List<String> lines = FileUtils.readAllLines(file);
+    List<String> lines = null;
+    try {
+      lines = FileUtils.readAllLines(file);
+    } catch (IOException e) {
+      throw reporter.fatal(new ExceptionDiagnostic(e, new SpecificationOrigin(file)));
+    }
     for (int i = 0; i < lines.size(); i++) {
       String line = lines.get(i);
       mapping.parseAndAdd(line, i);
@@ -63,15 +93,21 @@
     return mapping;
   }
 
-  public static FeatureClassMapping fromJarFiles(List<FeatureJar> featureJars, String baseName)
-      throws FeatureMappingException, IOException {
+  public static FeatureClassMapping fromJarFiles(
+      List<FeatureJar> featureJars, String baseName, DexSplitter.Reporter reporter)
+      throws FeatureMappingException {
     FeatureClassMapping mapping = new FeatureClassMapping();
     if (mapping.baseName != null) {
       mapping.baseName = baseName;
     }
     for (FeatureJar featureJar : featureJars) {
       Path jarPath = Paths.get(featureJar.getJar());
-      ArchiveClassFileProvider provider = new ArchiveClassFileProvider(jarPath);
+      ArchiveClassFileProvider provider = null;
+      try {
+        provider = new ArchiveClassFileProvider(jarPath);
+      } catch (IOException e) {
+        throw reporter.fatal(new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)));
+      }
       for (String javaDescriptor : provider.getClassDescriptors()) {
           String javaType = DescriptorUtils.descriptorToJavaType(javaDescriptor);
           mapping.addMapping(javaType, featureJar.getOutputName());
@@ -94,7 +130,7 @@
     }
   }
 
-  public String featureForClass(String clazz) throws FeatureMappingException {
+  public String featureForClass(String clazz) {
     if (usesOnlyExactMappings) {
       return parsedRules.getOrDefault(clazz, baseName);
     } else {
diff --git a/src/main/java/com/android/tools/r8/utils/IOExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/IOExceptionDiagnostic.java
deleted file mode 100644
index 642f2d7..0000000
--- a/src/main/java/com/android/tools/r8/utils/IOExceptionDiagnostic.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.Position;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileSystemException;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Paths;
-
-public class IOExceptionDiagnostic extends DiagnosticWithThrowable {
-
-  private final Origin origin;
-  private final String message;
-
-  public IOExceptionDiagnostic(IOException e) {
-    super(e);
-    origin = extractOrigin(e);
-    message = extractMessage(e);
-  }
-
-  public IOExceptionDiagnostic(IOException e, Origin origin) {
-    super(e);
-    this.origin = origin;
-    message = extractMessage(e);
-  }
-
-  private static String extractMessage(IOException e) {
-    String message = e.getMessage();
-    if (message == null || message.isEmpty()) {
-      if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
-        message = "File not found";
-      } else if (e instanceof FileAlreadyExistsException) {
-        message = "File already exists";
-      }
-    }
-    return message;
-  }
-
-  private static Origin extractOrigin(IOException e) {
-    Origin origin = Origin.unknown();
-
-    if (e instanceof FileSystemException) {
-      FileSystemException fse = (FileSystemException) e;
-      if (fse.getFile() != null && !fse.getFile().isEmpty()) {
-        origin = new PathOrigin(Paths.get(fse.getFile()));
-      }
-    }
-    return origin;
-  }
-
-  @Override
-  public Origin getOrigin() {
-    return origin;
-  }
-
-  @Override
-  public Position getPosition() {
-    return Position.UNKNOWN;
-  }
-
-  @Override
-  public String getDiagnosticMessage() {
-    return message;
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index ed7bbf8..dc16cac 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -15,8 +15,10 @@
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -139,7 +141,7 @@
     Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
     ProcessResult result =
         ToolHelper.forkD8(Paths.get("."), input.toString(), "--output", existingDir.toString());
-    assertEquals(0, result.exitCode);
+    assertEquals(result.toString(), 0, result.exitCode);
     assertTrue(Files.exists(classesFiles.get(0)));
     for (int i = 1; i < classesFiles.size(); i++) {
       Path file = classesFiles.get(i);
@@ -360,6 +362,62 @@
         .build());
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void errorOnEmptyClassfile() throws IOException, CompilationFailedException {
+    Path emptyFile = temp.getRoot().toPath().resolve("empty-file.class");
+    FileUtils.writeToFile(emptyFile, null, new byte[0]);
+    DiagnosticsChecker.checkErrorsContains(
+        "empty",
+        handler ->
+            D8.run(
+                D8Command.builder(handler)
+                    .addProgramFiles(emptyFile)
+                    .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+                    .build()));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void errorOnInvalidClassfileHeader() throws IOException, CompilationFailedException {
+    Path emptyFile = temp.getRoot().toPath().resolve("empty-file.class");
+    FileUtils.writeToFile(emptyFile, null, new byte[] {'C', 'A', 'F', 'E', 'B', 'A', 'B', 'F'});
+    DiagnosticsChecker.checkErrorsContains(
+        "header",
+        handler ->
+            D8.run(
+                D8Command.builder(handler)
+                    .addProgramFiles(emptyFile)
+                    .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+                    .build()));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void errorOnEmptyDex() throws IOException, CompilationFailedException {
+    Path emptyFile = temp.getRoot().toPath().resolve("empty-file.dex");
+    FileUtils.writeToFile(emptyFile, null, new byte[0]);
+    DiagnosticsChecker.checkErrorsContains(
+        "empty",
+        handler ->
+            D8.run(
+                D8Command.builder(handler)
+                    .addProgramFiles(emptyFile)
+                    .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+                    .build()));
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void errorOnInvalidDexHeader() throws IOException, CompilationFailedException {
+    Path emptyFile = temp.getRoot().toPath().resolve("empty-file.dex");
+    FileUtils.writeToFile(emptyFile, null, new byte[] {'C', 'A', 'F', 'E', 'B', 'A', 'B', 'E'});
+    DiagnosticsChecker.checkErrorsContains(
+        "header",
+        handler ->
+            D8.run(
+                D8Command.builder(handler)
+                    .addProgramFiles(emptyFile)
+                    .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+                    .build()));
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 7cd32c7..fc22895 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -40,6 +40,8 @@
     try {
       runner.run(handler);
     } catch (CompilationFailedException e) {
+      System.out.println("Expecting match for '" + snippet + "'");
+      System.out.println("StdErr:\n" + handler.errors);
       assertTrue(
           "Expected to find snippet '"
               + snippet
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f17359d..afa8c12 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -789,12 +789,6 @@
     return R8Command.builder(app).setProgramConsumer(DexIndexedConsumer.emptyConsumer());
   }
 
-  public static R8Command.Builder prepareR8CommandBuilder(
-      AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
-    return R8Command.builder(app, diagnosticsHandler)
-        .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-  }
-
   public static AndroidApp runR8(AndroidApp app) throws IOException, CompilationException {
     try {
       return runR8(prepareR8CommandBuilder(app).build());
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
new file mode 100644
index 0000000..36cdad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2018, 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.debug;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test to check that locals that are introduced in a block that is not hit, still start in the
+ * block where they first become visible.
+ *
+ * <p>See b/75251251 or b/78617758
+ */
+public class LocalsLiveAtBlockEntryDebugTest extends DebugTestBase {
+
+  final String className = "LocalsLiveAtEntry";
+  final String sourcefile = className + ".j";
+  final String methodName = "test";
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testCF() throws Throwable {
+    JasminBuilder builder = getBuilderForTest(className, methodName);
+    Path outdir = temp.newFolder().toPath();
+    builder.writeClassFiles(outdir);
+    CfDebugTestConfig config = new CfDebugTestConfig();
+    config.addPaths(outdir);
+    runTest(config);
+  }
+
+  @Test
+  @Ignore("b/78617758")
+  public void testD8() throws Throwable {
+    JasminBuilder builder = getBuilderForTest(className, methodName);
+    List<Path> outputs = builder.writeClassFiles(temp.newFolder().toPath());
+    runTest(new D8DebugTestConfig().compileAndAdd(temp, outputs));
+  }
+
+  private void runTest(DebugTestConfig config) throws Throwable {
+    DexInspector inspector =
+        new DexInspector(
+            (config instanceof CfDebugTestConfig)
+                ? Collections.singletonList(config.getPaths().get(1).resolve(className + ".class"))
+                : config.getPaths());
+    ClassSubject clazz = inspector.clazz(className);
+    MethodSubject method = clazz.method("void", methodName, ImmutableList.of("java.lang.Object"));
+    assertTrue(method.isPresent());
+    runDebugTest(
+        config,
+        className,
+        breakpoint(className, methodName),
+        run(),
+        checkLine(sourcefile, 1),
+        checkNoLocal("obj"),
+        stepOver(),
+        checkLine(sourcefile, 3),
+        checkLocal("obj"),
+        stepOver(),
+        checkLine(sourcefile, 100),
+        run());
+  }
+
+  private JasminBuilder getBuilderForTest(String testClassName, String testMethodName) {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass(testClassName);
+
+    clazz.addStaticMethod(
+        testMethodName,
+        ImmutableList.of("Ljava/lang/Object;"),
+        "V",
+        ".limit stack 2",
+        ".limit locals 3",
+        ".var 0 is obj L" + testClassName + "; from L1 to L3",
+        "L0:", // Preamble code that does not have any live locals (eg, no formals too!).
+        ".line 1",
+        " ldc 42",
+        " lookupswitch",
+        "   0: L1",
+        "   default: L2",
+        "L1:", // late introduction of formals.
+        ".line 2",
+        " aconst_null",
+        " pop",
+        "L2:", // target block with first visible location of locals.
+        ".line 3",
+        " aconst_null",
+        " pop",
+        " return",
+        "L3:");
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        ".line 100",
+        "aconst_null",
+        "invokestatic " + testClassName + "/" + testMethodName + "(Ljava/lang/Object;)V",
+        "return");
+
+    return builder;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
new file mode 100644
index 0000000..f03e6f6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2018, 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.ir.regalloc;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import org.junit.Test;
+
+public class B79405526 extends TestBase {
+  @Test
+  public void test() throws Exception {
+    AndroidApp app = compileWithD8(readClasses(TestClass.class));
+    DexInspector inspector = new DexInspector(app);
+    DexInspector.ClassSubject clazz = inspector.clazz(TestClass.class);
+    assertThat(clazz, isPresent());
+    // TODO(christofferqa): Ensure runOnArt checks that there are no verification errors, and then
+    // use runOnArt instead of runOnArtRaw.
+    ToolHelper.ProcessResult d8Result = runOnArtRaw(app, TestClass.class.getCanonicalName());
+    assertThat(d8Result.stderr, not(containsString("Verification error")));
+  }
+
+  private static class TestClass {
+    public void method() {
+      Object x = this;
+      TestClass y = this;
+      nop(null, getObject("", this));
+      TestClass z = getObject(null);
+      nop(null, getObject("", this, X.Y.getLong(getLong() - 0L)));
+      if (getBoolean()) {
+        z = getObject(this);
+        nop1(null, null);
+      }
+      nop2(null, null, null, null);
+    }
+
+    public static void nop(Object a, Object b, Object... c) {}
+
+    public static void nop1(Object... a) {}
+
+    private void nop2(Object a, Object b, Object c, Object d, Object... e) {}
+
+    public boolean getBoolean() {
+      return true;
+    }
+
+    public long getLong() {
+      return 0L;
+    }
+
+    private TestClass getObject(Object a) {
+      return null;
+    }
+
+    private static TestClass getObject(Object a, Object... c) {
+      return null;
+    }
+
+    public enum X {
+      Y {};
+
+      public long getLong(long var1) {
+        return 0L;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 515b1f9..c053e62 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -6,15 +6,16 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
@@ -40,20 +41,21 @@
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testNoMainDex() throws Exception {
     Reporter reporter = new Reporter();
     try {
       Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
-      R8Command command =
-          ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class), reporter)
-              .setMainDexListOutputPath(mainDexListOutput)
-              .build();
+      R8Command.builder(reporter)
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .addClassProgramData(
+              ToolHelper.getClassAsBytes(HelloWorldMain.class), Origin.unknown())
+          .setMainDexListOutputPath(mainDexListOutput)
+          .build();
     } catch (CompilationFailedException e) {
       assertEquals(1, reporter.errorCount);
-      return;
+      throw e;
     }
-    fail("Expected CompilationFailedException");
   }
 
   @Test
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 47bf7d2..0a7f122 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -74,7 +74,7 @@
   with open(version_file, 'r') as file:
     for line in file:
       if 'final String LABEL ' in line:
-        result = line[line.find('"v') + 2:]
+        result = line[line.find('"') + 1:]
         result = result[:result.find('"')]
         return result
   raise Exception('Unable to determine version.')