Merge "Do not replace assumevalue target twice."
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index dc3ae37..a2edd92 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -175,6 +175,9 @@
public String toString(DexEncodedMethod method, ClassNameMapper naming) {
StringBuilder builder = new StringBuilder();
+ if (method != null) {
+ builder.append(method.toSourceString()).append("\n");
+ }
builder.append("registers: ").append(registerSize);
builder.append(", inputs: ").append(incomingRegisterSize);
builder.append(", outputs: ").append(outgoingRegisterSize).append("\n");
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 39ec7d0..7bfa534 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -5,6 +5,8 @@
import com.android.tools.r8.ir.code.MoveType;
import com.google.common.collect.ImmutableMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -44,6 +46,7 @@
private boolean prologueEnd = false;
private boolean epilogueBegin = false;
private final Map<Integer, LocalEntry> locals = new HashMap<>();
+ private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>();
// Delayed construction of an entry. Is finalized once locals information has been collected.
private DexDebugEntry pending = null;
@@ -65,7 +68,7 @@
if (!method.accessFlags.isStatic()) {
DexString name = factory.thisName;
DexType type = method.method.getHolder();
- startLocal(argumentRegister, name, type, null);
+ startArgument(argumentRegister, name, type);
argumentRegister += MoveType.fromDexType(type).requiredRegisters();
}
DexType[] types = method.method.proto.parameters.values;
@@ -73,9 +76,9 @@
for (int i = 0; i < types.length; i++) {
// If null, the parameter has a parameterized type and the local is introduced in the stream.
if (names[i] != null) {
- startLocal(argumentRegister, names[i], types[i], null);
- argumentRegister += MoveType.fromDexType(types[i]).requiredRegisters();
+ startArgument(argumentRegister, names[i], types[i]);
}
+ argumentRegister += MoveType.fromDexType(types[i]).requiredRegisters();
}
currentLine = info.startLine;
for (DexDebugEvent event : info.events) {
@@ -83,6 +86,10 @@
}
}
+ public Int2ReferenceMap<DebugLocalInfo> getArguments() {
+ return arguments;
+ }
+
public void setFile(DexString file) {
currentFile = file;
}
@@ -104,6 +111,12 @@
epilogueBegin = true;
}
+ public void startArgument(int register, DexString name, DexType type) {
+ DebugLocalInfo argument = canonicalize(name, type, null);
+ arguments.put(register, argument);
+ getEntry(register).set(argument);
+ }
+
public void startLocal(int register, DexString name, DexType type, DexString signature) {
getEntry(register).set(canonicalize(name, type, signature));
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index dd1df27..968e560 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -349,6 +349,8 @@
List<Value> arguments = inlinee.collectArguments();
assert invoke.inValues().size() == arguments.size();
for (int i = 0; i < invoke.inValues().size(); i++) {
+ // TODO(zerny): Support inlining in --debug mode.
+ assert arguments.get(i).getDebugInfo() == null;
if ((i == 0) && (downcast != null)) {
Value invokeValue = invoke.inValues().get(0);
Value receiverValue = arguments.get(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 1abdb1d..7e05dc9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -231,8 +231,11 @@
StringBuilder builder = new StringBuilder();
builder.append("v");
builder.append(number);
+ if (getLocalInfo() != null) {
+ builder.append("(").append(getLocalInfo()).append(")");
+ }
builder.append(" <- phi");
- StringUtils.append(builder, ListUtils.map(operands, (Value operand) -> "v" + operand.number));
+ StringUtils.append(builder, ListUtils.map(operands, Value::toString));
return builder.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index b1cdcbc..ddc83f8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -509,7 +509,7 @@
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
- if (invoke.outValue() != null) {
+ if (invoke.outValue() != null && invoke.outValue().getLocalInfo() == null) {
DexEncodedMethod target = invoke.computeSingleTarget(appInfo.withSubtyping());
// We have a set of library classes with optimization information - consider those
// as well.
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 3ad6596..3ab5d7b 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -188,6 +188,29 @@
}
}
+ public static int stepEmptyForLoopBody1(int n) {
+ int i;
+ for (i = 0; i < n; i++) ;
+ return i;
+ }
+
+ public static int stepEmptyForLoopBody2(int n) {
+ int i;
+ for (i = 0; i < n; i++) {
+ // has a line but still empty...
+ }
+ return i;
+ }
+
+ public static int stepNonEmptyForLoopBody(int n) {
+ int i;
+ for (i = 0; i < n; i++)
+ nop();
+ return i;
+ }
+
+ public static void nop() {}
+
public static void main(String[] args) {
noLocals();
unusedLocals();
@@ -198,6 +221,9 @@
reverseRange(1,2,3,4,5,6,7);
new Locals().lotsOfArrayLength();
new Locals().foo(21);
+ stepEmptyForLoopBody1(3);
+ stepEmptyForLoopBody2(3);
+ stepNonEmptyForLoopBody(3);
}
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 1683557..6d6412b 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -640,18 +640,36 @@
Consumer<ArtCommandBuilder> extras,
DexVm version)
throws IOException {
- // Run art on original.
- for (String file : files1) {
- assertTrue("file1 " + file + " must exists", Files.exists(Paths.get(file)));
+ return checkArtOutputIdentical(
+ version,
+ mainClass,
+ extras,
+ ImmutableList.of(ListUtils.map(files1, Paths::get), ListUtils.map(files2, Paths::get)));
+ }
+
+ public static String checkArtOutputIdentical(
+ DexVm version,
+ String mainClass,
+ Consumer<ArtCommandBuilder> extras,
+ Collection<Collection<Path>> programs)
+ throws IOException {
+ for (Collection<Path> program : programs) {
+ for (Path path : program) {
+ assertTrue("File " + path + " must exist", Files.exists(path));
+ }
}
- String output1 = ToolHelper.runArtNoVerificationErrors(files1, mainClass, extras, version);
- // Run art on R8 processed version.
- for (String file : files2) {
- assertTrue("file2 " + file + " must exists", Files.exists(Paths.get(file)));
+ String output = null;
+ for (Collection<Path> program : programs) {
+ String result =
+ ToolHelper.runArtNoVerificationErrors(
+ ListUtils.map(program, Path::toString), mainClass, extras, version);
+ if (output != null) {
+ assertEquals(output, result);
+ } else {
+ output = result;
+ }
}
- String output2 = ToolHelper.runArtNoVerificationErrors(files2, mainClass, extras, version);
- assertEquals(output1, output2);
- return output1;
+ return output;
}
public static void runDex2Oat(Path file, Path outFile) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 6a5f738..57a1ce8 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -623,15 +623,24 @@
));
}
+ private void failNoLocal(String localName) {
+ Assert.fail(
+ "line " + getLineNumber() + ": Expected local '" + localName + "' not present");
+ }
+
public void checkLocal(String localName) {
Optional<Variable> localVar = JUnit3Wrapper
.getVariableAt(mirror, getLocation(), localName);
- Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
+ if (!localVar.isPresent()) {
+ failNoLocal(localName);
+ }
}
public void checkLocal(String localName, Value expectedValue) {
Optional<Variable> localVar = getVariableAt(mirror, getLocation(), localName);
- Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
+ if (!localVar.isPresent()) {
+ failNoLocal(localName);
+ }
// Get value
CommandPacket commandPacket = new CommandPacket(
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 1fd4bc9..16d1a60 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -348,4 +348,79 @@
run());
}
+ @Test
+ public void testStepEmptyForLoopBody1() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "stepEmptyForLoopBody1"),
+ run(),
+ checkLocal("n", Value.createInt(3)),
+ checkNoLocal("i"),
+ stepOver(),
+ checkLocal("n", Value.createInt(3)),
+ checkLocal("i", Value.createInt(3)),
+ run());
+ }
+
+ @Test
+ public void testStepEmptyForLoopBody2() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "stepEmptyForLoopBody2"),
+ run(),
+ checkLocal("n", Value.createInt(3)),
+ checkNoLocal("i"),
+ stepOver(),
+ checkLocal("n", Value.createInt(3)),
+ checkLocal("i", Value.createInt(3)),
+ run());
+ }
+
+ @Test
+ public void testStepNonEmptyForLoopBody() throws Throwable {
+ final int LOOP_HEADER_LINE = 207;
+ final int LOOP_BODY_LINE = 208;
+ final int RETURN_LINE = 209;
+ final Value N = Value.createInt(3);
+ final Value I0 = Value.createInt(0);
+ final Value I1 = Value.createInt(1);
+ final Value I2 = Value.createInt(2);
+ final Value I3 = Value.createInt(3);
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "stepNonEmptyForLoopBody"),
+ run(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkNoLocal("i"),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I0),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I0),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I1),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I1),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I2),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I2),
+ stepOver(),
+ checkLine(SOURCE_FILE, RETURN_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I3),
+ run());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java
new file mode 100644
index 0000000..5a83545
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java
@@ -0,0 +1,21 @@
+// 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.debuginfo;
+
+public class ConditionalLocalTest {
+
+ public void foo(int x) {
+ if (x % 2 != 0) {
+ Integer obj = new Integer(x + x);
+ long l = obj.longValue();
+ x = (int) l;
+ System.out.print(obj);
+ }
+ return;
+ }
+
+ public static void main(String[] args) {
+ new ConditionalLocalTest().foo(21);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
new file mode 100644
index 0000000..c793f3e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
@@ -0,0 +1,41 @@
+// 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.debuginfo;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Test;
+
+public class ConditionalLocalTestRunner extends DebugInfoTestBase {
+
+ @Test
+ public void testConditionalLocal() throws Exception {
+ Class clazz = ConditionalLocalTest.class;
+
+ AndroidApp d8App = compileWithD8(clazz);
+ AndroidApp dxApp = getDxCompiledSources();
+
+ String expected = "42";
+ assertEquals(expected, runOnJava(clazz));
+ assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
+ assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
+
+ checkConditonalLocal(inspectMethod(d8App, clazz, "void", "foo", "int"));
+ checkConditonalLocal(inspectMethod(dxApp, clazz, "void", "foo", "int"));
+ }
+
+ private void checkConditonalLocal(DebugInfoInspector info) {
+ String self = ConditionalLocalTest.class.getCanonicalName();
+ String Integer = "java.lang.Integer";
+ info.checkStartLine(6);
+ info.checkLineHasExactLocals(6, "this", self, "x", "int");
+ info.checkLineHasExactLocals(7, "this", self, "x", "int");
+ info.checkLineHasExactLocals(8, "this", self, "x", "int", "obj", Integer);
+ info.checkLineHasExactLocals(9, "this", self, "x", "int", "obj", Integer, "l", "long");
+ info.checkLineHasExactLocals(10, "this", self, "x", "int", "obj", Integer, "l", "long");
+ info.checkNoLine(11);
+ info.checkLineHasExactLocals(12, "this", self, "x", "int");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
index 8df6f79..8a5b77d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
@@ -22,11 +22,11 @@
assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
- checkBackBranchToSelf(inspectMethod(d8App, clazz, "int", "localsAtThrow", "int"));
- checkBackBranchToSelf(inspectMethod(dxApp, clazz, "int", "localsAtThrow", "int"));
+ checkLocalsAtThrow(inspectMethod(d8App, clazz, "int", "localsAtThrow", "int"));
+ checkLocalsAtThrow(inspectMethod(dxApp, clazz, "int", "localsAtThrow", "int"));
}
- private void checkBackBranchToSelf(DebugInfoInspector info) {
+ private void checkLocalsAtThrow(DebugInfoInspector info) {
info.checkStartLine(9);
info.checkLineHasExactLocals(9, "x", "int");
info.checkLineHasExactLocals(10, "x", "int", "a", "int");
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
new file mode 100644
index 0000000..d0ade31
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class JumboStringTests extends JasminTestBase {
+
+ // String constants are split into several class files to ensure both the constant-pool and
+ // instruction count are below the class-file limits.
+ private static int CLASSES_COUNT = 10;
+ private static int MIN_STRING_COUNT = Constants.FIRST_JUMBO_INDEX + 1;
+ private static int EXTRA_STRINGS_PER_CLASSES_COUNT = MIN_STRING_COUNT % CLASSES_COUNT;
+ private static int STRINGS_PER_CLASSES_COUNT =
+ EXTRA_STRINGS_PER_CLASSES_COUNT + MIN_STRING_COUNT / CLASSES_COUNT;
+
+ @Test
+ @Ignore("b/35701208")
+ public void test() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ LinkedHashMap<String, MethodSignature> classes = new LinkedHashMap<>(CLASSES_COUNT);
+ for (int i = 0; i < CLASSES_COUNT; i++) {
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test" + i);
+ List<String> lines = new ArrayList<>(STRINGS_PER_CLASSES_COUNT + 100);
+ lines.addAll(
+ ImmutableList.of(
+ ".limit locals 3",
+ ".limit stack 4",
+ ".var 0 is this LTest; from L0 to L2",
+ ".var 1 is i I from L0 to L2",
+ ".var 2 is strings [Ljava/lang/String; from L1 to L2",
+ "L0:",
+ ".line 1",
+ " ldc " + STRINGS_PER_CLASSES_COUNT,
+ " anewarray java/lang/String",
+ " astore 2",
+ "L1:",
+ ".line 2"));
+ for (int j = 0; j < STRINGS_PER_CLASSES_COUNT; j++) {
+ lines.add(" aload 2");
+ lines.add(" ldc " + j);
+ lines.add(" ldc \"string" + i + "_" + j + "\"");
+ lines.add(" aastore");
+ }
+ lines.addAll(
+ ImmutableList.of(
+ "L2:",
+ " .line 3",
+ " aload 2",
+ " iload 1",
+ " aaload",
+ " checkcast java/lang/String",
+ " areturn"));
+ MethodSignature foo =
+ clazz.addVirtualMethod(
+ "foo", ImmutableList.of("I"), "Ljava/lang/String;", lines.toArray(new String[0]));
+ classes.put(clazz.name, foo);
+ }
+
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+ clazz.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ " new Test0",
+ " dup",
+ " invokespecial Test0/<init>()V",
+ " ldc 42",
+ " invokevirtual Test0/foo(I)Ljava/lang/String;",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " swap",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " return");
+
+ String expected = "string0_42";
+ assertEquals(expected, runOnJava(builder, clazz.name));
+
+ AndroidApp jasminApp = builder.build();
+ AndroidApp d8App = ToolHelper.runD8(jasminApp);
+ assertEquals(expected, runOnArt(d8App, clazz.name));
+
+ DexInspector inspector = new DexInspector(d8App);
+ for (Entry<String, MethodSignature> entry : classes.entrySet()) {
+ DebugInfoInspector info = new DebugInfoInspector(inspector, entry.getKey(), entry.getValue());
+ info.checkStartLine(1);
+ // If jumbo-string processing fails to keep debug info, some methods will have lost 'i' here.
+ info.checkLineHasExactLocals(1, "this", entry.getKey(), "i", "int");
+ info.checkLineHasExactLocals(
+ 2, "this", entry.getKey(), "i", "int", "strings", "java.lang.String[]");
+ }
+ }
+}