Fix incorrect comparison of virtual registers in rematerialization
This fixes a register allocation bug that only manifests when methods require more than 256 registers.
The const instructions in DEX have an 8 bit register constraint. When determining if a value can be rematerialized, the register allocator therefore finds the max non-spilled register for the live intervals of the value to check if it fits in 8 bits.
However, in the computation of the max non-spilled register, the register allocator failed to consider that the virtual registers 0...N where N=number of arguments are larger than registers N+1...M where M is the max virtual register.
Bug: b/408438353
Change-Id: I6c9d8c0d99bdb77a1678411995df5579f91b9ef3
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 1816c75..b091a88 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
@@ -948,6 +948,7 @@
}
}
} else {
+ assert !mode.is8BitRetry();
assert !mode.is16Bit();
}
@@ -963,7 +964,8 @@
break;
case ALLOW_ARGUMENT_REUSE_U8BIT:
- if (highestUsedRegister() > Constants.U8BIT_MAX
+ if (!succeeded
+ || highestUsedRegister() > Constants.U8BIT_MAX
|| options().getTestingOptions().alwaysUsePessimisticRegisterAllocation) {
// Redo allocation in mode ALLOW_ARGUMENT_REUSE_U16BIT. This always succeed.
unusedRegisters = null;
@@ -980,7 +982,8 @@
break;
case ALLOW_ARGUMENT_REUSE_U8BIT_REFINEMENT:
- if (highestUsedRegister() > Constants.U8BIT_MAX
+ if (!succeeded
+ || highestUsedRegister() > Constants.U8BIT_MAX
|| numberOf4BitArgumentRegisters > computeNumberOf4BitArgumentRegisters()) {
// Redo allocation in mode ALLOW_ARGUMENT_REUSE_U8BIT_RETRY. This always succeed.
numberOf4BitArgumentRegisters = 0;
@@ -3113,7 +3116,7 @@
private void computeRematerializableBits() {
for (LiveIntervals liveInterval : liveIntervals) {
- liveInterval.computeRematerializable(this);
+ liveInterval.computeRematerializable(this, mode);
}
}
@@ -3208,6 +3211,23 @@
return register < numberOfArgumentRegisters;
}
+ public int maxVirtualRegister(int register, int other) {
+ if (register == NO_REGISTER) {
+ return other;
+ }
+ if (other == NO_REGISTER) {
+ return register;
+ }
+ if (isArgumentRegister(register)) {
+ if (!isArgumentRegister(other)) {
+ return register;
+ }
+ } else if (isArgumentRegister(other)) {
+ return other;
+ }
+ return Math.max(register, other);
+ }
+
boolean canSkipArgumentMove(LiveIntervals intervals) {
if (!isPinnedArgumentRegister(intervals)) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 816d263..1703894 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -348,7 +348,7 @@
liveAtMoveExceptionEntry = true;
}
- private int computeMaxNonSpilledRegister() {
+ private int computeMaxNonSpilledRegister(LinearScanRegisterAllocator allocator) {
assert splitParent == this;
assert maxNonSpilledRegister == NO_REGISTER;
if (!isSpilled()) {
@@ -356,22 +356,23 @@
}
for (LiveIntervals child : splitChildren) {
if (!child.isSpilled()) {
- maxNonSpilledRegister = Math.max(maxNonSpilledRegister, child.getRegister());
+ maxNonSpilledRegister =
+ allocator.maxVirtualRegister(maxNonSpilledRegister, child.getRegister());
}
}
return maxNonSpilledRegister;
}
- public void setMaxNonSpilledRegister(int i) {
- assert i >= splitParent.maxNonSpilledRegister;
- splitParent.maxNonSpilledRegister = i;
+ public void setMaxNonSpilledRegister(int register, LinearScanRegisterAllocator allocator) {
+ assert allocator.maxVirtualRegister(register, splitParent.maxNonSpilledRegister) == register;
+ splitParent.maxNonSpilledRegister = register;
}
- public int getMaxNonSpilledRegister() {
+ public int getMaxNonSpilledRegister(LinearScanRegisterAllocator allocator) {
if (splitParent.maxNonSpilledRegister != NO_REGISTER) {
return splitParent.maxNonSpilledRegister;
}
- return splitParent.computeMaxNonSpilledRegister();
+ return splitParent.computeMaxNonSpilledRegister(allocator);
}
public boolean usesRegister(int n, boolean otherIsWide) {
@@ -716,7 +717,8 @@
return builder.toString();
}
- public void computeRematerializable(LinearScanRegisterAllocator allocator) {
+ public void computeRematerializable(
+ LinearScanRegisterAllocator allocator, ArgumentReuseMode mode) {
assert splitParent == this;
if (value.isArgument()) {
isRematerializable = true;
@@ -732,6 +734,11 @@
return;
}
+ if (!mode.is16Bit()) {
+ isRematerializable = true;
+ return;
+ }
+
// If the constant is spilled when flowing to a phi and the phi has a register higher than what
// can be const rematerialized then this value is not rematerializable and needs a register even
// when spilled.
@@ -757,12 +764,13 @@
// these computations. We use the unadjusted real register number to make sure that
// isRematerializable for the same intervals does not change from one phase of
// compilation to the next.
- if (getMaxNonSpilledRegister() == NO_REGISTER) {
+ int maxNonSpilledRegister = getMaxNonSpilledRegister(allocator);
+ if (maxNonSpilledRegister == NO_REGISTER) {
assert allSplitsAreSpilled();
isRematerializable = true;
- return;
+ } else {
+ int maxRealRegister = allocator.unadjustedRealRegisterFromAllocated(maxNonSpilledRegister);
+ isRematerializable = maxRealRegister < U8BIT_MAX;
}
- int max = allocator.unadjustedRealRegisterFromAllocated(getMaxNonSpilledRegister());
- isRematerializable = max < U8BIT_MAX;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
index 3edfd86..aed222b 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
@@ -29,13 +29,13 @@
return type.hashCode() + 3 * from.getRegister() + 5 * to.getRegister();
}
- public void updateMaxNonSpilled() {
- int maxFrom = from.getMaxNonSpilledRegister();
- int maxTo = to.getMaxNonSpilledRegister();
- if (maxFrom > maxTo) {
- to.setMaxNonSpilledRegister(maxFrom);
+ public void updateMaxNonSpilled(LinearScanRegisterAllocator allocator) {
+ int maxFrom = from.getMaxNonSpilledRegister(allocator);
+ int maxTo = to.getMaxNonSpilledRegister(allocator);
+ if (allocator.maxVirtualRegister(maxFrom, maxTo) == maxFrom) {
+ to.setMaxNonSpilledRegister(maxFrom, allocator);
} else {
- from.setMaxNonSpilledRegister(maxTo);
+ from.setMaxNonSpilledRegister(maxTo, allocator);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index 736ed62..dfe4e74 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -100,7 +100,7 @@
public void addPhiMove(int i, LiveIntervals to, LiveIntervals from) {
assert i % 2 == 1;
SpillMove move = new SpillMove(moveTypeForIntervals(to, from), to, from);
- move.updateMaxNonSpilled();
+ move.updateMaxNonSpilled(allocator);
instructionToPhiMoves.computeIfAbsent(i, (k) -> new LinkedHashSet<>()).add(move);
}