Merge "Minor refactor of -identifiernamestring warnings"
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index b4a6009..22bbf46 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCode.LiveAtEntrySets;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.Phi;
@@ -25,7 +26,6 @@
import java.util.Map;
import java.util.NavigableSet;
import java.util.PriorityQueue;
-import java.util.Set;
import java.util.TreeSet;
/**
@@ -41,7 +41,7 @@
private final InternalOptions options;
// Mapping from basic blocks to the set of values live at entry to that basic block.
- private Map<BasicBlock, Set<Value>> liveAtEntrySets;
+ private Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets;
// List of all top-level live intervals for all SSA values.
private final List<LiveIntervals> liveIntervals = new ArrayList<>();
@@ -106,7 +106,7 @@
ImmutableList<BasicBlock> blocks = computeLivenessInformation();
performLinearScan();
if (options.debug) {
- LinearScanRegisterAllocator.computeDebugInfo(blocks, liveIntervals, this);
+ LinearScanRegisterAllocator.computeDebugInfo(blocks, liveIntervals, this, liveAtEntrySets);
}
}
@@ -252,10 +252,10 @@
}
public void addToLiveAtEntrySet(BasicBlock block, Collection<Phi> phis) {
- liveAtEntrySets.get(block).addAll(phis);
+ liveAtEntrySets.get(block).liveValues.addAll(phis);
}
public Collection<Value> getLocalsAtBlockEntry(BasicBlock block) {
- return liveAtEntrySets.get(block);
+ return liveAtEntrySets.get(block).liveValues;
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 644f2c2..71900a9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -41,7 +41,8 @@
@Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
- state.setPosition(position);
- builder.addDebugPosition(position);
+ Position canonical = code.getCanonicalPosition(position);
+ state.setPosition(canonical);
+ builder.addDebugPosition(canonical);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index d588ab5..bd67a45 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -65,6 +65,11 @@
public CfLabel getEnd() {
return end;
}
+
+ @Override
+ public String toString() {
+ return "" + index + " => " + local;
+ }
}
private final DexMethod method;
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 6e4c464..ff126c6 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
@@ -14,8 +14,10 @@
*
* <p>All instructions may have attached local information (defined as the local information of
* their outgoing value). This instruction is needed to mark a transition of an existing value (with
- * a possible local attached) to a new value that has a local (possibly the same one). If all
- * ingoing values end up having the same local this can be safely removed.
+ * a possible local attached) to a new value that has a local (possibly the same one). Even if the
+ * debug info of the ingoing value is equal to that of the outgoing value, the write may still be
+ * needed since an explicit end may have ended the visiblity range of the local which now becomes
+ * visible again.
*
* <p>For valid debug info, this instruction should have at least one debug user, denoting the end
* of its range, and thus it should be live.
@@ -25,7 +27,6 @@
public DebugLocalWrite(Value dest, Value src) {
super(dest, src);
assert dest.hasLocalInfo();
- assert dest.getLocalInfo() != src.getLocalInfo() || src.isPhi();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 72728a8..0e2bc36 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -3,6 +3,7 @@
// 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.graph.DexEncodedMethod;
import com.android.tools.r8.utils.CfgPrinter;
@@ -25,6 +26,35 @@
public class IRCode {
+ public static class LiveAtEntrySets {
+ // Set of live SSA values (regardless of whether they denote a local variable).
+ public final Set<Value> liveValues;
+
+ // Subset of live local-variable values.
+ public final Set<Value> liveLocalValues;
+
+ public LiveAtEntrySets(Set<Value> liveValues, Set<Value> liveLocalValues) {
+ assert liveValues.containsAll(liveLocalValues);
+ this.liveValues = liveValues;
+ this.liveLocalValues = liveLocalValues;
+ }
+
+ @Override
+ public int hashCode() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ LiveAtEntrySets other = (LiveAtEntrySets) o;
+ return liveValues.equals(other.liveValues) && liveLocalValues.equals(other.liveLocalValues);
+ }
+
+ public boolean isEmpty() {
+ return liveValues.isEmpty() && liveLocalValues.isEmpty();
+ }
+ }
+
// Stack marker to denote when all successors of a block have been processed when topologically
// sorting.
private static class BlockMarker {
@@ -72,8 +102,8 @@
/**
* Compute the set of live values at the entry to each block using a backwards data-flow analysis.
*/
- public Map<BasicBlock, Set<Value>> computeLiveAtEntrySets() {
- Map<BasicBlock, Set<Value>> liveAtEntrySets = new IdentityHashMap<>();
+ public Map<BasicBlock, LiveAtEntrySets> computeLiveAtEntrySets() {
+ Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets = new IdentityHashMap<>();
Queue<BasicBlock> worklist = new ArrayDeque<>();
// Since this is a backwards data-flow analysis we process the blocks in reverse
// topological order to reduce the number of iterations.
@@ -82,24 +112,37 @@
while (!worklist.isEmpty()) {
BasicBlock block = worklist.poll();
Set<Value> live = new HashSet<>();
+ Set<Value> liveLocals = new HashSet<>();
for (BasicBlock succ : block.getSuccessors()) {
- Set<Value> succLiveAtEntry = liveAtEntrySets.get(succ);
- if (succLiveAtEntry != null) {
- live.addAll(succLiveAtEntry);
+ LiveAtEntrySets liveAtSucc = liveAtEntrySets.get(succ);
+ if (liveAtSucc != null) {
+ live.addAll(liveAtSucc.liveValues);
+ liveLocals.addAll(liveAtSucc.liveLocalValues);
}
int predIndex = succ.getPredecessors().indexOf(block);
for (Phi phi : succ.getPhis()) {
- live.add(phi.getOperand(predIndex));
+ Value operand = phi.getOperand(predIndex);
+ live.add(operand);
+ // TODO(zerny): Assert that operand.hasLocalInfo iff phi.hasLocalInfo
+ if (operand.hasLocalInfo()) {
+ liveLocals.add(operand);
+ }
assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+ assert phi.getDebugValues().stream().allMatch(Value::hasLocalInfo);
live.addAll(phi.getDebugValues());
+ liveLocals.addAll(phi.getDebugValues());
}
}
- ListIterator<Instruction> iterator =
- block.getInstructions().listIterator(block.getInstructions().size());
- while (iterator.hasPrevious()) {
- Instruction instruction = iterator.previous();
- if (instruction.outValue() != null) {
- live.remove(instruction.outValue());
+ Iterator<Instruction> iterator = block.getInstructions().descendingIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ Value outValue = instruction.outValue();
+ if (outValue != null) {
+ live.remove(outValue);
+ assert outValue.hasLocalInfo() || !liveLocals.contains(outValue);
+ if (outValue.hasLocalInfo()) {
+ liveLocals.remove(outValue);
+ }
}
for (Value use : instruction.inValues()) {
if (use.needsRegister()) {
@@ -107,15 +150,22 @@
}
}
assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
+ assert instruction.getDebugValues().stream().allMatch(Value::hasLocalInfo);
live.addAll(instruction.getDebugValues());
+ liveLocals.addAll(instruction.getDebugValues());
}
for (Phi phi : block.getPhis()) {
live.remove(phi);
+ assert phi.hasLocalInfo() || !liveLocals.contains(phi);
+ if (phi.hasLocalInfo()) {
+ liveLocals.remove(phi);
+ }
}
- Set<Value> previousLiveAtEntry = liveAtEntrySets.put(block, live);
+ LiveAtEntrySets liveAtEntry = new LiveAtEntrySets(live, liveLocals);
+ LiveAtEntrySets previousLiveAtEntry = liveAtEntrySets.put(block, liveAtEntry);
// If the live-at-entry set changed, add the predecessors to the worklist if they are not
// already there.
- if (previousLiveAtEntry == null || !previousLiveAtEntry.equals(live)) {
+ if (previousLiveAtEntry == null || !previousLiveAtEntry.equals(liveAtEntry)) {
for (BasicBlock pred : block.getPredecessors()) {
if (!worklist.contains(pred)) {
worklist.add(pred);
@@ -123,8 +173,9 @@
}
}
}
- assert liveAtEntrySets.get(sorted.get(0)).size() == 0
- : "Unexpected values live at entry to first block: " + liveAtEntrySets.get(sorted.get(0));
+ assert liveAtEntrySets.get(sorted.get(0)).isEmpty()
+ : "Unexpected values live at entry to first block: "
+ + liveAtEntrySets.get(sorted.get(0)).liveValues;
return liveAtEntrySets;
}
@@ -495,9 +546,14 @@
}
public boolean consistentBlockNumbering() {
- return blocks.stream()
+ blocks
+ .stream()
.collect(Collectors.groupingBy(BasicBlock::getNumber, Collectors.counting()))
- .entrySet().stream().noneMatch((bb2count) -> bb2count.getValue() > 1);
+ .forEach(
+ (key, value) -> {
+ assert value == 1;
+ });
+ return true;
}
private boolean consistentBlockInstructions() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 7892afb..5f62c6c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -366,46 +366,116 @@
}
@Override
+ public void buildBlockTransfer(
+ IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+ if (predecessorOffset == IRBuilder.INITIAL_BLOCK_OFFSET) {
+ return;
+ }
+ if (currentInstructionIndex != predecessorOffset) {
+ // If transfer is not still in the same block, then update the state to that of the successor.
+ // The builder's lookup of local variables relies on this state for starting locals.
+ currentInstructionIndex = successorOffset;
+ state.reset(incomingState.get(currentInstructionIndex), false);
+ setLocalVariableLists();
+ // The transfer has not yet taken place, so the current position is that of the predecessor.
+ int positionOffset = predecessorOffset;
+ List<CfInstruction> instructions = code.getInstructions();
+ CfInstruction instruction = instructions.get(positionOffset);
+ while (0 < positionOffset && !(instruction instanceof CfPosition)) {
+ instruction = instructions.get(--positionOffset);
+ }
+ if (instruction instanceof CfPosition) {
+ CfPosition position = (CfPosition) instruction;
+ state.setPosition(getCanonicalPosition(position.getPosition()));
+ } else {
+ state.setPosition(canonicalPositions.getPreamblePosition());
+ }
+ }
+ // Manually compute the local variable change for the block transfer.
+ LocalVariableList atSource = getLocalVariables(predecessorOffset);
+ LocalVariableList atTarget = getLocalVariables(successorOffset);
+ if (!isExceptional) {
+ for (Entry<DebugLocalInfo> entry : atSource.locals.int2ObjectEntrySet()) {
+ if (atTarget.locals.get(entry.getIntKey()) != entry.getValue()) {
+ builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
+ }
+ }
+ }
+ for (Entry<DebugLocalInfo> entry : atTarget.locals.int2ObjectEntrySet()) {
+ if (atSource.locals.get(entry.getIntKey()) != entry.getValue()) {
+ builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
+ }
+ }
+ }
+
+ @Override
public void buildInstruction(
IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
CfInstruction instruction = code.getInstructions().get(instructionIndex);
currentInstructionIndex = instructionIndex;
if (firstBlockInstruction) {
currentBlockInfo = builder.getCFG().get(instructionIndex);
+ if (instructionIndex == 0 && currentBlockInfo == null) {
+ // If the entry block is also a target the actual entry block is at offset -1.
+ currentBlockInfo = builder.getCFG().get(IRBuilder.INITIAL_BLOCK_OFFSET);
+ }
state.reset(incomingState.get(instructionIndex), instructionIndex == 0);
}
+ assert currentBlockInfo != null;
setLocalVariableLists();
- readEndingLocals(builder);
- if (currentBlockInfo != null && instruction.canThrow()) {
+
+ if (instruction.canThrow()) {
Snapshot exceptionTransfer =
state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
for (int target : currentBlockInfo.exceptionalSuccessors) {
recordStateForTarget(target, exceptionTransfer);
}
}
- if (isControlFlow(instruction)) {
- ensureDebugValueLivenessControl(builder);
- instruction.buildIR(builder, state, this);
- Snapshot stateSnapshot = state.getSnapshot();
- for (int target : getTargets(instructionIndex)) {
- recordStateForTarget(target, stateSnapshot);
+
+ boolean localsChanged = localsChanged();
+ boolean hasNextInstructionInCurrentBlock =
+ instructionIndex + 1 != instructionCount()
+ && !builder.getCFG().containsKey(instructionIndex + 1);
+
+ if (instruction.isReturn() || instruction instanceof CfThrow) {
+ // Ensure that all live locals are marked as ending on method exits.
+ assert currentBlockInfo.normalSuccessors.isEmpty();
+ if (currentBlockInfo.exceptionalSuccessors.isEmpty()) {
+ incomingLocals.forEach(builder::addDebugLocalEnd);
+ } else if (!incomingLocals.isEmpty()) {
+ // If the throw instruction does not exit the method, we must end (ensure liveness of) all
+ // locals that are not kept live by at least one of the exceptional successors.
+ Int2ReferenceMap<DebugLocalInfo> live = new Int2ReferenceOpenHashMap<>();
+ for (int successorOffset : currentBlockInfo.exceptionalSuccessors) {
+ live.putAll(getLocalVariables(successorOffset).locals);
+ }
+ for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+ if (live.get(entry.getIntKey()) != entry.getValue()) {
+ builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
+ }
+ }
}
- state.clear();
- } else {
- if (instruction instanceof CfPosition) {
- CfPosition cfPosition = (CfPosition) instruction;
- Position position = cfPosition.getPosition();
- CfPosition newCfPosition =
- new CfPosition(cfPosition.getLabel(), getCanonicalPosition(position));
- newCfPosition.buildIR(builder, state, this);
- } else {
- instruction.buildIR(builder, state, this);
- }
- ensureDebugValueLiveness(builder);
- if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
- recordStateForTarget(currentInstructionIndex + 1, state.getSnapshot());
- }
+ } else if (localsChanged && hasNextInstructionInCurrentBlock) {
+ endLocals(builder);
}
+
+ build(instruction, builder);
+ if (!hasNextInstructionInCurrentBlock) {
+ Snapshot stateSnapshot = state.getSnapshot();
+ if (isControlFlow(instruction)) {
+ for (int target : getTargets(instructionIndex)) {
+ recordStateForTarget(target, stateSnapshot);
+ }
+ } else {
+ recordStateForTarget(instructionIndex + 1, stateSnapshot);
+ }
+ } else if (localsChanged) {
+ startLocals(builder);
+ }
+ }
+
+ private void build(CfInstruction instruction, IRBuilder builder) {
+ instruction.buildIR(builder, state, this);
}
private void recordStateForTarget(int target, Snapshot snapshot) {
@@ -521,13 +591,24 @@
}
}
- private void readEndingLocals(IRBuilder builder) {
- if (!outgoingLocals.equals(incomingLocals)) {
- // Add reads of locals ending after the current instruction.
- for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
- if (!entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
- builder.addDebugLocalRead(entry.getIntKey(), entry.getValue());
- }
+ private boolean localsChanged() {
+ return !incomingLocals.equals(outgoingLocals);
+ }
+
+ private void endLocals(IRBuilder builder) {
+ assert localsChanged();
+ for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+ if (!entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
+ builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
+ }
+ }
+ }
+
+ private void startLocals(IRBuilder builder) {
+ assert localsChanged();
+ for (Entry<DebugLocalInfo> entry : outgoingLocals.int2ObjectEntrySet()) {
+ if (!entry.getValue().equals(incomingLocals.get(entry.getIntKey()))) {
+ builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
}
}
}
@@ -553,40 +634,6 @@
return result;
}
- private void ensureDebugValueLiveness(IRBuilder builder) {
- if (incomingLocals.equals(outgoingLocals)) {
- return;
- }
- for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
- if (entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
- continue;
- }
- builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
- }
- for (Entry<DebugLocalInfo> entry : outgoingLocals.int2ObjectEntrySet()) {
- if (entry.getValue().equals(incomingLocals.get(entry.getIntKey()))) {
- continue;
- }
- builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
- }
- }
-
- private void ensureDebugValueLivenessControl(IRBuilder builder) {
- if (incomingLocals.equals(outgoingLocals)) {
- return;
- }
- for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
- if (entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
- continue;
- }
- builder.addDebugLocalRead(entry.getIntKey(), entry.getValue());
- }
- assert outgoingLocals
- .int2ObjectEntrySet()
- .stream()
- .allMatch(entry -> entry.getValue().equals(incomingLocals.get(entry.getIntKey())));
- }
-
private boolean isControlFlow(CfInstruction currentInstruction) {
return currentInstruction.isReturn()
|| currentInstruction.getTarget() != null
@@ -641,7 +688,7 @@
return state.getPosition();
}
- private Position getCanonicalPosition(Position position) {
+ public Position getCanonicalPosition(Position position) {
return canonicalPositions.getCanonical(
new Position(
position.line,
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 94516a2..81de890 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
@@ -164,6 +164,12 @@
}
@Override
+ public void buildBlockTransfer(
+ IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+ // Intentionally empty. Dex front-end does not support debug locals so no transfer info needed.
+ }
+
+ @Override
public void buildInstruction(
IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
updateCurrentCatchHandlers(instructionIndex);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 3612944..05553c1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -54,6 +54,7 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeCustom;
+import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.MoveException;
@@ -137,18 +138,25 @@
}
private static class MoveExceptionWorklistItem extends WorklistItem {
+ private final int sourceOffset;
private final int targetOffset;
- private MoveExceptionWorklistItem(BasicBlock block, int targetOffset) {
+ private MoveExceptionWorklistItem(BasicBlock block, int sourceOffset, int targetOffset) {
super(block, -1);
+ this.sourceOffset = sourceOffset;
this.targetOffset = targetOffset;
}
}
private static class SplitBlockWorklistItem extends WorklistItem {
+ private final int sourceOffset;
+ private final int targetOffset;
- public SplitBlockWorklistItem(BasicBlock block) {
- super(block, -1);
+ public SplitBlockWorklistItem(
+ int firstInstructionIndex, BasicBlock block, int sourceOffset, int targetOffset) {
+ super(block, firstInstructionIndex);
+ this.sourceOffset = sourceOffset;
+ this.targetOffset = targetOffset;
}
}
@@ -234,8 +242,9 @@
return all;
}
- boolean hasJustOneNormalExit() {
- return normalSuccessors.size() == 1 && exceptionalSuccessors.isEmpty();
+ boolean hasMoreThanASingleNormalExit() {
+ return normalSuccessors.size() > 1
+ || (normalSuccessors.size() == 1 && !exceptionalSuccessors.isEmpty());
}
BlockInfo split(
@@ -312,6 +321,8 @@
private BasicBlock entryBlock = null;
private BasicBlock currentBlock = null;
+ private int currentInstructionOffset = -1;
+
final private ValueNumberGenerator valueNumberGenerator;
private final DexEncodedMethod method;
private final AppInfo appInfo;
@@ -324,7 +335,7 @@
// Pending local reads.
private Value previousLocalValue = null;
- private final List<Value> debugLocalReads = new ArrayList<>();
+ private final List<Value> debugLocalEnds = new ArrayList<>();
// Lazily populated list of local values that are referenced without being actually defined.
private Int2ReferenceMap<List<Value>> uninitializedDebugLocalValues = null;
@@ -580,6 +591,7 @@
if (item.block.isFilled()) {
continue;
}
+ assert debugLocalEnds.isEmpty();
setCurrentBlock(item.block);
blocks.add(currentBlock);
currentBlock.setNumber(nextBlockNumber++);
@@ -589,10 +601,21 @@
closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
continue;
}
- // Split blocks are just pending close.
+ // Process split blocks which need to emit the locals transfer.
if (item instanceof SplitBlockWorklistItem) {
- closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
- continue;
+ SplitBlockWorklistItem splitEdgeItem = (SplitBlockWorklistItem) item;
+ source.buildBlockTransfer(
+ this, splitEdgeItem.sourceOffset, splitEdgeItem.targetOffset, false);
+ if (item.firstInstructionIndex == -1) {
+ // If the block is a pure split-edge block emit goto (picks up local ends) and close.
+ addInstruction(new Goto(), Position.none());
+ closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+ continue;
+ } else if (!debugLocalEnds.isEmpty()) {
+ // Otherwise, if some locals ended, insert a read so it takes place at the
+ // predecessor position.
+ addInstruction(new DebugLocalRead());
+ }
}
// Build IR for each dex instruction in the block.
int instCount = source.instructionCount();
@@ -600,12 +623,14 @@
if (currentBlock == null) {
break;
}
- BlockInfo info = targets.get(source.instructionOffset(i));
+ int instructionOffset = source.instructionOffset(i);
+ BlockInfo info = targets.get(instructionOffset);
if (info != null && info.block != currentBlock) {
- closeCurrentBlockWithFallThrough(info.block);
addToWorklist(info.block, i);
+ closeCurrentBlockWithFallThrough(info.block);
break;
}
+ currentInstructionOffset = instructionOffset;
source.buildInstruction(this, i, i == item.firstInstructionIndex);
}
}
@@ -615,17 +640,29 @@
// TODO(zerny): Link with outer try-block handlers, if any. b/65203529
int targetIndex = source.instructionIndex(moveExceptionItem.targetOffset);
int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
+ Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
if (moveExceptionDest >= 0) {
Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
- Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
MoveException moveException = new MoveException(out);
moveException.setPosition(position);
currentBlock.add(moveException);
}
- Goto exit = new Goto();
- currentBlock.add(exit);
+ // The block-transfer for exceptional edges needs to inform that this is an exceptional transfer
+ // so that local ends become implicit. The reason for this issue is that the "split block" for
+ // and exceptional edge is *after* control transfer, so inserting an end will end up causing
+ // locals to remain live longer than they should. The problem with this is that it is now
+ // possible to resurrect a local by declaring debug info that does not contain the exception
+ // handler but then loading the value from the local index. This should not be a problem in
+ // practice since the stack is empty so the known case of extending the local liveness via the
+ // stack can't happen. If this does end up being an issue, it can potentially be solved by
+ // ending any local that could possibly end in any of the exceptional targets and then
+ // explicitly restart the local on each split-edge that does not end the local.
+ boolean isExceptionalEdge = true;
+ source.buildBlockTransfer(
+ this, moveExceptionItem.sourceOffset, moveExceptionItem.targetOffset, isExceptionalEdge);
BasicBlock targetBlock = getTarget(moveExceptionItem.targetOffset);
currentBlock.link(targetBlock);
+ addInstruction(new Goto(), position);
addToWorklist(targetBlock, targetIndex);
}
@@ -680,7 +717,7 @@
private Value getIncomingLocalValue(int register, DebugLocalInfo local) {
assert options.debug;
assert local != null;
- assert local == getIncomingLocal(register);
+ // TODO(b/111251032): Here we lookup a value with type based on debug info. That's just wrong!
ValueType valueType = ValueType.fromDexType(local.type);
return readRegisterIgnoreLocal(register, valueType, local);
}
@@ -691,47 +728,21 @@
return !value.isUninitializedLocal() && value.getLocalInfo() == local;
}
- public void addDebugLocalRead(int register, DebugLocalInfo local) {
- if (!options.debug) {
- return;
- }
- Value value = getIncomingLocalValue(register, local);
- if (isValidFor(value, local)) {
- debugLocalReads.add(value);
- }
- }
-
public void addDebugLocalStart(int register, DebugLocalInfo local) {
if (!options.debug) {
return;
}
assert local != null;
assert local == getOutgoingLocal(register);
+ // TODO(b/111251032): Here we lookup a value with type based on debug info. That's just wrong!
ValueType valueType = ValueType.fromDexType(local.type);
- // TODO(mathiasr): Here we create a Phi with type based on debug info. That's just wrong!
Value incomingValue = readRegisterIgnoreLocal(register, valueType, local);
-
- // TODO(mathiasr): This can be simplified once trivial phi removal is local-info aware.
- if (incomingValue.isPhi() || incomingValue.getLocalInfo() != local) {
+ // If the local was not introduced by the previous instruction, start it here.
+ if (incomingValue.getLocalInfo() != local
+ || currentBlock.isEmpty()
+ || currentBlock.getInstructions().getLast().outValue() != incomingValue) {
addDebugLocalWrite(ValueType.fromDexType(local.type), register, incomingValue);
- return;
}
- assert incomingValue.getLocalInfo() == local;
- assert !incomingValue.isUninitializedLocal();
-
- // When inserting a start there are three possibilities:
- // 1. The block is empty (eg, instructions from block entry until now materialized to nothing).
- // 2. The block is non-empty and the last instruction defines the local to start.
- // 3. The block is non-empty and the last instruction does not define the local to start.
- if (currentBlock.getInstructions().isEmpty()) {
- addInstruction(new DebugLocalRead());
- }
- Instruction instruction = currentBlock.getInstructions().getLast();
- if (instruction.outValue() == incomingValue) {
- return;
- }
- instruction.addDebugValue(incomingValue);
- incomingValue.addDebugLocalStart(instruction);
}
public void addDebugLocalEnd(int register, DebugLocalInfo local) {
@@ -739,38 +750,18 @@
return;
}
Value value = getIncomingLocalValue(register, local);
- if (!isValidFor(value, local)) {
- return;
- }
- // When inserting an end there are three possibilities:
- // 1. The block is empty (eg, instructions from block entry until now materialized to nothing).
- // 2. The block has an instruction not defining the local being ended.
- // 3. The block has an instruction defining the local being ended.
- if (currentBlock.getInstructions().isEmpty()) {
- addInstruction(new DebugLocalRead());
- }
- Instruction instruction = currentBlock.getInstructions().getLast();
- if (instruction.outValue() != value) {
- instruction.addDebugValue(value);
- value.addDebugLocalEnd(instruction);
- return;
- }
- // In case 3. there are two cases:
- // a. The defining instruction is a debug-write, in which case it should be removed.
- // b. The defining instruction is overwriting the local value, in which case we de-associate it.
- assert !instruction.outValue().isUsed();
- if (instruction.isDebugLocalWrite()) {
- DebugLocalWrite write = instruction.asDebugLocalWrite();
- currentBlock.replaceCurrentDefinitions(value, write.src());
- currentBlock.listIterator(write).removeOrReplaceByDebugLocalRead();
- } else {
- instruction.outValue().clearLocalInfo();
+ if (isValidFor(value, local)) {
+ debugLocalEnds.add(value);
}
}
public void addDebugPosition(Position position) {
if (options.debug) {
assert source.getCurrentPosition().equals(position);
+ if (!debugLocalEnds.isEmpty()) {
+ // If there are pending local ends, end them before changing the line.
+ addInstruction(new DebugLocalRead(), Position.none());
+ }
addInstruction(new DebugPosition());
}
}
@@ -1007,12 +998,11 @@
}
public void addGoto(int targetOffset) {
- addInstruction(new Goto());
BasicBlock targetBlock = getTarget(targetOffset);
assert !currentBlock.hasCatchSuccessor(targetBlock);
currentBlock.link(targetBlock);
addToWorklist(targetBlock, source.instructionIndex(targetOffset));
- closeCurrentBlock();
+ closeCurrentBlock(new Goto());
}
private void addTrivialIf(int trueTargetOffset, int falseTargetOffset) {
@@ -1024,14 +1014,12 @@
// We expected an if here and therefore we incremented the expected predecessor count
// twice for the following block.
target.decrementUnfilledPredecessorCount();
- addInstruction(new Goto());
currentBlock.link(target);
addToWorklist(target, source.instructionIndex(trueTargetOffset));
- closeCurrentBlock();
+ closeCurrentBlock(new Goto());
}
private void addNonTrivialIf(If instruction, int trueTargetOffset, int falseTargetOffset) {
- addInstruction(instruction);
BasicBlock trueTarget = getTarget(trueTargetOffset);
BasicBlock falseTarget = getTarget(falseTargetOffset);
currentBlock.link(trueTarget);
@@ -1039,7 +1027,7 @@
// Generate fall-through before the block that is branched to.
addToWorklist(falseTarget, source.instructionIndex(falseTargetOffset));
addToWorklist(trueTarget, source.instructionIndex(trueTargetOffset));
- closeCurrentBlock();
+ closeCurrentBlock(instruction);
}
public void addIf(If.Type type, ValueType operandType, int value1, int value2,
@@ -1406,8 +1394,7 @@
// Attach the live locals to the return instruction to avoid a local change on monitor exit.
attachLocalValues(ret);
source.buildPostlude(this);
- addInstruction(ret);
- closeCurrentBlock();
+ closeCurrentBlock(ret);
}
public void addStaticGet(int dest, DexField field) {
@@ -1497,8 +1484,8 @@
// Create a switch with only the non-fallthrough targets.
keys = nonFallthroughKeys.toIntArray();
labelOffsets = nonFallthroughOffsets.toIntArray();
- addInstruction(createSwitch(switchValue, keys, fallthroughOffset, labelOffsets));
- closeCurrentBlock();
+ Switch aSwitch = createSwitch(switchValue, keys, fallthroughOffset, labelOffsets);
+ closeCurrentBlock(aSwitch);
}
private Switch createSwitch(Value value, int[] keys, int fallthroughOffset, int[] targetOffsets) {
@@ -1537,6 +1524,9 @@
public void addThrow(int value) {
Value in = readRegister(value, ValueType.OBJECT);
+ // The only successors to a throw instruction are exceptional, so we directly add it (ensuring
+ // the exceptional edges which are split-edge by construction) and then we close the block which
+ // cannot have any additional edges that need splitting.
addInstruction(new Throw(in));
closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
}
@@ -1932,7 +1922,8 @@
header = new BasicBlock();
header.incrementUnfilledPredecessorCount();
moveExceptionHeaders.put(target, header);
- ssaWorklist.add(new MoveExceptionWorklistItem(header, targetOffset));
+ ssaWorklist.add(
+ new MoveExceptionWorklistItem(header, currentInstructionOffset, targetOffset));
}
targets.add(header);
}
@@ -1944,20 +1935,22 @@
private void attachLocalValues(Instruction ir) {
if (!options.debug) {
assert previousLocalValue == null;
- assert debugLocalReads.isEmpty();
+ assert debugLocalEnds.isEmpty();
return;
}
// Add a use if this instruction is overwriting a previous value of the same local.
if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) {
assert ir.outValue() != null;
ir.addDebugValue(previousLocalValue);
+ previousLocalValue.addDebugLocalEnd(ir);
}
// Add reads of locals if any are pending.
- for (Value value : debugLocalReads) {
+ for (Value value : debugLocalEnds) {
ir.addDebugValue(value);
+ value.addDebugLocalEnd(ir);
}
previousLocalValue = null;
- debugLocalReads.clear();
+ debugLocalEnds.clear();
}
// Package (ie, SourceCode accessed) helpers.
@@ -2067,34 +2060,74 @@
}
private void closeCurrentBlockGuaranteedNotToNeedEdgeSplitting() {
- // TODO(zerny): To ensure liveness of locals throughout the entire block, we might want to
- // insert reads before closing the block. It is unclear if we can rely on a local-end to ensure
- // liveness in all blocks where the local should be live.
assert currentBlock != null;
currentBlock.close(this);
setCurrentBlock(null);
throwingInstructionInCurrentBlock = false;
+ currentInstructionOffset = -1;
+ assert debugLocalEnds.isEmpty();
}
- private void closeCurrentBlock() {
+ private void closeCurrentBlock(JumpInstruction jumpInstruction) {
+ assert !jumpInstruction.instructionTypeCanThrow();
assert currentBlock != null;
+ assert currentBlock.getInstructions().isEmpty()
+ || !currentBlock.getInstructions().getLast().isJumpInstruction();
+ generateSplitEdgeBlocks();
+ addInstruction(jumpInstruction);
+ closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+ }
+
+ private void closeCurrentBlockWithFallThrough(BasicBlock nextBlock) {
+ assert currentBlock != null;
+ assert !currentBlock.hasCatchSuccessor(nextBlock);
+ currentBlock.link(nextBlock);
+ closeCurrentBlock(new Goto());
+ }
+
+ private void generateSplitEdgeBlocks() {
+ assert currentBlock != null;
+ assert currentBlock.isEmpty() || !currentBlock.getInstructions().getLast().isJumpInstruction();
BlockInfo info = getBlockInfo(currentBlock);
- if (!info.hasJustOneNormalExit()) {
+ if (info.hasMoreThanASingleNormalExit()) {
// Exceptional edges are always split on construction, so no need to split edges to them.
+ // Introduce split-edge blocks for all normal edges and push them on the work list.
for (int successorOffset : info.normalSuccessors) {
BlockInfo successorInfo = getBlockInfo(successorOffset);
- if (successorInfo.predecessorCount() > 1) {
+ if (successorInfo.predecessorCount() == 1) {
+ // re-add to worklist as a unique succ
+ WorklistItem oldItem = null;
+ for (WorklistItem item : ssaWorklist) {
+ if (item.block == successorInfo.block) {
+ oldItem = item;
+ }
+ }
+ assert oldItem.firstInstructionIndex == source.instructionIndex(successorOffset);
+ ssaWorklist.remove(oldItem);
+ ssaWorklist.add(
+ new SplitBlockWorklistItem(
+ oldItem.firstInstructionIndex,
+ oldItem.block,
+ currentInstructionOffset,
+ successorOffset));
+ } else {
BasicBlock splitBlock = createSplitEdgeBlock(currentBlock, successorInfo.block);
- ssaWorklist.add(new SplitBlockWorklistItem(splitBlock));
+ ssaWorklist.add(
+ new SplitBlockWorklistItem(
+ -1, splitBlock, currentInstructionOffset, successorOffset));
}
}
+ } else if (info.normalSuccessors.size() == 1) {
+ int successorOffset = info.normalSuccessors.iterator().nextInt();
+ source.buildBlockTransfer(this, currentInstructionOffset, successorOffset, false);
+ } else {
+ // TODO(zerny): Consider refactoring to compute the live-at-exit via callback here too.
+ assert info.allSuccessors().isEmpty();
}
- closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
}
private static BasicBlock createSplitEdgeBlock(BasicBlock source, BasicBlock target) {
BasicBlock splitBlock = new BasicBlock();
- splitBlock.add(new Goto());
splitBlock.incrementUnfilledPredecessorCount();
splitBlock.getPredecessors().add(source);
splitBlock.getSuccessors().add(target);
@@ -2103,14 +2136,6 @@
return splitBlock;
}
- private void closeCurrentBlockWithFallThrough(BasicBlock nextBlock) {
- assert currentBlock != null;
- addInstruction(new Goto());
- assert !currentBlock.hasCatchSuccessor(nextBlock);
- currentBlock.link(nextBlock);
- closeCurrentBlock();
- }
-
/**
* Change to control-flow graph to avoid repeated phi operands when all the same values
* flow in from multiple predecessors.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 01c4dd0..e528a90 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
import com.android.tools.r8.ir.conversion.JarState.Local;
+import com.android.tools.r8.ir.conversion.JarState.LocalChangeAtOffset;
import com.android.tools.r8.ir.conversion.JarState.Slot;
import com.android.tools.r8.logging.Log;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -467,6 +468,32 @@
}
@Override
+ public void buildBlockTransfer(
+ IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+ assert currentInstruction == null || predecessorOffset == getOffset(currentInstruction);
+ currentInstruction = null;
+ if (predecessorOffset == IRBuilder.INITIAL_BLOCK_OFFSET
+ || successorOffset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
+ return;
+ }
+ currentPosition = getCanonicalDebugPositionAtOffset(predecessorOffset);
+
+ LocalChangeAtOffset localChange = state.getLocalChange(predecessorOffset, successorOffset);
+ if (!isExceptional) {
+ for (Local toClose : localChange.getLocalsToClose()) {
+ builder.addDebugLocalEnd(toClose.slot.register, toClose.info);
+ }
+ }
+ List<Local> localsToOpen = localChange.getLocalsToOpen();
+ if (!localsToOpen.isEmpty()) {
+ state.restoreState(successorOffset);
+ for (Local toOpen : localsToOpen) {
+ builder.addDebugLocalStart(toOpen.slot.register, toOpen.info);
+ }
+ }
+ }
+
+ @Override
public void buildInstruction(
IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
@@ -482,13 +509,7 @@
// current position will be updated by LineNumberNode into this block.
if (firstBlockInstruction || instructionIndex == 0) {
state.restoreState(instructionIndex);
- // Don't include line changes when processing a label. Doing so will end up emitting local
- // writes after the line has changed and thus causing locals to become visible too late.
- currentPosition =
- getCanonicalDebugPositionAtOffset(
- ((instructionIndex > 0) && (insn instanceof LabelNode))
- ? instructionIndex - 1
- : instructionIndex);
+ currentPosition = getCanonicalDebugPositionAtOffset(instructionIndex);
}
String preInstructionState;
@@ -496,30 +517,20 @@
preInstructionState = state.toString();
}
- if (firstBlockInstruction && insn != initialLabel) {
- int offset = getOffset(insn);
- state.beginTransactionAtBlockStart(offset);
- assert state.getLocalsToClose().isEmpty();
- for (Local local : state.getLocalsToOpen()) {
- builder.addDebugLocalStart(local.slot.register, local.info);
- }
- state.endTransaction();
- }
-
boolean hasNextInstruction =
instructionIndex + 1 != instructionCount()
&& !builder.getCFG().containsKey(instructionIndex + 1);
state.beginTransaction(instructionIndex + 1, hasNextInstruction);
- build(insn, builder);
- if (hasNextInstruction || !isControlFlowInstruction(insn)) {
- // We're either in straight-line code or at the end of a fallthrough block.
- // Close locals starting at this point.
+ if (hasNextInstruction) {
+ // Explicitly end all locals ending at this point.
for (Local local : state.getLocalsToClose()) {
builder.addDebugLocalEnd(local.slot.register, local.info);
}
}
+ build(insn, builder);
+ // If the block continues past this instruction then local state should be updated.
if (hasNextInstruction) {
- // Open the scope of locals starting at this point.
+ // Ensure starts of locals starting at this point.
for (Local local : state.getLocalsToOpen()) {
builder.addDebugLocalStart(local.slot.register, local.info);
}
@@ -536,6 +547,8 @@
offset, instructionToString(insn), preInstructionState, state);
}
}
+
+ currentInstruction = null;
}
private boolean verifyExceptionEdgesAreRecorded(AbstractInsnNode insn) {
@@ -1862,23 +1875,12 @@
}
}
- private void processLocalVariablesAtControlEdge(AbstractInsnNode insn, IRBuilder builder) {
- assert isControlFlowInstruction(insn) && !isReturn(insn);
- int offset = getOffset(insn);
- int blockOffset = builder.getCFG().headMap(offset).lastIntKey();
- BlockInfo blockInfo = builder.getCFG().get(blockOffset);
- // Read all locals that are not live on all successors to ensure liveness.
- for (Local local : state.localsNotLiveAtAllSuccessors(blockInfo.allSuccessors())) {
- builder.addDebugLocalRead(local.slot.register, local.info);
- }
- }
-
private void processLocalVariablesAtExit(AbstractInsnNode insn, IRBuilder builder) {
assert isReturn(insn) || isThrow(insn);
// Read all locals live at exit to ensure liveness.
for (Local local : state.getLocals()) {
if (local.info != null) {
- builder.addDebugLocalRead(local.slot.register, local.info);
+ builder.addDebugLocalEnd(local.slot.register, local.info);
}
}
}
@@ -2323,7 +2325,24 @@
if (isExitingThrow(insn)) {
processLocalVariablesAtExit(insn, builder);
} else {
- processLocalVariablesAtControlEdge(insn, builder);
+ int offset = getOffset(insn);
+ Int2ReferenceSortedMap<BlockInfo> cfg = builder.getCFG();
+ BlockInfo info = cfg.get(cfg.headMap(offset + 1).lastIntKey());
+ assert info.normalSuccessors.isEmpty();
+ assert !info.exceptionalSuccessors.isEmpty();
+ Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
+ for (int successorOffset : info.exceptionalSuccessors) {
+ if (successorOffset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
+ // TODO(zerny): It would likely be beneficial to keep locals live until the exit from the
+ // exceptional sync exit block.
+ continue;
+ }
+ LocalChangeAtOffset localChange = state.getLocalChange(offset, successorOffset);
+ for (Local localEnd : localChange.getLocalsToClose()) {
+ ending.put(localEnd.slot.register, localEnd.info);
+ }
+ }
+ ending.forEach(builder::addDebugLocalEnd);
}
builder.addThrow(register);
}
@@ -2659,7 +2678,6 @@
}
private void build(JumpInsnNode insn, IRBuilder builder) {
- processLocalVariablesAtControlEdge(insn, builder);
int[] targets = getTargets(insn);
int opcode = insn.getOpcode();
if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
@@ -2749,12 +2767,10 @@
}
private void build(TableSwitchInsnNode insn, IRBuilder builder) {
- processLocalVariablesAtControlEdge(insn, builder);
buildSwitch(insn.dflt, insn.labels, new int[]{insn.min}, builder);
}
private void build(LookupSwitchInsnNode insn, IRBuilder builder) {
- processLocalVariablesAtControlEdge(insn, builder);
int[] keys = new int[insn.keys.size()];
for (int i = 0; i < insn.keys.size(); i++) {
keys[i] = (int) insn.keys.get(i);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index b67d12e..062905a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -81,7 +81,7 @@
}
// Collection of locals information at a program point.
- private static class LocalsAtOffset {
+ static class LocalsAtOffset {
// Note that we assume live is always a super-set of starts.
final List<LocalNodeInfo> live;
final List<LocalNodeInfo> starts;
@@ -259,6 +259,41 @@
}
}
+ public static class LocalChangeAtOffset {
+
+ final LocalsAtOffset atExit;
+ final LocalsAtOffset atEntry;
+ private JarState state;
+
+ private LocalChangeAtOffset(LocalsAtOffset atExit, LocalsAtOffset atEntry, JarState state) {
+ this.atExit = atExit;
+ this.atEntry = atEntry;
+ this.state = state;
+ }
+
+ public List<Local> getLocalsToClose() {
+ List<Local> toClose = new ArrayList<>(atExit.live.size());
+ for (LocalNodeInfo liveAtExit : atExit.live) {
+ if (!atEntry.isLive(liveAtExit.info)) {
+ int register = state.getLocalRegister(liveAtExit.node.index, liveAtExit.type);
+ toClose.add(new Local(new Slot(register, liveAtExit.type), liveAtExit.info));
+ }
+ }
+ return toClose;
+ }
+
+ public List<Local> getLocalsToOpen() {
+ List<Local> toOpen = new ArrayList<>(atEntry.live.size());
+ for (LocalNodeInfo liveAtEntry : atEntry.live) {
+ if (!atExit.isLive(liveAtEntry.info)) {
+ int register = state.getLocalRegister(liveAtEntry.node.index, liveAtEntry.type);
+ toOpen.add(new Local(new Slot(register, liveAtEntry.type), liveAtEntry.info));
+ }
+ }
+ return toOpen;
+ }
+ }
+
final int startOfStack;
private int topOfStack;
@@ -529,6 +564,18 @@
localsToClose.clear();
}
+ private LocalsAtOffset getLocalsAtOffset(int offset) {
+ Int2ReferenceSortedMap<LocalsAtOffset> headMap = localsAtOffsetTable.headMap(offset + 1);
+ return localsAtOffsetTable.get(headMap.lastIntKey());
+ }
+
+ public LocalChangeAtOffset getLocalChange(int predecessorIndex, int successorIndex) {
+ return new LocalChangeAtOffset(
+ getLocalsAtOffset(predecessorIndex),
+ getLocalsAtOffset(successorIndex),
+ this);
+ }
+
public List<Local> getLocalsToClose() {
return localsToClose;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index 2bc2aa1..9b5dc20 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -49,6 +49,9 @@
void buildInstruction(IRBuilder builder, int instructionIndex, boolean firstBlockInstruction);
+ void buildBlockTransfer(
+ IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional);
+
void buildPostlude(IRBuilder builder);
// Helper to resolve switch payloads and build switch instructions (dex code only).
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 df6e65a..b856cc5 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
@@ -2221,6 +2221,7 @@
Value overwrittenLocal = instruction.removeDebugValue(localInfo);
if (overwrittenLocal != null) {
inValue.definition.addDebugValue(overwrittenLocal);
+ overwrittenLocal.addDebugLocalEnd(inValue.definition);
}
if (prevInstruction != null) {
instruction.moveDebugValues(prevInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 5a7cba6..3e3413a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1022,6 +1022,12 @@
}
@Override
+ public void buildBlockTransfer(
+ IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+ throw new Unreachable("Outliner does not support control flow");
+ }
+
+ @Override
public void buildPostlude(IRBuilder builder) {
// Intentionally left empty. (Needed for Java-bytecode-frontend synchronization support.)
}
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 8fd09d0..0d93eee 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
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCode.LiveAtEntrySets;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
@@ -31,7 +32,6 @@
import com.android.tools.r8.ir.regalloc.RegisterPositions.Type;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
@@ -126,7 +126,7 @@
private final InternalOptions options;
// Mapping from basic blocks to the set of values live at entry to that basic block.
- private Map<BasicBlock, Set<Value>> liveAtEntrySets;
+ private Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets;
// The value of the first argument, or null if the method has no arguments.
protected Value firstArgumentValue;
// The value of the last argument, or null if the method has no arguments.
@@ -254,12 +254,14 @@
}
private void computeDebugInfo(ImmutableList<BasicBlock> blocks) {
- computeDebugInfo(blocks, liveIntervals, this);
+ computeDebugInfo(blocks, liveIntervals, this, liveAtEntrySets);
}
public static void computeDebugInfo(
- ImmutableList<BasicBlock> blocks, List<LiveIntervals> liveIntervals,
- RegisterAllocator allocator) {
+ ImmutableList<BasicBlock> blocks,
+ List<LiveIntervals> liveIntervals,
+ RegisterAllocator allocator,
+ Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets) {
// Collect live-ranges for all SSA values with local information.
List<LocalRange> ranges = new ArrayList<>();
for (LiveIntervals interval : liveIntervals) {
@@ -267,46 +269,19 @@
if (!value.hasLocalInfo()) {
continue;
}
- List<Integer> starts = ListUtils.map(value.getDebugLocalStarts(), Instruction::getNumber);
- List<Integer> ends = ListUtils.map(value.getDebugLocalEnds(), Instruction::getNumber);
- List<LiveRange> liveRanges = new ArrayList<>();
- liveRanges.addAll(interval.getRanges());
+ List<LiveRange> liveRanges = new ArrayList<>(interval.getRanges());
for (LiveIntervals child : interval.getSplitChildren()) {
assert child.getValue() == value;
assert child.getSplitChildren() == null || child.getSplitChildren().isEmpty();
liveRanges.addAll(child.getRanges());
}
liveRanges.sort((r1, r2) -> Integer.compare(r1.start, r2.start));
- starts.sort(Integer::compare);
- ends.sort(Integer::compare);
-
for (LiveRange liveRange : liveRanges) {
int start = liveRange.start;
int end = liveRange.end;
- Integer nextEnd;
- while ((nextEnd = nextInRange(start, end, ends)) != null) {
- // If an argument value has been split, we have disallowed argument reuse and therefore,
- // the argument value is also in the argument register throughout the method. For debug
- // information, we always use the argument register whenever a local corresponds to an
- // argument value. That avoids ending and restarting locals whenever we move arguments
- // to lower register.
- int register = allocator.getArgumentOrAllocateRegisterForValue(value, start);
- ranges.add(new LocalRange(value, register, start, nextEnd));
- Integer nextStart = nextInRange(nextEnd, end, starts);
- if (nextStart == null) {
- start = -1;
- break;
- }
- start = nextStart;
- }
- if (start >= 0) {
- ranges.add(
- new LocalRange(
- value,
- allocator.getArgumentOrAllocateRegisterForValue(value, start),
- start,
- end));
- }
+ ranges.add(
+ new LocalRange(
+ value, allocator.getArgumentOrAllocateRegisterForValue(value, start), start, end));
}
}
if (ranges.isEmpty()) {
@@ -321,11 +296,33 @@
Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
Int2ReferenceMap<DebugLocalInfo> starting = new Int2ReferenceOpenHashMap<>();
+ boolean isEntryBlock = true;
for (BasicBlock block : blocks) {
- // Skip past all spill moves to obtain the instruction number of the actual first instruction.
InstructionListIterator instructionIterator = block.listIterator();
- instructionIterator.nextUntil(
- i -> !i.isArgument() && !i.isMoveException() && !isSpillInstruction(i));
+ Set<Value> liveLocalValues = new HashSet<>(liveAtEntrySets.get(block).liveLocalValues);
+ // Skip past arguments and open argument and phi locals.
+ if (isEntryBlock) {
+ isEntryBlock = false;
+ assert block.getPhis().isEmpty();
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (!instruction.isArgument()) {
+ break;
+ }
+ if (instruction.outValue().hasLocalInfo()) {
+ liveLocalValues.add(instruction.outValue());
+ }
+ }
+ instructionIterator.previous();
+ } else {
+ for (Phi phi : block.getPhis()) {
+ if (phi.hasLocalInfo()) {
+ liveLocalValues.add(phi);
+ }
+ }
+ }
+ // Skip past all spill moves to obtain the instruction number of the actual first instruction.
+ instructionIterator.nextUntil(i -> !i.isMoveException() && !isSpillInstruction(i));
Instruction firstInstruction = instructionIterator.previous();
int firstIndex = firstInstruction.getNumber();
@@ -333,14 +330,18 @@
// might be live upon entering the first instruction (if they are used by it). Since we
// skipped move-exception this closes locals at the move exception which should close as part
// of the exceptional transfer.
- openRanges.removeIf(openRange -> !isLocalLiveAtInstruction(firstInstruction, openRange));
+ openRanges.removeIf(
+ openRange ->
+ !liveLocalValues.contains(openRange.value)
+ || !isLocalLiveAtInstruction(firstInstruction, openRange));
// Open ranges up-to but excluding the first instruction. Starts are inclusive but entry is
// prior to the first instruction.
while (nextStartingRange != null && nextStartingRange.start < firstIndex) {
// If the range is live at this index open it. Again the end is inclusive here because the
// instruction is live at block entry if it is live at entry to the first instruction.
- if (isLocalLiveAtInstruction(firstInstruction, nextStartingRange)) {
+ if (liveLocalValues.contains(nextStartingRange.value)
+ && isLocalLiveAtInstruction(firstInstruction, nextStartingRange)) {
openRanges.add(nextStartingRange);
}
nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
@@ -359,30 +360,51 @@
// Iterate the block instructions and emit locals changed events.
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
- if (instruction.isDebugLocalRead()) {
- // Remove debug local reads now that local liveness is computed.
- assert !instruction.getDebugValues().isEmpty();
- instruction.clearDebugValues();
- instructionIterator.remove();
- }
if (!instructionIterator.hasNext()) {
break;
}
+
+ if (!instruction.getDebugValues().isEmpty()) {
+ for (Value endAnnotation : instruction.getDebugValues()) {
+ ListIterator<LocalRange> it = openRanges.listIterator();
+ while (it.hasNext()) {
+ LocalRange openRange = it.next();
+ if (openRange.value == endAnnotation) {
+ it.remove();
+ assert currentLocals.get(openRange.register) == openRange.local;
+ currentLocals.remove(openRange.register);
+ ending.put(openRange.register, openRange.local);
+ break;
+ }
+ }
+ }
+ // Remove the end markers now that local liveness is computed.
+ instruction.clearDebugValues();
+ if (instruction.isDebugLocalRead()) {
+ Instruction prev = instructionIterator.previous();
+ assert prev == instruction;
+ instructionIterator.remove();
+ }
+ }
+
Instruction nextInstruction = instructionIterator.peekNext();
if (isSpillInstruction(nextInstruction)) {
// No need to insert a DebugLocalsChange instruction before a spill instruction.
continue;
}
int index = nextInstruction.getNumber();
- ListIterator<LocalRange> it = openRanges.listIterator(0);
- while (it.hasNext()) {
- LocalRange openRange = it.next();
- // Close ranges up-to but excluding the first instruction.
- if (!isLocalLiveAtInstruction(nextInstruction, openRange)) {
- it.remove();
- assert currentLocals.get(openRange.register) == openRange.local;
- currentLocals.remove(openRange.register);
- ending.put(openRange.register, openRange.local);
+ {
+ ListIterator<LocalRange> it = openRanges.listIterator();
+ while (it.hasNext()) {
+ LocalRange openRange = it.next();
+ // Close ranges up-to but excluding the first instruction.
+ if (!isLocalLiveAtInstruction(nextInstruction, openRange)) {
+ it.remove();
+ // It may be that currentLocals does not contain this local because an explicit end
+ // already closed the local.
+ currentLocals.remove(openRange.register);
+ ending.put(openRange.register, openRange.local);
+ }
}
}
while (nextStartingRange != null && nextStartingRange.start < index) {
@@ -2292,7 +2314,7 @@
int toInstruction = successor.entry().getNumber();
// Insert spill/restore moves when a value changes across a block boundary.
- Set<Value> liveAtEntry = liveAtEntrySets.get(successor);
+ Set<Value> liveAtEntry = liveAtEntrySets.get(successor).liveValues;
for (Value value : liveAtEntry) {
LiveIntervals parentInterval = value.getLiveIntervals();
LiveIntervals fromIntervals = parentInterval.getSplitCovering(fromInstruction);
@@ -2388,8 +2410,8 @@
LiveIntervals thisIntervals = thisValue.getLiveIntervals();
thisIntervals.getRanges().clear();
thisIntervals.addRange(new LiveRange(0, code.getNextInstructionNumber()));
- for (Set<Value> values : liveAtEntrySets.values()) {
- values.add(thisValue);
+ for (LiveAtEntrySets values : liveAtEntrySets.values()) {
+ values.liveValues.add(thisValue);
}
return;
}
@@ -2397,20 +2419,18 @@
}
}
- /**
- * Compute live ranges based on liveAtEntry sets for all basic blocks.
- */
+ /** Compute live ranges based on liveAtEntry sets for all basic blocks. */
public static void computeLiveRanges(
InternalOptions options,
IRCode code,
- Map<BasicBlock, Set<Value>> liveAtEntrySets,
+ Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets,
List<LiveIntervals> liveIntervals) {
for (BasicBlock block : code.topologicallySortedBlocks()) {
Set<Value> live = new HashSet<>();
List<BasicBlock> successors = block.getSuccessors();
Set<Value> phiOperands = new HashSet<>();
for (BasicBlock successor : successors) {
- live.addAll(liveAtEntrySets.get(successor));
+ live.addAll(liveAtEntrySets.get(successor).liveValues);
for (Phi phi : successor.getPhis()) {
live.remove(phi);
phiOperands.add(phi.getOperand(successor.getPredecessors().indexOf(block)));
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 991d371..3d416fb 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -192,6 +192,12 @@
}
@Override
+ public void buildBlockTransfer(
+ IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+ // Intensionally empty as synthetic code does not contain locals information.
+ }
+
+ @Override
public final void resolveAndBuildSwitch(
int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
throw new Unreachable("Unexpected call to resolveAndBuildSwitch");
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 e9ef5ed..7911016 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -246,6 +246,9 @@
@Override
public JUnit3Wrapper.DebuggeeState get() {
+ if (wrapper.state == JUnit3Wrapper.State.Exit) {
+ return null;
+ }
assert verifyStateLocation(wrapper.getDebuggeeState());
if (initial) {
if (DEBUG_TESTS) {
diff --git a/src/test/java/com/android/tools/r8/debug/LocalEndTest.java b/src/test/java/com/android/tools/r8/debug/LocalEndTest.java
new file mode 100644
index 0000000..036e590
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalEndTest.java
@@ -0,0 +1,27 @@
+// 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;
+
+public class LocalEndTest {
+
+ public void foo() {
+ {
+ int x = 42;
+ try {
+ bar();
+ x = 7;
+ } catch (Throwable e) {}
+ }
+ int y = 11; // Replaced by stack value of previously visible x (which must not become visible).
+ System.out.println(y);
+ }
+
+ private void bar() {
+ // nothing to do
+ }
+
+ public static void main(String[] args) {
+ new LocalEndTest().foo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalEndTestDump.java b/src/test/java/com/android/tools/r8/debug/LocalEndTestDump.java
new file mode 100644
index 0000000..ad6737c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalEndTestDump.java
@@ -0,0 +1,148 @@
+// 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 org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class LocalEndTestDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/debug/LocalEndTest",
+ null,
+ "java/lang/Object",
+ null);
+
+ cw.visitSource("LocalEndTest.java", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(6, l0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Lcom/android/tools/r8/debug/LocalEndTest;", null, l0, l1, 0);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ Label l2 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable");
+ Label l3 = new Label();
+ mv.visitLabel(l3);
+ mv.visitLineNumber(10, l3);
+ mv.visitIntInsn(BIPUSH, 42);
+ mv.visitVarInsn(ISTORE, 1);
+ mv.visitLabel(l0);
+ mv.visitLineNumber(12, l0);
+ mv.visitVarInsn(ILOAD, 1); // push x on the stack for later use in the join block.
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "com/android/tools/r8/debug/LocalEndTest", "bar", "()V", false);
+ Label l4 = new Label();
+ mv.visitLabel(l4);
+ mv.visitLineNumber(13, l4);
+ mv.visitIntInsn(BIPUSH, 7);
+ mv.visitVarInsn(ISTORE, 1);
+ mv.visitLabel(l1);
+ mv.visitLineNumber(14, l1);
+ Label l5 = new Label();
+ mv.visitJumpInsn(GOTO, l5);
+ mv.visitLabel(l2);
+ mv.visitFrame(
+ Opcodes.F_FULL,
+ 2,
+ new Object[] {"com/android/tools/r8/debug/LocalEndTest", Opcodes.INTEGER},
+ 1,
+ new Object[] {"java/lang/Throwable"});
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitVarInsn(ILOAD, 1); // push x on the stack again (should be same as above).
+ // mv.visitInsn(ICONST_0);
+ mv.visitLabel(l5);
+ mv.visitLineNumber(16, l5);
+ // mv.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"com/android/tools/r8/debug/LocalEndTest"},
+ 1,
+ new Object[] {Opcodes.INTEGER});
+ // Load the on-stack copy of x
+ mv.visitVarInsn(ISTORE, 1);
+ Label l6 = new Label();
+ mv.visitLabel(l6);
+ mv.visitLineNumber(17, l6);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+ Label l7 = new Label();
+ mv.visitLabel(l7);
+ mv.visitLineNumber(18, l7);
+ mv.visitInsn(RETURN);
+ Label l8 = new Label();
+ mv.visitLabel(l8);
+ mv.visitLocalVariable("x", "I", null, l0, l5, 1);
+ mv.visitLocalVariable("this", "Lcom/android/tools/r8/debug/LocalEndTest;", null, l3, l8, 0);
+ mv.visitLocalVariable("y", "I", null, l6, l8, 1);
+ mv.visitMaxs(2, 3);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PRIVATE, "bar", "()V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(22, l0);
+ mv.visitInsn(RETURN);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLocalVariable("this", "Lcom/android/tools/r8/debug/LocalEndTest;", null, l0, l1, 0);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitLineNumber(25, l0);
+ mv.visitTypeInsn(NEW, "com/android/tools/r8/debug/LocalEndTest");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "com/android/tools/r8/debug/LocalEndTest", "<init>", "()V", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "com/android/tools/r8/debug/LocalEndTest", "foo", "()V", false);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitLineNumber(26, l1);
+ mv.visitInsn(RETURN);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l2, 0);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalEndTestRunner.java b/src/test/java/com/android/tools/r8/debug/LocalEndTestRunner.java
new file mode 100644
index 0000000..982332e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalEndTestRunner.java
@@ -0,0 +1,71 @@
+// 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 com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LocalEndTestRunner extends DebugTestBase {
+
+ static final Class CLASS = LocalEndTest.class;
+ static final String NAME = CLASS.getCanonicalName();
+ static final String DESC = DescriptorUtils.javaTypeToDescriptor(NAME);
+ static final String FILE = CLASS.getSimpleName() + ".java";
+
+ private static Path inputJarCache = null;
+
+ private final String name;
+ private final DebugTestConfig config;
+
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf = temp -> new CfDebugTestConfig().addPaths(getInputJar(temp));
+ DelayedDebugTestConfig d8 =
+ temp -> new D8DebugTestConfig().compileAndAdd(temp, getInputJar(temp));
+ return ImmutableList.of(new Object[] {"CF", cf}, new Object[] {"D8", d8});
+ }
+
+ private static Path getInputJar(TemporaryFolder temp) {
+ if (inputJarCache == null) {
+ inputJarCache = temp.getRoot().toPath().resolve("input.jar");
+ ClassFileConsumer jarWriter = new ArchiveConsumer(inputJarCache);
+ jarWriter.accept(LocalEndTestDump.dump(), DESC, null);
+ jarWriter.finished(null);
+ }
+ return inputJarCache;
+ }
+
+ public LocalEndTestRunner(String name, DelayedDebugTestConfig config) {
+ this.name = name;
+ this.config = config.getConfig(temp);
+ }
+
+ @Test
+ public void test() throws Throwable {
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "foo", 13),
+ run(),
+ checkLine(FILE, 13),
+ checkLocal("x"),
+ stepOver(),
+ checkLine(FILE, 14),
+ checkLocal("x"),
+ stepOver(),
+ checkLine(FILE, 16),
+ checkNoLocal("x"),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/PostIncrementTest.java b/src/test/java/com/android/tools/r8/debug/PostIncrementTest.java
new file mode 100644
index 0000000..543a5a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/PostIncrementTest.java
@@ -0,0 +1,23 @@
+// 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;
+
+public class PostIncrementTest {
+
+ static final int LENGTH = 4 * 16;
+
+ private static void loop(int[] a) {
+ int i = 0;
+ int s = 128;
+ while (i++ < LENGTH - 2) {
+ if (i % 2 == 0) {
+ a[i] = s++;
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ loop(new int[LENGTH]);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java b/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
new file mode 100644
index 0000000..c805c15
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/PostIncrementTestRunner.java
@@ -0,0 +1,30 @@
+// 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 com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import java.util.stream.Stream;
+import org.junit.Test;
+
+// See b/80385846
+public class PostIncrementTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = PostIncrementTest.class;
+ private static final String NAME = CLASS.getCanonicalName();
+
+ @Test
+ public void test() throws Exception {
+ DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DebugTestConfig d8Config = new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ new DebugStreamComparator()
+ .add("CF", createStream(cfConfig))
+ .add("D8", createStream(d8Config))
+ .compare();
+ }
+
+ private Stream<DebuggeeState> createStream(DebugTestConfig config) throws Exception {
+ return streamDebugTest(config, NAME, ANDROID_FILTER);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
index a23f3f4..17f0c04 100644
--- a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
@@ -5,25 +5,61 @@
import static org.hamcrest.core.IsNot.not;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collection;
import org.junit.Assume;
-import org.junit.BeforeClass;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
-/**
- * Test single stepping behaviour of synchronized blocks.
- */
+/** Test single stepping behaviour of synchronized blocks. */
+@RunWith(Parameterized.class)
public class SynchronizedBlockTest extends DebugTestBase {
public static final String CLASS = "SynchronizedBlock";
public static final String FILE = "SynchronizedBlock.java";
- private static DebugTestConfig config;
+ private final String name;
+ private final DebugTestConfig config;
- @BeforeClass
- public static void setup() {
- config = new D8DebugTestResourcesConfig(temp);
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf =
+ temp -> new CfDebugTestConfig().addPaths(DebugTestBase.DEBUGGEE_JAR);
+ DelayedDebugTestConfig r8cf = temp -> new R8CfDebugTestResourcesConfig(temp);
+ DelayedDebugTestConfig r8cfcf =
+ temp -> {
+ Path out = temp.getRoot().toPath().resolve("r8cfcf.jar");
+ try {
+ ToolHelper.runR8(
+ R8Command.builder()
+ .setOutput(out, OutputMode.ClassFile)
+ .setMode(CompilationMode.DEBUG)
+ .addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
+ .build(),
+ options -> options.enableCfFrontend = true);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return new CfDebugTestConfig().addPaths(out);
+ };
+ DelayedDebugTestConfig d8 = temp -> new D8DebugTestResourcesConfig(temp);
+ return ImmutableList.of(
+ new Object[] {"CF", cf},
+ new Object[] {"D8", d8},
+ new Object[] {"R8/CF", r8cf},
+ new Object[] {"R8/CF/CF", r8cfcf});
+ }
+
+ public SynchronizedBlockTest(String name, DelayedDebugTestConfig config) {
+ this.name = name;
+ this.config = config.getConfig(temp);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index d56c0e7..a148a26 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -753,6 +753,12 @@
}
@Override
+ public void buildBlockTransfer(
+ IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+ throw new Unreachable();
+ }
+
+ @Override
public void buildPostlude(IRBuilder builder) {
// Intentionally empty.
}
diff --git a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
new file mode 100644
index 0000000..6602d0c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
@@ -0,0 +1,106 @@
+// 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+
+interface SuperInterface {
+ Super foo();
+}
+
+interface SubInterface extends SuperInterface {
+ @Override
+ Sub foo();
+}
+
+class Super {
+ protected int bar() {
+ return 0;
+ }
+}
+
+class Sub extends Super {
+ @Override
+ protected int bar() {
+ return 1;
+ }
+}
+
+class SuperImplementer implements SuperInterface {
+ @Override
+ public Super foo() {
+ return new Super();
+ }
+}
+
+class SubImplementer implements SubInterface {
+ @Override
+ public Sub foo() {
+ return new Sub();
+ }
+}
+
+class TestMain {
+ public static void main(String[] args) {
+ SubImplementer subImplementer = new SubImplementer();
+ Super sup = subImplementer.foo();
+ System.out.println(sup.bar());
+ }
+}
+
+public class CovariantReturnTypeInSubInterfaceTest extends TestBase {
+
+ @Ignore("b/112185748")
+ @Test
+ public void test() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-printmapping",
+ "-useuniqueclassmembernames",
+ "-keep class " + TestMain.class.getCanonicalName() + " {",
+ " public void main(...);",
+ "}",
+ "-keep,allowobfuscation class **.Super* {",
+ " <methods>;",
+ "}",
+ "-keep,allowobfuscation class **.Sub* {",
+ " <methods>;",
+ "}"
+ );
+ AndroidApp app = readClasses(
+ SuperInterface.class,
+ SubInterface.class,
+ Super.class,
+ Sub.class,
+ SuperImplementer.class,
+ SubImplementer.class,
+ TestMain.class
+ );
+ AndroidApp processedApp = compileWithR8(app, String.join(System.lineSeparator(), config));
+ CodeInspector inspector = new CodeInspector(processedApp);
+ ClassSubject superInterface = inspector.clazz(SuperInterface.class);
+ assertThat(superInterface, isRenamed());
+ MethodSubject foo1 = superInterface.method(
+ Super.class.getCanonicalName(), "foo", ImmutableList.of());
+ assertThat(foo1, isRenamed());
+ ClassSubject subInterface = inspector.clazz(SubInterface.class);
+ assertThat(subInterface, isRenamed());
+ MethodSubject foo2 = subInterface.method(
+ Sub.class.getCanonicalName(), "foo", ImmutableList.of());
+ assertThat(foo2, isRenamed());
+ assertEquals(foo1.getFinalName(), foo2.getFinalName());
+ }
+
+}