Merge "Do not share constant use one time by a phi"
diff --git a/build.gradle b/build.gradle
index d13029e..4060a7a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -317,6 +317,7 @@
task R8(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
from sourceSets.main.output
+ from files('LICENSE')
baseName 'r8'
classifier = null
version = null
@@ -342,6 +343,7 @@
task D8(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
from sourceSets.main.output
+ from files('LICENSE')
baseName 'd8'
classifier = null
version = null
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 21ed16e..7e2e851 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
@@ -42,6 +42,41 @@
this.valueNumberGenerator = valueNumberGenerator;
}
+ /**
+ * Trace blocks and attempt to put fallthrough blocks immediately after the block that
+ * falls through. When we fail to do that we create a new fallthrough block with an explicit
+ * goto to the actual fallthrough block.
+ */
+ public void traceBlocks() {
+ BasicBlock[] sorted = topologicallySortedBlocks();
+ clearMarks();
+ int nextBlockNumber = blocks.size();
+ LinkedList<BasicBlock> tracedBlocks = new LinkedList<>();
+ for (BasicBlock block : sorted) {
+ if (!block.isMarked()) {
+ block.mark();
+ tracedBlocks.add(block);
+ BasicBlock current = block;
+ BasicBlock fallthrough = block.exit().fallthroughBlock();
+ while (fallthrough != null && !fallthrough.isMarked()) {
+ fallthrough.mark();
+ tracedBlocks.add(fallthrough);
+ current = fallthrough;
+ fallthrough = fallthrough.exit().fallthroughBlock();
+ }
+ if (fallthrough != null) {
+ BasicBlock newFallthrough = BasicBlock.createGotoBlock(fallthrough, nextBlockNumber++);
+ current.exit().setFallthroughBlock(newFallthrough);
+ newFallthrough.getPredecessors().add(current);
+ fallthrough.replacePredecessor(current, newFallthrough);
+ newFallthrough.mark();
+ tracedBlocks.add(newFallthrough);
+ }
+ }
+ }
+ blocks = tracedBlocks;
+ }
+
private void ensureBlockNumbering() {
if (!numbered) {
numbered = true;
@@ -327,6 +362,10 @@
return normalExitBlock;
}
+ public void invalidateNormalExitBlock() {
+ normalExitBlock = null;
+ }
+
public ListIterator<BasicBlock> listIterator() {
return new BasicBlockIterator(this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 337ed71..ba2fcb0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -67,7 +67,7 @@
@Override
public void buildDex(DexBuilder builder) {
- builder.add(this, createDexInstruction(builder));
+ builder.addReturn(this, createDexInstruction(builder));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index cbd8941..8540bf3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -402,6 +402,33 @@
add(argument, new FallThroughInfo(argument));
}
+ public void addReturn(Return ret, Instruction dex) {
+ if (nextBlock != null) {
+ Return followingRet = nextBlock.exit().asReturn();
+ if (nextBlock.getInstructions().size() == 1
+ && followingRet != null
+ && ret.getReturnType() == followingRet.getReturnType()) {
+ if (ret.isReturnVoid() && followingRet.isReturnVoid()) {
+ addNop(ret);
+ return;
+ }
+ if (!ret.isReturnVoid()
+ && !followingRet.isReturnVoid()
+ && ret.returnValue().outType() == followingRet.returnValue().outType()) {
+ int thisRegister = registerAllocator.getRegisterForValue(
+ ret.returnValue(), ret.getNumber());
+ int otherRegister = registerAllocator.getRegisterForValue(
+ followingRet.returnValue(), followingRet.getNumber());
+ if (thisRegister == otherRegister) {
+ addNop(ret);
+ return;
+ }
+ }
+ }
+ }
+ add(ret, dex);
+ }
+
private void add(com.android.tools.r8.ir.code.Instruction ir, Info info) {
assert ir != null;
assert info != null;
@@ -436,10 +463,24 @@
return info;
}
}
- assert instruction != null && instruction.isGoto();
+ assert instruction != null;
+ if (instruction.isReturn()) {
+ assert getInfo(instruction) instanceof FallThroughInfo;
+ return getTargetInfo(computeNextBlock(block));
+ }
+ assert instruction.isGoto();
return getTargetInfo(instruction.asGoto().getTarget());
}
+ private BasicBlock computeNextBlock(BasicBlock block) {
+ ListIterator<BasicBlock> it = ir.listIterator();
+ BasicBlock current = it.next();
+ while (current != block) {
+ current = it.next();
+ }
+ return it.next();
+ }
+
// Helper for computing switch payloads.
private Nop createSwitchPayload(SwitchPayloadInfo info, int offset) {
Switch ir = info.ir;
@@ -844,8 +885,10 @@
// Set the size to the min of the size of the return and the size of the goto. When
// adding instructions, we use the return if the computed size matches the size of the
// return.
+ assert !(targetInfo instanceof FallThroughInfo);
size = Math.min(targetInfo.getSize(), size);
}
+ assert size != 0;
return size;
}
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 1b7a276..77f1a16 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
@@ -399,7 +399,7 @@
// Create block order and make sure that all blocks are immediately followed by their
// fallthrough block if any.
- traceBlocks(ir);
+ ir.traceBlocks();
// Clear the code so we don't build multiple times.
source.clear();
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 0ddd43a..8e5b26f 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
@@ -522,6 +522,9 @@
}
printMethod(code, "Optimized IR (SSA)");
+
+ codeRewriter.inlineReturnBlock(code);
+
// Perform register allocation.
RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
method.setCode(code, registerAllocator, appInfo.dexItemFactory);
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 5570abe..5c1114b 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
@@ -87,6 +87,7 @@
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
@@ -1475,6 +1476,87 @@
}
}
+ /**
+ * Inline the return block at its targets.
+ *
+ * The inlining of return mostly undoes the merge performed at IR build time. This helps avoid
+ * unneeded moves as values are forced into the same register at all returns, which there can be
+ * a lot of when compiling in debug mode. Measurements show that iterating the inlining of returns
+ * does not pay off as it can lead to code size increase, eg, when a sequence of ifs all jump to
+ * a common return.
+ */
+ public void inlineReturnBlock(IRCode code) {
+ BasicBlock block = code.getNormalExitBlock();
+ code.invalidateNormalExitBlock();
+ if (block == null
+ || block.getPredecessors().size() <= 1
+ || block.getInstructions().size() > 1) {
+ return;
+ }
+ int predIndex = 0;
+ for (BasicBlock pred : block.getPredecessors()) {
+ ListIterator<Instruction> iterator = pred.listIterator(pred.exit());
+ iterator.previous();
+ for (Instruction origInstruction : block.getInstructions()) {
+ assert origInstruction.isReturn();
+ // Create an instruction copy replacing phi values of this block by their operands.
+ Instruction instruction;
+ Return ret = origInstruction.asReturn();
+ if (ret.isReturnVoid()) {
+ instruction = new Return();
+ } else {
+ Value origValue = ret.returnValue();
+ Value copyValue = origValue.isPhi() && block.getPhis().contains(origValue)
+ ? origValue.asPhi().getOperand(predIndex)
+ : origValue;
+ instruction = new Return(copyValue, ret.getReturnType());
+ }
+ // Copy over each debug value replacing phi values of this block by their operands.
+ for (Value value : origInstruction.getDebugValues()) {
+ assert value.getLocalInfo() != null;
+ if (value.isPhi() && block.getPhis().contains(value)) {
+ Phi phi = value.asPhi();
+ Value operand = phi.getOperand(predIndex);
+ if (phi.getLocalInfo() == operand.getLocalInfo()) {
+ instruction.addDebugValue(operand);
+ } else {
+ // If the phi and its operand are different locals insert a local write.
+ Value localValue = code.createValue(phi.outType(), phi.getLocalInfo());
+ DebugLocalWrite write = new DebugLocalWrite(localValue, operand);
+ write.setBlock(pred);
+ iterator.add(write);
+ instruction.addDebugValue(localValue);
+ }
+ } else {
+ instruction.addDebugValue(value);
+ }
+ }
+ instruction.setBlock(pred);
+ iterator.add(instruction);
+ }
+ iterator.previous();
+ Instruction ret = iterator.next();
+ Instruction jump = iterator.next();
+ assert !iterator.hasNext();
+ jump.moveDebugValues(ret);
+ iterator.remove();
+ assert pred.exit().isReturn();
+ pred.removeSuccessor(block);
+ predIndex++;
+ }
+ // Clean out all users and remove the inlined block.
+ while (!block.getPredecessors().isEmpty()) {
+ block.removePredecessor(block.getPredecessors().get(0));
+ }
+ for (Instruction instruction : block.getInstructions()) {
+ for (Value value : instruction.inValues()) {
+ value.removeUser(instruction);
+ }
+ instruction.clearDebugValues();
+ }
+ code.removeBlocks(Collections.singletonList(block));
+ }
+
private static class ExpressionEquivalence extends Equivalence<Instruction> {
@Override
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 2aa9015..1926f01 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
@@ -369,10 +369,10 @@
}
}
spillCount = 0;
- if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
+ if (localsChanged && instruction.getBlock().exit() != instruction) {
DebugLocalsChange change = createLocalsChange(ending, starting);
if (change != null) {
- if (instruction.isDebugPosition() || instruction.isJumpInstruction()) {
+ if (instruction.isDebugPosition()) {
instructionIterator.previous();
instructionIterator.add(change);
instructionIterator.next();
@@ -505,14 +505,6 @@
return new DebugLocalsChange(ending, starting);
}
- private boolean shouldEmitChangesAtInstruction(Instruction instruction) {
- BasicBlock block = instruction.getBlock();
- // We emit local changes on all non-exit instructions or, since we have only a singe return
- // block, any exits directly targeting that.
- return instruction != block.exit()
- || (instruction.isGoto() && instruction.asGoto().getTarget() == code.getNormalExitBlock());
- }
-
private void clearState() {
liveAtEntrySets = null;
liveIntervals = null;
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
index 919f569..9c41f21 100644
--- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
+++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -443,6 +443,7 @@
DexCode code = method.getCode().asDexCode();
// TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the
// expectation might need changing with other optimizations.
- assertEquals(27, code.instructions.length);
+ // TODO(zerny): Consider optimizing the fallthrough branch of conditionals to not be return.
+ assertEquals(28, code.instructions.length);
}
}