Add test of kotlin stdlib.

Currently the kotlin stdlib has local information that D8 will remove, or which
will cause the new frontend to fail compilation.

Bug: 110804489
Change-Id: I1bab67ca2d2e8596ec075192bb14d6fe5e9f5516
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 66664e6..3325219 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -64,9 +64,16 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.IntList;
-import java.util.HashMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.util.Printer;
@@ -78,28 +85,46 @@
  */
 public class CfPrinter {
 
+  private static final boolean PRINT_INSTRUCTION_INDEX = true;
+  private static final boolean PRINT_INLINE_LOCALS = true;
+
   private final String indent;
-  private final Map<CfLabel, String> labels;
+
+  // Sorted list of labels.
+  private final List<CfLabel> sortedLabels;
+  // Map from label to its sorted-order index.
+  private final Reference2IntMap<CfLabel> labelToIndex;
+  // Map from sorted-order label index to the locals live at that label.
+  private final List<List<LocalVariableInfo>> localsAtLabel;
 
   private final StringBuilder builder = new StringBuilder();
   private final ClassNameMapper mapper;
 
+  private int nextInstructionIndex = 0;
+  private final int instructionIndexSpace;
+
   /** Entry for printing single instructions without global knowledge (eg, label numbers). */
   public CfPrinter() {
     indent = "";
-    labels = null;
+    labelToIndex = null;
     mapper = null;
+    instructionIndexSpace = 0;
+
+    sortedLabels = Collections.emptyList();
+    localsAtLabel = Collections.emptyList();
   }
 
   /** Entry for printing a complete code object. */
   public CfPrinter(CfCode code, ClassNameMapper mapper) {
     this.mapper = mapper;
     indent = "  ";
-    labels = new HashMap<>();
-    int nextLabelNumber = 0;
+    instructionIndexSpace = ("" + code.getInstructions().size()).length();
+    labelToIndex = new Reference2IntOpenHashMap<>();
+    sortedLabels = new ArrayList<>();
     for (CfInstruction instruction : code.getInstructions()) {
       if (instruction instanceof CfLabel) {
-        labels.put((CfLabel) instruction, "L" + nextLabelNumber++);
+        labelToIndex.put((CfLabel) instruction, sortedLabels.size());
+        sortedLabels.add((CfLabel) instruction);
       }
     }
     builder.append(".method ");
@@ -108,7 +133,11 @@
     builder.append(".limit stack ").append(code.getMaxStack());
     newline();
     builder.append(".limit locals ").append(code.getMaxLocals());
-    for (LocalVariableInfo local : code.getLocalVariables()) {
+
+    List<LocalVariableInfo> localVariables = getSortedLocalVariables(code);
+    localsAtLabel = computeLocalsAtLabels(localVariables);
+
+    for (LocalVariableInfo local : localVariables) {
       DebugLocalInfo info = local.getLocal();
       newline();
       builder
@@ -150,6 +179,60 @@
     newline();
   }
 
+  private List<List<LocalVariableInfo>> computeLocalsAtLabels(
+      List<LocalVariableInfo> localVariables) {
+    if (!PRINT_INLINE_LOCALS) {
+      return null;
+    }
+    List<List<LocalVariableInfo>> localsAtLabel = new ArrayList<>(sortedLabels.size());
+    Set<LocalVariableInfo> openLocals = new HashSet<>();
+    ListIterator<LocalVariableInfo> nextLocalVariableEntry = localVariables.listIterator();
+    for (CfLabel orderedLabel : sortedLabels) {
+      int thisIndex = labelToIndex.getInt(orderedLabel);
+      openLocals.removeIf(o -> labelToIndex.getInt(o.getEnd()) <= thisIndex);
+      while (nextLocalVariableEntry.hasNext()) {
+        LocalVariableInfo next = nextLocalVariableEntry.next();
+        int startIndex = labelToIndex.getInt(next.getStart());
+        int endIndex = labelToIndex.getInt(next.getEnd());
+        if (startIndex <= thisIndex) {
+          if (thisIndex < endIndex) {
+            openLocals.add(next);
+          }
+        } else {
+          nextLocalVariableEntry.previous();
+          break;
+        }
+      }
+      ArrayList<LocalVariableInfo> locals = new ArrayList<>(openLocals);
+      locals.sort((a, b) -> Integer.compare(a.getIndex(), b.getIndex()));
+      localsAtLabel.add(locals);
+    }
+    return localsAtLabel;
+  }
+
+  @NotNull
+  private List<LocalVariableInfo> getSortedLocalVariables(CfCode code) {
+    List<LocalVariableInfo> localVariables = new ArrayList<>(code.getLocalVariables());
+    localVariables.sort(
+        (a, b) -> {
+          // Start with locals starting early.
+          int first =
+              Integer.compare(labelToIndex.getInt(a.getStart()), labelToIndex.getInt(b.getStart()));
+          if (first != 0) {
+            return first;
+          }
+          // Then locals with longer scope (ie, reverse order).
+          int second =
+              Integer.compare(labelToIndex.getInt(b.getEnd()), labelToIndex.getInt(a.getEnd()));
+          if (second != 0) {
+            return second;
+          }
+          // Finally, locals with smallest index.
+          return Integer.compare(a.getIndex(), b.getIndex());
+        });
+    return localVariables;
+  }
+
   private void print(String name) {
     indent();
     builder.append(name);
@@ -394,7 +477,17 @@
 
   public void print(CfLabel label) {
     newline();
+    instructionIndex();
     builder.append(getLabel(label)).append(':');
+    if (PRINT_INLINE_LOCALS) {
+      int labelNumber = labelToIndex.getInt(label);
+      List<LocalVariableInfo> locals = localsAtLabel.get(labelNumber);
+      appendComment(
+          "locals: "
+              + String.join(
+                  ", ",
+                  locals.stream().map(LocalVariableInfo::toString).collect(Collectors.toList())));
+    }
   }
 
   public void print(CfPosition instruction) {
@@ -549,7 +642,7 @@
   }
 
   private String getLabel(CfLabel label) {
-    return labels != null ? labels.get(label) : "L?";
+    return labelToIndex != null ? ("L" + labelToIndex.getInt(label)) : "L?";
   }
 
   private void newline() {
@@ -558,8 +651,15 @@
     }
   }
 
+  private void instructionIndex() {
+    if (PRINT_INSTRUCTION_INDEX) {
+      builder.append(String.format("%" + instructionIndexSpace + "d: ", nextInstructionIndex++));
+    }
+  }
+
   private void indent() {
     newline();
+    instructionIndex();
     builder.append(indent);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index a30c6e6..a36faab 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -194,16 +194,19 @@
           // Input stack:
           Slot value1 = state.pop();
           Slot value2 = state.pop();
-          assert !value2.type.isWide();
-          if (value1.type.isWide()) {
+          if (value1.type.isWide() && value2.type.isWide()) {
             // Input stack: ..., value2, value1
-            dupX1(builder, state, value1, value2);
             // Output stack: ..., value1, value2, value1
-            throw new Unimplemented("Building IR for Dup2X2 wide not supported");
+            dupX1(builder, state, value1, value2);
+          } else if (value1.type.isWide()) {
+            throw new Unimplemented("Building IR for Dup2X2 narrow,narrow,wide not supported");
+          } else if (value2.type.isWide()) {
+            throw new Unimplemented("Building IR for Dup2X2 wide,narrow,narrow not supported");
           } else {
-            throw new Unimplemented("Building IR for Dup2X2 narrow not supported");
+            throw new Unimplemented(
+                "Building IR for Dup2X2 narrow,narrow,narrow,narrow not supported");
           }
-          // break;
+          break;
         }
       case Swap:
         {
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
new file mode 100644
index 0000000..b40a9d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -0,0 +1,74 @@
+// 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.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinStdLibCompilationTest {
+
+  private static final Path kotlinStdLib =
+      Paths.get("third_party", "kotlin", "kotlinc", "lib", "kotlin-stdlib.jar");
+
+  private final boolean useCfFrontend;
+
+  @Parameters(name = "UseCf={0}")
+  public static Object[] setup() {
+    return new Object[] {true, false};
+  }
+
+  public KotlinStdLibCompilationTest(boolean useCfFrontend) {
+    this.useCfFrontend = useCfFrontend;
+  }
+
+  @Test
+  public void testD8() throws CompilationFailedException, IOException {
+    Assume.assumeFalse("b/110804489 New CF frontend fails on type conflict.", useCfFrontend);
+    KeepingDiagnosticHandler handler = new KeepingDiagnosticHandler();
+    ToolHelper.runD8(
+        D8Command.builder(handler)
+            .setMode(CompilationMode.DEBUG)
+            .addProgramFiles(kotlinStdLib)
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer()),
+        internalOptions -> internalOptions.enableCfFrontend = useCfFrontend);
+    // TODO(b/110804489): Find a way to recover from type-incorrect Kotlin frame markers.
+    assertTrue(
+        handler.infos.stream().anyMatch(d -> d.getDiagnosticMessage().contains("invalid locals")));
+  }
+
+  @Test
+  public void testR8CF() throws CompilationFailedException, IOException {
+    Assume.assumeFalse("b/110804489 New CF frontend fails on type conflict.", useCfFrontend);
+    KeepingDiagnosticHandler handler = new KeepingDiagnosticHandler();
+    ToolHelper.runR8(
+        R8Command.builder(handler)
+            .setMode(CompilationMode.DEBUG)
+            .addProgramFiles(kotlinStdLib)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+            .build(),
+        internalOptions -> internalOptions.enableCfFrontend = useCfFrontend);
+    // TODO(b/110804489): Find a way to recover from type-incorrect Kotlin frame markers.
+    assertTrue(
+        handler.infos.stream().anyMatch(d -> d.getDiagnosticMessage().contains("invalid locals")));
+  }
+}