Make DEX/JVM stepping behaviour even less different
Fixes: b/495501447
Change-Id: I7168a1b0e011be2acfc363b4786b2bb28942ab0e
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 4ff74e3..cb07299 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
@@ -182,7 +182,7 @@
} else {
throw new Unreachable("Unexpected result type " + outType());
}
- builder.add(this, instruction, moveResult);
+ builder.addInvokeAndMoveResult(this, instruction, moveResult);
} else {
builder.add(this, instruction);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index ff6589b..3aa130b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -62,6 +62,7 @@
import com.android.tools.r8.ir.code.InstructionList;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -129,6 +130,9 @@
// Keeps track of the previous non-fallthrough info added to the dex builder.
private Info previousNonFallthroughInfo;
+ // Keeps track of a move-result instruction that should be emitted at the following DebugPosition.
+ private DexInstruction pendingMoveResult;
+
// The number of ingoing and outgoing argument registers for the code.
private int inRegisterCount = 0;
private int outRegisterCount = 0;
@@ -181,6 +185,7 @@
instructionToInfo = new Info[instructionNumberToIndex(ir.numberRemainingInstructions())];
inRegisterCount = 0;
outRegisterCount = 0;
+ pendingMoveResult = null;
nextBlock = null;
}
@@ -336,6 +341,7 @@
TryInfo tryInfo = computeTryInfo(dexInstructions);
// Return the dex code.
+ assert pendingMoveResult == null;
DexCode code =
new DexCode(
registerAllocator.registersUsed(),
@@ -355,7 +361,8 @@
BasicBlock previousBlock, BasicBlock currentBlock) {
return previousBlock.exit().isGoto()
&& currentBlock.getPredecessors().size() == 1
- && currentBlock.getPredecessors().get(0) == previousBlock;
+ && currentBlock.getPredecessors().get(0) == previousBlock
+ && !previousBlock.hasCatchSuccessor(currentBlock);
}
@SuppressWarnings("ReferenceEquality")
@@ -750,10 +757,57 @@
add(ir, new FixedSizeInfo(ir, new DexNop()));
}
+ private Instruction skipDebugLocalChangeNotStartingNewLocals(Instruction next) {
+ while (next != null && next.isDebugLocalsChange()) {
+ if (!next.asDebugLocalsChange().getStarting().isEmpty()) {
+ return null;
+ }
+ next = next.getNext();
+ }
+ return next;
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private boolean hasDebugPositionForMoveResult(Invoke invoke) {
+ if (isBuildingForComparison()
+ || !options.debug
+ || options.disableAdditionalDebuggerSupport
+ || !options.ensureJvmCompatibleStepOutBehavior
+ || invoke.outValue().hasLocalInfo()) {
+ return false;
+ }
+ BasicBlock currentBlock = invoke.getBlock();
+ Instruction next = skipDebugLocalChangeNotStartingNewLocals(invoke.getNext());
+ if (next != null
+ && next.isGoto()
+ && next.asGoto().getTarget() == nextBlock
+ && isTrivialFallthroughTarget(currentBlock, nextBlock)) {
+ currentBlock = nextBlock;
+ next = skipDebugLocalChangeNotStartingNewLocals(currentBlock.entry());
+ }
+ return next != null && next.isDebugPosition();
+ }
+
+ public void addInvokeAndMoveResult(
+ Invoke invoke, DexInstruction dexInvoke, DexInstruction dexMoveResult) {
+ if (hasDebugPositionForMoveResult(invoke)) {
+ assert pendingMoveResult == null;
+ pendingMoveResult = dexMoveResult;
+ add(invoke, dexInvoke);
+ } else {
+ add(invoke, dexInvoke, dexMoveResult);
+ }
+ }
+
public void addDebugPosition(DebugPosition position) {
- // Remaining debug positions always require we emit an actual nop instruction.
- // See removeRedundantDebugPositions.
- addNop(position);
+ if (pendingMoveResult != null) {
+ add(position, new FixedSizeInfo(position, pendingMoveResult));
+ pendingMoveResult = null;
+ } else {
+ // Remaining debug positions always require we emit an actual nop instruction.
+ // See removeRedundantDebugPositions.
+ addNop(position);
+ }
}
public void add(Instruction instr, DexInstruction dex) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 00bc67a..2dd1b74 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -169,9 +169,18 @@
public void buildInstruction(
IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
updateCurrentCatchHandlers(instructionIndex, builder.appView.dexItemFactory());
- updateDebugPosition(instructionIndex, builder);
currentDexInstruction = code.instructions[instructionIndex];
- currentDexInstruction.buildIR(builder);
+ if (isMoveResult(currentDexInstruction)) {
+ // In DEX, a move-result instruction can have its own debug position entry (see
+ // hasDebugPositionForMoveResult in DexBuilder). In IR, move-result is merged into the
+ // preceding invoke instruction, so we must build the IR for move-result before emitting
+ // the debug position instruction.
+ currentDexInstruction.buildIR(builder);
+ updateDebugPosition(instructionIndex, builder);
+ } else {
+ updateDebugPosition(instructionIndex, builder);
+ currentDexInstruction.buildIR(builder);
+ }
}
@Override
diff --git a/src/test/java8/debug/com/android/tools/r8/debug/StepOutOfKotlinFunctionWhereKotlincAddedNopTest.java b/src/test/java8/debug/com/android/tools/r8/debug/StepOutOfKotlinFunctionWhereKotlincAddedNopTest.java
index c147a28..56570a1 100644
--- a/src/test/java8/debug/com/android/tools/r8/debug/StepOutOfKotlinFunctionWhereKotlincAddedNopTest.java
+++ b/src/test/java8/debug/com/android/tools/r8/debug/StepOutOfKotlinFunctionWhereKotlincAddedNopTest.java
@@ -50,11 +50,14 @@
.writeToZip();
}
- @Test
- public void test() throws Throwable {
+ private void runTest(
+ byte[] classFileData,
+ int expectedStepOutLine,
+ int expectedStepOutLineWithoutAdditionalDebuggerSupport)
+ throws Throwable {
runDebugTest(
testForRuntime(parameters)
- .addProgramClassFileData(dump())
+ .addProgramClassFileData(classFileData)
.debugConfig(parameters.getRuntime())
.addPaths(
parameters.isCfRuntime()
@@ -65,46 +68,59 @@
run(),
checkLine("B495501447.kt", 1),
stepOut(INTELLIJ_FILTER),
- checkLine("B495501447.kt", parameters.isCfRuntime() ? 4 : 5),
+ checkLine("B495501447.kt", expectedStepOutLine),
run());
+ if (parameters.isDexRuntime()) {
+ Path dex =
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(classFileData)
+ .setMinApi(parameters)
+ .compile()
+ .writeToZip();
+ runDebugTest(
+ testForD8(parameters.getBackend())
+ .addProgramFiles(dex)
+ .setMinApi(parameters)
+ .addOptionsModification(options -> options.passthroughDexCode = false)
+ .debugConfig(parameters.getRuntime())
+ .addPaths(kotlinStdlibDex),
+ "B495501447Kt",
+ breakpoint("B495501447Kt", "foo"),
+ run(),
+ checkLine("B495501447.kt", 1),
+ stepOut(INTELLIJ_FILTER),
+ checkLine("B495501447.kt", expectedStepOutLine),
+ run());
+ runDebugTest(
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(classFileData)
+ .setMinApi(parameters)
+ .addOptionsModification(options -> options.disableAdditionalDebuggerSupport = true)
+ .debugConfig(parameters.getRuntime())
+ .addPaths(kotlinStdlibDex),
+ "B495501447Kt",
+ breakpoint("B495501447Kt", "foo"),
+ run(),
+ checkLine("B495501447.kt", 1),
+ stepOut(INTELLIJ_FILTER),
+ checkLine("B495501447.kt", expectedStepOutLineWithoutAdditionalDebuggerSupport),
+ run());
+ }
+ }
+
+ @Test
+ public void test() throws Throwable {
+ runTest(dump(), 4, 5);
}
@Test
public void testTryCatch() throws Throwable {
- runDebugTest(
- testForRuntime(parameters)
- .addProgramClassFileData(dumpTryCatch())
- .debugConfig(parameters.getRuntime())
- .addPaths(
- parameters.isCfRuntime()
- ? KOTLINC_2_4_20.getCompiler().getKotlinStdlibJar()
- : kotlinStdlibDex),
- "B495501447Kt",
- breakpoint("B495501447Kt", "foo"),
- run(),
- checkLine("B495501447.kt", 1),
- stepOut(INTELLIJ_FILTER),
- checkLine("B495501447.kt", parameters.isCfRuntime() ? 5 : 6),
- run());
+ runTest(dumpTryCatch(), 5, 6);
}
@Test
public void testAssignToLocal() throws Throwable {
- runDebugTest(
- testForRuntime(parameters)
- .addProgramClassFileData(dumpAssignToLocal())
- .debugConfig(parameters.getRuntime())
- .addPaths(
- parameters.isCfRuntime()
- ? KOTLINC_2_4_20.getCompiler().getKotlinStdlibJar()
- : kotlinStdlibDex),
- "B495501447Kt",
- breakpoint("B495501447Kt", "foo"),
- run(),
- checkLine("B495501447.kt", 1),
- stepOut(INTELLIJ_FILTER),
- checkLine("B495501447.kt", 6),
- run());
+ runTest(dumpAssignToLocal(), 6, 6);
}
/*