Extend shorten-live-ranges to canonicalized values
Change-Id: I317e0781773a43dda6f8374ba5fd38bde264a0d4
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 06b2d1c..52dc8b3 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
@@ -1428,7 +1428,9 @@
// TODO(mkroghj) Test if shorten live ranges is worth it.
if (!options.isGeneratingClassFiles()) {
timing.begin("Canonicalize constants");
- new ConstantCanonicalizer(appView, codeRewriter, context, code).canonicalize();
+ ConstantCanonicalizer constantCanonicalizer =
+ new ConstantCanonicalizer(appView, codeRewriter, context, code);
+ constantCanonicalizer.canonicalize();
timing.end();
previous = printMethod(code, "IR after constant canonicalization (SSA)", previous);
timing.begin("Create constants for literal instructions");
@@ -1436,7 +1438,7 @@
timing.end();
previous = printMethod(code, "IR after constant literals (SSA)", previous);
timing.begin("Shorten live ranges");
- codeRewriter.shortenLiveRanges(code);
+ codeRewriter.shortenLiveRanges(code, constantCanonicalizer);
timing.end();
previous = printMethod(code, "IR after shorten live ranges (SSA)", previous);
}
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 1df3aef..38afb5d 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
@@ -7,6 +7,12 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import com.android.tools.r8.algorithms.scc.SCC;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -43,15 +49,18 @@
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.Binop;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstInstruction;
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;
import com.android.tools.r8.ir.code.IRCode;
@@ -100,7 +109,6 @@
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.utils.LongInterval;
-import com.android.tools.r8.utils.SetUtils;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
@@ -144,7 +152,6 @@
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
-import java.util.function.Consumer;
import java.util.function.Predicate;
public class CodeRewriter {
@@ -1834,112 +1841,136 @@
return true;
}
- public void shortenLiveRanges(IRCode code) {
+ public void shortenLiveRanges(IRCode code, ConstantCanonicalizer canonicalizer) {
if (options.testing.disableShortenLiveRanges) {
return;
}
- // Currently, we are only shortening the live range of ConstNumbers in the entry block
- // and ConstStrings with one user.
- // TODO(ager): Generalize this to shorten live ranges for more instructions? Currently
- // doing so seems to make things worse.
LazyBox<DominatorTree> dominatorTreeMemoization = new LazyBox<>(() -> new DominatorTree(code));
Map<BasicBlock, LinkedHashMap<Value, Instruction>> addConstantInBlock = new IdentityHashMap<>();
LinkedList<BasicBlock> blocks = code.blocks;
for (BasicBlock block : blocks) {
- if (block == blocks.getFirst()) {
- // For the first block process all ConstNumber as well as ConstString instructions.
- shortenLiveRangesInsideBlock(
- code,
- block,
- dominatorTreeMemoization,
- addConstantInBlock,
- insn -> insn.isConstNumber() || insn.isConstString());
- } else {
- // For all following blocks only process ConstString with just one use.
- shortenLiveRangesInsideBlock(
- code,
- block,
- dominatorTreeMemoization,
- addConstantInBlock,
- insn -> insn.isConstString() && insn.outValue().numberOfAllUsers() == 1);
- }
+ shortenLiveRangesInsideBlock(
+ code,
+ block,
+ dominatorTreeMemoization,
+ addConstantInBlock,
+ canonicalizer::isConstantCanonicalizationCandidate);
}
// Heuristic to decide if constant instructions are shared in dominator block
// of usages or moved to the usages.
// Process all blocks in stable order to avoid non-determinism of hash map iterator.
- for (BasicBlock block : blocks) {
- LinkedHashMap<Value, Instruction> constants = addConstantInBlock.get(block);
- if (constants == null) {
+ BasicBlockIterator blockIterator = code.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ Map<Value, Instruction> movedInstructions = addConstantInBlock.get(block);
+ if (movedInstructions == null) {
continue;
}
-
- Set<Value> alreadyMoved = SetUtils.newIdentityHashSet(constants.size());
- if (block != blocks.getFirst() && constants.size() > STOP_SHARED_CONSTANT_THRESHOLD) {
- // If there are too many constants in the same block, they are copied rather than shared
- // except if they are used by phi instructions or they are a string constants.
- for (Instruction constantInstruction : constants.values()) {
- if (!constantInstruction.outValue().hasPhiUsers()
- && !constantInstruction.isConstString()) {
- assert constantInstruction.isConstNumber();
- ConstNumber constNumber = constantInstruction.asConstNumber();
- Value constantValue = constantInstruction.outValue();
- assert constantValue.hasUsers();
- assert constantValue.numberOfUsers() == constantValue.numberOfAllUsers();
- for (Instruction user : constantValue.uniqueUsers()) {
- ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
- newCstNum.setPosition(user.getPosition());
- InstructionListIterator iterator = user.getBlock().listIterator(code, user);
- iterator.previous();
- iterator.add(newCstNum);
- user.replaceValue(constantValue, newCstNum.outValue());
- }
- constantValue.clearUsers();
- alreadyMoved.add(constantInstruction.outValue());
- }
- }
+ assert !movedInstructions.isEmpty();
+ if (!block.isEntry() && movedInstructions.size() > STOP_SHARED_CONSTANT_THRESHOLD) {
+ // If there are too many constant numbers in the same block they are copied rather than
+ // shared unless used by a phi.
+ movedInstructions
+ .values()
+ .removeIf(
+ movedInstruction -> {
+ if (movedInstruction.outValue().hasPhiUsers()
+ || !movedInstruction.isConstNumber()) {
+ return false;
+ }
+ ConstNumber constNumber = movedInstruction.asConstNumber();
+ Value constantValue = movedInstruction.outValue();
+ for (Instruction user : constantValue.uniqueUsers()) {
+ ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
+ newCstNum.setPosition(user.getPosition());
+ InstructionListIterator iterator = user.getBlock().listIterator(code, user);
+ iterator.previous();
+ iterator.add(newCstNum);
+ user.replaceValue(constantValue, newCstNum.outValue());
+ }
+ constantValue.clearUsers();
+ return true;
+ });
}
// Add constant into the dominator block of usages.
boolean hasCatchHandlers = block.hasCatchHandlers();
- InstructionListIterator it = block.listIterator(code);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.isJumpInstruction()
- || (hasCatchHandlers && instruction.instructionTypeCanThrow())
- || (options.canHaveCmpIfFloatBug() && instruction.isCmp())) {
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction insertionPoint = instructionIterator.next();
+ if (insertionPoint.isJumpInstruction()
+ || (hasCatchHandlers && insertionPoint.instructionTypeCanThrow())
+ || (options.canHaveCmpIfFloatBug() && insertionPoint.isCmp())) {
break;
}
- forEachUse(
- instruction,
- use -> {
- Instruction constantInstruction = constants.get(use);
- if (constantInstruction != null && !alreadyMoved.contains(use)) {
- it.previous();
- constantInstruction.setPosition(instruction.getPosition());
- it.add(constantInstruction);
- it.next();
- alreadyMoved.add(use);
- }
- });
- }
- // Insert remaining constant instructions prior to the "exit".
- Instruction next = it.previous();
- for (Instruction constantInstruction : constants.values()) {
- if (!alreadyMoved.contains(constantInstruction.outValue())) {
- constantInstruction.setPosition(next.getPosition());
- it.add(constantInstruction);
+ for (Value use :
+ Iterables.concat(insertionPoint.inValues(), insertionPoint.getDebugValues())) {
+ Instruction movedInstruction = movedInstructions.remove(use);
+ if (movedInstruction != null) {
+ instructionIterator =
+ insertInstructionWithShortenedLiveRange(
+ code, blockIterator, instructionIterator, movedInstruction, insertionPoint);
+ }
}
}
+
+ // Insert remaining constant instructions prior to the "exit".
+ Instruction insertionPoint = instructionIterator.peekPrevious();
+ for (Instruction movedInstruction : movedInstructions.values()) {
+ instructionIterator =
+ insertInstructionWithShortenedLiveRange(
+ code, blockIterator, instructionIterator, movedInstruction, insertionPoint);
+ }
}
assert code.isConsistentSSA(appView);
}
- private void forEachUse(Instruction instruction, Consumer<Value> fn) {
- instruction.inValues().forEach(fn);
- instruction.getDebugValues().forEach(fn);
+ private InstructionListIterator insertInstructionWithShortenedLiveRange(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ Instruction movedInstruction,
+ Instruction insertionPoint) {
+ Instruction previous = instructionIterator.previous();
+ assert previous == insertionPoint;
+ movedInstruction.setPosition(
+ getPositionForMovedNonThrowingInstruction(movedInstruction, insertionPoint));
+ if (movedInstruction.instructionTypeCanThrow()
+ && insertionPoint.getBlock().hasCatchHandlers()) {
+ // Split the block and reset the block iterator.
+ BasicBlock splitBlock =
+ instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options());
+ BasicBlock previousBlock = blockIterator.previousUntil(b -> b == splitBlock);
+ assert previousBlock == splitBlock;
+ blockIterator.next();
+
+ // Add the constant instruction before the exit instruction.
+ assert !instructionIterator.hasNext();
+ instructionIterator.previous();
+ instructionIterator.add(movedInstruction);
+
+ // Continue insertion at the entry of the split block.
+ instructionIterator = splitBlock.listIterator(code);
+ } else {
+ instructionIterator.add(movedInstruction);
+ }
+ Instruction next = instructionIterator.next();
+ assert next == insertionPoint;
+ return instructionIterator;
+ }
+
+ private Position getPositionForMovedNonThrowingInstruction(
+ Instruction movedInstruction, Instruction insertionPoint) {
+ // If the type of the moved instruction is throwing and we don't have a position at the
+ // insertion point, we use the special synthetic-none position, which is OK as the moved
+ // instruction instance is known not to throw (or we would not be allowed the move it).
+ if (movedInstruction.instructionTypeCanThrow() && !insertionPoint.getPosition().isSome()) {
+ return Position.syntheticNone();
+ }
+ return insertionPoint.getPosition();
}
private void shortenLiveRangesInsideBlock(
@@ -1951,17 +1982,7 @@
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
- if (!instruction.isConstInstruction()
- || instruction.hasUnusedOutValue()
- || instruction.outValue().hasLocalInfo()) {
- continue;
- }
-
- // We don't want to push const string instructions down into code that has monitors since
- // we may attach catch handlers that are not catch-all when inlining. This is symmetric in how
- // we don't do const string canonicalization.
- if ((instruction.isConstString() || instruction.isDexItemBasedConstString())
- && code.metadata().mayHaveMonitorInstruction()) {
+ if (instruction.hasUnusedOutValue() || instruction.outValue().hasLocalInfo()) {
continue;
}
@@ -2049,9 +2070,29 @@
}
}
- ConstInstruction copy = instruction.isConstNumber()
- ? ConstNumber.copyOf(code, instruction.asConstNumber())
- : ConstString.copyOf(code, instruction.asConstString());
+ Instruction copy;
+ switch (instruction.opcode()) {
+ case CONST_CLASS:
+ copy = ConstClass.copyOf(code, instruction.asConstClass());
+ break;
+ case CONST_NUMBER:
+ copy = ConstNumber.copyOf(code, instruction.asConstNumber());
+ break;
+ case CONST_STRING:
+ copy = ConstString.copyOf(code, instruction.asConstString());
+ break;
+ case DEX_ITEM_BASED_CONST_STRING:
+ copy = DexItemBasedConstString.copyOf(code, instruction.asDexItemBasedConstString());
+ break;
+ case INSTANCE_GET:
+ copy = InstanceGet.copyOf(code, instruction.asInstanceGet());
+ break;
+ case STATIC_GET:
+ copy = StaticGet.copyOf(code, instruction.asStaticGet());
+ break;
+ default:
+ throw new Unreachable();
+ }
instruction.outValue().replaceUsers(copy.outValue());
addConstantInBlock
.computeIfAbsent(dominator, k -> new LinkedHashMap<>())
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 8d8410a..8fffe1e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -417,12 +417,6 @@
break;
case CONST_STRING:
case DEX_ITEM_BASED_CONST_STRING:
- // Do not canonicalize ConstString instructions if there are monitor operations in the code.
- // That could lead to unbalanced locking and could lead to situations where OOM exceptions
- // could leave a synchronized method without unlocking the monitor.
- if (code.metadata().mayHaveMonitorInstruction()) {
- return false;
- }
break;
case INSTANCE_GET:
{
@@ -466,6 +460,12 @@
if (instruction.outValue().hasLocalInfo()) {
return false;
}
+ // Do not canonicalize throwing instructions if there are monitor operations in the code.
+ // That could lead to unbalanced locking and could lead to situations where OOM exceptions
+ // could leave a synchronized method without unlocking the monitor.
+ if (instruction.instructionTypeCanThrow() && code.metadata().mayHaveMonitorInstruction()) {
+ return false;
+ }
// Constants that are used by invoke range are not canonicalized to be compliant with the
// optimization splitRangeInvokeConstant that gives the register allocator more freedom in
// assigning register to ranged invokes which can greatly reduce the number of register
diff --git a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
index 3d3aecc..bdb4119 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
@@ -126,10 +126,9 @@
DexCode code = method.getMethod().getCode().asDexCode();
assertTrue(code.instructions[0] instanceof DexConstClass);
- assertTrue(code.instructions[1] instanceof DexConstClass);
- assertTrue(code.instructions[2] instanceof DexConstString);
- DexConstString constString = (DexConstString) code.instructions[2];
- assertNotEquals("foo", constString.getString().toString());
+ assertTrue(code.instructions[1] instanceof DexConstString);
+ assertNotEquals("foo", code.instructions[1].asConstString().getString().toString());
+ assertTrue(code.instructions[2] instanceof DexConstClass);
assertTrue(code.instructions[3] instanceof DexInvokeStatic);
assertTrue(code.instructions[4] instanceof DexReturnVoid);
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 7a30afd..812eb63 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -93,15 +93,14 @@
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof DexConstClass);
- assertTrue(code.instructions[1] instanceof DexConst4);
- assertTrue(code.instructions[2] instanceof DexNewArray);
- assertTrue(code.instructions[3] instanceof DexConst4);
+ assertTrue(code.instructions[0] instanceof DexConst4);
+ assertTrue(code.instructions[1] instanceof DexNewArray);
+ assertTrue(code.instructions[2] instanceof DexConst4);
+ assertTrue(code.instructions[3] instanceof DexConstClass);
assertTrue(code.instructions[4] instanceof DexAputObject);
assertTrue(code.instructions[5] instanceof DexConstClass);
assertTrue(code.instructions[6] instanceof DexConstString);
- DexConstString constString = (DexConstString) code.instructions[6];
- assertNotEquals("foo", constString.getString().toString());
+ assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
assertTrue(code.instructions[8] instanceof DexReturnVoid);
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java b/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java
index 3e93e17..8add503 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java
@@ -54,7 +54,7 @@
while (it.hasNext()) {
numbers.add(it.next().getConstNumber());
}
- assertEquals(new LongArrayList(new long[] {4, 5, 0, 1, 2, 3}), numbers);
+ assertEquals(new LongArrayList(new long[] {0, 1, 2, 3, 4, 5}), numbers);
});
}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 039ba9a..83ea780 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -651,10 +651,10 @@
code,
ImmutableList.of(
DexInvokeDirect.class,
- DexConstClass.class,
DexConst4.class,
DexNewArray.class,
DexConst4.class,
+ DexConstClass.class,
DexAputObject.class,
DexConstString.class,
DexInvokeStatic.class,
@@ -712,10 +712,10 @@
code,
ImmutableList.of(
DexInvokeDirect.class,
- DexConstClass.class,
DexConst4.class,
DexNewArray.class,
DexConst4.class,
+ DexConstClass.class,
DexAputObject.class,
DexConstString.class,
DexInvokeStatic.class,
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 b9cf6c9..fd50cd6 100644
--- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
+++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -180,8 +180,8 @@
":return",
" return v0");
DexCode code = method.getCode().asDexCode();
- assertEquals(10, code.instructions.length);
- assertTrue(code.instructions[9] instanceof DexReturn);
+ assertEquals(12, code.instructions.length);
+ assertTrue(code.instructions[11] instanceof DexReturn);
}
@Test