Merge "Update run_on_as_app.py"
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 1544570..9c3bcc3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -107,7 +107,6 @@
throw new Unreachable("unless a new type lattice is introduced.");
}
-
public static TypeLatticeElement join(
Iterable<TypeLatticeElement> typeLattices, AppInfo appInfo) {
TypeLatticeElement result = BOTTOM;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 77f7fb8..9b8a00e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -27,17 +27,6 @@
assert !ending.isEmpty() || !starting.isEmpty();
this.ending = ending;
this.starting = starting;
- super.setPosition(Position.none());
- }
-
- @Override
- public void setPosition(Position position) {
- throw new Unreachable();
- }
-
- @Override
- public boolean verifyValidPositionInfo(boolean debug) {
- return true;
}
public Int2ReferenceMap<DebugLocalInfo> getEnding() {
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 8e0d572..cb93a5e 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
@@ -662,13 +662,9 @@
}
// After the throwing instruction only debug instructions and the final jump
// instruction is allowed.
- // TODO(ager): For now allow const instructions due to the way consts are pushed
- // towards their use
if (seenThrowing) {
assert instruction.isDebugInstruction()
|| instruction.isJumpInstruction()
- || instruction.isConstInstruction()
- || instruction.isNewArrayFilledData()
|| instruction.isStore()
|| instruction.isPop();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
index b98662c..cdb40bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
@@ -19,10 +19,7 @@
@Override
public boolean hasNext() {
- if (instructionIterator.hasNext()) {
- return true;
- }
- return blockIterator.hasNext();
+ return instructionIterator.hasNext() || blockIterator.hasNext();
}
@Override
@@ -39,6 +36,35 @@
}
@Override
+ public boolean hasPrevious() {
+ return instructionIterator.hasPrevious() || blockIterator.hasPrevious();
+ }
+
+ @Override
+ public Instruction previous() {
+ if (instructionIterator.hasPrevious()) {
+ return instructionIterator.previous();
+ }
+ if (!blockIterator.hasPrevious()) {
+ throw new NoSuchElementException();
+ }
+ BasicBlock block = blockIterator.previous();
+ instructionIterator = block.listIterator(block.getInstructions().size());
+ assert instructionIterator.hasPrevious();
+ return instructionIterator.previous();
+ }
+
+ @Override
+ public int nextIndex() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int previousIndex() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void add(Instruction instruction) {
instructionIterator.add(instruction);
}
@@ -49,6 +75,11 @@
}
@Override
+ public void set(Instruction instruction) {
+ instructionIterator.set(instruction);
+ }
+
+ @Override
public void replaceCurrentInstruction(Instruction newInstruction) {
instructionIterator.replaceCurrentInstruction(newInstruction);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 2979039..25d1fc4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -142,6 +142,7 @@
return super.toString()
+ " "
+ type
+ + (isZeroTest() ? "Z" : " ")
+ " block "
+ getTrueTarget().getNumberAsString()
+ " (fallthrough "
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
index 2f2b3f1..72aa3ee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.ir.code;
-public interface InstructionIterator extends NextUntilIterator<Instruction> {
+import java.util.ListIterator;
+
+public interface InstructionIterator
+ extends ListIterator<Instruction>, NextUntilIterator<Instruction> {
/**
* Replace the current instruction (aka the {@link Instruction} returned by the previous call to
* {@link #next} with the passed in <code>newInstruction</code>.
@@ -22,15 +25,6 @@
*/
void replaceCurrentInstruction(Instruction newInstruction);
- /**
- * Adds an instruction. The instruction will be added just before the current
- * cursor position.
- *
- * The instruction will be assigned to the block it is added to.
- *
- * @param instruction The instruction to add.
- */
- void add(Instruction instruction);
/**
* Safe removal function that will insert a DebugLocalRead to take over the debug values if any
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index a5e5d78..c232d3f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -11,9 +11,7 @@
import java.util.ListIterator;
public interface InstructionListIterator
- extends ListIterator<Instruction>,
- NextUntilIterator<Instruction>,
- PreviousUntilIterator<Instruction> {
+ extends InstructionIterator, PreviousUntilIterator<Instruction> {
/**
* Peek the previous instruction.
@@ -50,27 +48,6 @@
}
/**
- * Safe removal function that will insert a DebugLocalRead to take over the debug values if any
- * are associated with the current instruction.
- */
- void removeOrReplaceByDebugLocalRead();
-
- /**
- * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
- * {@link #next} with the passed in <code>newInstruction</code>.
- *
- * The current instruction will be completely detached from the instruction stream with uses
- * of its in-values removed.
- *
- * If the current instruction produces an out-value the new instruction must also produce
- * an out-value, and all uses of the current instructions out-value will be replaced by the
- * new instructions out-value.
- *
- * @param newInstruction the instruction to insert instead of the current.
- */
- void replaceCurrentInstruction(Instruction newInstruction);
-
- /**
* Split the block into two blocks at the point of the {@link ListIterator} cursor. The existing
* block will have all the instructions before the cursor, and the new block all the
* instructions after the cursor.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index f95b988..374acd5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -797,7 +797,17 @@
}
public boolean knownToBeBoolean() {
- return knownToBeBoolean;
+ if (knownToBeBoolean) {
+ return true;
+ }
+ if (getTypeLattice().isInt()) {
+ Value aliasedValue = getAliasedValue();
+ if (!aliasedValue.isPhi() && aliasedValue.definition.isConstNumber()) {
+ ConstNumber definition = aliasedValue.definition.asConstNumber();
+ return definition.isZero() || definition.getRawValue() == 1;
+ }
+ }
+ return false;
}
public void markAsThis(boolean receiverCanBeNull) {
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 4b2fabb..ec2fe4c 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
@@ -377,8 +377,11 @@
unresolvedPosition = instruction.asDebugPosition();
localsAtUnresolvedPosition = new Int2ReferenceOpenHashMap<>(locals);
}
- } else if (instruction.getPosition().isSome()) {
- if (!isNopInstruction(instruction, nextBlock)) {
+ } else {
+ assert instruction.getPosition().isSome();
+ if (instruction.isDebugLocalsChange()) {
+ instruction.asDebugLocalsChange().apply(locals);
+ } else if (!isNopInstruction(instruction, nextBlock)) {
if (unresolvedPosition != null) {
if (unresolvedPosition.getPosition() == instruction.getPosition()
&& locals.equals(localsAtUnresolvedPosition)) {
@@ -389,12 +392,6 @@
}
currentMaterializedPosition = instruction.getPosition();
}
- } else {
- // Only local-change instructions don't have positions in debug mode but fail gracefully.
- assert instruction.isDebugLocalsChange();
- if (instruction.isDebugLocalsChange()) {
- instruction.asDebugLocalsChange().apply(locals);
- }
}
}
}
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 b1ea3f9..7b55543 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
@@ -997,6 +997,7 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code);
+ codeRewriter.redundantConstNumberRemoval(code);
new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
if (options.testing.invertConditionals) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 62cddc9..cf88f0f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -210,7 +210,15 @@
iterator.replaceCurrentInstruction(newInvoke);
if (constantReturnMaterializingInstruction != null) {
- iterator.add(constantReturnMaterializingInstruction);
+ if (block.hasCatchHandlers()) {
+ // Split the block to ensure no instructions after throwing instructions.
+ iterator
+ .split(code, blocks)
+ .listIterator()
+ .add(constantReturnMaterializingInstruction);
+ } else {
+ iterator.add(constantReturnMaterializingInstruction);
+ }
}
DexType actualReturnType = actualTarget.proto.returnType;
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 7447f76..3227d45 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
@@ -26,6 +26,7 @@
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.ThrowableMethods;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
@@ -120,6 +121,8 @@
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.longs.Long2ReferenceMap;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -3144,6 +3147,210 @@
simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(cond));
}
+ /**
+ * This optimization exploits that we can sometimes learn the constant value of an SSA value that
+ * flows into an if-eq of if-neq instruction.
+ *
+ * <p>Consider the following example:
+ *
+ * <pre>
+ * 1. if (obj != null) {
+ * 2. return doStuff();
+ * 3. }
+ * 4. return null;
+ * </pre>
+ *
+ * <p>Since we know that `obj` is null in all blocks that are dominated by the false-target of the
+ * if-instruction in line 1, we can safely replace the null-constant in line 4 by `obj`, and
+ * thereby save a const-number instruction.
+ */
+ public void redundantConstNumberRemoval(IRCode code) {
+ Supplier<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
+ Suppliers.memoize(() -> getConstantsByValue(code));
+ Supplier<DominatorTree> dominatorTree = Suppliers.memoize(() -> new DominatorTree(code));
+
+ boolean changed = false;
+ for (BasicBlock block : code.blocks) {
+ Instruction lastInstruction = block.getInstructions().getLast();
+ if (!lastInstruction.isIf()) {
+ continue;
+ }
+
+ If ifInstruction = lastInstruction.asIf();
+ Type type = ifInstruction.getType();
+
+ Value lhs = ifInstruction.inValues().get(0);
+ Value rhs = !ifInstruction.isZeroTest() ? ifInstruction.inValues().get(1) : null;
+
+ if (!ifInstruction.isZeroTest() && !lhs.isConstNumber() && !rhs.isConstNumber()) {
+ // We can only conclude anything from an if-instruction if it is a zero-test or if one of
+ // the two operands is a constant.
+ continue;
+ }
+
+ // If the type is neither EQ nor NE, we cannot conclude anything about any of the in-values
+ // of the if-instruction from the outcome of the if-instruction.
+ if (type != Type.EQ && type != Type.NE) {
+ continue;
+ }
+
+ BasicBlock trueTarget, falseTarget;
+ if (type == Type.EQ) {
+ trueTarget = ifInstruction.getTrueTarget();
+ falseTarget = ifInstruction.fallthroughBlock();
+ } else {
+ falseTarget = ifInstruction.getTrueTarget();
+ trueTarget = ifInstruction.fallthroughBlock();
+ }
+
+ if (ifInstruction.isZeroTest()) {
+ changed |=
+ replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, dominatorTree);
+ if (lhs.knownToBeBoolean()) {
+ changed |=
+ replaceDominatedConstNumbers(1, lhs, falseTarget, constantsByValue, dominatorTree);
+ }
+ } else {
+ assert rhs != null;
+ if (lhs.isConstNumber()) {
+ ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber();
+ changed |=
+ replaceDominatedConstNumbers(
+ lhsAsNumber.getRawValue(), rhs, trueTarget, constantsByValue, dominatorTree);
+ if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
+ changed |=
+ replaceDominatedConstNumbers(
+ negateBoolean(lhsAsNumber), rhs, falseTarget, constantsByValue, dominatorTree);
+ }
+ } else {
+ assert rhs.isConstNumber();
+ ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber();
+ changed |=
+ replaceDominatedConstNumbers(
+ rhsAsNumber.getRawValue(), lhs, trueTarget, constantsByValue, dominatorTree);
+ if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
+ changed |=
+ replaceDominatedConstNumbers(
+ negateBoolean(rhsAsNumber), lhs, falseTarget, constantsByValue, dominatorTree);
+ }
+ }
+ }
+
+ if (constantsByValue.get().isEmpty()) {
+ break;
+ }
+ }
+
+ if (changed) {
+ code.removeAllTrivialPhis();
+ }
+ assert code.isConsistentSSA();
+ }
+
+ private static Long2ReferenceMap<List<ConstNumber>> getConstantsByValue(IRCode code) {
+ // A map from the raw value of constants in `code` to the const number instructions that define
+ // the given raw value (irrespective of the type of the raw value).
+ Long2ReferenceMap<List<ConstNumber>> constantsByValue = new Long2ReferenceOpenHashMap<>();
+
+ // Initialize `constantsByValue`.
+ Iterable<Instruction> instructions = code::instructionIterator;
+ for (Instruction instruction : instructions) {
+ if (instruction.isConstNumber()) {
+ ConstNumber constNumber = instruction.asConstNumber();
+ if (constNumber.outValue().hasLocalInfo()) {
+ // Not necessarily constant, because it could be changed in the debugger.
+ continue;
+ }
+ long rawValue = constNumber.getRawValue();
+ if (constantsByValue.containsKey(rawValue)) {
+ constantsByValue.get(rawValue).add(constNumber);
+ } else {
+ List<ConstNumber> list = new ArrayList<>();
+ list.add(constNumber);
+ constantsByValue.put(rawValue, list);
+ }
+ }
+ }
+ return constantsByValue;
+ }
+
+ private static int negateBoolean(ConstNumber number) {
+ assert number.outValue().knownToBeBoolean();
+ return number.getRawValue() == 0 ? 1 : 0;
+ }
+
+ private boolean replaceDominatedConstNumbers(
+ long withValue,
+ Value newValue,
+ BasicBlock dominator,
+ Supplier<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
+ Supplier<DominatorTree> dominatorTree) {
+ if (newValue.hasLocalInfo()) {
+ // We cannot replace a constant with a value that has local info, because that could change
+ // debugging behavior.
+ return false;
+ }
+
+ Long2ReferenceMap<List<ConstNumber>> constantsByValue = constantsByValueSupplier.get();
+ List<ConstNumber> constantsWithValue = constantsByValue.get(withValue);
+ if (constantsWithValue == null || constantsWithValue.isEmpty()) {
+ return false;
+ }
+
+ boolean changed = false;
+
+ ListIterator<ConstNumber> constantWithValueIterator = constantsWithValue.listIterator();
+ while (constantWithValueIterator.hasNext()) {
+ ConstNumber constNumber = constantWithValueIterator.next();
+ Value value = constNumber.outValue();
+ assert !value.hasLocalInfo();
+ assert constNumber.getRawValue() == withValue;
+
+ BasicBlock block = constNumber.getBlock();
+
+ // If the following condition does not hold, then the if-instruction does not dominate the
+ // block containing the constant, although the true or false target does.
+ if (block == dominator && block.getPredecessors().size() != 1) {
+ // This should generally not happen, but it is possible to write bytecode where it does.
+ assert false;
+ continue;
+ }
+
+ if (value.knownToBeBoolean() && !newValue.knownToBeBoolean()) {
+ // We cannot replace a boolean by a none-boolean since that can lead to verification
+ // errors. For example, the following code fails with "register v1 has type Imprecise
+ // Constant: 127 but expected Boolean return-1nr".
+ //
+ // public boolean convertIntToBoolean(int v1) {
+ // const/4 v0, 0x1
+ // if-eq v1, v0, :eq_true
+ // const/4 v1, 0x0
+ // :eq_true
+ // return v1
+ // }
+ continue;
+ }
+
+ if (dominatorTree.get().dominatedBy(block, dominator)) {
+ if (newValue.getTypeLattice().lessThanOrEqual(value.getTypeLattice(), appInfo)) {
+ value.replaceUsers(newValue);
+ block.listIterator(constNumber).removeOrReplaceByDebugLocalRead();
+ constantWithValueIterator.remove();
+ changed = true;
+ } else if (value.getTypeLattice().isNullType()) {
+ // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of
+ // the use sites of `value` without introducing a type error.
+ }
+ }
+ }
+
+ if (constantsWithValue.isEmpty()) {
+ constantsByValue.remove(withValue);
+ }
+
+ return changed;
+ }
+
// Find all method invocations that never returns normally, split the block
// after each such invoke instruction and follow it with a block throwing a
// null value (which should result in NPE). Note that this throw is not
@@ -3439,7 +3646,7 @@
// Note that addSuppressed() and getSuppressed() methods are final in
// Throwable, so these changes don't have to worry about overrides.
public void rewriteThrowableAddAndGetSuppressed(IRCode code) {
- DexItemFactory.ThrowableMethods throwableMethods = dexItemFactory.throwableMethods;
+ ThrowableMethods throwableMethods = dexItemFactory.throwableMethods;
for (BasicBlock block : code.blocks) {
InstructionListIterator iterator = block.listIterator();
@@ -3579,7 +3786,7 @@
} else {
// Insert "if (argument != null) ...".
successor = block.unlinkSingleSuccessor();
- If theIf = new If(If.Type.NE, argument);
+ If theIf = new If(Type.NE, argument);
theIf.setPosition(position);
BasicBlock ifBlock = BasicBlock.createIfBlock(code.blocks.size(), theIf);
code.blocks.add(ifBlock);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 4da9f8d..99f9731 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,11 +17,13 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
@@ -29,6 +31,7 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardMemberRule;
import com.google.common.collect.Sets;
+import java.util.ListIterator;
import java.util.Set;
import java.util.function.Predicate;
@@ -133,151 +136,160 @@
public void rewriteWithConstantValues(
IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
- InstructionIterator iterator = code.instructionIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType invokedHolder = invokedMethod.getHolder();
- if (!invokedHolder.isClassType()) {
- continue;
- }
- // TODO(70550443): Maybe check all methods here.
- DexEncodedMethod definition = appInfo
- .lookup(invoke.getType(), invokedMethod, callingContext);
+ ListIterator<BasicBlock> blocks = code.blocks.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isInvokeMethod()) {
+ InvokeMethod invoke = current.asInvokeMethod();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ DexType invokedHolder = invokedMethod.getHolder();
+ if (!invokedHolder.isClassType()) {
+ continue;
+ }
+ // TODO(70550443): Maybe check all methods here.
+ DexEncodedMethod definition = appInfo
+ .lookup(invoke.getType(), invokedMethod, callingContext);
- boolean invokeReplaced = false;
- ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
- if (lookup != null) {
- if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
- && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
- // Remove invoke if marked as having no side effects and the return value is not used.
- iterator.remove();
- invokeReplaced = true;
- } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
- // Check to see if a constant value can be assumed.
- Instruction replacement =
- constantReplacementFromProguardRule(lookup.rule, code, invoke);
- if (replacement != null) {
- affectedValues.add(replacement.outValue());
- replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
+ boolean invokeReplaced = false;
+ ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
+ if (lookup != null) {
+ if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
+ && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
+ // Remove invoke if marked as having no side effects and the return value is not used.
+ iterator.remove();
invokeReplaced = true;
- } else {
- // Check to see if a value range can be assumed.
- setValueRangeFromProguardRule(lookup.rule, current.outValue());
+ } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
+ // Check to see if a constant value can be assumed.
+ Instruction replacement =
+ constantReplacementFromProguardRule(lookup.rule, code, invoke);
+ if (replacement != null) {
+ affectedValues.add(replacement.outValue());
+ replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
+ invokeReplaced = true;
+ } else {
+ // Check to see if a value range can be assumed.
+ setValueRangeFromProguardRule(lookup.rule, current.outValue());
+ }
}
}
- }
- // If no Proguard rule could replace the instruction check for knowledge about the
- // return value.
- if (!invokeReplaced && invoke.outValue() != null) {
- DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
- if (target != null) {
- if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
- Value knownToBeNonNullValue = invoke.outValue();
- knownToBeNonNullValue.markNeverNull();
- TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
- assert typeLattice.isNullable() && typeLattice.isReference();
- knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
- affectedValues.addAll(knownToBeNonNullValue.affectedValues());
- }
- if (target.getOptimizationInfo().returnsConstant()) {
- long constant = target.getOptimizationInfo().getReturnedConstant();
- ConstNumber replacement = createConstNumberReplacement(
- code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
- affectedValues.add(replacement.outValue());
- invoke.outValue().replaceUsers(replacement.outValue());
- invoke.setOutValue(null);
- replacement.setPosition(invoke.getPosition());
- invoke.moveDebugValues(replacement);
- iterator.add(replacement);
- }
- }
- }
- } else if (current.isInstancePut()) {
- InstancePut instancePut = current.asInstancePut();
- DexField field = instancePut.getField();
- DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
- if (target != null) {
- // Remove writes to dead (i.e. never read) fields.
- if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
- iterator.remove();
- }
- }
- } else if (current.isStaticGet()) {
- StaticGet staticGet = current.asStaticGet();
- DexField field = staticGet.getField();
- DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
- ProguardMemberRuleLookup lookup = null;
- if (target != null) {
- // Check if a this value is known const.
- Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
- if (replacement == null) {
- lookup = lookupMemberRule(target);
- if (lookup != null) {
- replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
- }
- }
- if (replacement == null) {
- // If no const replacement was found, at least store the range information.
- if (lookup != null) {
- setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
- }
- }
- if (replacement != null) {
- affectedValues.add(replacement.outValue());
- // Ignore assumenosideeffects for fields.
- if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
- replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
- } else {
- iterator.replaceCurrentInstruction(replacement);
- }
- } else if (staticGet.dest() != null) {
- // In case the class holder of this static field satisfying following criteria:
- // -- cannot trigger other static initializer except for its own
- // -- is final
- // -- has a class initializer which is classified as trivial
- // (see CodeRewriter::computeClassInitializerInfo) and
- // initializes the field being accessed
- //
- // ... and the field itself is not pinned by keep rules (in which case it might
- // be updated outside the class constructor, e.g. via reflections), it is safe
- // to assume that the static-get instruction reads the value it initialized value
- // in class initializer and is never null.
- //
- DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
- if (holderDefinition != null
- && holderDefinition.accessFlags.isFinal()
- && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
- Value outValue = staticGet.dest();
- DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
- if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
- TrivialInitializer info =
- classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
- if (info != null
- && ((TrivialClassInitializer) info).field == field
- && !appInfo.isPinned(field)
- && outValue.canBeNull()) {
- outValue.markNeverNull();
- TypeLatticeElement typeLattice = outValue.getTypeLattice();
- assert typeLattice.isNullable() && typeLattice.isReference();
- outValue.narrowing(appInfo, typeLattice.asNonNullable());
- affectedValues.addAll(outValue.affectedValues());
+ // If no Proguard rule could replace the instruction check for knowledge about the
+ // return value.
+ if (!invokeReplaced && invoke.outValue() != null) {
+ DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
+ if (target != null) {
+ if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue()
+ .canBeNull()) {
+ Value knownToBeNonNullValue = invoke.outValue();
+ knownToBeNonNullValue.markNeverNull();
+ TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+ }
+ if (target.getOptimizationInfo().returnsConstant()) {
+ long constant = target.getOptimizationInfo().getReturnedConstant();
+ ConstNumber replacement = createConstNumberReplacement(
+ code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
+ affectedValues.add(replacement.outValue());
+ invoke.outValue().replaceUsers(replacement.outValue());
+ invoke.setOutValue(null);
+ replacement.setPosition(invoke.getPosition());
+ invoke.moveDebugValues(replacement);
+ if (current.getBlock().hasCatchHandlers()) {
+ iterator.split(code, blocks).listIterator().add(replacement);
+ } else {
+ iterator.add(replacement);
}
}
}
}
- }
- } else if (current.isStaticPut()) {
- StaticPut staticPut = current.asStaticPut();
- DexField field = staticPut.getField();
- DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
- if (target != null) {
- // Remove writes to dead (i.e. never read) fields.
- if (!isFieldRead(target, true)) {
- iterator.removeOrReplaceByDebugLocalRead();
+ } else if (current.isInstancePut()) {
+ InstancePut instancePut = current.asInstancePut();
+ DexField field = instancePut.getField();
+ DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
+ if (target != null) {
+ // Remove writes to dead (i.e. never read) fields.
+ if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
+ iterator.remove();
+ }
+ }
+ } else if (current.isStaticGet()) {
+ StaticGet staticGet = current.asStaticGet();
+ DexField field = staticGet.getField();
+ DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+ ProguardMemberRuleLookup lookup = null;
+ if (target != null) {
+ // Check if a this value is known const.
+ Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
+ if (replacement == null) {
+ lookup = lookupMemberRule(target);
+ if (lookup != null) {
+ replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
+ }
+ }
+ if (replacement == null) {
+ // If no const replacement was found, at least store the range information.
+ if (lookup != null) {
+ setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
+ }
+ }
+ if (replacement != null) {
+ affectedValues.add(replacement.outValue());
+ // Ignore assumenosideeffects for fields.
+ if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
+ replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
+ } else {
+ iterator.replaceCurrentInstruction(replacement);
+ }
+ } else if (staticGet.dest() != null) {
+ // In case the class holder of this static field satisfying following criteria:
+ // -- cannot trigger other static initializer except for its own
+ // -- is final
+ // -- has a class initializer which is classified as trivial
+ // (see CodeRewriter::computeClassInitializerInfo) and
+ // initializes the field being accessed
+ //
+ // ... and the field itself is not pinned by keep rules (in which case it might
+ // be updated outside the class constructor, e.g. via reflections), it is safe
+ // to assume that the static-get instruction reads the value it initialized value
+ // in class initializer and is never null.
+ //
+ DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
+ if (holderDefinition != null
+ && holderDefinition.accessFlags.isFinal()
+ && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
+ Value outValue = staticGet.dest();
+ DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+ if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+ TrivialInitializer info =
+ classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+ if (info != null
+ && ((TrivialClassInitializer) info).field == field
+ && !appInfo.isPinned(field)
+ && outValue.canBeNull()) {
+ outValue.markNeverNull();
+ TypeLatticeElement typeLattice = outValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ outValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(outValue.affectedValues());
+ }
+ }
+ }
+ }
+ }
+ } else if (current.isStaticPut()) {
+ StaticPut staticPut = current.asStaticPut();
+ DexField field = staticPut.getField();
+ DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+ if (target != null) {
+ // Remove writes to dead (i.e. never read) fields.
+ if (!isFieldRead(target, true)) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index c46504c..5e32629 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -22,12 +23,14 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
public class PeepholeOptimizer {
/**
@@ -36,10 +39,159 @@
public static void optimize(IRCode code, LinearScanRegisterAllocator allocator) {
removeIdenticalPredecessorBlocks(code, allocator);
removeRedundantInstructions(code, allocator);
+ shareIdenticalBlockPrefix(code, allocator);
shareIdenticalBlockSuffix(code, allocator, 0);
assert code.isConsistentGraph();
}
+ /** Identify common prefixes in successor blocks and share them. */
+ private static void shareIdenticalBlockPrefix(IRCode code, RegisterAllocator allocator) {
+ InstructionEquivalence equivalence = new InstructionEquivalence(allocator);
+ Set<BasicBlock> blocksToBeRemoved = new HashSet<>();
+ for (BasicBlock block : code.blocks) {
+ if (blocksToBeRemoved.contains(block)) {
+ // This block has already been removed entirely.
+ continue;
+ }
+
+ List<BasicBlock> normalSuccessors = block.getNormalSuccessors();
+ if (normalSuccessors.size() != 2) {
+ continue;
+ }
+
+ // Exactly two normal successors.
+ BasicBlock normalSuccessor = normalSuccessors.get(0);
+ BasicBlock otherNormalSuccessor = normalSuccessors.get(1);
+
+ // Ensure that the current block is on all paths to the two successor blocks.
+ if (normalSuccessor.getPredecessors().size() != 1
+ || otherNormalSuccessor.getPredecessors().size() != 1) {
+ continue;
+ }
+
+ // Only try to share instructions if the two successors have the same locals state on entry.
+ if (!Objects.equals(
+ normalSuccessor.getLocalsAtEntry(), otherNormalSuccessor.getLocalsAtEntry())) {
+ continue;
+ }
+
+ // Share instructions from the two normal successors one-by-one.
+ while (true) {
+ // Check if all instructions were already removed from the two successors.
+ if (normalSuccessor.isEmpty() || otherNormalSuccessor.isEmpty()) {
+ assert blocksToBeRemoved.contains(normalSuccessor);
+ assert blocksToBeRemoved.contains(otherNormalSuccessor);
+ break;
+ }
+
+ Instruction instruction = normalSuccessor.entry();
+ Instruction otherInstruction = otherNormalSuccessor.entry();
+
+ // If the two instructions are not the same, we cannot merge them into the predecessor.
+ if (!equivalence.doEquivalent(instruction, otherInstruction)) {
+ break;
+ }
+
+ // Each block with one or more catch handlers may have at most one throwing instruction.
+ if (instruction.instructionTypeCanThrow() && block.hasCatchHandlers()) {
+ break;
+ }
+
+ // If the instruction can throw and one of the normal successor blocks has a catch handler,
+ // then we cannot merge the instruction into the predecessor, since this would change the
+ // exceptional control flow.
+ if (instruction.instructionInstanceCanThrow()
+ && (normalSuccessor.hasCatchHandlers() || otherNormalSuccessor.hasCatchHandlers())) {
+ break;
+ }
+
+ // Check for commutativity (semantics). If the instruction writes to a register, then we
+ // need to make sure that the instruction commutes with the last instruction in the
+ // predecessor. Consider the following example.
+ //
+ // <Block A>
+ // if-eqz r0 then goto Block B else goto Block C
+ // / \
+ // <Block B> <Block C>
+ // const r0, 1 const r0, 1
+ // ... ...
+ //
+ // In the example, it is not possible to change the order of "if-eqz r0" and
+ // "const r0, 1" without changing the semantics.
+ if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
+ int destinationRegister =
+ allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber());
+ boolean commutative =
+ block.exit().inValues().stream()
+ .allMatch(
+ inValue -> {
+ int operandRegister =
+ allocator.getRegisterForValue(inValue, block.exit().getNumber());
+ for (int i = 0; i < instruction.outValue().requiredRegisters(); i++) {
+ for (int j = 0; j < inValue.requiredRegisters(); j++) {
+ if (destinationRegister + i == operandRegister + j) {
+ // Overlap detected, the two instructions do not commute.
+ return false;
+ }
+ }
+ }
+ return true;
+ });
+ if (!commutative) {
+ break;
+ }
+ }
+
+ // Check for commutativity (debug info).
+ if (!instruction.getPosition().equals(block.exit().getPosition())
+ && !(block.exit().getPosition().isNone() && !block.exit().getDebugValues().isEmpty())) {
+ break;
+ }
+
+ // Remove the instruction from the two normal successors.
+ normalSuccessor.getInstructions().removeFirst();
+ otherNormalSuccessor.getInstructions().removeFirst();
+
+ // Move the instruction into the predecessor.
+ if (instruction.isJumpInstruction()) {
+ // Replace jump instruction in predecessor with the jump instruction from the two normal
+ // successors.
+ LinkedList<Instruction> instructions = block.getInstructions();
+ instructions.removeLast();
+ instructions.add(instruction);
+ instruction.setBlock(block);
+
+ // Update successors of predecessor block.
+ block.detachAllSuccessors();
+ for (BasicBlock newNormalSuccessor : normalSuccessor.getNormalSuccessors()) {
+ block.link(newNormalSuccessor);
+ }
+
+ // Detach the two normal successors from the rest of the graph.
+ normalSuccessor.detachAllSuccessors();
+ otherNormalSuccessor.detachAllSuccessors();
+
+ // Record that the two normal successors should be removed entirely.
+ blocksToBeRemoved.add(normalSuccessor);
+ blocksToBeRemoved.add(otherNormalSuccessor);
+ } else {
+ // Insert instruction before the jump instruction in the predecessor.
+ block.getInstructions().listIterator(block.getInstructions().size() - 1).add(instruction);
+ instruction.setBlock(block);
+
+ // Update locals-at-entry if needed.
+ if (instruction.isDebugLocalsChange()) {
+ DebugLocalsChange localsChange = instruction.asDebugLocalsChange();
+ localsChange.apply(normalSuccessor.getLocalsAtEntry());
+ localsChange.apply(otherNormalSuccessor.getLocalsAtEntry());
+ }
+ }
+ }
+ }
+
+ code.blocks.removeAll(blocksToBeRemoved);
+ }
+
/** Identify common suffixes in predecessor blocks and share them. */
public static void shareIdenticalBlockSuffix(
IRCode code, RegisterAllocator allocator, int overhead) {
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 1ac8555..a453346 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
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Or;
import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.StackValues;
import com.android.tools.r8.ir.code.Sub;
@@ -423,7 +424,8 @@
// Compute the final change in locals and insert it before nextInstruction.
boolean localsChanged = !ending.isEmpty() || !starting.isEmpty();
if (localsChanged) {
- DebugLocalsChange change = createLocalsChange(ending, starting);
+ DebugLocalsChange change =
+ createLocalsChange(ending, starting, instruction.getPosition());
if (change != null) {
// Insert the DebugLocalsChange instruction before nextInstruction.
instructionIterator.add(change);
@@ -506,36 +508,43 @@
starting.put(finalLocal.getIntKey(), finalLocal.getValue());
}
}
- DebugLocalsChange change = createLocalsChange(ending, starting);
+ DebugLocalsChange change = createLocalsChange(ending, starting, block.getPosition());
if (change != null) {
instructionIterator.add(change);
}
}
private static DebugLocalsChange createLocalsChange(
- Int2ReferenceMap<DebugLocalInfo> ending, Int2ReferenceMap<DebugLocalInfo> starting) {
+ Int2ReferenceMap<DebugLocalInfo> ending,
+ Int2ReferenceMap<DebugLocalInfo> starting,
+ Position position) {
+ assert position.isSome();
if (ending.isEmpty() && starting.isEmpty()) {
return null;
}
+ DebugLocalsChange localsChange;
if (ending.isEmpty() || starting.isEmpty()) {
- return new DebugLocalsChange(ending, starting);
- }
- IntSet unneeded = new IntArraySet(Math.min(ending.size(), starting.size()));
- for (Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
- if (starting.get(entry.getIntKey()) == entry.getValue()) {
- unneeded.add(entry.getIntKey());
+ localsChange = new DebugLocalsChange(ending, starting);
+ } else {
+ IntSet unneeded = new IntArraySet(Math.min(ending.size(), starting.size()));
+ for (Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
+ if (starting.get(entry.getIntKey()) == entry.getValue()) {
+ unneeded.add(entry.getIntKey());
+ }
}
+ if (unneeded.size() == ending.size() && unneeded.size() == starting.size()) {
+ return null;
+ }
+ IntIterator iterator = unneeded.iterator();
+ while (iterator.hasNext()) {
+ int key = iterator.nextInt();
+ ending.remove(key);
+ starting.remove(key);
+ }
+ localsChange = new DebugLocalsChange(ending, starting);
}
- if (unneeded.size() == ending.size() && unneeded.size() == starting.size()) {
- return null;
- }
- IntIterator iterator = unneeded.iterator();
- while (iterator.hasNext()) {
- int key = iterator.nextInt();
- ending.remove(key);
- starting.remove(key);
- }
- return new DebugLocalsChange(ending, starting);
+ localsChange.setPosition(position);
+ return localsChange;
}
private void clearState() {
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 65a57e6..a032fd6 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -29,6 +29,10 @@
abstract RR self();
+ public AndroidApp app() {
+ return app;
+ }
+
public String getStdOut() {
return result.stdout;
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
index c140610..798f1b0 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
@@ -10,10 +10,12 @@
import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.LONG;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.ir.analysis.AnalysisTestBase;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Streams;
import java.util.function.Consumer;
import org.junit.Test;
@@ -25,6 +27,21 @@
}
@Test
+ public void testOutput() throws Exception {
+ String expectedOutput =
+ StringUtils.lines("1", "2", "3", "1.0", "1.0", "2.0", "1", "1", "2", "1.0", "1.0", "2.0");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ testForR8(Backend.DEX)
+ .addInnerClasses(ConstrainedPrimitiveTypeTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ @Test
public void testIntWithInvokeUser() throws Exception {
buildAndCheckIR("intWithInvokeUserTest", testInspector(INT, 1));
}
@@ -83,26 +100,47 @@
static class TestClass {
- public static void intWithInvokeUserTest() {
- int x = 1;
- Integer.toString(x);
+ public static void main(String[] args) {
+ boolean unknownButTrue = args.length >= 0;
+ boolean unknownButFalse = args.length < 0;
+ intWithInvokeUserTest();
+ intWithIndirectInvokeUserTest(unknownButTrue);
+ intWithIndirectInvokeUserTest(unknownButFalse);
+ floatWithInvokeUserTest();
+ floatWithIndirectInvokeUserTest(unknownButTrue);
+ floatWithIndirectInvokeUserTest(unknownButFalse);
+ longWithInvokeUserTest();
+ longWithIndirectInvokeUserTest(unknownButTrue);
+ longWithIndirectInvokeUserTest(unknownButFalse);
+ doubleWithInvokeUserTest();
+ doubleWithIndirectInvokeUserTest(unknownButTrue);
+ doubleWithIndirectInvokeUserTest(unknownButFalse);
}
+ @NeverInline
+ public static void intWithInvokeUserTest() {
+ int x = 1;
+ System.out.println(Integer.toString(x));
+ }
+
+ @NeverInline
public static void intWithIndirectInvokeUserTest(boolean unknown) {
int x;
if (unknown) {
- x = 1;
- } else {
x = 2;
+ } else {
+ x = 3;
}
- Integer.toString(x);
+ System.out.println(Integer.toString(x));
}
+ @NeverInline
public static void floatWithInvokeUserTest() {
float x = 1f;
- Float.toString(x);
+ System.out.println(Float.toString(x));
}
+ @NeverInline
public static void floatWithIndirectInvokeUserTest(boolean unknown) {
float x;
if (unknown) {
@@ -110,14 +148,16 @@
} else {
x = 2f;
}
- Float.toString(x);
+ System.out.println(Float.toString(x));
}
+ @NeverInline
public static void longWithInvokeUserTest() {
long x = 1L;
- Long.toString(x);
+ System.out.println(Long.toString(x));
}
+ @NeverInline
public static void longWithIndirectInvokeUserTest(boolean unknown) {
long x;
if (unknown) {
@@ -125,14 +165,16 @@
} else {
x = 2L;
}
- Long.toString(x);
+ System.out.println(Long.toString(x));
}
+ @NeverInline
public static void doubleWithInvokeUserTest() {
double x = 1.0;
- Double.toString(x);
+ System.out.println(Double.toString(x));
}
+ @NeverInline
public static void doubleWithIndirectInvokeUserTest(boolean unknown) {
double x;
if (unknown) {
@@ -140,7 +182,7 @@
} else {
x = 2f;
}
- Double.toString(x);
+ System.out.println(Double.toString(x));
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
new file mode 100644
index 0000000..fc382ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -0,0 +1,248 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantConstNumberRemovalTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public RedundantConstNumberRemovalTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput =
+ StringUtils.lines(
+ "true", "true", "true", "true", "true", "true", "true", "true", "true", "true", "true",
+ "true", "true", "true", "true", "true");
+
+ if (backend == Backend.CF) {
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+ }
+
+ R8TestRunResult result =
+ testForR8(backend)
+ .addInnerClasses(RedundantConstNumberRemovalTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+
+ ClassSubject classSubject = result.inspector().clazz(TestClass.class);
+ verifyBooleanCheckTest(classSubject.uniqueMethodWithName("booleanCheckTest"));
+ verifyBooleanCheckTest(classSubject.uniqueMethodWithName("negateBooleanCheckTest"));
+ verifyIntCheckTest(classSubject.uniqueMethodWithName("intCheckTest"));
+ verifyIntCheckTest(classSubject.uniqueMethodWithName("negateIntCheckTest"));
+ verifyNullCheckTest(classSubject.uniqueMethodWithName("nullCheckTest"));
+ verifyNullCheckTest(classSubject.uniqueMethodWithName("invertedNullCheckTest"));
+ verifyNullCheckTest(classSubject.uniqueMethodWithName("nonNullCheckTest"));
+ verifyNullCheckWithWrongTypeTest(
+ classSubject.uniqueMethodWithName("nullCheckWithWrongTypeTest"));
+ }
+
+ private void verifyBooleanCheckTest(MethodSubject methodSubject) {
+ assertThat(methodSubject, isPresent());
+
+ if (backend == Backend.DEX) {
+ // Check that the generated code for booleanCheckTest() only has a return instruction.
+ assertEquals(1, methodSubject.streamInstructions().count());
+ assertTrue(methodSubject.iterateInstructions().next().isReturn());
+ } else {
+ assert backend == Backend.CF;
+ // Check that the generated code for booleanCheckTest() only has return instructions that
+ // return the argument.
+ // TODO(christofferqa): CF backend does not share identical prefix of successors.
+ IRCode code = methodSubject.buildIR();
+ assertTrue(
+ Streams.stream(code.instructionIterator())
+ .filter(Instruction::isReturn)
+ .allMatch(
+ instruction -> instruction.asReturn().returnValue().definition.isArgument()));
+ }
+ }
+
+ private void verifyIntCheckTest(MethodSubject methodSubject) {
+ assertThat(methodSubject, isPresent());
+ IRCode code = methodSubject.buildIR();
+
+ if (backend == Backend.DEX) {
+ // Only a single basic block.
+ assertEquals(1, code.blocks.size());
+ // The block only has three instructions.
+ BasicBlock entryBlock = code.blocks.get(0);
+ assertEquals(3, entryBlock.getInstructions().size());
+ // The first one is the `argument` instruction.
+ Instruction argument = entryBlock.getInstructions().getFirst();
+ assertTrue(argument.isArgument());
+ // The next one is a `const-number` instruction is not used for anything.
+ // TODO(christofferqa): D8 should be able to get rid of the unused const-number instruction.
+ Instruction unused = entryBlock.getInstructions().get(1);
+ assertTrue(unused.isConstNumber());
+ assertEquals(0, unused.outValue().numberOfAllUsers());
+ // The `return` instruction returns the argument.
+ Instruction ret = entryBlock.getInstructions().getLast();
+ assertTrue(ret.isReturn());
+ assertTrue(ret.asReturn().returnValue().definition.isArgument());
+ } else {
+ // Check that the generated code for intCheckTest() only has return instructions that
+ // return the argument.
+ // TODO(christofferqa): CF backend does not share identical prefix of successors.
+ assertTrue(
+ Streams.stream(code.instructionIterator())
+ .filter(Instruction::isReturn)
+ .allMatch(
+ instruction -> instruction.asReturn().returnValue().definition.isArgument()));
+ }
+ }
+
+ private void verifyNullCheckTest(MethodSubject methodSubject) {
+ // Check that the generated code for nullCheckTest() only has a single `const-null` instruction.
+ assertThat(methodSubject, isPresent());
+ assertEquals(
+ 1, methodSubject.streamInstructions().filter(InstructionSubject::isConstNull).count());
+
+ // Also check that one of the return instructions actually returns the argument.
+ IRCode code = methodSubject.buildIR();
+ assertEquals(1, code.collectArguments().size());
+ // TODO(b/120257211): D8 should replace `return null` by `return arg`.
+ assertFalse(
+ code.collectArguments().get(0).uniqueUsers().stream().anyMatch(Instruction::isReturn));
+ }
+
+ private void verifyNullCheckWithWrongTypeTest(MethodSubject methodSubject) {
+ // Check that the generated code for nullCheckWithWrongTypeTest() still has a `return null`
+ // instruction.
+ assertThat(methodSubject, isPresent());
+ IRCode code = methodSubject.buildIR();
+
+ // Check that the code returns null.
+ assertTrue(
+ Streams.stream(code.instructionIterator())
+ .anyMatch(
+ instruction ->
+ instruction.isReturn()
+ && instruction.asReturn().returnValue().definition.isConstNumber()));
+
+ // Also check that none of the return instructions actually returns the argument.
+ assertEquals(1, code.collectArguments().size());
+ assertTrue(
+ code.collectArguments().get(0).uniqueUsers().stream().noneMatch(Instruction::isReturn));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(booleanCheckTest(true) == true);
+ System.out.println(booleanCheckTest(false) == false);
+ System.out.println(negateBooleanCheckTest(true) == true);
+ System.out.println(negateBooleanCheckTest(false) == false);
+ System.out.println(intCheckTest(0) == 0);
+ System.out.println(intCheckTest(42) == 42);
+ System.out.println(negateIntCheckTest(0) == 0);
+ System.out.println(negateIntCheckTest(42) == 42);
+ System.out.println(nullCheckTest(new Object()) != null);
+ System.out.println(nullCheckTest(null) == null);
+ System.out.println(invertedNullCheckTest(new Object()) != null);
+ System.out.println(invertedNullCheckTest(null) == null);
+ System.out.println(nonNullCheckTest(new Object()) != null);
+ System.out.println(nonNullCheckTest(null) == null);
+ System.out.println(nullCheckWithWrongTypeTest(new Object()) != null);
+ System.out.println(nullCheckWithWrongTypeTest(null) == null);
+ }
+
+ @NeverInline
+ private static boolean booleanCheckTest(boolean x) {
+ if (x) {
+ return true; // should be replaced by `x`.
+ }
+ return false; // should be replaced by `x`.
+ }
+
+ @NeverInline
+ private static boolean negateBooleanCheckTest(boolean x) {
+ if (!x) {
+ return false; // should be replaced by `x`
+ }
+ return true; // should be replaced by `x`
+ }
+
+ @NeverInline
+ private static int intCheckTest(int x) {
+ if (x == 42) {
+ return 42; // should be replaced by `x`.
+ }
+ return x;
+ }
+
+ @NeverInline
+ private static int negateIntCheckTest(int x) {
+ if (x != 42) {
+ return x;
+ }
+ return 42; // should be replaced by `x`
+ }
+
+ @NeverInline
+ private static Object nullCheckTest(Object x) {
+ if (x != null) {
+ return new Object();
+ }
+ return null; // should be replaced by `x`.
+ }
+
+ @NeverInline
+ private static Object invertedNullCheckTest(Object x) {
+ if (null == x) {
+ return null; // should be replaced by `x`.
+ }
+ return new Object();
+ }
+
+ @NeverInline
+ private static Object nonNullCheckTest(Object x) {
+ if (x == null) {
+ return null; // should be replaced by `x`
+ }
+ return new Object();
+ }
+
+ @NeverInline
+ private static Throwable nullCheckWithWrongTypeTest(Object x) {
+ if (x != null) {
+ return new Throwable();
+ }
+ return null; // cannot be replaced by `x` because Object is not a subtype of Throwable.
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index c2ced01..8e24088 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -6,12 +6,18 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
public class AbsentMethodSubject extends MethodSubject {
@Override
+ public IRCode buildIR() {
+ throw new Unreachable("Cannot build IR for an absent method");
+ }
+
+ @Override
public boolean isPresent() {
return false;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 924ec2d..ee2155e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.utils.codeinspector;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.cf.code.CfArithmeticBinop;
import com.android.tools.r8.cf.code.CfCheckCast;
@@ -31,7 +30,6 @@
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfSwitch;
import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.code.Monitor.Type;
@@ -167,6 +165,11 @@
}
@Override
+ public boolean isReturn() {
+ return instruction instanceof CfReturn;
+ }
+
+ @Override
public boolean isReturnVoid() {
return instruction instanceof CfReturnVoid;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 1f520ff..9983320 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -58,7 +58,7 @@
public class CodeInspector {
- private final DexApplication application;
+ final DexApplication application;
final DexItemFactory dexItemFactory;
private final ClassNameMapper mapping;
final Map<String, String> originalToObfuscatedMapping;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 6d463da..7051226 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -70,6 +70,7 @@
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.Nop;
import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.Return;
import com.android.tools.r8.code.ReturnObject;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.code.Sget;
@@ -275,6 +276,11 @@
}
@Override
+ public boolean isReturn() {
+ return instruction instanceof Return;
+ }
+
+ @Override
public boolean isReturnVoid() {
return instruction instanceof ReturnVoid;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 6c23162..796532d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
@@ -17,10 +18,14 @@
import com.android.tools.r8.graph.DexDebugPositionState;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.JarCode;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.Arrays;
@@ -41,6 +46,19 @@
}
@Override
+ public IRCode buildIR() {
+ DexEncodedMethod method = getMethod();
+ return method
+ .getCode()
+ .buildIR(
+ method,
+ new AppInfo(codeInspector.application),
+ GraphLense.getIdentityLense(),
+ new InternalOptions(),
+ Origin.unknown());
+ }
+
+ @Override
public boolean isPresent() {
return true;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 6abeacf..294cc8c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -58,6 +58,8 @@
boolean isIfEqz();
+ boolean isReturn();
+
boolean isReturnVoid();
boolean isReturnObject();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index b0d2c9b..8fb4001 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.google.common.collect.Streams;
import java.util.Iterator;
@@ -13,6 +14,8 @@
public abstract class MethodSubject extends MemberSubject {
+ public abstract IRCode buildIR();
+
public abstract boolean isAbstract();
public abstract boolean isBridge();