Generalize switch rewriting to string switches
This also extends the switch rewriting to optimize switches where a single case is guaranteed to be hit into a goto instruction.
Change-Id: I5b0cc53425a63a75f59e368929997f79dfc33695
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 0801f16..0d2362b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -20,8 +20,8 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.IntSwitch;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.IRConverter;
@@ -263,7 +263,11 @@
}
@Override
- public boolean switchCaseIsUnreachable(IntSwitch theSwitch, int index) {
+ public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
+ if (theSwitch.isStringSwitch()) {
+ assert false : "Unexpected string-switch instruction in dynamicMethod()";
+ return false;
+ }
if (index != newBuilderOrdinal) {
return false;
}
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 268c065..c9d0d84 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
@@ -3,11 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.TypeChecker;
@@ -15,6 +18,7 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.origin.Origin;
@@ -1098,6 +1102,16 @@
return new ConstNumber(out, value);
}
+ public ConstString createStringConstant(AppView<?> appView, DexString value) {
+ return createStringConstant(appView, value, null);
+ }
+
+ public ConstString createStringConstant(
+ AppView<?> appView, DexString value, DebugLocalInfo local) {
+ Value out = createValue(TypeElement.stringClassType(appView, definitelyNotNull()), local);
+ return new ConstString(out, value, ThrowingInfo.defaultForConstString(appView.options()));
+ }
+
public Phi createPhi(BasicBlock block, TypeElement type) {
return new Phi(valueNumberGenerator.next(), block, type, null, RegisterReadType.NORMAL);
}
@@ -1107,8 +1121,7 @@
}
public ConstClass createConstClass(AppView<?> appView, DexType type) {
- Value out =
- createValue(TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView));
+ Value out = createValue(TypeElement.fromDexType(type, definitelyNotNull(), appView));
return new ConstClass(out, type);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index e46af2c..c562fe7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -250,6 +250,16 @@
return get(Opcodes.SUB);
}
+ @SuppressWarnings("ConstantConditions")
+ public boolean mayHaveSwitch() {
+ assert Opcodes.INT_SWITCH < 64;
+ assert Opcodes.STRING_SWITCH < 64;
+ long mask = (1L << Opcodes.INT_SWITCH) | (1L << Opcodes.STRING_SWITCH);
+ boolean result = isAnySetInFirst(mask);
+ assert result == (mayHaveIntSwitch() || mayHaveStringSwitch());
+ return result;
+ }
+
public boolean mayHaveUshr() {
return get(Opcodes.USHR);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
index f22ef1c..d5a83f5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.code.SparseSwitch;
import com.android.tools.r8.code.SparseSwitchPayload;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
@@ -51,6 +52,11 @@
}
@Override
+ public Instruction materializeFirstKey(AppView<?> appView, IRCode code) {
+ return code.createIntConstant(getFirstKey());
+ }
+
+ @Override
public boolean valid() {
assert super.valid();
assert keys.length >= 1;
diff --git a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
index 1b3d673..05acba1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -23,6 +24,10 @@
assert valid();
}
+ public DexString getFirstKey() {
+ return keys[0];
+ }
+
@Override
public int opcode() {
return Opcodes.STRING_SWITCH;
@@ -40,6 +45,11 @@
}
@Override
+ public Instruction materializeFirstKey(AppView<?> appView, IRCode code) {
+ return code.createStringConstant(appView, getFirstKey());
+ }
+
+ @Override
public boolean valid() {
assert super.valid();
assert keys.length >= 1;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 8dcb6a4..4e129aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.graph.AppView;
import java.util.function.Consumer;
public abstract class Switch extends JumpInstruction {
@@ -17,6 +18,8 @@
this.fallthroughBlockIndex = fallthroughBlockIndex;
}
+ public abstract Instruction materializeFirstKey(AppView<?> appView, IRCode code);
+
public Value value() {
return inValues.get(0);
}
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 9f34126..eb36e39 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
@@ -948,7 +948,7 @@
}
public boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
- if (!code.metadata().mayHaveIntSwitch()) {
+ if (!code.metadata().mayHaveSwitch()) {
return false;
}
@@ -959,8 +959,8 @@
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
- if (instruction.isIntSwitch()) {
- IntSwitch theSwitch = instruction.asIntSwitch();
+ if (instruction.isSwitch()) {
+ Switch theSwitch = instruction.asSwitch();
if (options.testing.enableDeadSwitchCaseElimination) {
SwitchCaseEliminator eliminator =
removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer);
@@ -975,119 +975,12 @@
continue;
}
- assert instruction.isIntSwitch();
- theSwitch = instruction.asIntSwitch();
+ assert instruction.isSwitch();
+ theSwitch = instruction.asSwitch();
}
}
- if (theSwitch.numberOfKeys() == 1) {
- // Rewrite the switch to an if.
- int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
- int caseBlockIndex = theSwitch.targetBlockIndices()[0];
- if (fallthroughBlockIndex < caseBlockIndex) {
- block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
- }
- if (theSwitch.getFirstKey() == 0) {
- iterator.replaceCurrentInstruction(new If(Type.EQ, theSwitch.value()));
- } else {
- ConstNumber labelConst = code.createIntConstant(theSwitch.getFirstKey());
- labelConst.setPosition(theSwitch.getPosition());
- iterator.previous();
- iterator.add(labelConst);
- Instruction dummy = iterator.next();
- assert dummy == theSwitch;
- If theIf = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.dest()));
- iterator.replaceCurrentInstruction(theIf);
- }
- } else {
- // If there are more than 1 key, we use the following algorithm to find keys to combine.
- // First, scan through the keys forward and combine each packed interval with the
- // previous interval if it gives a net saving.
- // Secondly, go through all created intervals and combine the ones without a saving into
- // a single interval and keep a max number of packed switches.
- // Finally, go through all intervals and check if the switch or part of the switch
- // should be transformed to ifs.
-
- // Phase 1: Combine packed intervals.
- InternalOutputMode mode = options.getInternalOutputMode();
- int[] keys = theSwitch.getKeys();
- int maxNumberOfIfsOrSwitches = 10;
- PriorityQueue<Interval> biggestPackedSavings =
- new PriorityQueue<>(
- (x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
- Set<Interval> biggestPackedSet = new HashSet<>();
- List<Interval> intervals = new ArrayList<>();
- int previousKey = keys[0];
- IntList currentKeys = new IntArrayList();
- currentKeys.add(previousKey);
- Interval previousInterval = null;
- for (int i = 1; i < keys.length; i++) {
- int key = keys[i];
- if (((long) key - (long) previousKey) > 1) {
- Interval current = new Interval(currentKeys);
- Interval added = combineOrAddInterval(intervals, previousInterval, current);
- if (added != current && biggestPackedSet.contains(previousInterval)) {
- biggestPackedSet.remove(previousInterval);
- biggestPackedSavings.remove(previousInterval);
- }
- tryAddToBiggestSavings(
- biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
- previousInterval = added;
- currentKeys = new IntArrayList();
- }
- currentKeys.add(key);
- previousKey = key;
- }
- Interval current = new Interval(currentKeys);
- Interval added = combineOrAddInterval(intervals, previousInterval, current);
- if (added != current && biggestPackedSet.contains(previousInterval)) {
- biggestPackedSet.remove(previousInterval);
- biggestPackedSavings.remove(previousInterval);
- }
- tryAddToBiggestSavings(
- biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
-
- // Phase 2: combine sparse intervals into a single bin.
- // Check if we should save a space for a sparse switch, if so, remove the switch with
- // the smallest savings.
- if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
- && maxNumberOfIfsOrSwitches < intervals.size()) {
- biggestPackedSet.remove(biggestPackedSavings.poll());
- }
- Interval sparse = null;
- List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
- for (int i = 0; i < intervals.size(); i++) {
- Interval interval = intervals.get(i);
- if (biggestPackedSet.contains(interval)) {
- newSwitches.add(interval);
- } else if (sparse == null) {
- sparse = interval;
- newSwitches.add(sparse);
- } else {
- sparse.addInterval(interval);
- }
- }
-
- // Phase 3: at this point we are guaranteed to have the biggest saving switches
- // in newIntervals, potentially with a switch combining the remaining intervals.
- // Now we check to see if we can create any if's to reduce size.
- IntList outliers = new IntArrayList();
- int outliersAsIfSize =
- appView.options().testing.enableSwitchToIfRewriting
- ? findIfsForCandidates(newSwitches, theSwitch, outliers)
- : 0;
-
- long newSwitchesSize = 0;
- List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
- for (Interval interval : newSwitches) {
- newSwitchesSize += interval.estimatedSize(mode);
- newSwitchSequences.add(interval.keys);
- }
-
- long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys());
- if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
- convertSwitchToSwitchAndIfs(
- code, blocksIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
- }
+ if (theSwitch.isIntSwitch()) {
+ rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch());
}
}
}
@@ -1107,9 +1000,127 @@
return !affectedValues.isEmpty();
}
+ private void rewriteIntSwitch(
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ BasicBlock block,
+ InstructionListIterator iterator,
+ IntSwitch theSwitch) {
+ if (theSwitch.numberOfKeys() == 1) {
+ // Rewrite the switch to an if.
+ int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
+ int caseBlockIndex = theSwitch.targetBlockIndices()[0];
+ if (fallthroughBlockIndex < caseBlockIndex) {
+ block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
+ }
+ If replacement;
+ if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
+ replacement = new If(Type.EQ, theSwitch.value());
+ } else {
+ Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
+ labelConst.setPosition(theSwitch.getPosition());
+ iterator.previous();
+ iterator.add(labelConst);
+ Instruction dummy = iterator.next();
+ assert dummy == theSwitch;
+ replacement = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
+ }
+ iterator.replaceCurrentInstruction(replacement);
+ return;
+ }
+
+ // If there are more than 1 key, we use the following algorithm to find keys to combine.
+ // First, scan through the keys forward and combine each packed interval with the
+ // previous interval if it gives a net saving.
+ // Secondly, go through all created intervals and combine the ones without a saving into
+ // a single interval and keep a max number of packed switches.
+ // Finally, go through all intervals and check if the switch or part of the switch
+ // should be transformed to ifs.
+
+ // Phase 1: Combine packed intervals.
+ InternalOutputMode mode = options.getInternalOutputMode();
+ int[] keys = theSwitch.getKeys();
+ int maxNumberOfIfsOrSwitches = 10;
+ PriorityQueue<Interval> biggestPackedSavings =
+ new PriorityQueue<>((x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
+ Set<Interval> biggestPackedSet = new HashSet<>();
+ List<Interval> intervals = new ArrayList<>();
+ int previousKey = keys[0];
+ IntList currentKeys = new IntArrayList();
+ currentKeys.add(previousKey);
+ Interval previousInterval = null;
+ for (int i = 1; i < keys.length; i++) {
+ int key = keys[i];
+ if (((long) key - (long) previousKey) > 1) {
+ Interval current = new Interval(currentKeys);
+ Interval added = combineOrAddInterval(intervals, previousInterval, current);
+ if (added != current && biggestPackedSet.contains(previousInterval)) {
+ biggestPackedSet.remove(previousInterval);
+ biggestPackedSavings.remove(previousInterval);
+ }
+ tryAddToBiggestSavings(
+ biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+ previousInterval = added;
+ currentKeys = new IntArrayList();
+ }
+ currentKeys.add(key);
+ previousKey = key;
+ }
+ Interval current = new Interval(currentKeys);
+ Interval added = combineOrAddInterval(intervals, previousInterval, current);
+ if (added != current && biggestPackedSet.contains(previousInterval)) {
+ biggestPackedSet.remove(previousInterval);
+ biggestPackedSavings.remove(previousInterval);
+ }
+ tryAddToBiggestSavings(biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+
+ // Phase 2: combine sparse intervals into a single bin.
+ // Check if we should save a space for a sparse switch, if so, remove the switch with
+ // the smallest savings.
+ if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
+ && maxNumberOfIfsOrSwitches < intervals.size()) {
+ biggestPackedSet.remove(biggestPackedSavings.poll());
+ }
+ Interval sparse = null;
+ List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
+ for (int i = 0; i < intervals.size(); i++) {
+ Interval interval = intervals.get(i);
+ if (biggestPackedSet.contains(interval)) {
+ newSwitches.add(interval);
+ } else if (sparse == null) {
+ sparse = interval;
+ newSwitches.add(sparse);
+ } else {
+ sparse.addInterval(interval);
+ }
+ }
+
+ // Phase 3: at this point we are guaranteed to have the biggest saving switches
+ // in newIntervals, potentially with a switch combining the remaining intervals.
+ // Now we check to see if we can create any if's to reduce size.
+ IntList outliers = new IntArrayList();
+ int outliersAsIfSize =
+ appView.options().testing.enableSwitchToIfRewriting
+ ? findIfsForCandidates(newSwitches, theSwitch, outliers)
+ : 0;
+
+ long newSwitchesSize = 0;
+ List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
+ for (Interval interval : newSwitches) {
+ newSwitchesSize += interval.estimatedSize(mode);
+ newSwitchSequences.add(interval.keys);
+ }
+
+ long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys());
+ if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
+ convertSwitchToSwitchAndIfs(
+ code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
+ }
+ }
+
private SwitchCaseEliminator removeUnnecessarySwitchCases(
IRCode code,
- IntSwitch theSwitch,
+ Switch theSwitch,
InstructionListIterator iterator,
SwitchCaseAnalyzer switchCaseAnalyzer) {
BasicBlock defaultTarget = theSwitch.fallthroughBlock();
@@ -1118,9 +1129,18 @@
new BasicBlockBehavioralSubsumption(appView, code.method.holder());
// Compute the set of switch cases that can be removed.
+ int alwaysHitCase = -1;
for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
BasicBlock targetBlock = theSwitch.targetBlock(i);
+ if (switchCaseAnalyzer.switchCaseIsAlwaysHit(theSwitch, i)) {
+ if (eliminator == null) {
+ eliminator = new SwitchCaseEliminator(theSwitch, iterator);
+ }
+ eliminator.markSwitchCaseAsAlwaysHit(i);
+ break;
+ }
+
// This switch case can be removed if the behavior of the target block is equivalent to the
// behavior of the default block, or if the switch case is unreachable.
if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, i)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
index 0aabe6e..733db77 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.StringSwitch;
+import com.android.tools.r8.ir.code.Switch;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@@ -21,12 +24,14 @@
private final BasicBlock block;
private final BasicBlock defaultTarget;
private final InstructionListIterator iterator;
- private final IntSwitch theSwitch;
+ private final Switch theSwitch;
+ private int alwaysHitCase = -1;
+ private BasicBlock alwaysHitTarget;
private boolean mayHaveIntroducedUnreachableBlocks = false;
private IntSet switchCasesToBeRemoved;
- SwitchCaseEliminator(IntSwitch theSwitch, InstructionListIterator iterator) {
+ SwitchCaseEliminator(Switch theSwitch, InstructionListIterator iterator) {
this.block = theSwitch.getBlock();
this.defaultTarget = theSwitch.fallthroughBlock();
this.iterator = iterator;
@@ -40,13 +45,34 @@
private boolean canBeOptimized() {
assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty();
- return switchCasesToBeRemoved != null;
+ return switchCasesToBeRemoved != null || hasAlwaysHitCase();
}
boolean mayHaveIntroducedUnreachableBlocks() {
return mayHaveIntroducedUnreachableBlocks;
}
+ public boolean isSwitchCaseLive(int index) {
+ if (hasAlwaysHitCase()) {
+ return index == alwaysHitCase;
+ }
+ return !switchCasesToBeRemoved.contains(index);
+ }
+
+ public boolean isFallthroughLive() {
+ return !hasAlwaysHitCase();
+ }
+
+ public boolean hasAlwaysHitCase() {
+ return alwaysHitCase >= 0;
+ }
+
+ void markSwitchCaseAsAlwaysHit(int i) {
+ assert alwaysHitCase < 0;
+ alwaysHitCase = i;
+ alwaysHitTarget = theSwitch.targetBlock(i);
+ }
+
void markSwitchCaseForRemoval(int i) {
if (switchCasesToBeRemoved == null) {
switchCasesToBeRemoved = new IntOpenHashSet();
@@ -58,8 +84,8 @@
if (canBeOptimized()) {
int originalNumberOfSuccessors = block.getSuccessors().size();
unlinkDeadSuccessors();
- if (allSwitchCasesMarkedForRemoval()) {
- // Replace switch with a simple goto since only the fall through is left.
+ if (hasAlwaysHitCase() || allSwitchCasesMarkedForRemoval()) {
+ // Replace switch with a simple goto.
replaceSwitchByGoto();
} else {
// Replace switch by a new switch where the dead switch cases have been removed.
@@ -90,12 +116,14 @@
private IntPredicate computeSuccessorHasBecomeDeadPredicate() {
int[] numberOfControlFlowEdgesToBlockWithIndex = new int[block.getSuccessors().size()];
for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
- if (!switchCasesToBeRemoved.contains(i)) {
+ if (isSwitchCaseLive(i)) {
int targetBlockIndex = theSwitch.getTargetBlockIndex(i);
numberOfControlFlowEdgesToBlockWithIndex[targetBlockIndex] += 1;
}
}
- numberOfControlFlowEdgesToBlockWithIndex[theSwitch.getFallthroughBlockIndex()] += 1;
+ if (isFallthroughLive()) {
+ numberOfControlFlowEdgesToBlockWithIndex[theSwitch.getFallthroughBlockIndex()] += 1;
+ }
for (int i : block.getCatchHandlersWithSuccessorIndexes().getUniqueTargets()) {
numberOfControlFlowEdgesToBlockWithIndex[i] += 1;
}
@@ -103,7 +131,9 @@
}
private void replaceSwitchByGoto() {
- iterator.replaceCurrentInstruction(new Goto(defaultTarget));
+ assert !hasAlwaysHitCase() || alwaysHitTarget != null;
+ BasicBlock target = hasAlwaysHitCase() ? alwaysHitTarget : defaultTarget;
+ iterator.replaceCurrentInstruction(new Goto(target));
}
private void replaceSwitchByOptimizedSwitch(int originalNumberOfSuccessors) {
@@ -121,11 +151,9 @@
}
int newNumberOfKeys = theSwitch.numberOfKeys() - switchCasesToBeRemoved.size();
- int[] newKeys = new int[newNumberOfKeys];
int[] newTargetBlockIndices = new int[newNumberOfKeys];
for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
if (!switchCasesToBeRemoved.contains(i)) {
- newKeys[j] = theSwitch.getKey(i);
newTargetBlockIndices[j] =
theSwitch.getTargetBlockIndex(i)
- targetBlockIndexOffset[theSwitch.getTargetBlockIndex(i)];
@@ -135,12 +163,41 @@
}
}
- iterator.replaceCurrentInstruction(
- new IntSwitch(
- theSwitch.value(),
- newKeys,
- newTargetBlockIndices,
- theSwitch.getFallthroughBlockIndex()
- - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]));
+ Switch replacement;
+ if (theSwitch.isIntSwitch()) {
+ IntSwitch intSwitch = theSwitch.asIntSwitch();
+ int[] newKeys = new int[newNumberOfKeys];
+ for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
+ if (!switchCasesToBeRemoved.contains(i)) {
+ newKeys[j] = intSwitch.getKey(i);
+ j++;
+ }
+ }
+ replacement =
+ new IntSwitch(
+ theSwitch.value(),
+ newKeys,
+ newTargetBlockIndices,
+ theSwitch.getFallthroughBlockIndex()
+ - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+ } else {
+ assert theSwitch.isStringSwitch();
+ StringSwitch stringSwitch = theSwitch.asStringSwitch();
+ DexString[] newKeys = new DexString[newNumberOfKeys];
+ for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
+ if (!switchCasesToBeRemoved.contains(i)) {
+ newKeys[j] = stringSwitch.getKey(i);
+ j++;
+ }
+ }
+ replacement =
+ new StringSwitch(
+ theSwitch.value(),
+ newKeys,
+ newTargetBlockIndices,
+ theSwitch.getFallthroughBlockIndex()
+ - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+ }
+ iterator.replaceCurrentInstruction(replacement);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
index 6c22a72..76f63ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.optimize.controlflow;
-import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.LongInterval;
public class SwitchCaseAnalyzer {
@@ -17,9 +20,35 @@
return INSTANCE;
}
- public boolean switchCaseIsUnreachable(IntSwitch theSwitch, int index) {
+ public boolean switchCaseIsAlwaysHit(Switch theSwitch, int index) {
Value switchValue = theSwitch.value();
- return switchValue.hasValueRange()
- && !switchValue.getValueRange().containsValue(theSwitch.getKey(index));
+ if (theSwitch.isIntSwitch()) {
+ LongInterval valueRange = switchValue.getValueRange();
+ return valueRange != null
+ && valueRange.isSingleValue()
+ && valueRange.containsValue(theSwitch.asIntSwitch().getKey(index));
+ }
+
+ assert theSwitch.isStringSwitch();
+
+ Value rootSwitchValue = switchValue.getAliasedValue();
+ DexString key = theSwitch.asStringSwitch().getKey(index);
+ return rootSwitchValue.isDefinedByInstructionSatisfying(Instruction::isConstString)
+ && key == rootSwitchValue.definition.asConstString().getValue();
+ }
+
+ public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
+ Value switchValue = theSwitch.value();
+ if (theSwitch.isIntSwitch()) {
+ return switchValue.hasValueRange()
+ && !switchValue.getValueRange().containsValue(theSwitch.asIntSwitch().getKey(index));
+ }
+
+ assert theSwitch.isStringSwitch();
+
+ Value rootSwitchValue = switchValue.getAliasedValue();
+ DexString key = theSwitch.asStringSwitch().getKey(index);
+ return rootSwitchValue.isDefinedByInstructionSatisfying(Instruction::isConstString)
+ && key != rootSwitchValue.definition.asConstString().getValue();
}
}