Reland "Make ThrowCatchOptimizer a CodeRewriterPass"
This reverts commit 57693cc9d715327ab0b44889fcb05f8637f7c442.
Change-Id: I5ba37246f42f5e563db57e1db13510c01158b0e5
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 8941176..994d45b 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
@@ -36,6 +36,7 @@
import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter;
import com.android.tools.r8.ir.conversion.passes.NaturalIntLoopRemover;
import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.RedundantConstNumberRemover;
import com.android.tools.r8.ir.conversion.passes.SplitBranch;
import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer;
import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover;
@@ -760,21 +761,16 @@
new StringBuilderAppendOptimizer(appView).run(code, timing);
}
new SparseConditionalConstantPropagation(appView, code).run(code, timing);
- timing.begin("Rewrite always throwing instructions");
- new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(code);
- timing.end();
- timing.begin("Simplify control flow");
- if (new BranchSimplifier(appView).simplifyBranches(code)) {
+ new ThrowCatchOptimizer(appView, isDebugMode).run(code, timing);
+ if (new BranchSimplifier(appView)
+ .run(code, timing)
+ .asControlFlowSimplificationResult()
+ .anyAffectedValues()) {
new TrivialCheckCastAndInstanceOfRemover(appView)
.run(code, methodProcessor, methodProcessingContext, timing);
}
- timing.end();
splitBranch.run(code, timing);
- if (options.enableRedundantConstNumberOptimization) {
- timing.begin("Remove const numbers");
- codeRewriter.redundantConstNumberRemoval(code);
- timing.end();
- }
+ new RedundantConstNumberRemover(appView).run(code, timing);
if (RedundantFieldLoadAndStoreElimination.shouldRun(appView, code)) {
timing.begin("Remove field loads");
new RedundantFieldLoadAndStoreElimination(appView, code).run();
@@ -788,13 +784,6 @@
invertConditionalsForTesting(code);
}
- if (!isDebugMode) {
- timing.begin("Rewrite throw NPE");
- new ThrowCatchOptimizer(appView).rewriteThrowNullPointerException(code);
- timing.end();
- previous = printMethod(code, "IR after rewrite throw null (SSA)", previous);
- }
-
timing.begin("Optimize class initializers");
ClassInitializerDefaultsResult classInitializerDefaultsResult =
classInitializerDefaultsOptimization.optimize(code, feedback);
@@ -934,7 +923,6 @@
// Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
assert code.verifyNoNullabilityBottomTypes();
-
assert code.verifyTypes(appView);
previous =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
index 28ddb01..44c9ddd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.ir.conversion.passes;
+import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.NO_CHANGE;
+import static com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult.create;
+
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -35,10 +39,10 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.collect.ImmutableList;
@@ -59,20 +63,27 @@
import java.util.PriorityQueue;
import java.util.Set;
-public class BranchSimplifier {
-
- private final AppView<?> appView;
- private final InternalOptions options;
+public class BranchSimplifier extends CodeRewriterPass<AppInfo> {
public BranchSimplifier(AppView<?> appView) {
- this.appView = appView;
- this.options = appView.options();
+ super(appView);
}
- public boolean simplifyBranches(IRCode code) {
- boolean anyAffectedValues = rewriteSwitch(code);
- anyAffectedValues |= simplifyIf(code).anyAffectedValues();
- return anyAffectedValues;
+ @Override
+ protected String getTimingId() {
+ return "BranchSimplifier";
+ }
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code) {
+ return true;
+ }
+
+ @Override
+ protected CodeRewriterResult rewriteCode(IRCode code) {
+ ControlFlowSimplificationResult switchResult = rewriteSwitch(code);
+ ControlFlowSimplificationResult ifResult = simplifyIf(code);
+ return switchResult.combine(ifResult);
}
public ControlFlowSimplificationResult simplifyIf(IRCode code) {
@@ -126,10 +137,26 @@
}
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
- return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified);
+ return create(!affectedValues.isEmpty(), simplified);
}
- public static class ControlFlowSimplificationResult {
+ public static class ControlFlowSimplificationResult implements CodeRewriterResult {
+
+ static ControlFlowSimplificationResult create(
+ boolean anyAffectedValues, boolean anySimplifications) {
+ if (anyAffectedValues) {
+ assert anySimplifications;
+ return ALL_CHANGED;
+ }
+ return anySimplifications ? ONLY_SIMPLIFICATIONS : NO_CHANGE;
+ }
+
+ static final ControlFlowSimplificationResult ALL_CHANGED =
+ new ControlFlowSimplificationResult(true, true);
+ static final ControlFlowSimplificationResult ONLY_SIMPLIFICATIONS =
+ new ControlFlowSimplificationResult(false, true);
+ static final ControlFlowSimplificationResult NO_CHANGE =
+ new ControlFlowSimplificationResult(false, false);
private final boolean anyAffectedValues;
private final boolean anySimplifications;
@@ -140,6 +167,11 @@
this.anySimplifications = anySimplifications;
}
+ @Override
+ public ControlFlowSimplificationResult asControlFlowSimplificationResult() {
+ return this;
+ }
+
public boolean anyAffectedValues() {
return anyAffectedValues;
}
@@ -147,6 +179,18 @@
public boolean anySimplifications() {
return anySimplifications;
}
+
+ @Override
+ public boolean hasChanged() {
+ assert !anyAffectedValues || anySimplifications;
+ return anySimplifications();
+ }
+
+ public ControlFlowSimplificationResult combine(ControlFlowSimplificationResult ifResult) {
+ return create(
+ anyAffectedValues || ifResult.anyAffectedValues,
+ anySimplifications || ifResult.anySimplifications);
+ }
}
private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) {
@@ -567,22 +611,25 @@
return true;
}
- private boolean rewriteSwitch(IRCode code) {
+ private ControlFlowSimplificationResult rewriteSwitch(IRCode code) {
return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance());
}
- private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
+ private ControlFlowSimplificationResult rewriteSwitch(
+ IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
if (!options.isSwitchRewritingEnabled()) {
- return false;
+ return NO_CHANGE;
}
if (!code.metadata().mayHaveSwitch()) {
- return false;
+ return NO_CHANGE;
}
return rewriteSwitchFull(code, switchCaseAnalyzer);
}
- private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
+ private ControlFlowSimplificationResult rewriteSwitchFull(
+ IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
boolean needToRemoveUnreachableBlocks = false;
+ boolean anySimplifications = false;
ListIterator<BasicBlock> blocksIterator = code.listIterator();
while (blocksIterator.hasNext()) {
BasicBlock block = blocksIterator.next();
@@ -594,6 +641,7 @@
if (options.testing.enableDeadSwitchCaseElimination) {
SwitchCaseEliminator eliminator =
removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer);
+ anySimplifications |= eliminator.canBeOptimized();
if (eliminator.mayHaveIntroducedUnreachableBlocks()) {
needToRemoveUnreachableBlocks = true;
}
@@ -608,7 +656,8 @@
theSwitch = instruction.asSwitch();
}
if (theSwitch.isIntSwitch()) {
- rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch());
+ anySimplifications |=
+ rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch());
}
}
}
@@ -621,12 +670,13 @@
Set<Value> affectedValues =
needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of();
- if (!affectedValues.isEmpty()) {
+ boolean anyAffectedValues = !affectedValues.isEmpty();
+ if (anyAffectedValues) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
- return !affectedValues.isEmpty();
+ return create(anyAffectedValues, anySimplifications);
}
public void rewriteSingleKeySwitchToIf(
@@ -652,19 +702,19 @@
iterator.replaceCurrentInstruction(replacement);
}
- private void rewriteIntSwitch(
+ private boolean rewriteIntSwitch(
IRCode code,
ListIterator<BasicBlock> blockIterator,
BasicBlock block,
InstructionListIterator iterator,
IntSwitch theSwitch) {
if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) {
- return;
+ return false;
}
if (theSwitch.numberOfKeys() == 1) {
rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch);
- return;
+ return true;
}
// If there are more than 1 key, we use the following algorithm to find keys to combine.
@@ -753,7 +803,9 @@
if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
convertSwitchToSwitchAndIfs(
code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
+ return true;
}
+ return false;
}
// TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
new file mode 100644
index 0000000..70402cc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
@@ -0,0 +1,278 @@
+// Copyright (c) 2023, 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.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.utils.LazyBox;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * 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 class RedundantConstNumberRemover extends CodeRewriterPass<AppInfo> {
+
+ public RedundantConstNumberRemover(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ protected String getTimingId() {
+ return "RedundantConstNumberRemover";
+ }
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code) {
+ return options.enableRedundantConstNumberOptimization && code.metadata().mayHaveConstNumber();
+ }
+
+ @Override
+ protected CodeRewriterResult rewriteCode(IRCode code) {
+ redundantConstNumberRemoval(code);
+ return CodeRewriterResult.NONE;
+ }
+
+ public void redundantConstNumberRemoval(IRCode code) {
+ if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
+ && !appView.options().testing.forceRedundantConstNumberRemoval) {
+ // See also b/124152497.
+ return;
+ }
+
+ LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
+ new LazyBox<>(() -> getConstantsByValue(code));
+ LazyBox<DominatorTree> dominatorTree = new LazyBox<>(() -> new DominatorTree(code));
+
+ boolean changed = false;
+ for (BasicBlock block : code.blocks) {
+ Instruction lastInstruction = block.getInstructions().getLast();
+ if (!lastInstruction.isIf()) {
+ continue;
+ }
+
+ If ifInstruction = lastInstruction.asIf();
+ IfType 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 != IfType.EQ && type != IfType.NE) {
+ continue;
+ }
+
+ BasicBlock trueTarget, falseTarget;
+ if (type == IfType.EQ) {
+ trueTarget = ifInstruction.getTrueTarget();
+ falseTarget = ifInstruction.fallthroughBlock();
+ } else {
+ falseTarget = ifInstruction.getTrueTarget();
+ trueTarget = ifInstruction.fallthroughBlock();
+ }
+
+ if (ifInstruction.isZeroTest()) {
+ changed |=
+ replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree);
+ if (lhs.knownToBeBoolean()) {
+ changed |=
+ replaceDominatedConstNumbers(
+ 1, lhs, falseTarget, constantsByValue, code, dominatorTree);
+ }
+ } else {
+ assert rhs != null;
+ if (lhs.isConstNumber()) {
+ ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber();
+ changed |=
+ replaceDominatedConstNumbers(
+ lhsAsNumber.getRawValue(),
+ rhs,
+ trueTarget,
+ constantsByValue,
+ code,
+ dominatorTree);
+ if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
+ changed |=
+ replaceDominatedConstNumbers(
+ negateBoolean(lhsAsNumber),
+ rhs,
+ falseTarget,
+ constantsByValue,
+ code,
+ dominatorTree);
+ }
+ } else {
+ assert rhs.isConstNumber();
+ ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber();
+ changed |=
+ replaceDominatedConstNumbers(
+ rhsAsNumber.getRawValue(),
+ lhs,
+ trueTarget,
+ constantsByValue,
+ code,
+ dominatorTree);
+ if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
+ changed |=
+ replaceDominatedConstNumbers(
+ negateBoolean(rhsAsNumber),
+ lhs,
+ falseTarget,
+ constantsByValue,
+ code,
+ dominatorTree);
+ }
+ }
+ }
+
+ if (constantsByValue.computeIfAbsent().isEmpty()) {
+ break;
+ }
+ }
+
+ if (changed) {
+ code.removeAllDeadAndTrivialPhis();
+ }
+ assert code.isConsistentSSA(appView);
+ }
+
+ 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`.
+ for (Instruction instruction : code.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,
+ LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
+ IRCode code,
+ LazyBox<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.computeIfAbsent();
+ 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.computeIfAbsent().dominatedBy(block, dominator)) {
+ if (newValue.getType().lessThanOrEqual(value.getType(), appView)) {
+ value.replaceUsers(newValue);
+ block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
+ constantWithValueIterator.remove();
+ changed = true;
+ } else if (value.getType().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;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java
index cbda630..4a1a1d4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java
@@ -45,7 +45,7 @@
&& switchCasesToBeRemoved.size() == theSwitch.numberOfKeys();
}
- private boolean canBeOptimized() {
+ boolean canBeOptimized() {
assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty();
return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive();
}
@@ -96,7 +96,7 @@
liveFallthrough = false;
}
- boolean optimize() {
+ void optimize() {
if (canBeOptimized()) {
int originalNumberOfSuccessors = block.getSuccessors().size();
IntList removedSuccessorIndices = unlinkDeadSuccessors();
@@ -107,9 +107,7 @@
// Replace switch by a new switch where the dead switch cases have been removed.
replaceSwitchByOptimizedSwitch(originalNumberOfSuccessors, removedSuccessorIndices);
}
- return true;
}
- return false;
}
private IntList unlinkDeadSuccessors() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
index f34a6bd..85cd30e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
@@ -5,10 +5,10 @@
package com.android.tools.r8.ir.conversion.passes;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -32,6 +32,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
@@ -40,18 +41,42 @@
import java.util.ListIterator;
import java.util.Set;
-public class ThrowCatchOptimizer {
+public class ThrowCatchOptimizer extends CodeRewriterPass<AppInfo> {
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
+ private final boolean rewriteThrowNull;
+
+ public ThrowCatchOptimizer(AppView<?> appView, boolean isDebug) {
+ super(appView);
+ this.rewriteThrowNull = !isDebug;
+ }
public ThrowCatchOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
+ super(appView);
+ this.rewriteThrowNull = false;
+ }
+
+ @Override
+ protected String getTimingId() {
+ return "ThrowCatchOptimizer";
+ }
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code) {
+ return true;
+ }
+
+ @Override
+ protected CodeRewriterResult rewriteCode(IRCode code) {
+ optimizeAlwaysThrowingInstructions(code);
+ if (rewriteThrowNull) {
+ rewriteThrowNullPointerException(code);
+ }
+ return CodeRewriterResult.NONE;
}
// Rewrite 'throw new NullPointerException()' to 'throw null'.
- public void rewriteThrowNullPointerException(IRCode code) {
+ private void rewriteThrowNullPointerException(IRCode code) {
+ boolean hasChanged = false;
boolean shouldRemoveUnreachableBlocks = false;
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator(code);
@@ -64,7 +89,7 @@
if (appView
.dexItemFactory()
.objectsMethods
- .isRequireNonNullMethod(code.method().getReference())) {
+ .isRequireNonNullMethod(code.context().getReference())) {
continue;
}
@@ -126,6 +151,7 @@
valueIsNullTarget,
throwInstruction.getPosition());
shouldRemoveUnreachableBlocks = true;
+ hasChanged = true;
}
// Check for 'new-instance NullPointerException' with 2 users, not declaring a local and
@@ -172,6 +198,7 @@
// Replace them with 'const 0' and 'throw'.
it.add(nullPointer);
it.add(throwInstruction);
+ hasChanged = true;
}
}
}
@@ -186,13 +213,16 @@
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
+ if (hasChanged) {
+ code.removeRedundantBlocks();
+ }
assert code.isConsistentSSA(appView);
}
// Find all instructions that always throw, split the block after each such instruction and follow
// it with a block throwing a null value (which should result in NPE). Note that this throw is not
// expected to be ever reached, but is intended to satisfy verifier.
- public void optimizeAlwaysThrowingInstructions(IRCode code) {
+ private void optimizeAlwaysThrowingInstructions(IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java
index 68d5c0d..1af288a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/result/CodeRewriterResult.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.conversion.passes.result;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.conversion.passes.BranchSimplifier.ControlFlowSimplificationResult;
public interface CodeRewriterResult {
@@ -37,4 +38,8 @@
}
boolean hasChanged();
+
+ default ControlFlowSimplificationResult asControlFlowSimplificationResult() {
+ throw new Unreachable("Not a control flow simplification result.");
+ }
}
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 e696879..19f053f 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
@@ -21,11 +21,9 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
-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.DominatorTree;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.IfType;
@@ -40,7 +38,6 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.utils.LazyBox;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
@@ -51,11 +48,7 @@
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
-import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
-import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
-import java.util.ArrayList;
import java.util.List;
-import java.util.ListIterator;
import java.util.Set;
public class CodeRewriter {
@@ -210,242 +203,6 @@
}
/**
- * 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) {
- if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
- && !appView.options().testing.forceRedundantConstNumberRemoval) {
- // See also b/124152497.
- return;
- }
-
- if (!code.metadata().mayHaveConstNumber()) {
- return;
- }
-
- LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
- new LazyBox<>(() -> getConstantsByValue(code));
- LazyBox<DominatorTree> dominatorTree = new LazyBox<>(() -> new DominatorTree(code));
-
- boolean changed = false;
- for (BasicBlock block : code.blocks) {
- Instruction lastInstruction = block.getInstructions().getLast();
- if (!lastInstruction.isIf()) {
- continue;
- }
-
- If ifInstruction = lastInstruction.asIf();
- IfType 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 != IfType.EQ && type != IfType.NE) {
- continue;
- }
-
- BasicBlock trueTarget, falseTarget;
- if (type == IfType.EQ) {
- trueTarget = ifInstruction.getTrueTarget();
- falseTarget = ifInstruction.fallthroughBlock();
- } else {
- falseTarget = ifInstruction.getTrueTarget();
- trueTarget = ifInstruction.fallthroughBlock();
- }
-
- if (ifInstruction.isZeroTest()) {
- changed |=
- replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree);
- if (lhs.knownToBeBoolean()) {
- changed |=
- replaceDominatedConstNumbers(
- 1, lhs, falseTarget, constantsByValue, code, dominatorTree);
- }
- } else {
- assert rhs != null;
- if (lhs.isConstNumber()) {
- ConstNumber lhsAsNumber = lhs.getConstInstruction().asConstNumber();
- changed |=
- replaceDominatedConstNumbers(
- lhsAsNumber.getRawValue(),
- rhs,
- trueTarget,
- constantsByValue,
- code,
- dominatorTree);
- if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
- changed |=
- replaceDominatedConstNumbers(
- negateBoolean(lhsAsNumber),
- rhs,
- falseTarget,
- constantsByValue,
- code,
- dominatorTree);
- }
- } else {
- assert rhs.isConstNumber();
- ConstNumber rhsAsNumber = rhs.getConstInstruction().asConstNumber();
- changed |=
- replaceDominatedConstNumbers(
- rhsAsNumber.getRawValue(),
- lhs,
- trueTarget,
- constantsByValue,
- code,
- dominatorTree);
- if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) {
- changed |=
- replaceDominatedConstNumbers(
- negateBoolean(rhsAsNumber),
- lhs,
- falseTarget,
- constantsByValue,
- code,
- dominatorTree);
- }
- }
- }
-
- if (constantsByValue.computeIfAbsent().isEmpty()) {
- break;
- }
- }
-
- if (changed) {
- code.removeAllDeadAndTrivialPhis();
- }
- assert code.isConsistentSSA(appView);
- }
-
- 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`.
- for (Instruction instruction : code.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,
- LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier,
- IRCode code,
- LazyBox<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.computeIfAbsent();
- 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.computeIfAbsent().dominatedBy(block, dominator)) {
- if (newValue.getType().lessThanOrEqual(value.getType(), appView)) {
- value.replaceUsers(newValue);
- block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
- constantWithValueIterator.remove();
- changed = true;
- } else if (value.getType().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;
- }
-
- /**
* Remove moves that are not actually used by instructions in exiting paths. These moves can arise
* due to debug local info needing a particular value and the live-interval for it then moves it
* back into the properly assigned register. If the register is only used for debug purposes, it
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index e9b180c..94fabb7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -60,11 +60,15 @@
removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis);
removeDeadPhis(worklist, block, valueIsDeadAnalysis);
}
- } while (branchSimplifier.simplifyIf(code).anySimplifications()
+ } while (branchSimplifier
+ .simplifyIf(code)
+ .asControlFlowSimplificationResult()
+ .anySimplifications()
|| removeUnneededCatchHandlers(code));
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
+ assert verifyNoDeadCode(code);
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index d3d2a08..b46d0a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -252,7 +252,7 @@
new TrivialCheckCastAndInstanceOfRemover(appView)
.run(code, methodProcessor, methodProcessingContext, Timing.empty());
// If a method was inlined we may be able to prune additional branches.
- new BranchSimplifier(appView).simplifyBranches(code);
+ new BranchSimplifier(appView).run(code, Timing.empty());
// If a method was inlined we may see more trivial computation/conversion of String.
boolean isDebugMode =
appView.options().debug || method.getOrComputeReachabilitySensitive(appView);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 1c0d3de..f7cf0b5 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -270,7 +270,7 @@
rewriter.rewriteCode(ir, initializedClassesWithContexts, prunedFields);
// Run dead code elimination.
- new ThrowCatchOptimizer(appView).optimizeAlwaysThrowingInstructions(ir);
+ new ThrowCatchOptimizer(appView).run(ir, Timing.empty());
rewriter.getDeadCodeRemover().run(ir, Timing.empty());
// Finalize to class files or dex.