Remove unneeded moves from exiting blocks.
Bug: 70227637
Change-Id: I1c773a140c5f7b73f136bfa5a2e97207df571c13
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7b55543..f068a6a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1290,6 +1290,7 @@
CodeRewriter.collapseTrivialGotos(method, code);
PeepholeOptimizer.optimize(code, registerAllocator);
}
+ CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
CodeRewriter.collapseTrivialGotos(method, code);
if (Log.ENABLED) {
Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
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 3091c36..1eae24e 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
@@ -61,6 +61,7 @@
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.DebugLocalsChange;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.Goto;
@@ -78,6 +79,7 @@
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.NewInstance;
@@ -97,6 +99,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
+import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -115,12 +118,18 @@
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
@@ -3643,6 +3652,132 @@
assert code.isConsistentSSA();
}
+ /**
+ * Remove moves that are not actually used by instructions in exiting paths. These moves can arise
+ * due to debug local info needing a particular value and the live-interval for it then moves it
+ * back into the properly assigned register. If the register is only used for debug purposes, it
+ * is safe to just remove the move and update the local information accordingly.
+ */
+ public static void removeUnneededMovesOnExitingPaths(
+ IRCode code, LinearScanRegisterAllocator allocator) {
+ if (!code.options.debug) {
+ return;
+ }
+ for (BasicBlock block : code.blocks) {
+ // Skip non-exit blocks.
+ if (!block.getSuccessors().isEmpty()) {
+ continue;
+ }
+ // Skip blocks with no locals at entry.
+ Int2ReferenceMap<DebugLocalInfo> localsAtEntry = block.getLocalsAtEntry();
+ if (localsAtEntry == null || localsAtEntry.isEmpty()) {
+ continue;
+ }
+ // Find the locals state after spill moves.
+ DebugLocalsChange postSpillLocalsChange = null;
+ for (Instruction instruction : block.getInstructions()) {
+ if (instruction.getNumber() != -1 || postSpillLocalsChange != null) {
+ break;
+ }
+ postSpillLocalsChange = instruction.asDebugLocalsChange();
+ }
+ // Skip if the locals state did not change.
+ if (postSpillLocalsChange == null
+ || !postSpillLocalsChange.apply(new Int2ReferenceOpenHashMap<>(localsAtEntry))) {
+ continue;
+ }
+ // Collect the moves that can safely be removed.
+ Set<Move> unneededMoves = computeUnneededMoves(block, postSpillLocalsChange, allocator);
+ if (unneededMoves.isEmpty()) {
+ continue;
+ }
+ Int2IntMap previousMapping = new Int2IntOpenHashMap();
+ Int2IntMap mapping = new Int2IntOpenHashMap();
+ ListIterator<Instruction> it = block.getInstructions().listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isMove()) {
+ Move move = instruction.asMove();
+ if (unneededMoves.contains(move)) {
+ int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+ int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+ int mappedSrc = mapping.getOrDefault(src, src);
+ mapping.put(dst, mappedSrc);
+ it.remove();
+ }
+ } else if (instruction.isDebugLocalsChange()) {
+ DebugLocalsChange change = instruction.asDebugLocalsChange();
+ updateDebugLocalsRegisterMap(previousMapping, change.getEnding());
+ updateDebugLocalsRegisterMap(mapping, change.getStarting());
+ previousMapping = mapping;
+ mapping = new Int2IntOpenHashMap(previousMapping);
+ }
+ }
+ }
+ }
+
+ private static Set<Move> computeUnneededMoves(
+ BasicBlock block,
+ DebugLocalsChange postSpillLocalsChange,
+ LinearScanRegisterAllocator allocator) {
+ Set<Move> unneededMoves = Sets.newIdentityHashSet();
+ IntSet usedRegisters = new IntOpenHashSet();
+ IntSet clobberedRegisters = new IntOpenHashSet();
+ // Backwards instruction scan collecting the registers used by actual instructions.
+ boolean inEntrySpillMoves = false;
+ InstructionListIterator it = block.listIterator(block.getInstructions().size());
+ while (it.hasPrevious()) {
+ Instruction instruction = it.previous();
+ if (instruction == postSpillLocalsChange) {
+ inEntrySpillMoves = true;
+ }
+ // If this is a move in the block-entry spill moves check if it is unneeded.
+ if (inEntrySpillMoves && instruction.isMove()) {
+ Move move = instruction.asMove();
+ int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+ int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+ if (!usedRegisters.contains(dst) && !clobberedRegisters.contains(src)) {
+ unneededMoves.add(move);
+ continue;
+ }
+ }
+ if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
+ int register =
+ allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber());
+ // The register is defined anew, so uses before this are on distinct values.
+ usedRegisters.remove(register);
+ // Mark it clobbered to avoid any uses in locals after this point to become invalid.
+ clobberedRegisters.add(register);
+ }
+ if (!instruction.inValues().isEmpty()) {
+ for (Value inValue : instruction.inValues()) {
+ if (inValue.needsRegister()) {
+ int register = allocator.getRegisterForValue(inValue, instruction.getNumber());
+ // Record the register as being used.
+ usedRegisters.add(register);
+ }
+ }
+ }
+ }
+ return unneededMoves;
+ }
+
+ private static void updateDebugLocalsRegisterMap(
+ Int2IntMap mapping, Int2ReferenceMap<DebugLocalInfo> locals) {
+ // If nothing is mapped nothing needs to be changed.
+ if (mapping.isEmpty()) {
+ return;
+ }
+ // Locals is final, so we copy and clear it during update.
+ Int2ReferenceMap<DebugLocalInfo> copy = new Int2ReferenceOpenHashMap<>(locals);
+ locals.clear();
+ for (Entry<DebugLocalInfo> entry : copy.int2ReferenceEntrySet()) {
+ int oldRegister = entry.getIntKey();
+ int newRegister = mapping.getOrDefault(oldRegister, oldRegister);
+ locals.put(newRegister, entry.getValue());
+ }
+ }
+
// Removes calls to Throwable.addSuppressed(Throwable) and rewrites
// Throwable.getSuppressed() into new Throwable[0].
//
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index 10d2a31..96cb926 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -10,12 +10,13 @@
@Keep
public final class ArrayReference implements TypeReference {
- private final int dimentions;
+ private final int dimensions;
private final TypeReference baseType;
private String descriptor;
- private ArrayReference(int dimentions, TypeReference baseType, String descriptor) {
- this.dimentions = dimentions;
+ private ArrayReference(int dimensions, TypeReference baseType, String descriptor) {
+ assert dimensions > 0;
+ this.dimensions = dimensions;
this.baseType = baseType;
this.descriptor = descriptor;
}
@@ -23,15 +24,18 @@
static ArrayReference fromDescriptor(String descriptor) {
for (int i = 0; i < descriptor.length(); i++) {
if (descriptor.charAt(i) != '[') {
- return new ArrayReference(
- i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+ if (i > 0) {
+ return new ArrayReference(
+ i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+ }
+ break;
}
}
throw new Unreachable("Invalid array type descriptor: " + descriptor);
}
- public int getDimentions() {
- return dimentions;
+ public int getDimensions() {
+ return dimensions;
}
public TypeReference getMemberType() {
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index c4425b1..1a4e0ec 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -4,8 +4,12 @@
package com.android.tools.r8;
+import static org.hamcrest.MatcherAssert.assertThat;
+
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
@@ -17,4 +21,15 @@
protected Dex2OatTestRunResult self() {
return this;
}
+
+ public Dex2OatTestRunResult assertNoVerificationErrors() {
+ assertSuccess();
+ Matcher<? super String> matcher =
+ CoreMatchers.not(CoreMatchers.containsString("Verification error"));
+ assertThat(
+ errorMessage("Run dex2oat produced verification errors.", matcher.toString()),
+ getStdErr(),
+ matcher);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index a032fd6..89d0fff 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -108,11 +108,11 @@
return builder.toString();
}
- private String errorMessage(String message) {
+ String errorMessage(String message) {
return errorMessage(message, null);
}
- private String errorMessage(String message, String expected) {
+ String errorMessage(String message, String expected) {
StringBuilder builder = new StringBuilder(message).append('\n');
if (expected != null) {
if (expected.contains(System.lineSeparator())) {