Emit local changes accurately at all instruction points.
Bug: 63243012
Change-Id: I874308618da6366b79b68e5b8eb0c0e1b08aa447
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 9b62842..3535bfa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -184,14 +184,12 @@
}
int instructionNumber = 0;
for (Instruction insn : instructions) {
- StringUtils.appendLeftPadded(builder, Integer.toString(instructionNumber++), 5);
- builder.append(": ")
- .append(insn.toString(naming))
- .append('\n');
while (debugInfo != null && debugInfo.address == insn.getOffset()) {
builder.append(" ").append(debugInfo).append("\n");
debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
}
+ StringUtils.appendLeftPadded(builder, Integer.toString(instructionNumber++), 5);
+ builder.append(": ").append(insn.toString(naming)).append('\n');
}
if (debugInfoIterator.hasNext()) {
throw new Unreachable("Could not print all debug information.");
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 2342b3b..c468613 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -5,14 +5,18 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.DebugPosition;
-import com.google.common.collect.ImmutableMap;
+import com.android.tools.r8.ir.code.Instruction;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
/**
* Builder for constructing a list of debug events suitable for DexDebugInfo.
@@ -25,135 +29,226 @@
private static final int NO_PC_INFO = -1;
private static final int NO_LINE_INFO = -1;
- private static class PositionState {
- int pc = NO_PC_INFO;
- int line = NO_LINE_INFO;
- DexString file = null;
- ImmutableMap<Integer, DebugLocalInfo> locals = null;
- }
-
- private final DexMethod method;
-
- private final DexItemFactory dexItemFactory;
-
- // Previous and current position info to delay emitting position changes.
- private final PositionState previous;
- private final PositionState current;
+ private final DexEncodedMethod method;
+ private final DexItemFactory factory;
// In order list of non-this argument locals.
- private int lastArgumentRegister = -1;
- private final List<DebugLocalInfo> arguments;
-
- // Mapping from register to local for currently open/visible locals.
- private final Map<Integer, DebugLocalInfo> openLocals = new HashMap<>();
+ private ArrayList<DebugLocalInfo> arguments;
// Mapping from register to the last known local in that register (See DBG_RESTART_LOCAL).
- private final Map<Integer, DebugLocalInfo> lastKnownLocals = new HashMap<>();
+ private Int2ReferenceMap<DebugLocalInfo> lastKnownLocals;
- // Flushed events.
+ // Mapping from register to local for currently open/visible locals.
+ private Int2ReferenceMap<DebugLocalInfo> pendingLocals = null;
+
+ // Conservative pending-state of locals to avoid some equality checks on locals.
+ // pendingLocalChanges == true ==> localsEqual(emittedLocals, pendingLocals).
+ private boolean pendingLocalChanges = false;
+
+ // State of pc, line, file and locals in the emitted event stream.
+ private int emittedPc = NO_PC_INFO;
+ private int emittedLine = NO_LINE_INFO;
+ private DexString emittedFile = null;
+ private Int2ReferenceMap<DebugLocalInfo> emittedLocals;
+
+ // If lastMoveInstructionPc != NO_PC_INFO, then the last pc-advancing instruction was a
+ // move-exception at lastMoveInstructionPc. This is needed to maintain the art/dx specific
+ // behaviour that the move-exception pc is associated with the catch-declaration line.
+ // See debug.ExceptionTest.testStepOnCatch().
+ private int lastMoveInstructionPc = NO_PC_INFO;
+
+ // Emitted events.
private final List<DexDebugEvent> events = new ArrayList<>();
+ // Initial known line for the method.
private int startLine = NO_LINE_INFO;
- public DexDebugEventBuilder(DexMethod method, DexItemFactory dexItemFactory) {
+ public DexDebugEventBuilder(DexEncodedMethod method, DexItemFactory factory) {
this.method = method;
- this.dexItemFactory = dexItemFactory;
- arguments = new ArrayList<>(method.proto.parameters.values.length);
- current = new PositionState();
- previous = new PositionState();
+ this.factory = factory;
}
- public void startArgument(int register, DebugLocalInfo local, boolean isThis) {
- // Verify that arguments are started in order.
- assert register > lastArgumentRegister;
- lastArgumentRegister = register;
- // If this is an actual argument record it for header information.
- if (!isThis) {
- arguments.add(local);
- }
- // If the argument does not have a parametrized type, implicitly open it.
- if (local != null && local.signature == null) {
- openLocals.put(register, local);
- lastKnownLocals.put(register, local);
- }
+ // Public method for the DebugStripper.
+ public void setPosition(int pc, int line) {
+ emitDebugPosition(pc, line, null);
}
- /** Emits a positions entry if the position has changed and associates any local changes. */
- public void setPosition(int pc, DebugPosition position) {
- setPosition(pc, position.line, position.file, position.getLocals());
- }
-
- public void setPosition(
- int pc, int line, DexString file, ImmutableMap<Integer, DebugLocalInfo> locals) {
- // If we have a pending position and the next differs from it flush the pending one.
- if (previous.pc != current.pc && positionChanged(current, pc, line, file)) {
- flushCurrentPosition();
+ /** Add events at pc for instruction. */
+ public void add(int pc, Instruction instruction) {
+ // Initialize locals state on block entry.
+ if (instruction.getBlock().entry() == instruction) {
+ updateBlockEntry(instruction);
}
- current.pc = pc;
- current.line = line;
- current.file = file;
- current.locals = locals;
- }
+ assert pendingLocals != null;
- private void flushCurrentPosition() {
- // If this is the first emitted possition, initialize previous state: start-line is forced to be
- // the first actual line, in-effect, causing the first position to be a zero-delta line change.
- if (startLine == NO_LINE_INFO) {
- assert events.isEmpty();
- assert previous.pc == NO_PC_INFO;
- assert previous.line == NO_LINE_INFO;
- startLine = current.line;
- previous.line = current.line;
- previous.pc = 0;
+ // If this is a position emit and exit as it always emits events.
+ if (instruction.isDebugPosition()) {
+ emitDebugPosition(pc, instruction.asDebugPosition());
+ return;
}
- // Emit position change (which might result in additional advancement events).
- emitAdvancementEvents();
- // Emit local changes for new current position (they relate to the already emitted position).
- // Locals are either defined on all positions or on none.
- assert current.locals != null || previous.locals == null;
- if (current.locals != null) {
- emitLocalChanges();
+
+ if (instruction.isArgument()) {
+ startArgument(instruction.asArgument());
+ } else if (instruction.isDebugLocalsChange()) {
+ updateLocals(instruction.asDebugLocalsChange());
+ } else if (instruction.getBlock().exit() == instruction) {
+ // If this is the end of the block clear out the pending state and exit.
+ pendingLocals = null;
+ pendingLocalChanges = false;
+ return;
+ } else if (instruction.isMoveException()) {
+ lastMoveInstructionPc = pc;
+ } else {
+ // For non-exit / pc-advancing instructions emit any pending changes.
+ emitLocalChanges(pc);
}
}
/** Build the resulting DexDebugInfo object. */
public DexDebugInfo build() {
- if (previous.pc != current.pc) {
- flushCurrentPosition();
- }
+ assert pendingLocals == null;
+ assert !pendingLocalChanges;
if (startLine == NO_LINE_INFO) {
return null;
}
- DexString[] params = new DexString[method.proto.parameters.values.length];
- assert arguments.isEmpty() || params.length == arguments.size();
- for (int i = 0; i < arguments.size(); i++) {
- DebugLocalInfo local = arguments.get(i);
- params[i] = (local == null || local.signature != null) ? null : local.name;
+ DexString[] params = new DexString[method.method.proto.parameters.values.length];
+ if (arguments != null) {
+ assert params.length == arguments.size();
+ for (int i = 0; i < arguments.size(); i++) {
+ DebugLocalInfo local = arguments.get(i);
+ params[i] = (local == null || local.signature != null) ? null : local.name;
+ }
}
return new DexDebugInfo(startLine, params, events.toArray(new DexDebugEvent[events.size()]));
}
- private static boolean positionChanged(
- PositionState current, int nextPc, int nextLine, DexString nextFile) {
- return nextPc != current.pc && (nextLine != current.line || nextFile != current.file);
+ private void updateBlockEntry(Instruction instruction) {
+ assert pendingLocals == null;
+ assert !pendingLocalChanges;
+ Int2ReferenceMap<DebugLocalInfo> locals = instruction.getBlock().getLocalsAtEntry();
+ if (locals == null) {
+ pendingLocals = Int2ReferenceMaps.emptyMap();
+ } else {
+ pendingLocals = new Int2ReferenceOpenHashMap<>(locals);
+ pendingLocalChanges = true;
+ }
+ if (emittedLocals == null) {
+ initialize(locals);
+ }
}
- private void emitAdvancementEvents() {
- int pcDelta = current.pc - previous.pc;
- int lineDelta = current.line - previous.line;
+ private void initialize(Int2ReferenceMap<DebugLocalInfo> locals) {
+ assert arguments == null;
+ assert emittedLocals == null;
+ assert lastKnownLocals == null;
+ assert startLine == NO_LINE_INFO;
+ if (locals == null) {
+ emittedLocals = Int2ReferenceMaps.emptyMap();
+ lastKnownLocals = Int2ReferenceMaps.emptyMap();
+ return;
+ }
+ // Implicitly open all unparameterized arguments.
+ emittedLocals = new Int2ReferenceOpenHashMap<>();
+ for (Entry<DebugLocalInfo> entry : locals.int2ReferenceEntrySet()) {
+ if (entry.getValue().signature == null) {
+ emittedLocals.put(entry.getIntKey(), entry.getValue());
+ }
+ }
+ lastKnownLocals = new Int2ReferenceOpenHashMap<>(emittedLocals);
+ }
+
+ private void startArgument(Argument argument) {
+ if (arguments == null) {
+ arguments = new ArrayList<>(method.method.proto.parameters.values.length);
+ }
+ if (!argument.outValue().isThis()) {
+ arguments.add(argument.getLocalInfo());
+ }
+ }
+
+ private void updateLocals(DebugLocalsChange change) {
+ pendingLocalChanges = true;
+ for (Entry<DebugLocalInfo> end : change.getEnding().int2ReferenceEntrySet()) {
+ assert pendingLocals.get(end.getIntKey()) == end.getValue();
+ pendingLocals.remove(end.getIntKey());
+ }
+ for (Entry<DebugLocalInfo> start : change.getStarting().int2ReferenceEntrySet()) {
+ assert !pendingLocals.containsKey(start.getIntKey());
+ pendingLocals.put(start.getIntKey(), start.getValue());
+ }
+ }
+
+ private boolean localsChanged() {
+ if (!pendingLocalChanges) {
+ return false;
+ }
+ pendingLocalChanges = !localsEqual(emittedLocals, pendingLocals);
+ return pendingLocalChanges;
+ }
+
+ private void emitDebugPosition(int pc, DebugPosition position) {
+ emitDebugPosition(pc, position.line, position.file);
+ }
+
+ private void emitDebugPosition(int pc, int line, DexString file) {
+ int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
+ lastMoveInstructionPc = NO_PC_INFO;
+ // The position requires a pc change event and possible events for line, file and local changes.
+ // Verify that we do not ever produce two subsequent positions at the same pc.
+ assert emittedPc != emitPc;
+ if (startLine == NO_LINE_INFO) {
+ assert emittedLine == NO_LINE_INFO;
+ startLine = line;
+ emittedLine = line;
+ }
+ emitAdvancementEvents(emittedPc, emittedLine, emittedFile, emitPc, line, file, events, factory);
+ emittedPc = emitPc;
+ emittedLine = line;
+ emittedFile = file;
+ if (localsChanged()) {
+ emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
+ assert localsEqual(emittedLocals, pendingLocals);
+ }
+ pendingLocalChanges = false;
+ }
+
+ private void emitLocalChanges(int pc) {
+ // If pc advanced since the locals changed and locals indeed have changed, emit the changes.
+ if (localsChanged()) {
+ int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
+ lastMoveInstructionPc = NO_PC_INFO;
+ emitAdvancementEvents(
+ emittedPc, emittedLine, emittedFile, emitPc, emittedLine, emittedFile, events, factory);
+ emittedPc = emitPc;
+ emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
+ pendingLocalChanges = false;
+ assert localsEqual(emittedLocals, pendingLocals);
+ }
+ }
+
+ private static void emitAdvancementEvents(
+ int previousPc,
+ int previousLine,
+ DexString previousFile,
+ int nextPc,
+ int nextLine,
+ DexString nextFile,
+ List<DexDebugEvent> events,
+ DexItemFactory factory) {
+ int pcDelta = previousPc == NO_PC_INFO ? nextPc : nextPc - previousPc;
+ int lineDelta = nextLine == NO_LINE_INFO ? 0 : nextLine - previousLine;
assert pcDelta >= 0;
- if (current.file != previous.file) {
- assert current.file == null || !current.file.equals(previous.file);
- events.add(dexItemFactory.createSetFile(current.file));
+ if (nextFile != previousFile) {
+ events.add(factory.createSetFile(nextFile));
}
if (lineDelta < Constants.DBG_LINE_BASE
|| lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
- events.add(dexItemFactory.createAdvanceLine(lineDelta));
+ events.add(factory.createAdvanceLine(lineDelta));
// TODO(herhut): To be super clever, encode only the part that is above limit.
lineDelta = 0;
}
if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
- events.add(dexItemFactory.createAdvancePC(pcDelta));
+ events.add(factory.createAdvancePC(pcDelta));
pcDelta = 0;
}
// TODO(herhut): Maybe only write this one if needed (would differ from DEX).
@@ -161,37 +256,65 @@
0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
assert specialOpcode >= 0x0a;
assert specialOpcode <= 0xff;
- events.add(dexItemFactory.createDefault(specialOpcode));
- previous.pc = current.pc;
- previous.line = current.line;
- previous.file = current.file;
+ events.add(factory.createDefault(specialOpcode));
}
- private void emitLocalChanges() {
- if (previous.locals == current.locals) {
- return;
- }
- SortedSet<Integer> currentRegisters = new TreeSet<>(openLocals.keySet());
- SortedSet<Integer> positionRegisters = new TreeSet<>(current.locals.keySet());
- for (Integer register : currentRegisters) {
- if (!positionRegisters.contains(register)) {
- events.add(dexItemFactory.createEndLocal(register));
- openLocals.remove(register);
+ public static void emitLocalChangeEvents(
+ Int2ReferenceMap<DebugLocalInfo> previousLocals,
+ Int2ReferenceMap<DebugLocalInfo> nextLocals,
+ Int2ReferenceMap<DebugLocalInfo> lastKnownLocals,
+ List<DexDebugEvent> events,
+ DexItemFactory factory) {
+ Int2ReferenceSortedMap<DebugLocalInfo> ending = new Int2ReferenceAVLTreeMap<>();
+ Int2ReferenceSortedMap<DebugLocalInfo> starting = new Int2ReferenceAVLTreeMap<>();
+ for (Entry<DebugLocalInfo> entry : previousLocals.int2ReferenceEntrySet()) {
+ int register = entry.getIntKey();
+ DebugLocalInfo local = entry.getValue();
+ if (nextLocals.get(register) != local) {
+ ending.put(register, local);
}
}
- for (Integer register : positionRegisters) {
- DebugLocalInfo positionLocal = current.locals.get(register);
- DebugLocalInfo currentLocal = openLocals.get(register);
- if (currentLocal != positionLocal) {
- openLocals.put(register, positionLocal);
- if (currentLocal == null && lastKnownLocals.get(register) == positionLocal) {
- events.add(dexItemFactory.createRestartLocal(register));
- } else {
- events.add(new StartLocal(register, positionLocal));
- lastKnownLocals.put(register, positionLocal);
- }
+ for (Entry<DebugLocalInfo> entry : nextLocals.int2ReferenceEntrySet()) {
+ int register = entry.getIntKey();
+ DebugLocalInfo local = entry.getValue();
+ if (previousLocals.get(register) != local) {
+ starting.put(register, local);
}
}
- previous.locals = current.locals;
+ assert !ending.isEmpty() || !starting.isEmpty();
+ for (Entry<DebugLocalInfo> end : ending.int2ReferenceEntrySet()) {
+ int register = end.getIntKey();
+ if (!starting.containsKey(register)) {
+ previousLocals.remove(register);
+ events.add(factory.createEndLocal(register));
+ }
+ }
+ for (Entry<DebugLocalInfo> start : starting.int2ReferenceEntrySet()) {
+ int register = start.getIntKey();
+ DebugLocalInfo local = start.getValue();
+ previousLocals.put(register, local);
+ if (lastKnownLocals.get(register) == local) {
+ events.add(factory.createRestartLocal(register));
+ } else {
+ events.add(new StartLocal(register, local));
+ lastKnownLocals.put(register, local);
+ }
+ }
+ }
+
+ private static boolean localsEqual(
+ Int2ReferenceMap<DebugLocalInfo> locals1, Int2ReferenceMap<DebugLocalInfo> locals2) {
+ if (locals1 == locals2) {
+ return true;
+ }
+ if (locals1.size() != locals2.size()) {
+ return false;
+ }
+ for (Int2ReferenceMap.Entry<DebugLocalInfo> entry : locals1.int2ReferenceEntrySet()) {
+ if (locals2.get(entry.getIntKey()) != entry.getValue()) {
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index b9508b5..46149e7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -4,13 +4,16 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -31,6 +34,16 @@
*/
public class BasicBlock {
+ private Int2ReferenceMap<DebugLocalInfo> localsAtEntry;
+
+ public void setLocalsAtEntry(Int2ReferenceMap<DebugLocalInfo> localsAtEntry) {
+ this.localsAtEntry = localsAtEntry;
+ }
+
+ public Int2ReferenceMap<DebugLocalInfo> getLocalsAtEntry() {
+ return localsAtEntry;
+ }
+
public enum ThrowingInfo {
NO_THROW, CAN_THROW
}
@@ -770,6 +783,11 @@
} else {
builder.append("no phis\n");
}
+ if (localsAtEntry != null) {
+ builder.append("locals: ");
+ StringUtils.append(builder, localsAtEntry.int2ReferenceEntrySet(), ", ", BraceType.NONE);
+ builder.append("\n");
+ }
for (Instruction instruction : instructions) {
StringUtils.appendLeftPadded(builder, Integer.toString(instruction.getNumber()), 6);
builder.append(": ");
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 6c5fbb7..0da5b43 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -90,7 +90,7 @@
@Override
public void buildDex(DexBuilder builder) {
if (!dest().needsRegister()) {
- builder.addFallThrough(this);
+ builder.addNop(this);
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
new file mode 100644
index 0000000..a08b044
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -0,0 +1,84 @@
+// 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.ir.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+
+public class DebugLocalsChange extends Instruction {
+
+ private final Int2ReferenceMap<DebugLocalInfo> ending;
+ private final Int2ReferenceMap<DebugLocalInfo> starting;
+
+ public DebugLocalsChange(
+ Int2ReferenceMap<DebugLocalInfo> ending, Int2ReferenceMap<DebugLocalInfo> starting) {
+ super(null);
+ this.ending = ending;
+ this.starting = starting;
+ }
+
+ public Int2ReferenceMap<DebugLocalInfo> getEnding() {
+ return ending;
+ }
+
+ public Int2ReferenceMap<DebugLocalInfo> getStarting() {
+ return starting;
+ }
+
+ @Override
+ public boolean isDebugLocalsChange() {
+ return true;
+ }
+
+ @Override
+ public DebugLocalsChange asDebugLocalsChange() {
+ return this;
+ }
+
+ @Override
+ public void buildDex(DexBuilder builder) {
+ builder.addNop(this);
+ }
+
+ @Override
+ public boolean identicalNonValueParts(Instruction other) {
+ assert other.isDebugLocalsChange();
+ return false;
+ }
+
+ @Override
+ public int compareNonValueParts(Instruction other) {
+ assert other.isDebugLocalsChange();
+ return 0;
+ }
+
+ @Override
+ public int maxInValueRegister() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public int maxOutValueRegister() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(super.toString());
+ builder.append("ending: ");
+ StringUtils.append(builder, ending.int2ReferenceEntrySet());
+ builder.append(", starting: ");
+ StringUtils.append(builder, starting.int2ReferenceEntrySet());
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index e7917c6..ee6b65f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -4,18 +4,14 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableMap;
public class DebugPosition extends Instruction {
public final int line;
public final DexString file;
- private ImmutableMap<Integer, DebugLocalInfo> locals;
public DebugPosition(int line, DexString file) {
super(null);
@@ -72,18 +68,6 @@
builder.append(file).append(":");
}
builder.append(line);
- if (locals != null && !locals.isEmpty()) {
- builder.append(", locals: ");
- StringUtils.append(builder, locals.values());
- }
return builder.toString();
}
-
- public void setLocals(ImmutableMap<Integer, DebugLocalInfo> locals) {
- this.locals = locals;
- }
-
- public ImmutableMap<Integer, DebugLocalInfo> getLocals() {
- return locals;
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 0c4697d..083100b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -713,6 +713,7 @@
public boolean isDebugInstruction() {
return isDebugPosition()
+ || isDebugLocalsChange()
|| isDebugLocalWrite()
|| isDebugLocalUninitialized();
}
@@ -725,6 +726,14 @@
return null;
}
+ public boolean isDebugLocalsChange() {
+ return false;
+ }
+
+ public DebugLocalsChange asDebugLocalsChange() {
+ return null;
+ }
+
public boolean isDebugLocalUninitialized() {
return false;
}
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 fc1eab5..de19fdd 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
@@ -59,6 +59,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -192,27 +193,16 @@
} while (!ifsNeedingRewrite.isEmpty());
// Build instructions.
- DexDebugEventBuilder debugEventBuilder =
- new DexDebugEventBuilder(ir.method.method, dexItemFactory);
+ DexDebugEventBuilder debugEventBuilder = new DexDebugEventBuilder(ir.method, dexItemFactory);
List<Instruction> dexInstructions = new ArrayList<>(numberOfInstructions);
int instructionOffset = 0;
InstructionIterator instructionIterator = ir.instructionIterator();
- int lastMoveExceptionOffset = -1;
while (instructionIterator.hasNext()) {
com.android.tools.r8.ir.code.Instruction ir = instructionIterator.next();
Info info = getInfo(ir);
int previousInstructionCount = dexInstructions.size();
info.addInstructions(this, dexInstructions);
-
- if (ir.isArgument()) {
- int register = registerAllocator.getRegisterForValue(ir.outValue(), ir.getNumber());
- debugEventBuilder.startArgument(register, ir.getLocalInfo(), ir.outValue().isThis());
- } else if (ir.isDebugPosition()) {
- int pc = lastMoveExceptionOffset >= 0 ? lastMoveExceptionOffset : instructionOffset;
- debugEventBuilder.setPosition(pc, ir.asDebugPosition());
- }
- lastMoveExceptionOffset = ir.isMoveException() ? instructionOffset : -1;
-
+ debugEventBuilder.add(instructionOffset, ir);
if (previousInstructionCount < dexInstructions.size()) {
while (previousInstructionCount < dexInstructions.size()) {
Instruction instruction = dexInstructions.get(previousInstructionCount++);
@@ -332,13 +322,8 @@
public void addGoto(com.android.tools.r8.ir.code.Goto jump) {
if (jump.getTarget() != nextBlock) {
add(jump, new GotoInfo(jump));
- return;
- }
- List<com.android.tools.r8.ir.code.Instruction> instructions = jump.getBlock().getInstructions();
- if (instructions.size() > 1) {
- addFallThroughOrNop(jump, instructions.get(instructions.size() - 2), nextBlock.entry());
} else {
- addFallThrough(jump);
+ addNop(jump);
}
}
@@ -351,30 +336,41 @@
add(move, new MoveInfo(move));
}
- public void addFallThrough(com.android.tools.r8.ir.code.Instruction instruction) {
+ public void addNop(com.android.tools.r8.ir.code.Instruction instruction) {
add(instruction, new FallThroughInfo(instruction));
}
- private void addFallThroughOrNop(
- com.android.tools.r8.ir.code.Instruction key,
- com.android.tools.r8.ir.code.Instruction instruction,
- com.android.tools.r8.ir.code.Instruction nextInstruction) {
- if (nextInstruction != null
- && instruction.isDebugPosition()
- && nextInstruction.isDebugPosition()) {
- add(key, new FixedSizeInfo(key, new Nop()));
- } else {
- addFallThrough(key);
- }
+ private boolean isNopInstruction(com.android.tools.r8.ir.code.Instruction instruction) {
+ return instruction.isDebugLocalsChange()
+ || (instruction.isConstNumber() && !instruction.outValue().needsRegister());
}
public void addDebugPosition(DebugPosition position) {
BasicBlock block = position.getBlock();
- int nextIndex = block.getInstructions().indexOf(position) + 1;
- List<com.android.tools.r8.ir.code.Instruction> instructions = block.getInstructions();
- com.android.tools.r8.ir.code.Instruction nextInstruction =
- nextIndex < instructions.size() ? instructions.get(nextIndex) : null;
- addFallThroughOrNop(position, position, nextInstruction);
+ int blockIndex = ir.blocks.indexOf(block);
+ Iterator<com.android.tools.r8.ir.code.Instruction> iterator =
+ block.listIterator(1 + block.getInstructions().indexOf(position));
+
+ com.android.tools.r8.ir.code.Instruction next = null;
+ while (next == null) {
+ next = iterator.next();
+ while (isNopInstruction(next)) {
+ next = iterator.next();
+ }
+ if (next.isGoto()) {
+ ++blockIndex;
+ BasicBlock nextBlock = blockIndex < ir.blocks.size() ? ir.blocks.get(blockIndex) : null;
+ if (next.asGoto().getTarget() == nextBlock) {
+ iterator = nextBlock.iterator();
+ next = null;
+ }
+ }
+ }
+ if (next.isDebugPosition()) {
+ add(position, new FixedSizeInfo(position, new Nop()));
+ } else {
+ addNop(position);
+ }
}
public void add(com.android.tools.r8.ir.code.Instruction ir, Instruction dex) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index eb0aa77..13f2d29 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.ir.code.Add;
import com.android.tools.r8.ir.code.And;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.DebugPosition;
+import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -32,8 +32,12 @@
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntArraySet;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
@@ -268,10 +272,8 @@
ranges.sort(LocalRange::compareTo);
// For each debug position compute the set of live locals.
- // The previously known locals is used to share locals when unchanged.
boolean localsChanged = false;
- ImmutableMap<Integer, DebugLocalInfo> previousLocals = ImmutableMap.of();
- Map<Integer, DebugLocalInfo> currentLocals = new HashMap<>();
+ Int2ReferenceMap<DebugLocalInfo> currentLocals = new Int2ReferenceOpenHashMap<>();
LinkedList<LocalRange> openRanges = new LinkedList<>();
Iterator<LocalRange> rangeIterator = ranges.iterator();
@@ -282,54 +284,89 @@
currentLocals.put(nextStartingRange.register, nextStartingRange.local);
openRanges.add(nextStartingRange);
nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
- localsChanged = true;
}
for (BasicBlock block : blocks) {
- Iterator<Instruction> instructionIterator = block.getInstructions().iterator();
+ boolean blockEntry = true;
+ ListIterator<Instruction> instructionIterator = block.listIterator();
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
- if (!instruction.isDebugPosition()) {
- continue;
- }
int index = instruction.getNumber();
ListIterator<LocalRange> it = openRanges.listIterator(0);
+ Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
+ Int2ReferenceMap<DebugLocalInfo> starting = new Int2ReferenceOpenHashMap<>();
while (it.hasNext()) {
LocalRange openRange = it.next();
if (openRange.end <= index) {
it.remove();
assert currentLocals.get(openRange.register) == openRange.local;
- currentLocals.put(openRange.register, null);
+ currentLocals.remove(openRange.register);
localsChanged = true;
+ ending.put(openRange.register, openRange.local);
}
}
while (nextStartingRange != null && nextStartingRange.start <= index) {
// If the full range is between the two debug positions ignore it.
if (nextStartingRange.end > index) {
openRanges.add(nextStartingRange);
- assert currentLocals.get(nextStartingRange.register) == null;
+ assert !currentLocals.containsKey(nextStartingRange.register);
currentLocals.put(nextStartingRange.register, nextStartingRange.local);
+ starting.put(nextStartingRange.register, nextStartingRange.local);
localsChanged = true;
}
nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
}
- DebugPosition position = instruction.asDebugPosition();
- if (localsChanged) {
- ImmutableMap.Builder<Integer, DebugLocalInfo> locals = ImmutableMap.builder();
- for (Map.Entry<Integer, DebugLocalInfo> entry : currentLocals.entrySet()) {
- if (entry.getValue() != null) {
- locals.put(entry);
+ if (blockEntry) {
+ blockEntry = false;
+ block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
+ } else if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
+ DebugLocalsChange change = createLocalsChange(ending, starting);
+ if (change != null) {
+ if (instruction.isDebugPosition() || instruction.isJumpInstruction()) {
+ instructionIterator.previous();
+ instructionIterator.add(new DebugLocalsChange(ending, starting));
+ instructionIterator.next();
+ } else {
+ instructionIterator.add(new DebugLocalsChange(ending, starting));
}
}
- localsChanged = false;
- previousLocals = locals.build();
}
- assert verifyLocalsEqual(previousLocals, currentLocals);
- position.setLocals(previousLocals);
+ localsChanged = false;
}
}
}
+ private DebugLocalsChange createLocalsChange(
+ Int2ReferenceMap<DebugLocalInfo> ending, Int2ReferenceMap<DebugLocalInfo> starting) {
+ if (ending.isEmpty() || starting.isEmpty()) {
+ return new DebugLocalsChange(ending, starting);
+ }
+ IntSet unneeded = new IntArraySet(Math.min(ending.size(), starting.size()));
+ for (Int2ReferenceMap.Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
+ if (starting.get(entry.getIntKey()) == entry.getValue()) {
+ unneeded.add(entry.getIntKey());
+ }
+ }
+ if (unneeded.size() == ending.size() && unneeded.size() == starting.size()) {
+ return null;
+ }
+ IntIterator iterator = unneeded.iterator();
+ while (iterator.hasNext()) {
+ int key = iterator.nextInt();
+ ending.remove(key);
+ starting.remove(key);
+ }
+ return new DebugLocalsChange(ending, starting);
+ }
+
+ private boolean shouldEmitChangesAtInstruction(Instruction instruction) {
+ BasicBlock block = instruction.getBlock();
+ // We emit local changes on all non-exit instructions or, since we have only a singe return
+ // block, any exits directly targeting that.
+ return instruction != block.exit()
+ || (instruction.isGoto() && instruction.asGoto().getTarget() == code.getNormalExitBlock());
+ }
+
private boolean verifyLocalsEqual(
ImmutableMap<Integer, DebugLocalInfo> a, Map<Integer, DebugLocalInfo> b) {
int size = 0;
diff --git a/src/main/java/com/android/tools/r8/optimize/DebugStripper.java b/src/main/java/com/android/tools/r8/optimize/DebugStripper.java
index 9d39d29..3f83606 100644
--- a/src/main/java/com/android/tools/r8/optimize/DebugStripper.java
+++ b/src/main/java/com/android/tools/r8/optimize/DebugStripper.java
@@ -3,14 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize;
-import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEntry;
import com.android.tools.r8.graph.DexDebugEventBuilder;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -19,7 +17,6 @@
import com.android.tools.r8.naming.MemberNaming.Range;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.List;
@@ -65,8 +62,8 @@
}
}
- private NumberedDebugInfo processDebugInfo(DexMethod method, DexDebugInfo info,
- MemberNaming naming, int startLine) {
+ private NumberedDebugInfo processDebugInfo(
+ DexEncodedMethod method, DexDebugInfo info, MemberNaming naming, int startLine) {
if (info == null || naming == null) {
return new NumberedDebugInfo(0, null);
}
@@ -75,13 +72,8 @@
// that pertains to a different method body.
Range currentRange = naming.topLevelRange;
DexDebugEventBuilder builder = new DexDebugEventBuilder(method, dexItemFactory);
- // Always start with a no-op bytecode to make sure that the start-line is manifested by
- // the Dalvik VM and the event based processing in R8. This also avoids empty bytecode
- // sequences.
- int entryCount = 1;
- DexString file = null;
- ImmutableMap<Integer, DebugLocalInfo> locals = null;
- builder.setPosition(0, startLine, file, locals);
+ // Below we insert the start-line at pc-0 except if another entry already defines pc-0.
+ int entryCount = 0;
for (DexDebugEntry entry : info.computeEntries()) {
boolean addEntry = false;
// We are in a range, check whether we have left it.
@@ -98,13 +90,21 @@
}
}
if (addEntry) {
+ if (entryCount == 0 && entry.address > 0) {
+ ++entryCount;
+ builder.setPosition(0, startLine);
+ }
int line = options.skipDebugLineNumberOpt
? entry.line
: startLine + ranges.indexOf(currentRange) + 1;
- builder.setPosition(entry.address, line, file, locals);
+ builder.setPosition(entry.address, line);
++entryCount;
}
}
+ if (entryCount == 0) {
+ ++entryCount;
+ builder.setPosition(0, startLine);
+ }
return new NumberedDebugInfo(entryCount, builder.build());
}
@@ -133,8 +133,8 @@
}
}
- NumberedDebugInfo numberedInfo = processDebugInfo(
- encodedMethod.method, originalInfo, naming, startLine);
+ NumberedDebugInfo numberedInfo =
+ processDebugInfo(encodedMethod, originalInfo, naming, startLine);
DexDebugInfo newInfo = numberedInfo.info;
if (!options.skipDebugLineNumberOpt) {
// Fix up the line information.
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 d10357c..a272eca 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -73,7 +73,7 @@
ART
}
- // Set to true to run tests with java
+ // Set to JAVA to run tests with java
private static final RuntimeKind RUNTIME_KIND = RuntimeKind.ART;
// Set to true to enable verbose logs
@@ -280,6 +280,13 @@
return inspect(t -> t.checkLocal(localName, expectedValue));
}
+ protected final JUnit3Wrapper.Command checkNoLocal(String localName) {
+ return inspect(t -> {
+ List<String> localNames = t.getLocalNames();
+ Assert.assertFalse("Unexpected local: " + localName, localNames.contains(localName));
+ });
+ }
+
protected final JUnit3Wrapper.Command checkNoLocal() {
return inspect(t -> {
List<String> localNames = t.getLocalNames();
diff --git a/src/test/java/com/android/tools/r8/debug/ExceptionTest.java b/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
index 3d4202b..7ca9eae 100644
--- a/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
@@ -14,22 +14,31 @@
@Test
public void testStepOnCatch() throws Throwable {
- int catchLine;
if (isRunningJava()) {
// Java jumps to first instruction of the catch handler, matching the source code.
- catchLine = 11;
+ runDebugTest("Exceptions",
+ breakpoint("Exceptions", "catchException"),
+ run(),
+ checkLine(SOURCE_FILE, 9), // line of the method call throwing the exception
+ stepOver(),
+ checkLine(SOURCE_FILE, 11), // first line in the catch handler
+ checkLocal("e"),
+ run());
} else {
// ART/Dalvik jumps to 'move-exception' which initializes the local variable with the pending
// exception. Thus it is "attached" to the line declaring the exception in the catch handler.
- catchLine = 10;
+ runDebugTest("Exceptions",
+ breakpoint("Exceptions", "catchException"),
+ run(),
+ checkLine(SOURCE_FILE, 9), // line of the method call throwing the exception
+ stepOver(),
+ checkLine(SOURCE_FILE, 10), // line of the catch declaration
+ checkNoLocal("e"),
+ stepOver(),
+ checkLine(SOURCE_FILE, 11), // first line in the catch handler
+ checkLocal("e"),
+ run());
}
- runDebugTest("Exceptions",
- breakpoint("Exceptions", "catchException"),
- run(),
- checkLine(SOURCE_FILE, 9), // line of the method call throwing the exception
- stepOver(),
- checkLine(SOURCE_FILE, catchLine), // line of the catch declaration
- run());
}
}
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 810ec25..6a6a20f 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -285,7 +285,7 @@
run());
}
- @Test(expected = Throwable.class)
+ @Test
public void testInvokeRangeLong() throws Throwable {
final int initialValueOfX = 21;
final long expectedValueOfL = (long) initialValueOfX * 2;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index 368a8fc..ad6f925 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -111,8 +111,10 @@
private int checkLines(int line, Consumer<DexDebugEntry> check) {
int found = 0;
- for (DexDebugEntry entry : entries) {
- if (entry.line == line) {
+ for (int i = 0; i < entries.size(); i++) {
+ DexDebugEntry entry = entries.get(i);
+ // Matches each entry at 'line' that is not a zero-line increment.
+ if (entry.line == line && (i == 0 || entries.get(i - 1).line != line)) {
found++;
check.accept(entry);
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index d53b6d6..72bbaed 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -161,12 +161,7 @@
info.checkStartLine(1);
info.checkLineHasExactLocals(1, "this", "Test");
info.checkLineHasExactLocals(2, "this", "Test", "x", "int");
- // TODO(zerny): Reintroduce this check once we compute exact local changes. b/63243012
- // Without the exact local changes between the last debug position and the return we fail to
- // end the local scope of 'x'.
- if (false) {
- info.checkLineHasExactLocals(3, "this", "Test");
- }
+ info.checkLineHasExactLocals(3, "this", "Test");
String artResult = runOnArt(d8App, clazz.name);
assertEquals(expected, artResult);