Add --ir option to Disassemble

Extend AssemblyWriter with a boolean flag writeIR that makes it use
IRConverter and CfgPrinter to output the IR of each method before and
after optimization (instead of outputting the JAR/DEX disassembly).

Change-Id: Idffbc2e7b379f19ffbfb571f36240fc9605b9dbe
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 9b1599f..7b17ec8 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -33,6 +32,7 @@
       private Path proguardMapFile = null;
       private boolean useSmali = false;
       private boolean allInfo = false;
+      private boolean useIr;
 
       @Override
       Builder self() {
@@ -63,6 +63,11 @@
         return this;
       }
 
+      public Builder setUseIr(boolean useIr) {
+        this.useIr = useIr;
+        return this;
+      }
+
       @Override
       protected DisassembleCommand makeCommand() {
         // If printing versions ignore everything else.
@@ -74,24 +79,26 @@
             getOutputPath(),
             proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
             allInfo,
-            useSmali);
+            useSmali,
+            useIr);
       }
     }
 
-    static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-        "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.",
-        "  --version               # Print the version of r8.",
-        "  --help                  # Print this message."));
-
+    static final String USAGE_MESSAGE =
+        "Usage: disasm [options] <input-files>\n"
+            + " where <input-files> are dex files\n"
+            + " and options are:\n"
+            + "  --all                   # Include all information in disassembly.\n"
+            + "  --smali                 # Disassemble using smali syntax.\n"
+            + "  --ir                    # Print IR before and after optimization.\n"
+            + "  --pg-map <file>         # Proguard map <file> for mapping names.\n"
+            + "  --output                # Specify a file or directory to write to.\n"
+            + "  --version               # Print the version of r8.\n"
+            + "  --help                  # Print this message.";
 
     private final boolean allInfo;
     private final boolean useSmali;
+    private final boolean useIr;
 
     public static Builder builder() {
       return new Builder();
@@ -112,10 +119,12 @@
           builder.setPrintHelp(true);
         } else if (arg.equals("--version")) {
           builder.setPrintVersion(true);
-          } else if (arg.equals("--all")) {
+        } else if (arg.equals("--all")) {
           builder.setAllInfo(true);
         } else if (arg.equals("--smali")) {
           builder.setUseSmali(true);
+        } else if (arg.equals("--ir")) {
+          builder.setUseIr(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
         } else if (arg.equals("--output")) {
@@ -132,13 +141,18 @@
     }
 
     private DisassembleCommand(
-        AndroidApp inputApp, Path outputPath, StringResource proguardMap,
-        boolean allInfo, boolean useSmali) {
+        AndroidApp inputApp,
+        Path outputPath,
+        StringResource proguardMap,
+        boolean allInfo,
+        boolean useSmali,
+        boolean useIr) {
       super(inputApp);
       this.outputPath = outputPath;
       this.proguardMap = proguardMap;
       this.allInfo = allInfo;
       this.useSmali = useSmali;
+      this.useIr = useIr;
     }
 
     private DisassembleCommand(boolean printHelp, boolean printVersion) {
@@ -147,6 +161,7 @@
       proguardMap = null;
       allInfo = false;
       useSmali = false;
+      useIr = false;
     }
 
     public Path getOutputPath() {
@@ -157,6 +172,10 @@
       return useSmali;
     }
 
+    public boolean useIr() {
+      return useIr;
+    }
+
     @Override
     InternalOptions getInternalOptions() {
       InternalOptions internal = new InternalOptions();
@@ -190,9 +209,10 @@
     try {
       DexApplication application =
           new ApplicationReader(app, options, timing).read(command.proguardMap, executor);
-      DexByteCodeWriter writer = command.useSmali()
-          ? new SmaliWriter(application, options)
-          : new AssemblyWriter(application, options, command.allInfo);
+      DexByteCodeWriter writer =
+          command.useSmali()
+              ? new SmaliWriter(application, options)
+              : new AssemblyWriter(application, options, command.allInfo, command.useIr());
       if (command.getOutputPath() != null) {
         writer.write(command.getOutputPath());
       } else {
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 89abadd..23dbfbf 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -3,9 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.conversion.OptimizationFeedbackIgnore;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
 import java.io.PrintStream;
 
 public class AssemblyWriter extends DexByteCodeWriter {
@@ -13,12 +19,29 @@
   private final boolean writeAllClassInfo;
   private final boolean writeFields;
   private final boolean writeAnnotations;
+  private final boolean writeIR;
+  private final AppInfoWithSubtyping appInfo;
+  private final Timing timing = new Timing("AssemblyWriter");
+  private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
 
-  public AssemblyWriter(DexApplication application, InternalOptions options, boolean allInfo) {
+  public AssemblyWriter(
+      DexApplication application, InternalOptions options, boolean allInfo, boolean writeIR) {
     super(application, options);
     this.writeAllClassInfo = allInfo;
     this.writeFields = allInfo;
     this.writeAnnotations = allInfo;
+    this.writeIR = writeIR;
+    if (writeIR) {
+      this.appInfo = new AppInfoWithSubtyping(application.toDirect());
+      if (options.programConsumer == null) {
+        // Use class-file backend, since the CF frontend for testing does not support desugaring of
+        // synchronized methods for the DEX backend (b/109789541).
+        options.programConsumer = ClassFileConsumer.emptyConsumer();
+      }
+      options.outline.enabled = false;
+    } else {
+      this.appInfo = null;
+    }
   }
 
   @Override
@@ -88,10 +111,21 @@
     ps.println();
     Code code = method.getCode();
     if (code != null) {
-      ps.println(code.toString(method, naming));
+      if (writeIR) {
+        writeIR(method, ps);
+      } else {
+        ps.println(code.toString(method, naming));
+      }
     }
   }
 
+  private void writeIR(DexEncodedMethod method, PrintStream ps) {
+    CfgPrinter printer = new CfgPrinter();
+    new IRConverter(appInfo, options, timing, printer)
+        .processMethod(method, ignoreOptimizationFeedback, null, null, null);
+    ps.println(printer.toString());
+  }
+
   private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
     if (writeAnnotations) {
       if (!annotations.isEmpty()) {