Extend new-array-filled to support phis
This also changes the pass to be more conservative about
applying the transformation to arrays of classes that might
result in NoClassDefFoundError (mostly affects D8).
Bug: b/246971330
Change-Id: I8c57a1c86907ba8799e767fda27091c940b1b991
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 3570828..d784fa3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -438,6 +438,17 @@
}
}
+ public void removeAllExceptionalSuccessors() {
+ assert hasCatchHandlers();
+ IntList successorsToRemove = new IntArrayList();
+ int numberOfExceptionalSuccessors = numberOfExceptionalSuccessors();
+ for (int i = 0; i < numberOfExceptionalSuccessors; i++) {
+ successorsToRemove.add(i);
+ successors.get(i).getMutablePredecessors().remove(this);
+ }
+ removeSuccessorsByIndex(successorsToRemove);
+ }
+
public void swapSuccessors(BasicBlock a, BasicBlock b) {
assert a != b;
int aIndex = successors.indexOf(a);
@@ -1153,6 +1164,36 @@
return true;
}
+ private boolean isExceptionTrampoline() {
+ boolean ret = instructions.size() == 2 && entry().isMoveException() && exit().isGoto();
+ assert !ret || !hasCatchHandlers() : "Trampoline should not have catch handlers";
+ return ret;
+ }
+
+ /** Returns whether the given blocks are in the same try block. */
+ public boolean hasEquivalentCatchHandlers(BasicBlock other) {
+ if (this == other) {
+ return true;
+ }
+ List<Integer> targets1 = catchHandlers.getAllTargets();
+ List<Integer> targets2 = other.catchHandlers.getAllTargets();
+ int numHandlers = targets1.size();
+ if (numHandlers != targets2.size()) {
+ return false;
+ }
+ // If all catch handlers are trampolines to the same block, then they are from the same try.
+ for (int i = 0; i < numHandlers; ++i) {
+ BasicBlock catchBlock1 = successors.get(targets1.get(i));
+ BasicBlock catchBlock2 = other.successors.get(targets2.get(i));
+ if (!catchBlock1.isExceptionTrampoline()
+ || !catchBlock2.isExceptionTrampoline()
+ || catchBlock1.getUniqueSuccessor() != catchBlock2.getUniqueSuccessor()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public void clearCurrentDefinitions() {
currentDefinitions = null;
for (Phi phi : getPhis()) {
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 ba73a12..365708f 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
@@ -406,6 +406,14 @@
return uniquePhiUsers = ImmutableSet.copyOf(phiUsers);
}
+ public Set<BasicBlock> uniquePhiUserBlocks() {
+ Set<BasicBlock> ret = Sets.newIdentityHashSet();
+ for (Phi phi : phiUsers) {
+ ret.add(phi.getBlock());
+ }
+ return ret;
+ }
+
public Set<Instruction> debugUsers() {
return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
index 7d95d37..41379ad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -7,25 +7,28 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeUtils;
import com.android.tools.r8.ir.code.ArrayPut;
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.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilled;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.DominatorChecker;
import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
-import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.ValueUtils;
+import com.android.tools.r8.utils.ValueUtils.ArrayValues;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -80,8 +83,11 @@
*/
public class ArrayConstructionSimplifier extends CodeRewriterPass<AppInfo> {
+ private final RewriteArrayOptions rewriteArrayOptions;
+
public ArrayConstructionSimplifier(AppView<?> appView) {
super(appView);
+ rewriteArrayOptions = options.rewriteArrayOptions();
}
@Override
@@ -91,13 +97,13 @@
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
- boolean hasChanged = false;
- WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
- while (worklist.hasNext()) {
- BasicBlock block = worklist.next();
- hasChanged |= simplifyArrayConstructionBlock(block, worklist, code, appView.options());
+ ArrayList<ArrayValues> candidates = findOptimizableArrays(code);
+
+ if (candidates.isEmpty()) {
+ return CodeRewriterResult.NO_CHANGE;
}
- return CodeRewriterResult.hasChanged(hasChanged);
+ applyChanges(code, candidates);
+ return CodeRewriterResult.HAS_CHANGED;
}
@Override
@@ -105,214 +111,257 @@
return code.metadata().mayHaveNewArrayEmpty();
}
- private boolean simplifyArrayConstructionBlock(
- BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
- boolean hasChanged = false;
- RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
- InstructionListIterator it = block.listIterator(code);
- while (it.hasNext()) {
- FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions);
- if (candidate == null) {
- continue;
- }
- FilledArrayConversionInfo info =
- computeConversionInfo(
- code, candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
- if (info == null) {
- continue;
- }
-
- Instruction instructionAfterCandidate = it.peekNext();
- NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
- DexType arrayType = newArrayEmpty.type;
- int size = candidate.size;
- Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
- assert newArrayEmpty.getLocalInfo() == null;
- Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
- Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
- NewArrayFilled invoke =
- new NewArrayFilled(arrayType, invokeValue, Arrays.asList(info.values));
- invoke.setPosition(lastArrayPut.getPosition());
- for (Value value : newArrayEmpty.inValues()) {
- value.removeUser(newArrayEmpty);
- }
- newArrayEmpty.outValue().replaceUsers(invokeValue);
- instructionsToRemove.add(newArrayEmpty);
-
- boolean originalAllocationPointHasHandlers = block.hasCatchHandlers();
- boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
-
- if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
- info.lastArrayPutIterator.add(invoke);
- } else {
- BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
- if (originalAllocationPointHasHandlers) {
- if (!insertionBlock.isTrivialGoto()) {
- BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
- assert insertionBlock.isTrivialGoto();
- worklist.addIfNotSeen(blockAfterInsertion);
- }
- insertionBlock.moveCatchHandlers(block);
- } else {
- worklist.addIfNotSeen(insertionBlock);
- }
- insertionBlock.listIterator(code).add(invoke);
- }
-
- instructionsToRemove.addAll(info.arrayPutsToRemove);
- Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
- for (Instruction instruction : instructionsToRemove) {
- BasicBlock ownerBlock = instruction.getBlock();
- // If owner block is null, then the instruction has been removed already. We can't rely on
- // just having the block pointer nulled, so the visited blocks guards reprocessing.
- if (ownerBlock != null && visitedBlocks.add(ownerBlock)) {
- InstructionListIterator removeIt = ownerBlock.listIterator(code);
- while (removeIt.hasNext()) {
- if (instructionsToRemove.contains(removeIt.next())) {
- removeIt.removeOrReplaceByDebugLocalRead();
- }
- }
+ private ArrayList<ArrayValues> findOptimizableArrays(IRCode code) {
+ ArrayList<ArrayValues> candidates = new ArrayList<>();
+ for (Instruction instruction : code.instructions()) {
+ NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+ if (newArrayEmpty != null) {
+ ArrayValues arrayValues = analyzeCandidate(newArrayEmpty, code);
+ if (arrayValues != null) {
+ candidates.add(arrayValues);
}
}
-
- // The above has invalidated the block iterator so reset it and continue.
- it = block.listIterator(code, instructionAfterCandidate);
- hasChanged = true;
}
- if (hasChanged) {
- code.removeRedundantBlocks();
- }
-
- return hasChanged;
+ return candidates;
}
- private static class FilledArrayConversionInfo {
-
- Value[] values;
- List<ArrayPut> arrayPutsToRemove;
- LinearFlowInstructionListIterator lastArrayPutIterator;
-
- public FilledArrayConversionInfo(int size) {
- values = new Value[size];
- arrayPutsToRemove = new ArrayList<>(size);
+ private ArrayValues analyzeCandidate(NewArrayEmpty newArrayEmpty, IRCode code) {
+ if (newArrayEmpty.getLocalInfo() != null) {
+ return null;
}
+ if (!rewriteArrayOptions.isPotentialSize(newArrayEmpty.sizeIfConst())) {
+ return null;
+ }
+
+ ArrayValues arrayValues = ValueUtils.computeInitialArrayValues(newArrayEmpty);
+ // Holes (default-initialized entries) could be supported, but they are rare and would
+ // complicate the logic.
+ if (arrayValues == null || arrayValues.containsHoles()) {
+ return null;
+ }
+
+ // See if all instructions are in the same try/catch.
+ ArrayPut lastArrayPut = ArrayUtils.last(arrayValues.getArrayPutsByIndex());
+ if (!newArrayEmpty.getBlock().hasEquivalentCatchHandlers(lastArrayPut.getBlock())) {
+ // Possible improvements:
+ // 1) Ignore catch handlers that do not catch OutOfMemoryError / NoClassDefFoundError.
+ // 2) Use the catch handlers from the new-array-empty if all exception blocks exit without
+ // side effects (e.g. no method calls & no monitor instructions).
+ return null;
+ }
+
+ if (!checkTypesAreCompatible(arrayValues, code)) {
+ return null;
+ }
+ if (!checkDominance(arrayValues)) {
+ return null;
+ }
+ return arrayValues;
}
- @SuppressWarnings("ReferenceEquality")
- private FilledArrayConversionInfo computeConversionInfo(
- IRCode code, FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
- NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
- assert it.peekPrevious() == newArrayEmpty;
- Value arrayValue = newArrayEmpty.outValue();
- int size = candidate.size;
-
+ private boolean checkTypesAreCompatible(ArrayValues arrayValues, IRCode code) {
// aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
// if types require a cast.
// TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
// if aput-object throws a ClassCastException if given an object that does not implement the
// desired interface, then we could add check-cast instructions for arguments we're not sure
// about.
+ NewArrayEmpty newArrayEmpty = arrayValues.getDefinition().asNewArrayEmpty();
DexType elementType = newArrayEmpty.type.toArrayElementType(dexItemFactory);
boolean needsTypeCheck =
- !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+ !elementType.isPrimitiveType() && elementType.isNotIdenticalTo(dexItemFactory.objectType);
+ if (!needsTypeCheck) {
+ return true;
+ }
- FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
- Value[] values = info.values;
- int remaining = size;
- Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
- while (it.hasNext()) {
- Instruction instruction = it.next();
- BasicBlock block = instruction.getBlock();
- // If we encounter an instruction that can throw an exception we need to bail out of the
- // optimization so that we do not transform half-initialized arrays into fully initialized
- // arrays on exceptional edges. If the block has no handlers it is not observable so
- // we perform the rewriting.
- if (block.hasCatchHandlers()
- && instruction.instructionInstanceCanThrow(appView, code.context())) {
- return null;
- }
- if (!users.contains(instruction)) {
- // If any instruction can transfer control between the new-array and the last array put
- // then it is not safe to move the new array to the point of the last put.
- if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
- return null;
- }
+ // Not safe to move allocation if NoClassDefError is possible.
+ // TODO(b/246971330): Make this work for D8 where it ~always returns false by checking that
+ // all instructions between new-array-empty and the last array-put report
+ // !instruction.instructionMayHaveSideEffects(). Alternatively, we could replace the
+ // new-array-empty with a const-class instruction in this case.
+ if (!DexTypeUtils.isTypeAccessibleInMethodContext(
+ appView, elementType.toBaseType(dexItemFactory), code.context())) {
+ return false;
+ }
+
+ for (ArrayPut arrayPut : arrayValues.getArrayPutsByIndex()) {
+ Value value = arrayPut.value();
+ if (value.isAlwaysNull(appView)) {
continue;
}
- ArrayPut arrayPut = instruction.asArrayPut();
- // If the initialization sequence is broken by another use we cannot use a fill-array-data
- // instruction.
+ DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+ if (elementType.isArrayType()) {
+ if (elementType.isNotIdenticalTo(valueDexType)) {
+ return false;
+ }
+ } else if (valueDexType.isArrayType()) {
+ // isSubtype asserts for this case.
+ return false;
+ } else if (valueDexType.isNullValueType()) {
+ // Assume instructions can cause value.isAlwaysNull() == false while the DexType is
+ // null.
+ // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+ // that hits this case.
+ } else {
+ // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+ // library types (which this helper does not do).
+ if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static boolean checkDominance(ArrayValues arrayValues) {
+ Value arrayValue = arrayValues.getArrayValue();
+
+ Set<BasicBlock> usageBlocks = Sets.newIdentityHashSet();
+ Set<Instruction> uniqueUsers = arrayValue.uniqueUsers();
+ for (Instruction user : uniqueUsers) {
+ ArrayPut arrayPut = user.asArrayPut();
if (arrayPut == null || arrayPut.array() != arrayValue) {
- return null;
+ usageBlocks.add(user.getBlock());
}
- int index = arrayPut.indexIfConstAndInBounds(values.length);
- if (index < 0 || values[index] != null) {
- return null;
+ }
+
+ // Ensure all blocks for users of the array are dominated by the last array-put's block.
+ ArrayPut lastArrayPut = ArrayUtils.last(arrayValues.getArrayPutsByIndex());
+ BasicBlock lastArrayPutBlock = lastArrayPut.getBlock();
+ BasicBlock subgraphEntryBlock = arrayValue.definition.getBlock();
+ for (BasicBlock usageBlock : usageBlocks) {
+ if (!DominatorChecker.check(subgraphEntryBlock, usageBlock, lastArrayPutBlock)) {
+ return false;
}
- if (arrayPut.instructionInstanceCanThrow(appView, code.context())) {
- return null;
+ }
+
+ // Ensure all array users in the same block appear after the last array-put
+ for (Instruction inst : lastArrayPutBlock.getInstructions()) {
+ if (inst == lastArrayPut) {
+ break;
}
- Value value = arrayPut.value();
- if (needsTypeCheck && !value.isAlwaysNull(appView)) {
- DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
- if (elementType.isArrayType()) {
- if (elementType != valueDexType) {
- return null;
- }
- } else if (valueDexType.isArrayType()) {
- // isSubtype asserts for this case.
- return null;
- } else if (valueDexType.isNullValueType()) {
- // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null.
- // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
- // that hits this case.
- } else {
- // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
- // library types (which this helper does not do).
- if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
- return null;
+ if (uniqueUsers.contains(inst)) {
+ ArrayPut arrayPut = inst.asArrayPut();
+ if (arrayPut == null || arrayPut.array() != arrayValue) {
+ return false;
+ }
+ }
+ }
+
+ // It will not be the case that the newArrayEmpty dominates the phi user (or else it would
+ // just be a normal user). It is safe to optimize if all paths from the new-array-empty to the
+ // phi user include the last array-put (where the filled-new-array will end up).
+ if (anyPhiUsersReachableWhenOptimized(arrayValues)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determines if there are any paths from the new-array-empty to any of its phi users that do not
+ * go through the last array-put, and that do not go through exceptional edges for new-array-empty
+ * / array-puts that will be removed.
+ */
+ private static boolean anyPhiUsersReachableWhenOptimized(ArrayValues arrayValues) {
+ Value arrayValue = arrayValues.getArrayValue();
+ if (!arrayValue.hasPhiUsers()) {
+ return false;
+ }
+ Set<BasicBlock> phiUserBlocks = arrayValue.uniquePhiUserBlocks();
+ WorkList<BasicBlock> workList = WorkList.newIdentityWorkList();
+ // Mark the last array-put as seen in order to find paths that do not contain it.
+ workList.markAsSeen(ArrayUtils.last(arrayValues.getArrayPutsByIndex()).getBlock());
+ // Start with normal successors since if optimized, the new-array-empty block will have no
+ // throwing instructions.
+ workList.addIfNotSeen(arrayValue.definition.getBlock().getNormalSuccessors());
+ while (workList.hasNext()) {
+ BasicBlock current = workList.removeLast();
+ if (phiUserBlocks.contains(current)) {
+ return true;
+ }
+ if (current.hasCatchHandlers()) {
+ Instruction throwingInstruction = current.exceptionalExit();
+ if (throwingInstruction != null) {
+ ArrayPut arrayPut = throwingInstruction.asArrayPut();
+ // Ignore exceptional edges that will be remove if optimized.
+ if (arrayPut != null && arrayPut.array() == arrayValue) {
+ workList.addIfNotSeen(current.getNormalSuccessors());
+ continue;
}
}
}
- info.arrayPutsToRemove.add(arrayPut);
- values[index] = value;
- --remaining;
- if (remaining == 0) {
- info.lastArrayPutIterator = it;
- return info;
+ workList.addIfNotSeen(current.getSuccessors());
+ }
+ return false;
+ }
+
+ private void applyChanges(IRCode code, List<ArrayValues> candidates) {
+ Set<BasicBlock> relevantBlocks = Sets.newIdentityHashSet();
+ // All keys instructionsToChange are removed, and also maps lastArrayPut -> newArrayFilled.
+ Map<Instruction, Instruction> instructionsToChange = new IdentityHashMap<>();
+ boolean needToRemoveUnreachableBlocks = false;
+
+ for (ArrayValues arrayValues : candidates) {
+ NewArrayEmpty newArrayEmpty = arrayValues.getDefinition().asNewArrayEmpty();
+ instructionsToChange.put(newArrayEmpty, newArrayEmpty);
+ BasicBlock allocationBlock = newArrayEmpty.getBlock();
+ relevantBlocks.add(allocationBlock);
+
+ ArrayPut[] arrayPutsByIndex = arrayValues.getArrayPutsByIndex();
+ int lastArrayPutIndex = arrayPutsByIndex.length - 1;
+ for (int i = 0; i < lastArrayPutIndex; ++i) {
+ ArrayPut arrayPut = arrayPutsByIndex[i];
+ instructionsToChange.put(arrayPut, arrayPut);
+ relevantBlocks.add(arrayPut.getBlock());
+ }
+ ArrayPut lastArrayPut = arrayPutsByIndex[lastArrayPutIndex];
+ BasicBlock lastArrayPutBlock = lastArrayPut.getBlock();
+ relevantBlocks.add(lastArrayPutBlock);
+
+ // newArrayEmpty's outValue must be cleared before trying to remove newArrayEmpty. Rather than
+ // store the outValue for later, create and store newArrayFilled.
+ Value arrayValue = newArrayEmpty.clearOutValue();
+ NewArrayFilled newArrayFilled =
+ new NewArrayFilled(newArrayEmpty.type, arrayValue, arrayValues.getElementValues());
+ newArrayFilled.setPosition(lastArrayPut.getPosition());
+ instructionsToChange.put(lastArrayPut, newArrayFilled);
+
+ if (arrayValue.hasPhiUsers() && allocationBlock.hasCatchHandlers()) {
+ // When phi users exist, the phis belong to the exceptional successors of the allocation
+ // block. In order to preserve them, move them to the new allocation block.
+ // This is safe because we've already checked hasEquivalentCatchHandlers().
+ lastArrayPutBlock.removeAllExceptionalSuccessors();
+ lastArrayPutBlock.moveCatchHandlers(allocationBlock);
+ needToRemoveUnreachableBlocks = true;
}
}
- return null;
- }
- private static class FilledArrayCandidate {
+ for (BasicBlock block : relevantBlocks) {
+ boolean hasCatchHandlers = block.hasCatchHandlers();
+ InstructionListIterator it = block.listIterator(code);
+ while (it.hasNext()) {
+ Instruction possiblyNewArray = instructionsToChange.get(it.next());
+ if (possiblyNewArray != null) {
+ if (possiblyNewArray.isNewArrayFilled()) {
+ // Change the last array-put to the new-array-filled.
+ it.replaceCurrentInstruction(possiblyNewArray);
+ } else {
+ it.removeOrReplaceByDebugLocalRead();
+ if (hasCatchHandlers) {
+ // Removing these catch handlers shrinks their ranges to be only that where the
+ // allocation occurs.
+ needToRemoveUnreachableBlocks = true;
+ assert !block.canThrow();
+ block.removeAllExceptionalSuccessors();
+ }
+ }
+ }
+ }
+ }
- final NewArrayEmpty newArrayEmpty;
- final int size;
-
- public FilledArrayCandidate(NewArrayEmpty newArrayEmpty, int size) {
- assert size > 0;
- this.newArrayEmpty = newArrayEmpty;
- this.size = size;
+ if (needToRemoveUnreachableBlocks) {
+ code.removeUnreachableBlocks();
}
- }
-
- private FilledArrayCandidate computeFilledArrayCandidate(
- Instruction instruction, RewriteArrayOptions options) {
- NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
- if (newArrayEmpty == null) {
- return null;
- }
- if (instruction.getLocalInfo() != null) {
- return null;
- }
- if (!newArrayEmpty.size().isConstant()) {
- return null;
- }
- int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
- if (!options.isPotentialSize(size)) {
- return null;
- }
- return new FilledArrayCandidate(newArrayEmpty, size);
+ code.removeRedundantBlocks();
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/DominatorChecker.java b/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
index c2fd047..d1dc853 100644
--- a/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
+++ b/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Set;
@@ -32,20 +31,20 @@
}
class TraversingDominatorChecker implements DominatorChecker {
- private final BasicBlock sourceBlock;
- private final BasicBlock destBlock;
+ private final BasicBlock subgraphEntryBlock;
+ private final BasicBlock subgraphExitBlock;
private final Set<BasicBlock> knownDominators;
- private final ArrayDeque<BasicBlock> workQueue = new ArrayDeque<>();
- private final Set<BasicBlock> visited;
+ private final WorkList<BasicBlock> workList = WorkList.newIdentityWorkList();
private BasicBlock prevTargetBlock;
private TraversingDominatorChecker(
- BasicBlock sourceBlock, BasicBlock destBlock, Set<BasicBlock> knownDominators) {
- this.sourceBlock = sourceBlock;
- this.destBlock = destBlock;
+ BasicBlock subgraphEntryBlock,
+ BasicBlock subgraphExitBlock,
+ Set<BasicBlock> knownDominators) {
+ this.subgraphEntryBlock = subgraphEntryBlock;
+ this.subgraphExitBlock = subgraphExitBlock;
this.knownDominators = knownDominators;
- this.visited = Sets.newIdentityHashSet();
- prevTargetBlock = destBlock;
+ prevTargetBlock = subgraphExitBlock;
}
@Override
@@ -60,7 +59,7 @@
if (firstSplittingBlock.hasUniqueSuccessor()) {
do {
// knownDominators prevents firstSplittingBlock from being destBlock.
- assert firstSplittingBlock != destBlock;
+ assert firstSplittingBlock != subgraphExitBlock;
firstSplittingBlock = firstSplittingBlock.getUniqueSuccessor();
} while (firstSplittingBlock.hasUniqueSuccessor());
@@ -73,13 +72,12 @@
boolean ret;
// Since we know the previously checked block is a dominator, narrow the check by using it for
// either sourceBlock or destBlock.
- if (visited.contains(targetBlock)) {
- visited.clear();
- ret =
- checkWithTraversal(prevTargetBlock, destBlock, firstSplittingBlock, visited, workQueue);
+ if (workList.isSeen(targetBlock)) {
+ workList.clearSeen();
+ ret = checkWithTraversal(prevTargetBlock, subgraphExitBlock, firstSplittingBlock, workList);
prevTargetBlock = firstSplittingBlock;
} else {
- ret = checkWithTraversal(sourceBlock, prevTargetBlock, targetBlock, visited, workQueue);
+ ret = checkWithTraversal(subgraphEntryBlock, prevTargetBlock, targetBlock, workList);
prevTargetBlock = targetBlock;
}
if (ret) {
@@ -93,70 +91,71 @@
return ret;
}
+ /**
+ * Within the subgraph defined by the given entry/exit blocks, returns whether targetBlock
+ * dominates the exit block.
+ */
private static boolean checkWithTraversal(
- BasicBlock sourceBlock,
- BasicBlock destBlock,
+ BasicBlock subgraphEntryBlock,
+ BasicBlock subgraphExitBlock,
BasicBlock targetBlock,
- Set<BasicBlock> visited,
- ArrayDeque<BasicBlock> workQueue) {
- assert workQueue.isEmpty();
+ WorkList<BasicBlock> workList) {
+ assert workList.isEmpty();
- visited.add(targetBlock);
-
- workQueue.addAll(destBlock.getPredecessors());
- do {
- BasicBlock curBlock = workQueue.removeLast();
- if (!visited.add(curBlock)) {
- continue;
- }
- if (curBlock == sourceBlock) {
- // There is a path from sourceBlock -> destBlock that does not go through block.
+ workList.markAsSeen(targetBlock);
+ workList.addIfNotSeen(subgraphExitBlock.getPredecessors());
+ while (!workList.isEmpty()) {
+ BasicBlock curBlock = workList.removeLast();
+ if (curBlock == subgraphEntryBlock) {
+ // There is a path from subgraphExitBlock -> subgraphEntryBlock that does not go through
+ // targetBlock.
return false;
}
- assert !curBlock.isEntry() : "sourceBlock did not dominate destBlock";
- workQueue.addAll(curBlock.getPredecessors());
- } while (!workQueue.isEmpty());
+ assert !curBlock.isEntry() : "subgraphEntryBlock did not dominate subgraphExitBlock";
+ workList.addIfNotSeen(curBlock.getPredecessors());
+ }
return true;
}
}
- static DominatorChecker create(BasicBlock sourceBlock, BasicBlock destBlock) {
+ static DominatorChecker create(BasicBlock subgraphEntryBlock, BasicBlock subgraphExitBlock) {
// Fast-path: blocks are the same.
// As of Nov 2023: in Chrome for String.format() optimization, this covers 77% of cases.
- if (sourceBlock == destBlock) {
- return new PrecomputedDominatorChecker(Collections.singleton(sourceBlock));
+ if (subgraphEntryBlock == subgraphExitBlock) {
+ return new PrecomputedDominatorChecker(Collections.singleton(subgraphEntryBlock));
}
- // Shrink the subgraph by moving sourceBlock forward to the first block with multiple
+ // Shrink the subgraph by moving subgraphEntryBlock forward to the first block with multiple
// successors.
Set<BasicBlock> headAndTailDominators = Sets.newIdentityHashSet();
- headAndTailDominators.add(sourceBlock);
- while (sourceBlock.hasUniqueSuccessor()) {
- sourceBlock = sourceBlock.getUniqueSuccessor();
- if (!headAndTailDominators.add(sourceBlock)) {
+ headAndTailDominators.add(subgraphEntryBlock);
+ while (subgraphEntryBlock.hasUniqueSuccessor()) {
+ subgraphEntryBlock = subgraphEntryBlock.getUniqueSuccessor();
+ if (!headAndTailDominators.add(subgraphEntryBlock)) {
// Hit an infinite loop. Code would not verify in this case.
assert false;
return FALSE_CHECKER;
}
- if (sourceBlock == destBlock) {
+ if (subgraphEntryBlock == subgraphExitBlock) {
// As of Nov 2023: in Chrome for String.format() optimization, a linear path from
// source->dest was 14% of cases.
return new PrecomputedDominatorChecker(headAndTailDominators);
}
}
- if (sourceBlock.getSuccessors().isEmpty()) {
+ if (subgraphEntryBlock.getSuccessors().isEmpty()) {
return FALSE_CHECKER;
}
- // Shrink the subgraph by moving destBlock back to the first block with multiple predecessors.
- headAndTailDominators.add(destBlock);
- while (destBlock.hasUniquePredecessor()) {
- destBlock = destBlock.getUniquePredecessor();
- if (!headAndTailDominators.add(destBlock)) {
- if (sourceBlock == destBlock) {
- // This normally happens when moving sourceBlock forwards, but when moving destBlock
- // backwards when sourceBlock has multiple successors.
+ // Shrink the subgraph by moving subgraphExitBlock back to the first block with multiple
+ // predecessors.
+ headAndTailDominators.add(subgraphExitBlock);
+ while (subgraphExitBlock.hasUniquePredecessor()) {
+ subgraphExitBlock = subgraphExitBlock.getUniquePredecessor();
+ if (!headAndTailDominators.add(subgraphExitBlock)) {
+ if (subgraphEntryBlock == subgraphExitBlock) {
+ // This normally happens when moving subgraphEntryBlock forwards, but can also occur when
+ // moving subgraphExitBlock backwards when subgraphEntryBlock has multiple successors.
return new PrecomputedDominatorChecker(headAndTailDominators);
}
// Hit an infinite loop. Code would not verify in this case.
@@ -165,10 +164,28 @@
}
}
- if (destBlock.isEntry()) {
+ if (subgraphExitBlock.isEntry()) {
return FALSE_CHECKER;
}
- return new TraversingDominatorChecker(sourceBlock, destBlock, headAndTailDominators);
+ return new TraversingDominatorChecker(
+ subgraphEntryBlock, subgraphExitBlock, headAndTailDominators);
+ }
+
+ /**
+ * Returns whether targetBlock dominates subgraphExitBlock by performing a depth-first traversal
+ * from subgraphExitBlock to subgraphEntryBlock with targetBlock removed from the graph.
+ */
+ @SuppressWarnings("InconsistentOverloads")
+ static boolean check(
+ BasicBlock subgraphEntryBlock, BasicBlock subgraphExitBlock, BasicBlock targetBlock) {
+ if (targetBlock == subgraphExitBlock) {
+ return true;
+ }
+ if (subgraphEntryBlock == subgraphExitBlock) {
+ return false;
+ }
+ return TraversingDominatorChecker.checkWithTraversal(
+ subgraphEntryBlock, subgraphExitBlock, targetBlock, WorkList.newIdentityWorkList());
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ValueUtils.java b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
index 68d3364..9082be5 100644
--- a/src/main/java/com/android/tools/r8/utils/ValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
@@ -57,12 +57,15 @@
public static final class ArrayValues {
private List<Value> elementValues;
private ArrayPut[] arrayPutsByIndex;
+ private final Value arrayValue;
- private ArrayValues(List<Value> elementValues) {
+ private ArrayValues(Value arrayValue, List<Value> elementValues) {
+ this.arrayValue = arrayValue;
this.elementValues = elementValues;
}
- private ArrayValues(ArrayPut[] arrayPutsByIndex) {
+ private ArrayValues(Value arrayValue, ArrayPut[] arrayPutsByIndex) {
+ this.arrayValue = arrayValue;
this.arrayPutsByIndex = arrayPutsByIndex;
}
@@ -83,6 +86,28 @@
public int size() {
return elementValues != null ? elementValues.size() : arrayPutsByIndex.length;
}
+
+ public boolean containsHoles() {
+ for (ArrayPut arrayPut : arrayPutsByIndex) {
+ if (arrayPut == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public ArrayPut[] getArrayPutsByIndex() {
+ assert arrayPutsByIndex != null;
+ return arrayPutsByIndex;
+ }
+
+ public Value getArrayValue() {
+ return arrayValue;
+ }
+
+ public Instruction getDefinition() {
+ return arrayValue.definition;
+ }
}
/**
@@ -92,12 +117,8 @@
* 1) The Array has a single users (other than array-puts)
* * This constraint is to ensure other users do not modify the array.
* * When users are in different blocks, their order is hard to know.
- * 2) The array size is a constant.
+ * 2) The array size is a constant and non-negative.
* 3) All array-put instructions have constant and unique indices.
- * * With the exception of array-puts that are in the same block as singleUser, in which case
- * non-unique puts are allowed.
- * * Indices must be unique in other blocks because order is hard to know when multiple blocks
- * are concerned (and reassignment is rare anyways).
* 4) The array-put instructions are guaranteed to be executed before singleUser.
* </pre>
*
@@ -106,7 +127,6 @@
* @return The computed array values, or null if they could not be determined.
*/
public static ArrayValues computeSingleUseArrayValues(Value arrayValue, Instruction singleUser) {
- assert singleUser == null || arrayValue.uniqueUsers().contains(singleUser);
TypeElement arrayType = arrayValue.getType();
if (!arrayType.isArrayType() || arrayValue.hasDebugUsers() || arrayValue.isPhi()) {
return null;
@@ -121,7 +141,7 @@
if (!arrayValue.hasSingleUniqueUser() || arrayValue.hasPhiUsers()) {
return null;
}
- return new ArrayValues(newArrayFilled.inValues());
+ return new ArrayValues(arrayValue, newArrayFilled.inValues());
} else if (newArrayEmpty == null) {
return null;
}
@@ -132,28 +152,83 @@
return null;
}
- if (singleUser == null) {
- for (Instruction user : arrayValue.uniqueUsers()) {
- ArrayPut arrayPut = user.asArrayPut();
- if (arrayPut == null || arrayPut.array() != arrayValue || arrayPut.value() == arrayValue) {
- if (singleUser == null) {
- singleUser = user;
- } else {
- return null;
- }
+ return computeArrayValuesInternal(newArrayEmpty, arraySize, singleUser, false);
+ }
+
+ /**
+ * Determines the values for the given array at the point the last element is assigned.
+ *
+ * <pre>
+ * Returns null under the following conditions:
+ * * The array has a non-const, negative, or abnormally large size.
+ * * An array-put with non-constant index exists.
+ * * An array-put with an out-of-bounds index exists.
+ * * An array-put for the last index is not found.
+ * * An array-put is found after the last-index array-put.
+ * * An array-put is found where the array and value are the same: arr[index] = arr;
+ * * There are multiple array-put instructions for the same index.
+ * </pre>
+ */
+ public static ArrayValues computeInitialArrayValues(NewArrayEmpty newArrayEmpty) {
+ int arraySize = newArrayEmpty.sizeIfConst();
+ if (arraySize < 0 || arraySize > MAX_ARRAY_SIZE) {
+ // Array is non-const size.
+ return null;
+ }
+
+ Value arrayValue = newArrayEmpty.outValue();
+ // Find array-put for the last element, as well as blocks for other array users.
+ ArrayPut lastArrayPut = null;
+ int lastIndex = arraySize - 1;
+ for (Instruction user : arrayValue.uniqueUsers()) {
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut != null && arrayPut.array() == arrayValue) {
+ int index = arrayPut.indexOrDefault(-1);
+ if (index == lastIndex) {
+ lastArrayPut = arrayPut;
+ break;
}
}
}
+ if (lastArrayPut == null) {
+ return null;
+ }
+ // Find all array-puts up until the last one.
+ // Also checks that no array-puts appear after the last one.
+ ArrayValues ret = computeArrayValuesInternal(newArrayEmpty, arraySize, lastArrayPut, true);
+ if (ret == null) {
+ return null;
+ }
+ // Since the last array-put is used as firstUser, it will not already be in arrayPutsByIndex.
+ if (ret.arrayPutsByIndex[lastIndex] != null) {
+ return null;
+ }
+ ret.arrayPutsByIndex[lastIndex] = lastArrayPut;
+ return ret;
+ }
+
+ private static ArrayValues computeArrayValuesInternal(
+ NewArrayEmpty newArrayEmpty, int arraySize, Instruction firstUser, boolean allowOtherUsers) {
ArrayPut[] arrayPutsByIndex = new ArrayPut[arraySize];
- BasicBlock usageBlock = singleUser.getBlock();
+ Value arrayValue = newArrayEmpty.outValue();
+ BasicBlock usageBlock = firstUser.getBlock();
+
+ // Collect array-puts from non-usage blocks, and (optionally) check for multiple users.
for (Instruction user : arrayValue.uniqueUsers()) {
ArrayPut arrayPut = user.asArrayPut();
- if (arrayPut == null || arrayPut.array() != arrayValue || arrayPut.value() == arrayValue) {
- if (user == singleUser) {
+ if (arrayPut == null || arrayPut.array() != arrayValue) {
+ if (user == firstUser) {
continue;
}
// Found a second non-array-put user.
+ if (allowOtherUsers) {
+ continue;
+ }
+ return null;
+ } else if (arrayPut.value() == arrayValue) {
+ // An array that contains itself is uncommon and hard to reason about.
+ // e.g.: arr[0] = arr;
return null;
}
// Process same-block instructions later.
@@ -168,8 +243,10 @@
arrayPutsByIndex[index] = arrayPut;
}
- // Ensure that all paths from new-array-empty to |usage| contain all array-put instructions.
- DominatorChecker dominatorChecker = DominatorChecker.create(definition.getBlock(), usageBlock);
+ // Ensure that all paths from new-array-empty's block to |usage|'s block contain all array-put
+ // instructions.
+ DominatorChecker dominatorChecker =
+ DominatorChecker.create(newArrayEmpty.getBlock(), usageBlock);
// Visit in reverse order because array-puts generally appear in order, and DominatorChecker's
// cache is more effective when visiting in reverse order.
for (int i = arraySize - 1; i >= 0; --i) {
@@ -179,17 +256,18 @@
}
}
- boolean seenSingleUser = false;
+ // Collect array-puts from the usage block, and ensure no array-puts come after the first user.
+ boolean seenFirstUser = false;
for (Instruction inst : usageBlock.getInstructions()) {
- if (inst == singleUser) {
- seenSingleUser = true;
+ if (inst == firstUser) {
+ seenFirstUser = true;
continue;
}
ArrayPut arrayPut = inst.asArrayPut();
if (arrayPut == null || arrayPut.array() != arrayValue) {
continue;
}
- if (seenSingleUser) {
+ if (seenFirstUser) {
// Found an array-put after the array was used. This is too uncommon of a thing to support.
return null;
}
@@ -197,10 +275,14 @@
if (index < 0) {
return null;
}
- // We can allow reassignment at this point since we are visiting in order.
+ // Do not allow re-assignment so that we can use arrayPutsByIndex to find all array-put
+ // instructions.
+ if (arrayPutsByIndex[index] != null) {
+ return null;
+ }
arrayPutsByIndex[index] = arrayPut;
}
- return new ArrayValues(arrayPutsByIndex);
+ return new ArrayValues(arrayValue, arrayPutsByIndex);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index cdfb34c..ed63185 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -145,8 +145,8 @@
return seen.contains(item);
}
- public void markAsSeen(T item) {
- seen.add(item);
+ public boolean markAsSeen(T item) {
+ return seen.add(item);
}
public void markAsSeen(Iterable<T> items) {
@@ -154,16 +154,23 @@
}
public T next() {
- assert hasNext();
return workingList.removeFirst();
}
+ public T removeLast() {
+ return workingList.removeLast();
+ }
+
public T removeSeen() {
T next = next();
seen.remove(next);
return next;
}
+ public void clearSeen() {
+ seen.clear();
+ }
+
public Set<T> getSeenSet() {
return SetUtils.unmodifiableForTesting(seen);
}
diff --git a/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java b/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java
index f25b76b..6551be6 100644
--- a/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java
+++ b/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java
@@ -115,8 +115,7 @@
}
try {
- // Array creation that cannot be turned into fill-array-data because an exception would
- // cause the initialization sequence to be interrupted.
+ // Exception does not prevent fill-array-data since only usage is within the try.
int[] ints = new int[5];
ints[0] = 0;
ints[1] = 1;
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java
index 82ae38f..26a4674 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java
@@ -63,13 +63,16 @@
}
private void inspect(CodeInspector inspector) {
+ int canUseStrings = canUseFilledNewArrayOfStringObjects(parameters) ? 1 : 0;
+ int canUseObjects = canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0;
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- 2
- + (canUseFilledNewArrayOfStringObjects(parameters) ? 2 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 2 : 0),
+ 2 + 2 * canUseStrings + 2 * canUseObjects,
2);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 2, 2);
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
+ 2 + 2 * canUseStrings + 1 * canUseObjects,
+ compilationMode.isDebug() ? 10 : 11);
}
@Test
@@ -129,32 +132,32 @@
@NeverInline
public static void m2() {
+ Object[] array = null;
try {
- Object[] array = {
- new byte[] {(byte) 1},
- new short[] {(short) 1},
- new int[] {1},
- new long[] {1L},
- new char[] {(char) 1},
- new float[] {1.0f},
- new double[] {1.0d},
- new String[] {"one"},
- new Object[] {
- new byte[] {(byte) 2},
- new short[] {(short) 2},
- new int[] {2},
- new long[] {2L},
- new char[] {(char) 2},
- new float[] {2.0f},
- new double[] {2.0d},
- new String[] {"two"},
- }
- };
- printArray(array);
- System.out.println();
+ array = new Object[9];
+ array[0] = new byte[] {(byte) 1};
+ array[1] = new short[] {(short) 1};
+ array[2] = new int[] {1};
+ array[3] = new long[] {1L};
+ array[4] = new char[] {(char) 1};
+ array[5] = new float[] {1.0f};
+ array[6] = new double[] {1.0d};
+ array[7] = new String[] {"one"};
+ array[8] =
+ new Object[] {
+ new byte[] {(byte) 2},
+ new short[] {(short) 2},
+ new int[] {2},
+ new long[] {2L},
+ new char[] {(char) 2},
+ new float[] {2.0f},
+ new double[] {2.0d},
+ new String[] {"two"},
+ };
} catch (Exception e) {
- throw new RuntimeException();
}
+ printArray(array);
+ System.out.println();
}
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java
index 63fec32..b37c8a2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java
@@ -62,10 +62,7 @@
private void inspect(CodeInspector inspector) {
inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, 1);
- inspect(
- inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- 0,
- compilationMode.isDebug() ? 1 : 2);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, 4);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java
index edc54de..c429641 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java
@@ -59,15 +59,15 @@
private void inspect(CodeInspector inspector) {
// This test use smaller int arrays, where filled-new-array is preferred over filled-array-data.
+ int canUseObjects = canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0;
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- 4 + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0),
+ 4 + 1 * canUseObjects,
1);
- // With catch handler the int[][] creation is not converted to filled-new-array.
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- 4,
- compilationMode.isDebug() ? 1 : 2);
+ 4 + 1 * canUseObjects,
+ 4);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java
index 1934f24..d238b1a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java
@@ -63,28 +63,27 @@
}
private void inspect(CodeInspector inspector) {
+ int canUseStrings = canUseFilledNewArrayOfStringObjects(parameters) ? 1 : 0;
+ int canUseObjects = canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0;
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- (canUseFilledNewArrayOfStringObjects(parameters) ? 4 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0),
+ 4 * canUseStrings + 1 * canUseObjects,
1);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- 0,
- compilationMode.isDebug() ? 2 : 1);
+ 4 * canUseStrings + 1 * canUseObjects,
+ 4);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"),
- (canUseFilledNewArrayOfStringObjects(parameters) ? 4 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 5 : 0),
+ 4 * canUseStrings + 5 * canUseObjects,
5);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m4"),
- 0,
- compilationMode.isDebug() ? 2 : 1);
+ 4 * canUseStrings + 5 * canUseObjects,
+ 5);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m5"),
- (canUseFilledNewArrayOfStringObjects(parameters) ? 4 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0),
+ 4 * canUseStrings + 1 * canUseObjects,
compilationMode.isDebug() ? 6 : 4);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java
index 4b6e64c..c27a171 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -46,9 +45,8 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : 5,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -78,8 +76,8 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), true);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"));
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
index 3a364dd..7d8bba7 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
@@ -46,10 +46,8 @@
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "104");
- private void inspect(
- MethodSubject method, int constClasses, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int constClasses, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -68,12 +66,11 @@
}
private void inspectD8(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
maxMaterializingConstants == 2 ? 98 : 26,
- 104,
- false);
+ 104);
}
@Test
@@ -89,12 +86,11 @@
}
private void inspectR8(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
maxMaterializingConstants == 2 ? 32 : 26,
- 104,
- false);
+ 104);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
index c7f847b..3c7976f 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
@@ -52,9 +52,8 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -86,9 +85,9 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, true);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
deleted file mode 100644
index e28a4fc..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static com.android.tools.r8.cf.methodhandles.fields.ClassFieldMethodHandleTest.Main.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFillArrayData;
-import com.android.tools.r8.dex.code.DexNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class FilledArrayDataWithCatchHandlerTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public FilledArrayDataWithCatchHandlerTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkInstructions);
- }
-
- private void checkInstructions(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- List<InstructionSubject> newArrays =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexNewArray)
- .collect(Collectors.toList());
- assertEquals(1, newArrays.size());
-
- List<InstructionSubject> fillArrays =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFillArrayData)
- .collect(Collectors.toList());
- assertEquals(1, fillArrays.size());
-
- InstructionOffsetSubject offsetNew = newArrays.get(0).getOffset(foo);
- InstructionOffsetSubject offsetFill = newArrays.get(0).getOffset(foo);
- assertTrue(
- foo.streamTryCatches()
- .allMatch(r -> r.getRange().includes(offsetNew) && r.getRange().includes(offsetFill)));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- int[] array = null;
- try {
- array = new int[6];
- } catch (RuntimeException e) {
- return array[0];
- }
- array[0] = value;
- array[1] = value;
- array[2] = value;
- array[3] = value;
- array[4] = value;
- array[5] = value;
- return array[5];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
deleted file mode 100644
index f9e3ffe..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class NewArrayInCatchRangeTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayInCatchRangeTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkInstructions);
- }
-
- private void checkInstructions(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- List<InstructionSubject> filledArrayInstructions =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
- .collect(Collectors.toList());
- assertEquals(1, filledArrayInstructions.size());
- InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
- assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- int[] array = null;
- try {
- array = new int[1];
- } catch (Exception e) {
- return array == null ? -1 : array.length;
- }
- array[0] = value;
- return array[0];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
deleted file mode 100644
index 3ecd9de..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class NewArrayInTwoCatchRangesTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayInTwoCatchRangesTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkHasFilledNewArray);
- }
-
- private void checkHasFilledNewArray(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- assertTrue(
- foo.streamInstructions()
- .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- try {
- int[] array = new int[2];
- try {
- array[0] = value;
- try {
- array[1] = value;
- } catch (RuntimeException e) {
- return array[1];
- }
- } catch (RuntimeException e) {
- return array[0];
- }
- return array[0];
- } catch (RuntimeException e) {
- return 42;
- }
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
deleted file mode 100644
index c7e3b2a..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class NewArrayMonitorTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayMonitorTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayMonitorTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayMonitorTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkInstructions);
- }
-
- private void checkInstructions(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- List<InstructionSubject> filledArrayInstructions =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
- .collect(Collectors.toList());
- assertEquals(1, filledArrayInstructions.size());
- InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
- assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
- }
-
- static class TestClass {
-
- public static synchronized int foo() {
- int value = 1;
- int[] array = new int[1];
- try {
- array[0] = value;
- } catch (RuntimeException e) {
- return array[0];
- }
- return array[0];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
deleted file mode 100644
index a3fd2da..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.Collections;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-// Regression test for issue found in b/259986613
-@RunWith(Parameterized.class)
-public class NewArrayPutInCatchRangeTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayPutInCatchRangeTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayPutInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayPutInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkHasFilledNewArray);
- }
-
- private void checkHasFilledNewArray(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- assertTrue(
- foo.streamInstructions()
- .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
- assertEquals(Collections.emptyList(), foo.streamTryCatches().collect(Collectors.toList()));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- int[] array = new int[1];
- try {
- array[0] = value;
- } catch (RuntimeException e) {
- return array[0];
- }
- return array[0];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
index c552566..5ed624e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
@@ -72,7 +72,7 @@
int[] array;
synchronized (TestClass.class) {
array = new int[1];
- } // monitor exit here prohibits optimization as its failure could observe the lack of init.
+ }
array[0] = value;
return array[0];
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
index c80436c..7011411 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -63,7 +63,18 @@
"[1, null, 2]",
"[1, null, 2]",
"[1]",
- "[1, 2]",
+ "[1, 2, 3]",
+ "[2, 2, 2]",
+ "[6, 7]",
+ "[7]",
+ "[3, 4]",
+ "[99]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
"[1, 2, 3, 4, 5]",
"[1]",
"[a, 1, null, d, e, f]",
@@ -91,12 +102,16 @@
"[0, 1, 2, 3, 4]",
"[4, 0, 0, 0, 0]",
"[4, 1, 2, 3, 4]",
+ "[9]",
+ "[*]",
+ "[*]",
+ "finally: [1, 2]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1, 2]",
"[0, 1, 2]",
"[0]",
- "[0, 1, 2]",
- "[1, 2, 3]",
- "[1, 2, 3, 4, 5, 6]",
- "[0]",
"[null, null]",
};
@@ -176,6 +191,20 @@
mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray");
+ MethodSubject phiFilledNewArrayBlocks =
+ mainClass.uniqueMethodWithOriginalName("phiFilledNewArrayBlocks");
+ MethodSubject arrayWithDominatingPhiUsers =
+ mainClass.uniqueMethodWithOriginalName("arrayWithDominatingPhiUsers");
+ MethodSubject arrayWithNonDominatingPhiUsers =
+ mainClass.uniqueMethodWithOriginalName("arrayWithNonDominatingPhiUsers");
+ MethodSubject phiWithExceptionalPhiUser =
+ mainClass.uniqueMethodWithOriginalName("phiWithExceptionalPhiUser");
+ MethodSubject phiWithNestedCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("phiWithNestedCatchHandler");
+ MethodSubject multiUseArray = mainClass.uniqueMethodWithOriginalName("multiUseArray");
+ MethodSubject arrayWithHole = mainClass.uniqueMethodWithOriginalName("arrayWithHole");
+ MethodSubject reassignmentDoesNotOptimize =
+ mainClass.uniqueMethodWithOriginalName("reassignmentDoesNotOptimize");
MethodSubject intsThatUseFilledNewArray =
mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray");
MethodSubject twoDimensionalArrays =
@@ -191,26 +220,60 @@
mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage");
MethodSubject arrayWithExtraInitialPuts =
mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts");
- MethodSubject catchHandlerThrowing =
- mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing");
- MethodSubject catchHandlerNonThrowingFilledNewArray =
- mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray");
- MethodSubject catchHandlerNonThrowingFillArrayData =
- mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData");
+ MethodSubject catchHandlerWithoutSideeffects =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerWithoutSideeffects");
+ MethodSubject allocationWithCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("allocationWithCatchHandler");
+ MethodSubject allocationWithoutCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("allocationWithoutCatchHandler");
+ MethodSubject catchHandlerWithFinally =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerWithFinally");
+ MethodSubject simpleSynchronized1 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized1");
+ MethodSubject simpleSynchronized2 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized2");
+ MethodSubject simpleSynchronized3 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized3");
+ MethodSubject simpleSynchronized4 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized4");
+ MethodSubject arrayInsideCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("arrayInsideCatchHandler");
MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues");
+ // The explicit assignments can't be collapsed without breaking the debugger's ability to
+ // visit each line.
+ Class<?> filledNewArrayInRelease =
+ compilationMode == CompilationMode.DEBUG ? DexNewArray.class : DexFilledNewArray.class;
+
assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class);
assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class);
assertFilledArrayData(arraysThatUseFilledData);
- assertFilledArrayData(catchHandlerNonThrowingFillArrayData);
- if (compilationMode == CompilationMode.DEBUG) {
- // The explicit assignments can't be collapsed without breaking the debugger's ability to
- // visit each line.
- assertArrayTypes(reversedArray, DexNewArray.class);
- } else {
- assertArrayTypes(reversedArray, DexFilledNewArray.class);
- }
+ // Algorithm does not support out-of-order assignment.
+ assertArrayTypes(reversedArray, DexNewArray.class);
+ // Algorithm does not support assigning to array elements multiple times.
+ assertArrayTypes(reassignmentDoesNotOptimize, DexNewArray.class);
+ // Algorithm does not support default-initialized array elements.
+ assertArrayTypes(arrayWithHole, DexNewArray.class);
+ // ArrayPuts not dominated by return statement.
+ assertArrayTypes(phiFilledNewArrayBlocks, DexNewArray.class);
+ assertArrayTypes(arrayWithDominatingPhiUsers, filledNewArrayInRelease);
+ assertArrayTypes(arrayWithNonDominatingPhiUsers, DexNewArray.class);
+ assertArrayTypes(phiWithNestedCatchHandler, DexNewArray.class);
+ assertArrayTypes(phiWithExceptionalPhiUser, DexFilledNewArray.class, filledNewArrayInRelease);
+ // Not safe to change catch handlers.
+ assertArrayTypes(allocationWithoutCatchHandler, DexNewArray.class);
+ // Not safe to change catch handlers.
+ assertArrayTypes(allocationWithCatchHandler, DexNewArray.class);
+ assertArrayTypes(catchHandlerWithFinally, DexNewArray.class);
+ assertArrayTypes(simpleSynchronized1, DexFilledNewArray.class);
+ assertArrayTypes(simpleSynchronized2, DexFilledNewArray.class);
+ assertArrayTypes(simpleSynchronized3, DexNewArray.class);
+ assertArrayTypes(simpleSynchronized4, DexNewArray.class);
+ // Could be optimized if we had side-effect analysis of exceptional blocks.
+ assertArrayTypes(catchHandlerWithoutSideeffects, DexNewArray.class);
+ assertArrayTypes(arrayInsideCatchHandler, filledNewArrayInRelease);
+ assertArrayTypes(multiUseArray, filledNewArrayInRelease);
if (!canUseFilledNewArrayOfStringObjects(parameters)) {
assertArrayTypes(stringArrays, DexNewArray.class);
@@ -239,9 +302,7 @@
assertArrayTypes(referenceArraysWithInterfaceImplementations, DexNewArray.class);
}
- // TODO(b/246971330): Add support for arrays whose values have conditionals.
- // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
-
+ assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
assertArrayTypes(
objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class, DexNewArray.class);
@@ -261,9 +322,6 @@
// haven't bothered.
assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class);
assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class);
-
- assertArrayTypes(catchHandlerThrowing, DexNewArray.class);
- assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class);
}
private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) {
@@ -310,6 +368,14 @@
referenceArraysWithInterfaceImplementations();
interfaceArrayWithRawObject();
phiFilledNewArray();
+ phiFilledNewArrayBlocks();
+ arrayWithDominatingPhiUsers();
+ arrayWithNonDominatingPhiUsers();
+ phiWithNestedCatchHandler();
+ phiWithExceptionalPhiUser();
+ multiUseArray();
+ arrayWithHole();
+ reassignmentDoesNotOptimize();
intsThatUseFilledNewArray();
twoDimensionalArrays();
objectArraysFilledNewArrayRange();
@@ -318,9 +384,15 @@
reversedArray();
arrayWithCorrectCountButIncompleteCoverage();
arrayWithExtraInitialPuts();
- catchHandlerThrowing();
- catchHandlerNonThrowingFilledNewArray();
- catchHandlerNonThrowingFillArrayData();
+ arrayInsideCatchHandler();
+ allocationWithCatchHandler();
+ allocationWithoutCatchHandler();
+ catchHandlerWithFinally();
+ simpleSynchronized1();
+ simpleSynchronized2();
+ simpleSynchronized3();
+ simpleSynchronized4();
+ catchHandlerWithoutSideeffects();
arrayIntoAnotherArray();
assumedValues();
}
@@ -411,21 +483,12 @@
}
@NeverInline
- private static void catchHandlerNonThrowingFilledNewArray() {
+ private static void arrayInsideCatchHandler() {
try {
- int[] arr1 = {1, 2, 3};
+ // Test filled-new-array with a throwing instruction before the last array-put.
+ int[] arr = new int[1];
System.currentTimeMillis();
- System.out.println(Arrays.toString(arr1));
- } catch (Throwable t) {
- throw new RuntimeException(t);
- }
- }
-
- @NeverInline
- private static void catchHandlerNonThrowingFillArrayData() {
- try {
- int[] arr = {1, 2, 3, 4, 5, 6};
- System.currentTimeMillis();
+ arr[0] = 9;
System.out.println(Arrays.toString(arr));
} catch (Throwable t) {
throw new RuntimeException(t);
@@ -433,37 +496,115 @@
}
@NeverInline
- private static void catchHandlerThrowing() {
- int[] arr1 = new int[3];
- arr1[0] = 0;
- arr1[1] = 1;
- // Since the array is used in only one spot, and that spot is not within the try/catch, it
- // should be safe to use filled-new-array, but we don't.
+ private static void allocationWithCatchHandler() {
+ Object[] arr;
try {
- System.currentTimeMillis();
+ arr = new Object[1];
+ } catch (NoClassDefFoundError | OutOfMemoryError t) {
+ throw new RuntimeException(t);
+ }
+
+ // new-array-empty dominates this, but catch handlers are relevant
+ arr[0] = "*";
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void allocationWithoutCatchHandler() {
+ Object[] arr = new Object[1];
+ try {
+ // new-array-empty dominates this, but catch handlers are relevant.
+ arr[0] = "*";
+ } catch (NoClassDefFoundError | OutOfMemoryError t) {
+ throw new RuntimeException(t);
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void catchHandlerWithFinally() {
+ Object[] arr = new Object[2];
+ try {
+ System.out.print("finally: ");
+ } finally {
+ // This will be duplicated into the throwing and non-throwing blocks.
+ arr[0] = "1";
+ }
+ arr[1] = "2";
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void simpleSynchronized1() {
+ // Should optimize since array is contained within a try block.
+ synchronized (Main.class) {
+ int[] arr = new int[] {1, 2};
+ System.out.println(Arrays.toString(arr));
+ }
+ }
+
+ @NeverInline
+ private static synchronized void simpleSynchronized2() {
+ // Should optimize since array is contained within a try block.
+ try {
+ try {
+ int[] arr = new int[] {1, 2};
+ System.out.println(Arrays.toString(arr));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ } finally {
+ System.currentTimeMillis();
+ }
+ } catch (Exception e) {
+ // Ignore.
+ }
+ }
+
+ @NeverInline
+ private static void simpleSynchronized3() {
+ // Does not optimize because allocation has different catch handlers.
+ int[] arr = new int[2];
+ synchronized (Main.class) {
+ arr[0] = 1;
+ arr[1] = 2;
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void simpleSynchronized4() {
+ // Does not optimize because allocation has different catch handlers.
+ int[] arr;
+ synchronized (Main.class) {
+ arr = new int[2];
+ }
+ arr[0] = 1;
+ arr[1] = 2;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void catchHandlerWithoutSideeffects() {
+ // If we added logic to show that catch handlers exit without side-effects, we could optimize
+ // this case.z
+ int[] arr1;
+ try {
+ arr1 = new int[3];
+ } catch (Throwable t) {
+ throw new RuntimeException("1");
+ }
+ try {
+ arr1[0] = 0;
+ } catch (Throwable t) {
+ throw new RuntimeException("2");
+ }
+ arr1[1] = 1;
+ try {
arr1[2] = 2;
} catch (Throwable t) {
- throw new RuntimeException(t);
+ throw new RuntimeException("3");
}
System.out.println(Arrays.toString(arr1));
-
- try {
- // Test filled-new-array with a throwing instruction before the last array-put.
- int[] arr2 = new int[1];
- System.currentTimeMillis();
- arr2[0] = 0;
- System.out.println(Arrays.toString(arr2));
-
- // Test filled-array-data with a throwing instruction before the last array-put.
- short[] arr3 = new short[3];
- arr3[0] = 0;
- arr3[1] = 1;
- System.currentTimeMillis();
- arr3[2] = 2;
- System.out.println(Arrays.toString(arr3));
- } catch (Throwable t) {
- throw new RuntimeException(t);
- }
}
@NeverInline
@@ -485,11 +626,123 @@
@NeverInline
private static void phiFilledNewArray() {
// The presence of ? should not affect use of filled-new-array.
- Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3};
+ Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3, 3};
System.out.println(Arrays.toString(phiArray));
}
@NeverInline
+ private static void phiFilledNewArrayBlocks() {
+ int[] phiArray = new int[3];
+ if (System.currentTimeMillis() > 0) {
+ phiArray[0] = 2;
+ phiArray[1] = 2;
+ phiArray[2] = 2;
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void arrayWithDominatingPhiUsers() {
+ int[] phiArray = null;
+ try {
+ phiArray = new int[2];
+ phiArray[0] = 6;
+ phiArray[1] = 7;
+ } catch (Throwable t) {
+ System.out.println("Not reached");
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void arrayWithNonDominatingPhiUsers() {
+ int[] phiArray = null;
+ try {
+ phiArray = new int[1];
+ // If currentTimeMillis() throws, phiArray will have value of [0].
+ phiArray[0] = System.currentTimeMillis() > 0 ? 7 : 0;
+ } catch (Throwable t) {
+ System.out.println("Not reached");
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void phiWithNestedCatchHandler() {
+ int[] phiArray = null;
+ try {
+ phiArray = new int[2];
+ // If currentTimeMillis() throws, phiArray will have value of [0, 0].
+ try {
+ System.currentTimeMillis();
+ } catch (RuntimeException r) {
+ throw new RuntimeException(r);
+ }
+ phiArray[0] = 3;
+ phiArray[1] = 4;
+ } catch (Throwable t) {
+ System.out.println("Not reached");
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void phiWithExceptionalPhiUser() {
+ int[] arr = null;
+ try {
+ // Both of these should optimize, but care must be taken to ensure the phiUsers are properly
+ // dominated post-optimization.
+ if (System.currentTimeMillis() > 0) {
+ arr = new int[1];
+ arr[0] = 99;
+ } else {
+ arr = new int[] {1, 2};
+ }
+ } catch (RuntimeException e) {
+ // fall through
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void multiUseArray() {
+ int[] arr = new int[2];
+ arr[0] = 0;
+ arr[1] = System.nanoTime() > 0 ? 1 : 2;
+ System.out.println(Arrays.toString(arr));
+ System.out.println(Arrays.toString(arr));
+ // Usage in a different basic block.
+ if (System.nanoTime() > 0) {
+ System.nanoTime();
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void arrayWithHole() {
+ int[] arr = new int[2];
+ arr[1] = 1;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void reassignmentDoesNotOptimize() {
+ // Reassignment in same block, and of last index.
+ Integer[] arr = new Integer[2];
+ arr[0] = 0;
+ arr[1] = 2;
+ arr[1] = 1;
+ System.out.println(Arrays.toString(arr));
+
+ // Reassignment across blocks, of non-last index.
+ arr = new Integer[2];
+ arr[0] = 3;
+ arr[1] = System.nanoTime() > 0 ? 1 : 2;
+ arr[0] = 0;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
private static void intsThatUseFilledNewArray() {
// Up to 5 ints uses filled-new-array rather than filled-array-data.
int[] intArr = {1, 2, 3, 4, 5};
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
index be54d34..68b80fc 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
@@ -46,9 +46,9 @@
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "50");
- private void inspect(MethodSubject method, int staticGets, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int staticGets, int puts, boolean isD8) {
+ // D8 cannot optimize due to risk of NoClassDefFoundError.
+ boolean expectingFilledNewArray = !isD8 && canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -66,18 +66,9 @@
}
private void inspectD8(CodeInspector inspector) {
- inspect(
- inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- canUseFilledNewArrayOfNonStringObjects(parameters) ? 100 : 1,
- 100,
- false);
- inspect(
- inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- canUseFilledNewArrayOfNonStringObjects(parameters)
- ? 50
- : (maxMaterializingConstants == 2 ? 42 : 10),
- 50,
- false);
+ // D8 cannot optimize due to risk of NoClassDefFoundError.
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 100, 100, true);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 50, 50, true);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
index fce1e79..f64d0e8 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
@@ -52,9 +52,9 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(boolean isR8, MethodSubject method, int puts) {
+ // D8 cannot optimize due to risk of NoClassDefFoundError.
+ boolean expectingFilledNewArray = isR8 && canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -84,10 +84,10 @@
}
}
- private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, true);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100, false);
+ private void inspect(CodeInspector inspector, boolean isR8) {
+ inspect(isR8, inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5);
+ inspect(isR8, inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5);
+ inspect(isR8, inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100);
}
@Test
@@ -97,7 +97,7 @@
.addInnerClasses(getClass())
.setMinApi(parameters)
.run(parameters.getRuntime(), TestClass.class)
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, false))
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@@ -110,7 +110,7 @@
.enableInliningAnnotations()
.addDontObfuscate()
.run(parameters.getRuntime(), TestClass.class)
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, true))
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
index c433e6d..cf12961 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
@@ -46,10 +46,8 @@
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "104");
- private void inspect(
- MethodSubject method, int constStrings, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int constStrings, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -68,12 +66,11 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
maxMaterializingConstants == 2 ? 32 : 26,
- 104,
- false);
+ 104);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
index 837bea2..53f7f1f 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
@@ -52,9 +52,8 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -86,9 +85,9 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, true);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java b/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
index 3f7aba6..53794e3 100644
--- a/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
+++ b/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
@@ -6,14 +6,13 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -44,7 +43,7 @@
.release()
.setMinApi(parameters)
.compile()
- .inspect(inspector -> inspect(inspector, true))
+ .inspect(this::inspect)
.apply(
compileResult ->
compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors())
@@ -66,18 +65,17 @@
parameters.isDexRuntime(),
compileResult ->
compileResult
- .inspect(inspector -> inspect(inspector, false))
+ .inspect(this::inspect)
.runDex2Oat(parameters.getRuntime())
.assertNoVerificationErrors())
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
}
- private void inspect(CodeInspector inspector, boolean isD8) {
+ private void inspect(CodeInspector inspector) {
MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
assertThat(mainMethodSubject, isPresent());
- assertEquals(
- isD8 && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+ assertFalse(
mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isFilledNewArray));
}