Add debug local table to LIR.
Bug: b/225838009
Change-Id: I65d662b36fe818a4b569975a562fb79a4f10b2eb
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index b17536e..834fd24 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.lightir.LIRBuilder;
/**
* Instruction introducing an SSA value with attached local information.
@@ -87,4 +88,9 @@
assert verifyTypesHelper.isAssignable(src().getType(), getOutType());
return true;
}
+
+ @Override
+ public void buildLIR(LIRBuilder<Value, BasicBlock> builder) {
+ builder.addDebugLocalWrite(src());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
index 4e148ee..9bdc15a 100644
--- a/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/IR2LIRConverter.java
@@ -27,35 +27,45 @@
public class IR2LIRConverter {
- private IR2LIRConverter() {}
+ private final DexItemFactory factory;
+ private final IRCode irCode;
+ private final Reference2IntMap<BasicBlock> blocks = new Reference2IntOpenHashMap<>();
+ private final Reference2IntMap<Value> values = new Reference2IntOpenHashMap<>();
+ private final LIRBuilder<Value, BasicBlock> builder;
- public static LIRCode translate(IRCode irCode, DexItemFactory factory) {
- irCode.traceBlocks();
- Reference2IntMap<BasicBlock> blocks = new Reference2IntOpenHashMap<>();
- Reference2IntMap<Value> values = new Reference2IntOpenHashMap<>();
- int instructionIndex = 0;
- int valueIndex = 0;
- for (BasicBlock block : irCode.blocks) {
- blocks.put(block, instructionIndex);
- for (Phi phi : block.getPhis()) {
- values.put(phi, valueIndex);
- valueIndex++;
- instructionIndex++;
- }
- for (Instruction instruction : block.getInstructions()) {
- if (instruction.hasOutValue()) {
- values.put(instruction.outValue(), valueIndex);
- }
- valueIndex++;
- if (!instruction.isArgument()) {
- instructionIndex++;
- }
- }
- }
- LIRBuilder<Value, BasicBlock> builder =
+ private IR2LIRConverter(DexItemFactory factory, IRCode irCode) {
+ this.factory = factory;
+ this.irCode = irCode;
+ this.builder =
new LIRBuilder<Value, BasicBlock>(
irCode.context().getReference(), values::getInt, blocks::getInt, factory)
.setMetadata(irCode.metadata());
+ }
+
+ public static LIRCode translate(IRCode irCode, DexItemFactory factory) {
+ return new IR2LIRConverter(factory, irCode).internalTranslate();
+ }
+
+ private void recordBlock(BasicBlock block, int blockIndex) {
+ blocks.put(block, blockIndex);
+ }
+
+ private void recordValue(Value value, int valueIndex) {
+ values.put(value, valueIndex);
+ if (value.hasLocalInfo()) {
+ builder.setDebugValue(value.getLocalInfo(), valueIndex);
+ }
+ }
+
+ private LIRCode internalTranslate() {
+ irCode.traceBlocks();
+ computeBlockAndValueTables();
+ computeInstructions();
+ return builder.build();
+ }
+
+ private void computeInstructions() {
+ int currentInstructionIndex = 0;
BasicBlockIterator blockIt = irCode.listIterator();
while (blockIt.hasNext()) {
BasicBlock block = blockIt.next();
@@ -67,6 +77,7 @@
for (Phi phi : block.getPhis()) {
permuteOperands(phi.getOperands(), permutation, operands);
builder.addPhi(phi.getType(), Arrays.asList(operands));
+ currentInstructionIndex++;
}
}
if (block.hasCatchHandlers()) {
@@ -79,20 +90,47 @@
InstructionIterator it = block.iterator();
while (it.hasNext()) {
Instruction instruction = it.next();
+ assert !instruction.hasOutValue()
+ || currentInstructionIndex == values.getInt(instruction.outValue());
builder.setCurrentPosition(instruction.getPosition());
+ if (!instruction.getDebugValues().isEmpty()) {
+ builder.setDebugLocalEnds(currentInstructionIndex, instruction.getDebugValues());
+ }
+
if (instruction.isGoto()) {
BasicBlock nextBlock = blockIt.peekNext();
if (instruction.asGoto().getTarget() == nextBlock) {
builder.addFallthrough();
- } else {
- instruction.buildLIR(builder);
+ currentInstructionIndex++;
+ continue;
}
- } else {
- instruction.buildLIR(builder);
+ }
+ instruction.buildLIR(builder);
+ currentInstructionIndex++;
+ }
+ }
+ }
+
+ private void computeBlockAndValueTables() {
+ int instructionIndex = 0;
+ int valueIndex = 0;
+ for (BasicBlock block : irCode.blocks) {
+ recordBlock(block, instructionIndex);
+ for (Phi phi : block.getPhis()) {
+ recordValue(phi, valueIndex);
+ valueIndex++;
+ instructionIndex++;
+ }
+ for (Instruction instruction : block.getInstructions()) {
+ if (instruction.hasOutValue()) {
+ recordValue(instruction.outValue(), valueIndex);
+ }
+ valueIndex++;
+ if (!instruction.isArgument()) {
+ instructionIndex++;
}
}
}
- return builder.build();
}
private static void permuteOperands(List<Value> operands, int[] permutation, Value[] output) {
diff --git a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
index 056129b..28547c0 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIR2IRConverter.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DebugLocalWrite;
import com.android.tools.r8.ir.code.DebugPosition;
import com.android.tools.r8.ir.code.Div;
import com.android.tools.r8.ir.code.Goto;
@@ -192,18 +193,21 @@
return arguments;
}
+ public int toInstructionIndexInIR(int lirIndex) {
+ return lirIndex + code.getArgumentCount();
+ }
+
public int peekNextInstructionIndex() {
return nextInstructionIndex;
}
public Value getOutValueForNextInstruction(TypeElement type) {
- // TODO(b/225838009): Support debug locals.
- DebugLocalInfo localInfo = null;
- int index = peekNextInstructionIndex() + code.getArgumentCount();
- Value value = values[index];
+ int valueIndex = toInstructionIndexInIR(peekNextInstructionIndex());
+ DebugLocalInfo localInfo = code.getDebugLocalInfo(valueIndex);
+ Value value = values[valueIndex];
if (value == null) {
value = new Value(valueNumberGenerator.next(), type, localInfo);
- values[index] = value;
+ values[valueIndex] = value;
} else {
value.setType(type);
if (localInfo != null) {
@@ -214,17 +218,17 @@
}
public Phi getPhiForNextInstructionAndAdvanceState(TypeElement type) {
- int index = peekNextInstructionIndex() + code.getArgumentCount();
+ int valueIndex = toInstructionIndexInIR(peekNextInstructionIndex());
+ DebugLocalInfo localInfo = code.getDebugLocalInfo(valueIndex);
// TODO(b/225838009): The phi constructor implicitly adds to the block, so we need to ensure
// the block. However, we must grab the index above. Find a way to clean this up so it is
// uniform with instructions.
advanceInstructionState();
// Creating the phi implicitly adds it to currentBlock.
- DebugLocalInfo localInfo = null;
Phi phi =
new Phi(
valueNumberGenerator.next(), currentBlock, type, localInfo, RegisterReadType.NORMAL);
- Value value = values[index];
+ Value value = values[valueIndex];
if (value != null) {
// A fake ssa value has already been created, replace the users by the actual phi.
// TODO(b/225838009): We could consider encoding the value type as a bit in the value index
@@ -232,7 +236,7 @@
assert !value.isPhi();
value.replaceUsers(phi);
}
- values[index] = phi;
+ values[valueIndex] = phi;
return phi;
}
@@ -243,10 +247,18 @@
}
private void addInstruction(Instruction instruction) {
+ int index = toInstructionIndexInIR(peekNextInstructionIndex());
advanceInstructionState();
instruction.setPosition(currentPosition);
currentBlock.getInstructions().add(instruction);
instruction.setBlock(currentBlock);
+ int[] debugEndIndices = code.getDebugLocalEnds(index);
+ if (debugEndIndices != null) {
+ for (int debugEndIndex : debugEndIndices) {
+ Value debugValue = getValue(debugEndIndex);
+ debugValue.addDebugLocalEnd(instruction);
+ }
+ }
}
private void addThisArgument(DexType type) {
@@ -259,6 +271,10 @@
// which would otherwise advance the state.
Value dest = getValue(index);
dest.setType(type.toTypeElement(appView));
+ DebugLocalInfo localInfo = code.getDebugLocalInfo(index);
+ if (localInfo != null) {
+ dest.setLocalInfo(localInfo);
+ }
Argument argument = new Argument(dest, index, type.isBooleanType());
assert currentBlock != null;
assert currentPosition.isSyntheticPosition();
@@ -380,5 +396,15 @@
Value dest = getOutValueForNextInstruction(exceptionType.toTypeElement(appView));
addInstruction(new MoveException(dest, exceptionType, appView.options()));
}
+
+ @Override
+ public void onDebugLocalWrite(int srcIndex) {
+ Value src = getValue(srcIndex);
+ // The type is in the local table, so initialize it with bottom and reset with the local info.
+ Value dest = getOutValueForNextInstruction(TypeElement.getBottom());
+ TypeElement type = dest.getLocalInfo().type.toTypeElement(appView);
+ dest.setType(type);
+ addInstruction(new DebugLocalWrite(dest, src));
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
index 2971d2e..44876ed 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRBuilder.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
@@ -19,6 +20,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.lightir.LIRCode.DebugLocalInfoTable;
import com.android.tools.r8.lightir.LIRCode.PositionEntry;
import com.android.tools.r8.lightir.LIRCode.TryCatchTable;
import com.android.tools.r8.utils.ListUtils;
@@ -29,7 +31,9 @@
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.Set;
/**
* Builder for constructing LIR code from IR.
@@ -68,6 +72,11 @@
private final Int2ReferenceMap<CatchHandlers<Integer>> tryCatchRanges =
new Int2ReferenceOpenHashMap<>();
+ // Mapping from SSA value definition to the local name index in the constant pool.
+ private final Int2ReferenceMap<DebugLocalInfo> debugLocals = new Int2ReferenceOpenHashMap<>();
+ // Mapping from instruction to the end usage of SSA values with debug local info.
+ private final Int2ReferenceMap<int[]> debugLocalEnds = new Int2ReferenceOpenHashMap();
+
// TODO(b/225838009): Reconsider this fixed space as the operand count for phis is much larger.
// Pre-allocated space for caching value indexes when writing instructions.
private static final int MAX_VALUE_COUNT = 10;
@@ -87,6 +96,16 @@
flushedPosition = currentPosition;
}
+ public DexType toDexType(TypeElement typeElement) {
+ if (typeElement.isPrimitiveType()) {
+ return typeElement.asPrimitiveType().toDexType(factory);
+ }
+ if (typeElement.isReferenceType()) {
+ return typeElement.asReferenceType().toDexType(factory);
+ }
+ throw new Unreachable("Unexpected type element: " + typeElement);
+ }
+
public void addTryCatchHanders(int blockIndex, CatchHandlers<Integer> handlers) {
tryCatchRanges.put(blockIndex, handlers);
}
@@ -149,6 +168,23 @@
return this;
}
+ public LIRBuilder<V, B> setDebugValue(DebugLocalInfo debugInfo, int valueIndex) {
+ DebugLocalInfo old = debugLocals.put(valueIndex, debugInfo);
+ assert old == null;
+ return this;
+ }
+
+ public LIRBuilder<V, B> setDebugLocalEnds(int instructionIndex, Set<V> endValues) {
+ int size = endValues.size();
+ int[] indices = new int[size];
+ Iterator<V> iterator = endValues.iterator();
+ for (int i = 0; i < size; i++) {
+ indices[i] = getValueIndex(iterator.next());
+ }
+ debugLocalEnds.put(instructionIndex, indices);
+ return this;
+ }
+
public LIRBuilder<V, B> addArgument(int index, boolean knownToBeBoolean) {
// Arguments are implicitly given by method descriptor and not an actual instruction.
assert argumentCount == index;
@@ -387,18 +423,21 @@
}
public LIRBuilder<V, B> addPhi(TypeElement type, List<V> operands) {
- DexType dexType =
- type.isPrimitiveType()
- ? type.asPrimitiveType().toDexType(factory)
- : type.asReferenceType().toDexType(factory);
+ DexType dexType = toDexType(type);
return addInstructionTemplate(LIROpcodes.PHI, Collections.singletonList(dexType), operands);
}
+ public LIRBuilder<V, B> addDebugLocalWrite(V src) {
+ return addOneValueInstruction(LIROpcodes.DEBUGLOCALWRITE, src);
+ }
+
public LIRCode build() {
assert metadata != null;
int constantsCount = constants.size();
DexItem[] constantTable = new DexItem[constantsCount];
constants.forEach((item, index) -> constantTable[index] = item);
+ DebugLocalInfoTable debugTable =
+ debugLocals.isEmpty() ? null : new DebugLocalInfoTable(debugLocals, debugLocalEnds);
return new LIRCode(
metadata,
constantTable,
@@ -406,6 +445,7 @@
argumentCount,
byteWriter.toByteArray(),
instructionCount,
- new TryCatchTable(tryCatchRanges));
+ new TryCatchTable(tryCatchRanges),
+ debugTable);
}
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRCode.java b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
index 8caa8d6..09619f4 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRCode.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.lightir;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -40,6 +41,20 @@
}
}
+ public static class DebugLocalInfoTable {
+ private final Int2ReferenceMap<DebugLocalInfo> valueToLocalMap;
+ private final Int2ReferenceMap<int[]> instructionToEndUseMap;
+
+ public DebugLocalInfoTable(
+ Int2ReferenceMap<DebugLocalInfo> valueToLocalMap,
+ Int2ReferenceMap<int[]> instructionToEndUseMap) {
+ assert !valueToLocalMap.isEmpty();
+ assert !instructionToEndUseMap.isEmpty();
+ this.valueToLocalMap = valueToLocalMap;
+ this.instructionToEndUseMap = instructionToEndUseMap;
+ }
+ }
+
private final IRMetadata metadata;
/** Constant pool of items. */
@@ -59,6 +74,9 @@
/** Table of try-catch handlers for each basic block. */
private final TryCatchTable tryCatchTable;
+ /** Table of debug local information for each SSA value (if present). */
+ private final DebugLocalInfoTable debugLocalInfoTable;
+
public static <V, B> LIRBuilder<V, B> builder(
DexMethod method,
ValueIndexGetter<V> valueIndexGetter,
@@ -75,7 +93,8 @@
int argumentCount,
byte[] instructions,
int instructionCount,
- TryCatchTable tryCatchTable) {
+ TryCatchTable tryCatchTable,
+ DebugLocalInfoTable debugLocalInfoTable) {
this.metadata = metadata;
this.constants = constants;
this.positionTable = positions;
@@ -83,6 +102,7 @@
this.instructions = instructions;
this.instructionCount = instructionCount;
this.tryCatchTable = tryCatchTable;
+ this.debugLocalInfoTable = debugLocalInfoTable;
}
public int getArgumentCount() {
@@ -113,6 +133,20 @@
return tryCatchTable;
}
+ public DebugLocalInfoTable getDebugLocalInfoTable() {
+ return debugLocalInfoTable;
+ }
+
+ public DebugLocalInfo getDebugLocalInfo(int valueIndex) {
+ return debugLocalInfoTable == null ? null : debugLocalInfoTable.valueToLocalMap.get(valueIndex);
+ }
+
+ public int[] getDebugLocalEnds(int instructionIndex) {
+ return debugLocalInfoTable == null
+ ? null
+ : debugLocalInfoTable.instructionToEndUseMap.get(instructionIndex);
+ }
+
@Override
public LIRIterator iterator() {
return new LIRIterator(new ByteArrayIterator(instructions));
diff --git a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
index 0b0dfc8..abfea89 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIROpcodes.java
@@ -186,6 +186,7 @@
int PHI = 206;
int FALLTHROUGH = 207;
int MOVEEXCEPTION = 208;
+ int DEBUGLOCALWRITE = 209;
static String toString(int opcode) {
switch (opcode) {
@@ -498,6 +499,8 @@
return "FALLTHROUGH";
case MOVEEXCEPTION:
return "MOVEEXCEPTION";
+ case DEBUGLOCALWRITE:
+ return "DEBUGLOCALWRITE";
default:
throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
index 5c87faf..f0bc3f3 100644
--- a/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LIRParsedInstructionCallback.java
@@ -57,6 +57,8 @@
public void onMoveException(DexType exceptionType) {}
+ public void onDebugLocalWrite(int srcIndex) {}
+
public void onInvokeMethodInstruction(DexMethod method, IntList arguments) {}
public void onInvokeDirect(DexMethod method, IntList arguments) {
@@ -199,6 +201,12 @@
onMoveException(type);
return;
}
+ case LIROpcodes.DEBUGLOCALWRITE:
+ {
+ int srcIndex = view.getNextValueOperand();
+ onDebugLocalWrite(srcIndex);
+ return;
+ }
default:
throw new Unimplemented("No dispatch for opcode " + LIROpcodes.toString(opcode));
}
diff --git a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
index 1a8d281..2188ba6 100644
--- a/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LIRRoundtripTest.java
@@ -5,17 +5,17 @@
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.utils.AndroidApiLevel;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class LIRRoundtripTest extends TestBase {
+public class LIRRoundtripTest extends DebugTestBase {
static class TestClass {
public static void main(String[] args) {
@@ -64,9 +64,8 @@
.assertSuccessWithOutputLines("Hello, world!");
}
- // TODO(b/225838009): Support debug local info.
- @Test(expected = CompilationFailedException.class)
- public void testRoundtripDebug() throws Exception {
+ @Test
+ public void testRoundtripDebug() throws Throwable {
testForD8(parameters.getBackend())
.debug()
.setMinApi(AndroidApiLevel.B)
@@ -77,6 +76,17 @@
o.testing.roundtripThroughLIR = true;
})
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello, world!");
+ .assertSuccessWithOutputLines("Hello, world!")
+ .debugger(this::runDebugger);
+ }
+
+ private void runDebugger(DebugTestConfig config) throws Throwable {
+ runDebugTest(
+ config,
+ TestClass.class,
+ breakOnException(typeName(TestClass.class), "main", true, true),
+ run(),
+ checkLocals("args", "message"),
+ run());
}
}