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")));
+ }
+}