Decrease pressure on register allocator for /lit instructions
- Code size of SOR.execute decreases from 126 code units to 108 and the code
consumes a register less.
- Code size of GMSCore v10, v9 and framework decreases slightly (upto 1k) in
release mode and debug mode.
- Runtime performance of SOR benchmark is speedup by 8%.
Bug: 68188667
Change-Id: I4f4952fd20c2971006363288cf27fdd0bc7146e6
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 974f425..2c4f552 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -1371,4 +1372,29 @@
return sharedCatchSuccessors;
}
+
+ /**
+ * Return true if there is a path from the current {@link BasicBlock} to {@code target} or if
+ * {@code target} is the same block than the current {@link BasicBlock}.
+ */
+ public boolean hasPathTo(BasicBlock target) {
+ List<BasicBlock> visitedBlocks = new ArrayList<>();
+ ArrayDeque<BasicBlock> blocks = new ArrayDeque<>();
+ blocks.push(this);
+
+ while(!blocks.isEmpty()) {
+ BasicBlock block = blocks.pop();
+ if (block == target) {
+ return true;
+ }
+ visitedBlocks.add(block);
+ for (BasicBlock blockToVisit : block.getSuccessors()) {
+ if (!visitedBlocks.contains(blockToVisit)) {
+ blocks.push(blockToVisit);
+ }
+ }
+ }
+
+ return false;
+ }
}
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 0151b1a..00787ca 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
@@ -565,6 +565,7 @@
assert code.isConsistentSSA();
}
+ codeRewriter.useDedicatedConstantForLitInstruction(code);
codeRewriter.shortenLiveRanges(code);
codeRewriter.identifyReturnsArgument(method, code, feedback);
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 a1c529a..e8c6bac 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
@@ -1078,6 +1078,117 @@
}
}
+ /**
+ * If an instruction is known to be a /lit8 or /lit16 instruction, update the instruction to use
+ * its own constant that will be defined just before the instruction. This transformation allows
+ * to decrease pressure on register allocation by defining the shortest range of constant used
+ * by this kind of instruction. D8 knowns at build time that constant will be encoded
+ * directly into the final Dex instruction.
+ */
+ public void useDedicatedConstantForLitInstruction(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator instructionIterator = block.listIterator();
+ while (instructionIterator.hasNext()) {
+ Instruction currentInstruction = instructionIterator.next();
+ if (shouldBeLitInstruction(currentInstruction)) {
+ assert currentInstruction.isBinop();
+ Binop binop = currentInstruction.asBinop();
+ Value constValue;
+ if (binop.leftValue().isConstNumber()) {
+ constValue = binop.leftValue();
+ } else if (binop.rightValue().isConstNumber()) {
+ constValue = binop.rightValue();
+ } else {
+ throw new Unreachable();
+ }
+ if (constValue.numberOfAllUsers() > 1) {
+ // No need to do the transformation if the const value is already used only one time.
+ ConstNumber newConstant = ConstNumber
+ .copyOf(code, constValue.definition.asConstNumber());
+ newConstant.setPosition(currentInstruction.getPosition());
+ newConstant.setBlock(currentInstruction.getBlock());
+ currentInstruction.replaceValue(constValue, newConstant.outValue());
+ constValue.removeUser(currentInstruction);
+ instructionIterator.previous();
+ instructionIterator.add(newConstant);
+ instructionIterator.next();
+ }
+ }
+ }
+ }
+
+ assert code.isConsistentSSA();
+ }
+
+ /**
+ * A /lit8 or /lit16 instruction only concerns arithmetic or logical instruction. /lit8 or /lit16
+ * instructions generate bigger code than 2addr instructions, thus we favor 2addr instructions
+ * rather than /lit8 or /lit16 instructions.
+ */
+ private static boolean shouldBeLitInstruction(Instruction instruction) {
+ if (instruction.isArithmeticBinop() || instruction.isLogicalBinop()) {
+ Binop binop = instruction.asBinop();
+ if (!binop.needsValueInRegister(binop.leftValue()) ||
+ !binop.needsValueInRegister(binop.rightValue())) {
+ return !canBe2AddrInstruction(binop);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Estimate if a binary operation can be a 2addr form or not. It can be a 2addr form when an
+ * argument is no longer needed after the binary operation and can be overwritten. That is
+ * definitely the case if there is no path between the binary operation and all other usages.
+ */
+ private static boolean canBe2AddrInstruction(Binop binop) {
+ Value value = null;
+ if (binop.needsValueInRegister(binop.leftValue())) {
+ value = binop.leftValue();
+ } else if (binop.isCommutative() && binop.needsValueInRegister(binop.rightValue())) {
+ value = binop.rightValue();
+ }
+
+ if (value != null) {
+ Iterable<Instruction> users = value.debugUsers() != null ?
+ Iterables.concat(value.uniqueUsers(), value.debugUsers()) : value.uniqueUsers();
+
+ for (Instruction user : users) {
+ if (hasPath(binop, user)) {
+ return false;
+ }
+ }
+
+ Iterable<Phi> phiUsers = value.debugPhiUsers() != null
+ ? Iterables.concat(value.uniquePhiUsers(), value.debugPhiUsers())
+ : value.uniquePhiUsers();
+
+ for (Phi user : phiUsers) {
+ if (binop.getBlock().hasPathTo(user.getBlock())) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Return true if there is a path between {@code source} instruction and {@code target}
+ * instruction.
+ */
+ private static boolean hasPath(Instruction source, Instruction target) {
+ BasicBlock sourceBlock = source.getBlock();
+ BasicBlock targetBlock = target.getBlock();
+ if (sourceBlock == targetBlock) {
+ return sourceBlock.getInstructions().indexOf(source) <
+ targetBlock.getInstructions().indexOf(target);
+ }
+
+ return source.getBlock().hasPathTo(targetBlock);
+ }
+
public void shortenLiveRanges(IRCode code) {
// Currently, we are only shortening the live range of constants in the entry block.
// TODO(ager): Generalize this to shorten live ranges for more instructions? Currently