Merge commit '8aba099c7ddc36e0284ed4d9d701691923dfc5ab' into dev-release Change-Id: I431fd3cc4946a764fedf2a1dbf2606491382611b
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json index 5265c49..221f44c 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -1,5 +1,5 @@ { - "identifier": "com.tools.android:desugar_jdk_libs_configuration:2.1.2", + "identifier": "com.tools.android:desugar_jdk_libs_configuration:2.1.3", "configuration_format_version": 101, "required_compilation_api_level": 30, "synthesized_library_classes_package_prefix": "j$.",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json index a3cd551..f4febc3 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
@@ -1,5 +1,5 @@ { - "identifier": "com.tools.android:desugar_jdk_libs_configuration_minimal:2.1.2", + "identifier": "com.tools.android:desugar_jdk_libs_configuration_minimal:2.1.3", "configuration_format_version": 101, "required_compilation_api_level": 24, "synthesized_library_classes_package_prefix": "j$.",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json index 05c530d..2c8bcb4 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -1,5 +1,5 @@ { - "identifier": "com.tools.android:desugar_jdk_libs_configuration_nio:2.1.2", + "identifier": "com.tools.android:desugar_jdk_libs_configuration_nio:2.1.3", "configuration_format_version": 101, "required_compilation_api_level": 30, "synthesized_library_classes_package_prefix": "j$.",
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java index ad077ea..a23ee09 100644 --- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -319,7 +319,7 @@ private void updateHints(LiveIntervals intervals) { for (Phi phi : intervals.getValue().uniquePhiUsers()) { - if (!phi.isValueOnStack() && phi.getLiveIntervals().getHint() == null) { + if (!phi.isValueOnStack() && !phi.getLiveIntervals().hasHint()) { phi.getLiveIntervals().setHint(intervals, unhandled); for (Value value : phi.getOperands()) { value.getLiveIntervals().setHint(intervals, unhandled); @@ -329,7 +329,7 @@ } private boolean tryHint(LiveIntervals unhandled) { - if (unhandled.getHint() == null) { + if (!unhandled.hasHint()) { return false; } boolean isWide = unhandled.getType().isWide(); @@ -521,10 +521,11 @@ private void applyInstructionsBackwardsToRegisterLiveness( BasicBlock block, IntSet liveRegisters, int suffixSize) { - InstructionIterator iterator = block.iterator(block.getInstructions().size()); - int instructionsLeft = suffixSize; - while (--instructionsLeft >= 0 && iterator.hasPrevious()) { - Instruction current = iterator.previous(); + int i = 0; + for (var current = block.getLastInstruction(); current != null; current = current.getPrev()) { + if (++i > suffixSize) { + break; + } Value outValue = current.outValue(); if (outValue != null && outValue.needsRegister()) { int register = getRegisterForValue(outValue);
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java index 9139e16..a92f931 100644 --- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java +++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -213,7 +213,7 @@ storeBlock = it.split(this.code, this.blockIterator); it = storeBlock.listIterator(code); } - add(store, storeBlock, instruction.getPosition(), it); + add(store, instruction.getPosition(), it); if (hasCatchHandlers && !instruction.instructionTypeCanThrow()) { splitAfterStoredOutValue(it); } @@ -242,7 +242,7 @@ it = insertBlock.listIterator(code); } instruction.swapOutValue(newOutValue); - add(new Pop(newOutValue), insertBlock, instruction.getPosition(), it); + add(new Pop(newOutValue), instruction.getPosition(), it); } private static class PhiMove { @@ -261,7 +261,7 @@ List<StackValue> temps = new ArrayList<>(moves.size()); for (PhiMove move : moves) { StackValue tmp = createStackValue(move.phi, topOfStack++); - add(load(tmp, move.operand), move.phi.getBlock(), position, it); + add(load(tmp, move.operand), position, it); temps.add(tmp); move.operand.removePhiUser(move.phi); } @@ -269,7 +269,7 @@ PhiMove move = moves.get(i); StackValue tmp = temps.get(i); FixedLocalValue out = new FixedLocalValue(move.phi); - add(new Store(out, tmp), move.phi.getBlock(), position, it); + add(new Store(out, tmp), position, it); move.phi.replaceUsers(out); } } @@ -296,12 +296,11 @@ private static void add( Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) { - add(newInstruction, existingInstruction.getBlock(), existingInstruction.getPosition(), it); + add(newInstruction, existingInstruction.getPosition(), it); } private static void add( - Instruction newInstruction, BasicBlock block, Position position, InstructionListIterator it) { - newInstruction.setBlock(block); + Instruction newInstruction, Position position, InstructionListIterator it) { newInstruction.setPosition(position); it.add(newInstruction); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java index 62a8f65..0dedc1d 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.cf.code.frame.FrameType; import com.android.tools.r8.cf.code.frame.PreciseFrameType; import com.android.tools.r8.cf.code.frame.UninitializedFrameType; +import com.android.tools.r8.cf.code.frame.UninitializedNew; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCompareHelper; import com.android.tools.r8.graph.DexClassAndMethod; @@ -281,7 +282,10 @@ registry.registerTypeReference( frameType.asInitializedNonNullReferenceTypeWithoutInterfaces().getInitializedType()); } else if (frameType.isUninitializedNew()) { - registry.registerTypeReference(frameType.asUninitializedNew().getUninitializedNewType()); + UninitializedNew uninitializedNew = frameType.asUninitializedNew(); + if (uninitializedNew.getUninitializedNewType() != null) { + registry.registerTypeReference(uninitializedNew.getUninitializedNewType()); + } } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 3ac2bd6..586b968 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -625,6 +625,8 @@ public final DexType javaUtilLoggingLoggerType = createStaticallyKnownType("Ljava/util/logging/Logger;"); public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;"); + public final DexType javaUtilEnumMapType = createStaticallyKnownType("Ljava/util/EnumMap;"); + public final DexType javaUtilEnumSetType = createStaticallyKnownType("Ljava/util/EnumSet;"); public final DexType androidAppActivity = createStaticallyKnownType("Landroid/app/Activity;"); public final DexType androidAppFragment = createStaticallyKnownType("Landroid/app/Fragment;"); @@ -742,6 +744,8 @@ public final JavaUtilLocaleMembers javaUtilLocaleMembers = new JavaUtilLocaleMembers(); public final JavaUtilLoggingLevelMembers javaUtilLoggingLevelMembers = new JavaUtilLoggingLevelMembers(); + public final JavaUtilEnumMapMembers javaUtilEnumMapMembers = new JavaUtilEnumMapMembers(); + public final JavaUtilEnumSetMembers javaUtilEnumSetMembers = new JavaUtilEnumSetMembers(); public final List<LibraryMembers> libraryMembersCollection = ImmutableList.of( @@ -1547,6 +1551,29 @@ } } + public class JavaUtilEnumMapMembers { + public final DexMethod constructor = + createMethod(javaUtilEnumMapType, createProto(voidType, classType), constructorMethodName); + } + + public class JavaUtilEnumSetMembers { + private final DexString allOfString = createString("allOf"); + private final DexString noneOfString = createString("noneOf"); + private final DexString ofString = createString("of"); + private final DexString rangeString = createString("range"); + + public boolean isFactoryMethod(DexMethod invokedMethod) { + if (!invokedMethod.getHolderType().equals(javaUtilEnumSetType)) { + return false; + } + DexString name = invokedMethod.getName(); + return name.isIdenticalTo(allOfString) + || name.isIdenticalTo(noneOfString) + || name.isIdenticalTo(ofString) + || name.isIdenticalTo(rangeString); + } + } + public class LongMembers extends BoxedPrimitiveMembers { public final DexField TYPE = createField(boxedLongType, classType, "TYPE"); @@ -2153,7 +2180,6 @@ && accessFlags.isFinal(); } } - public class NullPointerExceptionMethods { public final DexMethod init =
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java index 5e52e33..660504d 100644 --- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java +++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -65,7 +65,11 @@ && definition.getStaticValue().isDexValueResourceNumber()) { appView .getResourceShrinkerState() - .trace(definition.getStaticValue().asDexValueResourceNumber().getValue()); + .trace( + definition.getStaticValue().asDexValueResourceNumber().getValue(), + // TODO(b/378625969): Consider wrapping this in a reachability structure + // to avoid decoding. + field.toString()); } } }); @@ -110,7 +114,7 @@ // these. if (integers != null) { for (Integer integer : integers) { - resourceShrinkerState.trace(integer); + resourceShrinkerState.trace(integer, field.toString()); } } }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java index adf8f89..ccffd96 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -149,7 +149,7 @@ NumberGenerator blockNumberGenerator = new NumberGenerator(); NumberGenerator valueNumberGenerator = new NumberGenerator(); - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(metadata); block.setNumber(blockNumberGenerator.next()); // Add "invoke-static <clinit>" for each of the class initializers to the exit block.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java index fc21b02..99f161e 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
@@ -131,9 +131,8 @@ if (user.getBlock() == block) { // When the value of interest has the definition if (!root.isPhi()) { - List<Instruction> instructions = block.getInstructions(); // Make sure we're not considering instructions prior to the value of interest. - if (instructions.indexOf(user) < instructions.indexOf(root.definition)) { + if (user.comesBefore(root.definition)) { continue; } }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java index c60e298..ac5a053 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -287,7 +287,7 @@ // TODO(b/279877113): Extend this analysis to analyze the full remainder of this method. if (instancePut.getBlock().getSuccessors().isEmpty()) { InstructionListIterator instructionIterator = - instancePut.getBlock().listIterator(code, instancePut); + instancePut.getBlock().listIterator(code, instancePut.getNext()); while (instructionIterator.hasNext()) { Instruction instruction = instructionIterator.next(); if (instruction.readSet(appView, context).contains(field)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java index fa31549..70eafed 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
@@ -20,7 +20,6 @@ import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InvokeVirtual; import com.android.tools.r8.ir.code.JumpInstruction; import com.android.tools.r8.ir.code.StringSwitch; @@ -78,21 +77,18 @@ // Run a bounded depth-first traversal to collect the path constraints that lead to early // returns. - InstructionIterator instructionIterator = - code.entryBlock().iterator(code.getNumberOfArguments()); - return analyzeInstructionsInBlock(code.entryBlock(), 0, 0, instructionIterator); + BasicBlock block = code.entryBlock(); + Instruction firstNonArgument = block.getInstructions().getNth(code.getNumberOfArguments()); + return analyzeInstructionsInBlock(block, 0, 0, firstNonArgument); } private SimpleInliningConstraintWithDepth analyzeInstructionsInBlock( BasicBlock block, int branchDepth, int instructionDepth) { - return analyzeInstructionsInBlock(block, branchDepth, instructionDepth, block.iterator()); + return analyzeInstructionsInBlock(block, branchDepth, instructionDepth, block.entry()); } private SimpleInliningConstraintWithDepth analyzeInstructionsInBlock( - BasicBlock block, - int branchDepth, - int instructionDepth, - InstructionIterator instructionIterator) { + BasicBlock block, int branchDepth, int instructionDepth, Instruction instruction) { if (!seen.add(block) || block.hasCatchHandlers() || block.exit().isThrow() @@ -102,7 +98,6 @@ // Move the instruction iterator forward to the block's jump instruction, while incrementing the // instruction depth of the depth-first traversal. - Instruction instruction = instructionIterator.next(); SimpleInliningConstraint blockConstraint = AlwaysSimpleInliningConstraint.getInstance(); while (!instruction.isJumpInstruction()) { assert !instruction.isArgument(); @@ -116,7 +111,7 @@ } else { blockConstraint = blockConstraint.meet(instructionConstraint); } - instruction = instructionIterator.next(); + instruction = instruction.getNext(); } // If we have exceeded the threshold, then all paths from this instruction will not lead to any
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java index 3dce430..84bf5e1 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -154,7 +154,7 @@ // Already removed. continue; } - IRCodeUtils.removeInstructionAndTransitiveInputsIfNotUsed(code, instruction); + IRCodeUtils.removeInstructionAndTransitiveInputsIfNotUsed(instruction); } code.removeRedundantBlocks(); assert code.isConsistentSSA(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java index 1b3837a..4919305 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -341,7 +341,8 @@ ProtoMessageInfo protoMessageInfo) { // Position iterator immediately before the call to newMessageInfo(). BasicBlock block = newMessageInfoInvoke.getBlock(); - InstructionListIterator instructionIterator = block.listIterator(code, newMessageInfoInvoke); + InstructionListIterator instructionIterator = + block.listIterator(code, newMessageInfoInvoke.getNext()); Instruction previous = instructionIterator.previous(); instructionIterator.setInsertionPosition(newMessageInfoInvoke.getPosition()); assert previous == newMessageInfoInvoke;
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 59fde05..5e5c563 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
@@ -47,7 +47,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -173,9 +172,8 @@ // Catch handler information about which successors are catch handlers and what their guards are. private CatchHandlers<Integer> catchHandlers = CatchHandlers.EMPTY_INDICES; - // TODO(b/270398965): Replace LinkedList. - @SuppressWarnings("JdkObsolete") - private LinkedList<Instruction> instructions = new LinkedList<>(); + // Linked list of instructions belonging to this block. + private final InstructionList instructions = new InstructionList(this); private int number = -1; private List<Phi> phis = new ArrayList<>(); @@ -202,6 +200,23 @@ // Map of registers to current SSA value. Used during SSA numbering and cleared once filled. private Map<Integer, Value> currentDefinitions = new HashMap<>(); + // Metadata shared by all blocks of an IRCode. + // TODO(b/376663044): Remove |metadata| parameter from methods in this class. + private IRMetadata metadata; + + public BasicBlock(IRMetadata metadata) { + this.metadata = metadata; + } + + public IRMetadata getMetadata() { + return metadata; + } + + // Required when moving a block between code objects (inlining). + void setMetadata(IRMetadata metadata) { + this.metadata = metadata; + } + public <BT, CT> TraversalContinuation<BT, CT> traverseNormalPredecessors( BiFunction<? super BasicBlock, ? super CT, TraversalContinuation<BT, CT>> fn, CT initialValue) { @@ -524,7 +539,8 @@ } else if (exit().isIf()) { if (indexOfNewBlock >= successors.size() - 2 && indexOfOldBlock >= successors.size() - 2) { // New and old are true target and fallthrough, replace last instruction with a goto. - Instruction instruction = getInstructions().removeLast(); + Instruction instruction = instructions.getLast(); + instructions.removeIgnoreValues(instruction); // Iterate in reverse order to ensure that POP instructions are inserted in correct order. for (int i = instruction.inValues().size() - 1; i >= 0; i--) { Value value = instruction.inValues().get(i); @@ -536,7 +552,6 @@ } else { assert !(value instanceof StackValues); Pop pop = new Pop(value); - pop.setBlock(this); pop.setPosition(instruction.getPosition()); getInstructions().addLast(pop); } @@ -546,7 +561,6 @@ } } Instruction exit = new Goto(); - exit.setBlock(this); exit.setPosition(instruction.getPosition()); getInstructions().addLast(exit); } else if (indexOfOldBlock >= successors.size() - 2) { @@ -738,7 +752,7 @@ return nextInstructionNumber; } - public LinkedList<Instruction> getInstructions() { + public InstructionList getInstructions() { return instructions; } @@ -797,22 +811,24 @@ } public Instruction entry() { - return instructions.get(0); + return instructions.getFirst(); } public JumpInstruction exit() { assert filled; - assert instructions.get(instructions.size() - 1).isJumpInstruction(); - return instructions.get(instructions.size() - 1).asJumpInstruction(); + assert instructions.getLast().isJumpInstruction(); + return instructions.getLast().asJumpInstruction(); + } + + public Instruction getLastInstruction() { + return instructions.getLast(); } public Instruction exceptionalExit() { assert hasCatchHandlers(); - InstructionIterator it = iterator(instructions.size()); - while (it.hasPrevious()) { - Instruction instruction = it.previous(); - if (instruction.instructionTypeCanThrow()) { - return instruction; + for (Instruction ins = getLastInstruction(); ins != null; ins = ins.getPrev()) { + if (ins.instructionTypeCanThrow()) { + return ins; } } return null; @@ -892,15 +908,9 @@ phis.removeAll(phisToRemove); } - public void add(Instruction next, IRCode code) { - add(next, code.metadata()); - } - - public void add(Instruction next, IRMetadata metadata) { + public void add(Instruction next, IRMetadata unused_metadata) { assert !isFilled(); - instructions.add(next); - metadata.record(next); - next.setBlock(this); + instructions.addLast(next); } public void close(IRBuilder builder) { @@ -1493,49 +1503,11 @@ return builder.toString(); } - public void addPhiMove(Move move) { - // TODO(ager): Consider this more, is it always the case that we should add it before the - // exit instruction? - Instruction branch = exit(); - instructions.set(instructions.size() - 1, move); - instructions.add(branch); - } - - public void setInstructions(LinkedList<Instruction> instructions) { - this.instructions = instructions; - } - - /** - * Remove a number of instructions. The instructions to remove are given as indexes in the - * instruction stream. - */ - // TODO(b/270398965): Replace LinkedList. - @SuppressWarnings("JdkObsolete") - public void removeInstructions(List<Integer> toRemove) { - if (!toRemove.isEmpty()) { - LinkedList<Instruction> newInstructions = new LinkedList<>(); - int nextIndex = 0; - for (Integer index : toRemove) { - assert index >= nextIndex; // Indexes in toRemove must be sorted ascending. - newInstructions.addAll(instructions.subList(nextIndex, index)); - instructions.get(index).clearBlock(); - nextIndex = index + 1; - } - if (nextIndex < instructions.size()) { - newInstructions.addAll(instructions.subList(nextIndex, instructions.size())); - } - assert instructions.size() == newInstructions.size() + toRemove.size(); - setInstructions(newInstructions); - } - } - /** * Remove an instruction. */ public void removeInstruction(Instruction toRemove) { - int index = instructions.indexOf(toRemove); - assert index >= 0; - removeInstructions(Collections.singletonList(index)); + instructions.removeIgnoreValues(toRemove); } /** @@ -1563,7 +1535,7 @@ */ public static BasicBlock createGotoBlock( int blockNumber, Position position, IRMetadata metadata) { - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(metadata); block.add(new Goto(), metadata); block.close(null); block.setNumber(blockNumber); @@ -1580,7 +1552,7 @@ * @param theIf the if instruction */ public static BasicBlock createIfBlock(int blockNumber, If theIf, IRMetadata metadata) { - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(metadata); block.add(theIf, metadata); block.close(null); block.setNumber(blockNumber); @@ -1598,7 +1570,7 @@ */ public static BasicBlock createIfBlock( int blockNumber, If theIf, IRMetadata metadata, Instruction... instructions) { - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(metadata); for (Instruction instruction : instructions) { block.add(instruction, metadata); } @@ -1610,7 +1582,7 @@ public static BasicBlock createSwitchBlock( int blockNumber, IntSwitch theSwitch, IRMetadata metadata) { - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(metadata); block.add(theSwitch, metadata); block.close(null); block.setNumber(blockNumber); @@ -1621,14 +1593,14 @@ IRCode code, Position position, DexType guard, AppView<?> appView) { TypeElement guardTypeLattice = TypeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView); - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(code.metadata()); MoveException moveException = new MoveException(code.createValue(guardTypeLattice), guard, appView.options()); moveException.setPosition(position); Throw throwInstruction = new Throw(moveException.outValue); throwInstruction.setPosition(position); - block.add(moveException, code); - block.add(throwInstruction, code); + block.instructions.addLast(moveException); + block.instructions.addLast(throwInstruction); block.close(null); block.setNumber(code.getNextBlockNumber()); return block; @@ -1779,9 +1751,9 @@ // visible to exceptional successors. private boolean verifyNoValuesAfterThrowingInstruction() { if (hasCatchHandlers()) { - InstructionIterator iterator = iterator(instructions.size()); - while (iterator.hasPrevious()) { - Instruction instruction = iterator.previous(); + for (Instruction instruction = getLastInstruction(); + instruction != null; + instruction = instruction.getPrev()) { if (instruction.instructionTypeCanThrow()) { return true; } @@ -1791,58 +1763,53 @@ return true; } - public BasicBlockInstructionIterator iterator() { - return new BasicBlockInstructionIterator(this); + public InstructionIterator iterator() { + return instructions.iterator(); } - public BasicBlockInstructionIterator iterator(int index) { - return new BasicBlockInstructionIterator(this, index); + public InstructionIterator iterator(Instruction instruction) { + return instructions.iterator(instruction); } - public BasicBlockInstructionIterator iterator(Instruction instruction) { - return new BasicBlockInstructionIterator(this, instruction); + public BasicBlockInstructionListIterator listIterator(IRCode unused_code) { + return listIterator(); } - public BasicBlockInstructionListIterator listIterator(IRCode code) { - return listIterator(code.metadata()); + public BasicBlockInstructionListIterator listIterator() { + return new BasicBlockInstructionListIterator(this); } - public BasicBlockInstructionListIterator listIterator(IRMetadata metadata) { - return new BasicBlockInstructionListIterator(metadata, this); + public BasicBlockInstructionListIterator listIterator(IRCode unused_code, int index) { + // TODO(b/376663044): Convert uses of index to use Instruction instead. + return new BasicBlockInstructionListIterator(this, index); } - public BasicBlockInstructionListIterator listIterator(IRCode code, int index) { - return new BasicBlockInstructionListIterator(code.metadata(), this, index); - } - - /** - * Creates an instruction list iterator starting at <code>instruction</code>. - * - * <p>The cursor will be positioned after <code>instruction</code>. Calling <code>next</code> on - * the returned iterator will return the instruction after <code>instruction</code>. Calling - * <code>previous</code> will return <code>instruction</code>. - */ - public BasicBlockInstructionListIterator listIterator(IRCode code, Instruction instruction) { - return new BasicBlockInstructionListIterator(code.metadata(), this, instruction); + /** Creates an instruction list iterator starting at <code>firstInstructionToReturn</code>. */ + public BasicBlockInstructionListIterator listIterator( + IRCode unused_code, Instruction firstInstructionToReturn) { + return new BasicBlockInstructionListIterator(this, firstInstructionToReturn); } /** * Creates a new empty block as a successor for this block. * - * The new block will have all the normal successors of the original block. + * <p>The new block will have all the normal successors of the original block. * - * The catch successors are either on the original block or the new block depending on the + * <p>The catch successors are either on the original block or the new block depending on the * value of <code>keepCatchHandlers</code>. * - * The current block still has all the instructions, and the new block is empty instruction-wise. + * <p>The current block still has all the instructions, and the new block is empty + * instruction-wise. * * @param blockNumber block number for new block * @param keepCatchHandlers keep catch successors on the original block + * @param firstInstructionOfNewBlock this and all following are moved to the new block * @return the new block */ - BasicBlock createSplitBlock(int blockNumber, boolean keepCatchHandlers) { + BasicBlock createSplitBlock( + int blockNumber, boolean keepCatchHandlers, Instruction firstInstructionOfNewBlock) { boolean hadCatchHandlers = hasCatchHandlers(); - BasicBlock newBlock = new BasicBlock(); + BasicBlock newBlock = new BasicBlock(metadata); newBlock.setNumber(blockNumber); // Copy all successors including catch handlers to the new block, and update predecessors. @@ -1862,6 +1829,10 @@ // Link the two blocks link(newBlock); + if (firstInstructionOfNewBlock != null) { + newBlock.instructions.severFrom(firstInstructionOfNewBlock); + } + // Mark the new block filled and sealed. newBlock.filled = true; newBlock.sealed = true; @@ -1936,7 +1907,7 @@ exceptionTypeLattice = move.getOutType(); exceptionType = move.getExceptionType(); assert move.getDebugValues().isEmpty(); - getInstructions().remove(0); + instructions.removeIgnoreValues(move); } // Create new predecessor blocks. List<BasicBlock> newPredecessors = new ArrayList<>(predecessors.size()); @@ -1946,19 +1917,19 @@ throw new CompilationError( "Invalid block structure: catch block reachable via non-exceptional flow."); } - BasicBlock newBlock = new BasicBlock(); + BasicBlock newBlock = new BasicBlock(metadata); newBlock.setNumber(code.getNextBlockNumber()); newPredecessors.add(newBlock); if (hasMoveException) { Value value = code.createValue(exceptionTypeLattice, move.getLocalInfo()); values.add(value); MoveException newMove = new MoveException(value, exceptionType, options); - newBlock.add(newMove, code); + newBlock.instructions.addLast(newMove); newMove.setPosition(position); } Goto next = new Goto(); next.setPosition(position); - newBlock.add(next, code); + newBlock.instructions.addLast(next); newBlock.close(null); newBlock.getMutableSuccessors().add(this); newBlock.getMutablePredecessors().add(predecessor);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java deleted file mode 100644 index f0771c3..0000000 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java +++ /dev/null
@@ -1,45 +0,0 @@ -// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.ir.code; - -import java.util.ListIterator; - -public class BasicBlockInstructionIterator implements InstructionIterator { - - private final ListIterator<Instruction> instructionIterator; - - BasicBlockInstructionIterator(BasicBlock block) { - this.instructionIterator = block.getInstructions().listIterator(); - } - - BasicBlockInstructionIterator(BasicBlock block, int index) { - this.instructionIterator = block.getInstructions().listIterator(index); - } - - BasicBlockInstructionIterator(BasicBlock block, Instruction instruction) { - this(block); - nextUntil(x -> x == instruction); - } - - @Override - public boolean hasPrevious() { - return instructionIterator.hasPrevious(); - } - - @Override - public Instruction previous() { - return instructionIterator.previous(); - } - - @Override - public boolean hasNext() { - return instructionIterator.hasNext(); - } - - @Override - public Instruction next() { - return instructionIterator.next(); - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java index d1fc0ed..af87309 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -33,35 +33,33 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.NoSuchElementException; import java.util.Set; import java.util.function.Consumer; import java.util.function.UnaryOperator; public class BasicBlockInstructionListIterator implements InstructionListIterator { - protected final BasicBlock block; - protected final ListIterator<Instruction> listIterator; + private Instruction next; protected Instruction current; - private Position position = null; + protected final BasicBlock block; + private final InstructionList instructionList; + private Position position; - private final IRMetadata metadata; - - BasicBlockInstructionListIterator(IRMetadata metadata, BasicBlock block) { - this.block = block; - this.listIterator = block.getInstructions().listIterator(); - this.metadata = metadata; + BasicBlockInstructionListIterator(BasicBlock block) { + this(block, 0); } - BasicBlockInstructionListIterator(IRMetadata metadata, BasicBlock block, int index) { - this.block = block; - this.listIterator = block.getInstructions().listIterator(index); - this.metadata = metadata; + BasicBlockInstructionListIterator(BasicBlock block, int index) { + // TODO(b/376663044): Convert uses of index to use Instruction instead. + this(block, index == block.size() ? null : block.getInstructions().getNth(index)); } - BasicBlockInstructionListIterator( - IRMetadata metadata, BasicBlock block, Instruction instruction) { - this(metadata, block); - nextUntil(x -> x == instruction); + BasicBlockInstructionListIterator(BasicBlock block, Instruction firstInstructionToReturn) { + this.block = block; + this.instructionList = block.getInstructions(); + this.current = firstInstructionToReturn == null ? null : firstInstructionToReturn.getPrev(); + this.next = firstInstructionToReturn; } public BasicBlock getBlock() { @@ -70,54 +68,59 @@ @Override public boolean hasNext() { - return listIterator.hasNext(); + return next != null; } @Override public Instruction next() { - current = listIterator.next(); - return current; + Instruction ret = next; + if (ret == null) { + throw new NoSuchElementException(); + } + assert ret.block == block : "Iterator invalidated: " + ret; + current = ret; + next = ret.next; + return ret; } @Override public int nextIndex() { - return listIterator.nextIndex(); + throw new UnsupportedOperationException(); } @Override public Instruction peekNext() { - // Reset current since listIterator.remove() changes based on whether next() or previous() was - // last called. - // E.g.: next() -> current=C - // peekNext(): next() -> current=D, previous() -> current=D - current = null; - return IteratorUtils.peekNext(listIterator); + assert next == null || next.block == block : "Iterator invalidated: " + next; + return next; } @Override public boolean hasPrevious() { - return listIterator.hasPrevious(); + return next == null ? !instructionList.isEmpty() : next.prev != null; } @Override public Instruction previous() { - current = listIterator.previous(); - return current; + Instruction ret = next == null ? instructionList.getLastOrNull() : next.prev; + if (ret == null) { + throw new NoSuchElementException(); + } + assert ret.block == block : "Iterator invalidated: " + next; + current = ret; + next = ret; + return ret; } @Override public int previousIndex() { - return listIterator.previousIndex(); + throw new UnsupportedOperationException(); } @Override public Instruction peekPrevious() { - // Reset current since listIterator.remove() changes based on whether next() or previous() was - // last called. - // E.g.: previous() -> current=B - // peekPrevious(): previous() -> current=A, next() -> current=A - current = null; - return IteratorUtils.peekPrevious(listIterator); + Instruction ret = next == null ? instructionList.getLastOrNull() : next.prev; + assert ret == null || ret.block == block : "Iterator invalidated: " + next; + return ret; } @Override @@ -153,13 +156,10 @@ */ @Override public void add(Instruction instruction) { - instruction.setBlock(block); - assert instruction.getBlock() == block; if (!instruction.hasPosition() && hasInsertionPosition()) { instruction.setPosition(getInsertionPosition()); } - listIterator.add(instruction); - metadata.record(instruction); + instructionList.addBefore(instruction, next); } private boolean hasPriorThrowingInstruction() { @@ -239,10 +239,14 @@ */ @Override public void set(Instruction instruction) { - instruction.setBlock(block); - assert instruction.getBlock() == block; - listIterator.set(instruction); - metadata.record(instruction); + if (current == null) { + throw new IllegalStateException(); + } + instructionList.replace(current, instruction); + if (current == next) { + next = instruction; + } + current = instruction; } @Override @@ -253,6 +257,19 @@ } } + /** Updates |current| and |next|, and returns the old |current|. */ + private Instruction removeHelper() { + Instruction target = current; + if (target == null) { + throw new IllegalStateException(); + } + if (target == next) { + next = target.next; + } + current = null; + return target; + } + /** * Remove the current instruction (aka the {@link Instruction} returned by the previous call to * {@link #next}. @@ -264,74 +281,31 @@ */ @Override public void remove() { - if (current == null) { - throw new IllegalStateException(); - } - assert current.outValue() == null || !current.outValue().isUsed(); - assert current.getDebugValues().isEmpty(); - for (int i = 0; i < current.inValues().size(); i++) { - Value value = current.inValues().get(i); - value.removeUser(current); - } - // These needs to stay to ensure that an optimization incorrectly not taking debug info into - // account still produces valid code when run without enabled assertions. - for (Value value : current.getDebugValues()) { - value.removeDebugUser(current); - } - if (current.getLocalInfo() != null) { - for (Instruction user : current.outValue().debugUsers()) { - user.removeDebugValue(current.outValue()); - } - } - listIterator.remove(); - current = null; + instructionList.removeAndDetachInValues(removeHelper()); } @Override public void removeInstructionIgnoreOutValue() { - if (current == null) { - throw new IllegalStateException(); - } - listIterator.remove(); - current = null; + instructionList.removeIgnoreValues(removeHelper()); } @Override public void removeOrReplaceByDebugLocalRead() { - if (current == null) { - throw new IllegalStateException(); - } - if (current.getDebugValues().isEmpty()) { - remove(); - } else { - replaceCurrentInstruction(new DebugLocalRead()); - } + instructionList.removeOrReplaceByDebugLocalRead(removeHelper()); } @Override - public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) { + public void replaceCurrentInstruction(Instruction newInstruction, AffectedValues affectedValues) { if (current == null) { throw new IllegalStateException(); } - for (Value value : current.inValues()) { - value.removeUser(current); + if (current == next) { + // TODO(b/376663044): This should not advance the cursor. Prior implementation used remove() + // and add() rather than set(), which causes replaced item to appear when iterating backwards. + // E.g.: Should be: next = newInstruction; + next = next.next; } - if (current.hasUsedOutValue()) { - assert newInstruction.outValue() != null; - if (affectedValues != null && !newInstruction.getOutType().equals(current.getOutType())) { - current.outValue().addAffectedValuesTo(affectedValues); - } - current.outValue().replaceUsers(newInstruction.outValue()); - } - current.moveDebugValues(newInstruction); - newInstruction.setBlock(block); - if (!newInstruction.hasPosition()) { - newInstruction.setPosition(current.getPosition()); - } - listIterator.remove(); - listIterator.add(newInstruction); - current.clearBlock(); - metadata.record(newInstruction); + instructionList.replace(current, newInstruction, affectedValues); current = newInstruction; } @@ -508,7 +482,7 @@ @Override public void replaceCurrentInstructionWithStaticGet( - AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) { + AppView<?> appView, IRCode code, DexField field, AffectedValues affectedValues) { if (current == null) { throw new IllegalStateException(); } @@ -691,22 +665,17 @@ // Get the position at which the block is being split. Position position = getPreviousPosition(); - // Prepare the new block, placing the exception handlers on the block with the throwing - // instruction. - BasicBlock newBlock = block.createSplitBlock(code.getNextBlockNumber(), keepCatchHandlers); - // Add a goto instruction. - Goto newGoto = new Goto(block); - listIterator.add(newGoto); + Goto newGoto = new Goto(); + instructionList.addBefore(newGoto, next); newGoto.setPosition(position); - // Move all remaining instructions to the new block. - while (listIterator.hasNext()) { - Instruction instruction = listIterator.next(); - newBlock.getInstructions().addLast(instruction); - instruction.setBlock(newBlock); - listIterator.remove(); - } + // Prepare the new block, placing the exception handlers on the block with the throwing + // instruction. + BasicBlock newBlock = + block.createSplitBlock(code.getNextBlockNumber(), keepCatchHandlers, next); + next = null; + current = null; // Insert the new block in the block list right after the current block. if (blocksIterator == null) { @@ -917,10 +886,8 @@ entryBlockIterator = entryBlock.listIterator(code); // Insert cast instruction into the new block. inlineEntry.listIterator(code).add(castInstruction); - castInstruction.setBlock(inlineEntry); assert castInstruction.getBlock().getInstructions().size() == 2; } else { - castInstruction.setBlock(entryBlock); entryBlockIterator = entryBlock.listIterator(code); entryBlockIterator.add(castInstruction); } @@ -1011,9 +978,12 @@ assert IteratorUtils.peekNext(blocksIterator) == invokeBlock; // Insert inlinee blocks into the IR code of the callee, before the invoke block. + IRMetadata ourMetadata = block.getMetadata(); + ourMetadata.merge(inlineEntry.getMetadata()); for (BasicBlock bb : inlinee.blocks) { bb.setNumber(code.getNextBlockNumber()); blocksIterator.add(bb); + bb.setMetadata(ourMetadata); } // If the invoke block had catch handlers copy those down to all inlined blocks. @@ -1057,7 +1027,7 @@ it.previous(); return it; } - BasicBlock newExitBlock = new BasicBlock(); + BasicBlock newExitBlock = new BasicBlock(code.metadata()); newExitBlock.setNumber(code.getNextBlockNumber()); Return newReturn; if (normalExits.get(0).exit().asReturn().isReturnVoid()) { @@ -1090,7 +1060,7 @@ } // The newly constructed return will be eliminated as part of inlining so we set position none. newReturn.setPosition(Position.none()); - newExitBlock.add(newReturn, metadata); + newExitBlock.add(newReturn, code.metadata()); for (BasicBlock exitBlock : normalExits) { InstructionListIterator it = exitBlock.listIterator(code, exitBlock.getInstructions().size()); Instruction oldExit = it.previous();
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java index 0b9c54c..0411cd2 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
@@ -105,12 +105,10 @@ throw new IllegalStateException(); } // Remove all instructions from the block before removing the block. - InstructionListIterator iterator = current.listIterator(code); - while (iterator.hasNext()) { - Instruction instruction = iterator.next(); - instruction.clearDebugValues(); - iterator.remove(); + for (Instruction ins = current.entry(); ins != null; ins = ins.getNext()) { + ins.detachInValues(); } + current.getInstructions().clear(); listIterator.remove(); current = null; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java index 20eadc9..e04ff68 100644 --- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java +++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -27,7 +27,7 @@ private final BasicBlock[] sorted; private BasicBlock[] doms; - private final BasicBlock normalExitBlock = new BasicBlock(); + private final BasicBlock normalExitBlock; private final int unreachableStartIndex; @@ -40,6 +40,7 @@ public DominatorTree(IRCode code, Assumption assumption) { assert assumption != null; assert assumption == MAY_HAVE_UNREACHABLE_BLOCKS || code.getUnreachableBlocks().isEmpty(); + normalExitBlock = new BasicBlock(code.metadata()); ImmutableList<BasicBlock> blocks = code.topologicallySortedBlocks(); // Add the internal exit block to the block list.
diff --git a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java index c1424b7..8e393fc 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java +++ b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
@@ -50,6 +50,19 @@ return register; } + public boolean usesRegister(FixedRegisterValue other) { + if (register == other.getRegister()) { + return true; + } + if (getType().isWidePrimitive() && register + 1 == other.getRegister()) { + return true; + } + if (other.getType().isWidePrimitive() && register == other.getRegister() + 1) { + return true; + } + return false; + } + @Override public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) { return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java index 9beaff7..3c5a357 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Goto.java +++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -8,25 +8,10 @@ import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; import com.android.tools.r8.lightir.LirBuilder; -import com.android.tools.r8.utils.ListUtils; import java.util.List; import java.util.ListIterator; public class Goto extends JumpInstruction { - - public Goto() { - super(); - } - - public Goto(BasicBlock block) { - this(); - setBlock(block); - } - - public static Builder builder() { - return new Builder(); - } - @Override public int opcode() { return Opcodes.GOTO; @@ -77,7 +62,7 @@ // Avoids BasicBlock.exit(), since it will assert when block is invalid. if (myBlock != null && !myBlock.getSuccessors().isEmpty() - && ListUtils.last(myBlock.getInstructions()) == this) { + && myBlock.getInstructions().getLastOrNull() == this) { return super.toString() + "block " + getTarget().getNumberAsString(); } return super.toString() + "block <unknown>"; @@ -129,24 +114,4 @@ public void buildLir(LirBuilder<Value, ?> builder) { builder.addGoto(getTarget()); } - - public static class Builder extends BuilderBase<Builder, Goto> { - - private BasicBlock target; - - public Builder setTarget(BasicBlock target) { - this.target = target; - return self(); - } - - @Override - public Goto build() { - return amend(new Goto(target)); - } - - @Override - public Builder self() { - return this; - } - } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index 307acdf..cbf7075 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -383,10 +383,10 @@ if (hasSeenThrowingInstruction) { List<BasicBlock> successors = block.getSuccessors(); if (successors.size() == 1 && ListUtils.first(successors).getPredecessors().size() > 1) { - BasicBlock splitBlock = block.createSplitBlock(getNextBlockNumber(), true); - Goto newGoto = new Goto(block); + BasicBlock splitBlock = block.createSplitBlock(getNextBlockNumber(), true, null); + Goto newGoto = new Goto(); newGoto.setPosition(Position.none()); - splitBlock.listIterator(this).add(newGoto); + splitBlock.getInstructions().addLast(newGoto); blockIterator.add(splitBlock); } } @@ -1202,10 +1202,10 @@ } public Argument getLastArgument() { - InstructionIterator instructionIterator = entryBlock().iterator(getNumberOfArguments() - 1); - Argument lastArgument = instructionIterator.next().asArgument(); + Instruction lastArgInstr = entryBlock().getInstructions().getNth(getNumberOfArguments() - 1); + Argument lastArgument = lastArgInstr.asArgument(); assert lastArgument != null; - assert !instructionIterator.peekNext().isArgument(); + assert !lastArgInstr.getNext().isArgument(); return lastArgument; }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java index 13e393a..6eb4a80 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -104,7 +104,7 @@ @Override public void replaceCurrentInstructionWithStaticGet( - AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) { + AppView<?> appView, IRCode code, DexField field, AffectedValues affectedValues) { instructionIterator.replaceCurrentInstructionWithStaticGet( appView, code, field, affectedValues); } @@ -278,7 +278,7 @@ } @Override - public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) { + public void replaceCurrentInstruction(Instruction newInstruction, AffectedValues affectedValues) { instructionIterator.replaceCurrentInstruction(newInstruction, affectedValues); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java index e918ad4..4bb02dd 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -100,7 +100,7 @@ } else { assert false; } - internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist); + internalRemoveInstructionAndTransitiveInputsIfNotUsed(worklist); } /** @@ -109,14 +109,12 @@ * * <p>Use with caution! */ - public static void removeInstructionAndTransitiveInputsIfNotUsed( - IRCode code, Instruction instruction) { - internalRemoveInstructionAndTransitiveInputsIfNotUsed( - code, DequeUtils.newArrayDeque(instruction)); + public static void removeInstructionAndTransitiveInputsIfNotUsed(Instruction instruction) { + internalRemoveInstructionAndTransitiveInputsIfNotUsed(DequeUtils.newArrayDeque(instruction)); } private static void internalRemoveInstructionAndTransitiveInputsIfNotUsed( - IRCode code, Deque<InstructionOrPhi> worklist) { + Deque<InstructionOrPhi> worklist) { Set<InstructionOrPhi> removed = Sets.newIdentityHashSet(); while (!worklist.isEmpty()) { InstructionOrPhi instructionOrPhi = worklist.removeFirst(); @@ -145,7 +143,7 @@ } else { Instruction current = instructionOrPhi.asInstruction(); if (!current.hasOutValue() || !current.outValue().hasAnyUsers()) { - current.getBlock().listIterator(code, current).removeOrReplaceByDebugLocalRead(); + current.removeOrReplaceByDebugLocalRead(); for (Value inValue : current.inValues()) { worklist.add(inValue.isPhi() ? inValue.asPhi() : inValue.definition); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java index d55ada3..7a67608 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -28,6 +28,7 @@ import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; import com.android.tools.r8.ir.conversion.MethodConversionOptions; +import com.android.tools.r8.ir.optimize.AffectedValues; import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; import com.android.tools.r8.ir.optimize.InliningConstraints; @@ -48,13 +49,15 @@ public abstract class Instruction implements AbstractInstruction, InstructionOrPhi, MaterializingInstructionsInfo { - - protected Value outValue = null; + // Package-private to be used by InstructionList. + BasicBlock block; + Instruction prev; + Instruction next; + protected Value outValue; protected final List<Value> inValues = new ArrayList<>(); - private BasicBlock block = null; private int number = -1; - private Set<Value> debugValues = null; - private Position position = null; + private Set<Value> debugValues; + private Position position; protected Instruction(Value outValue) { setOutValue(outValue); @@ -82,6 +85,18 @@ return position != null; } + public boolean hasPrev() { + return prev != null; + } + + public Instruction getPrev() { + return prev; + } + + public Instruction getNext() { + return next; + } + @Override public final Position getPosition() { assert position != null; @@ -341,32 +356,56 @@ return block; } - /** - * Set the basic block of this instruction. See IRBuilder. - */ - public void setBlock(BasicBlock block) { - assert block != null; - this.block = block; + /** Prepares instruction for removal by removing its in-values. */ + public Instruction detachInValues() { + for (Value value : inValues) { + value.removeUser(this); + } + // These needs to stay to ensure that an optimization incorrectly not taking debug info into + // account still produces valid code when run without enabled assertions. + if (debugValues != null) { + for (Value value : debugValues) { + value.removeDebugUser(this); + } + } + if (getLocalInfo() != null) { + for (Instruction user : outValue.debugUsers()) { + user.removeDebugValue(outValue); + } + } + return this; } - /** - * Clear the basic block of this instruction. Use when removing an instruction from a block. - */ - public void clearBlock() { - assert block != null; - block = null; + public void removeOrReplaceByDebugLocalRead() { + block.getInstructions().removeOrReplaceByDebugLocalRead(this); } - public void removeOrReplaceByDebugLocalRead(IRCode code) { - getBlock().listIterator(code, this).removeOrReplaceByDebugLocalRead(); + // TODO(b/376663044): Delete. + public void removeOrReplaceByDebugLocalRead(IRCode unused_code) { + block.getInstructions().removeOrReplaceByDebugLocalRead(this); } - public void replace(Instruction newInstruction, IRCode code) { - replace(newInstruction, code, null); + public void removeIgnoreValues() { + block.getInstructions().removeIgnoreValues(this); } - public void replace(Instruction newInstruction, IRCode code, Set<Value> affectedValues) { - getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction, affectedValues); + public void replace(Instruction newInstruction) { + block.getInstructions().replace(this, newInstruction); + } + + // TODO(b/376663044): Delete. + public void replace(Instruction newInstruction, IRCode unused_code) { + block.getInstructions().replace(this, newInstruction); + } + + public void replace(Instruction newInstruction, AffectedValues affectedValues) { + block.getInstructions().replace(this, newInstruction, affectedValues); + } + + // TODO(b/376663044): Delete. + public void replace( + Instruction newInstruction, IRCode unused_code, AffectedValues affectedValues) { + block.getInstructions().replace(this, newInstruction, affectedValues); } /** @@ -960,6 +999,10 @@ return null; } + public boolean isExit() { + return isJumpInstruction(); + } + public boolean isJumpInstruction() { return false; } @@ -1630,6 +1673,20 @@ // Intentionally empty. } + /** Returns whether the given instruction is encountered via continuous calls to getNext(). */ + public boolean comesBefore(Instruction target) { + assert target != this; + assert target.block == block; // Probably a bug if this does not hold. + Instruction cur = next; + while (cur != null) { + if (cur == target) { + return true; + } + cur = cur.next; + } + return false; + } + public static class SideEffectAssumption { public static final SideEffectAssumption NONE = new SideEffectAssumption();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionList.java b/src/main/java/com/android/tools/r8/ir/code/InstructionList.java new file mode 100644 index 0000000..f95b67a --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionList.java
@@ -0,0 +1,302 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.code; + +import com.android.tools.r8.ir.optimize.AffectedValues; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Linked list of instructions using Instruction.prev and Instruction.next fields. + * + * <pre> + * Design notes: + * - There is a 1:1 relationship between a BasicBlock and InstructionList. + * - We might want to just merge this into BasicBlock. + * - Updates IRMetadata and sets Instruction.block fields when instructions are added. + * - Does not implement Collection / List interfaces to better reflect that this is a + * special-purpose collection, where operations sometimes mutate the state of elements. + * - Does not implement a non-assert "contains" method. Use Instruction.block to check containment. + * - Does not clear next/prev fields upon removal. + * - Because there has not yet been a need to reuse instructions (except for the special case of + * block splitting). + * - |block| is cleared, and asserts prevent instructions from being added that already have a + * block set. + * </pre> + */ +public class InstructionList implements Iterable<Instruction> { + private final BasicBlock ownerBlock; + private Instruction head; + private Instruction tail; + private int size; + + public InstructionList(BasicBlock ownerBlock) { + this.ownerBlock = ownerBlock; + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return head == null; + } + + public Instruction getFirst() { + assert !isEmpty(); + return head; + } + + public Instruction getFirstOrNull() { + return head; + } + + /** Returns the instruction at the given index. This is O(n). */ + public Instruction getNth(int n) { + assert n >= 0 && n < size : "n=" + n + " size=" + size; + if (n > size / 2) { + Instruction ret = tail; + n = size - n - 1; + for (int i = 0; i < n; ++i) { + ret = ret.prev; + } + return ret; + } + Instruction ret = head; + for (int i = 0; i < n; ++i) { + ret = ret.next; + } + return ret; + } + + public Instruction getLast() { + assert !isEmpty(); + return tail; + } + + public Instruction getLastOrNull() { + return tail; + } + + public void addFirst(Instruction newInstruction) { + addBefore(newInstruction, head); + } + + public void addLast(Instruction newInstruction) { + addBefore(newInstruction, null); + } + + /** + * Inserts newInstruction before existingInstruction. If existingInstruction is null, adds at the + * end. + */ + public void addBefore(Instruction newInstruction, Instruction existingInstruction) { + assert newInstruction.block == null; + if (existingInstruction != null) { + assert linearScanFinds(existingInstruction); + } + if (size == 0) { + assert existingInstruction == null; + head = newInstruction; + tail = newInstruction; + } else if (existingInstruction == null) { + // Add to end. + newInstruction.prev = tail; + tail.next = newInstruction; + tail = newInstruction; + } else { + Instruction prevInstruction = existingInstruction.prev; + newInstruction.prev = prevInstruction; + newInstruction.next = existingInstruction; + existingInstruction.prev = newInstruction; + if (prevInstruction == null) { + assert head == existingInstruction; + head = newInstruction; + } else { + prevInstruction.next = newInstruction; + } + } + + size += 1; + newInstruction.block = ownerBlock; + ownerBlock.getMetadata().record(newInstruction); + } + + /** Adopt all instructions from another list (use this to split blocks). */ + public void severFrom(Instruction firstInstructionToMove) { + assert isEmpty(); + BasicBlock sourceBlock = firstInstructionToMove.block; + InstructionList sourceList = sourceBlock.getInstructions(); + // So far no need to sever between methods. + assert sourceBlock.getMetadata() == ownerBlock.getMetadata(); + + head = firstInstructionToMove; + tail = sourceList.tail; + + Instruction lastInstructionToRemain = firstInstructionToMove.prev; + sourceList.tail = lastInstructionToRemain; + if (lastInstructionToRemain == null) { + sourceList.head = null; + } else { + lastInstructionToRemain.next = null; + firstInstructionToMove.prev = null; + } + + int count = 0; + BasicBlock newBlock = ownerBlock; + for (Instruction current = firstInstructionToMove; current != null; current = current.next) { + count += 1; + current.block = newBlock; + } + size = count; + sourceList.size -= count; + } + + public void replace(Instruction oldInstruction, Instruction newInstruction) { + replace(oldInstruction, newInstruction, null); + } + + /** + * Replace the oldInstruction with newInstruction. + * + * <p>Removes in-values from oldInstruction. + * + * <p>If the current instruction produces an out-value the new instruction must also produce an + * out-value, and all uses of the current instructions out-value will be replaced by the new + * instructions out-value. + * + * <p>The debug information of the current instruction will be attached to the new instruction. + */ + public void replace( + Instruction target, Instruction newInstruction, AffectedValues affectedValues) { + for (Value value : target.inValues()) { + value.removeUser(target); + } + if (target.hasUsedOutValue()) { + assert newInstruction.outValue() != null; + if (affectedValues != null && !newInstruction.getOutType().equals(target.getOutType())) { + target.outValue().addAffectedValuesTo(affectedValues); + } + target.outValue().replaceUsers(newInstruction.outValue()); + } + target.moveDebugValues(newInstruction); + if (!newInstruction.hasPosition()) { + newInstruction.setPosition(target.getPosition()); + } + + addBefore(newInstruction, target); + removeIgnoreValues(target); + } + + /** + * Safe removal function that will insert a DebugLocalRead to take over the debug values if any + * are associated with the current instruction. + */ + public void removeOrReplaceByDebugLocalRead(Instruction target) { + if (target.getDebugValues().isEmpty()) { + removeAndDetachInValues(target); + } else { + replace(target, new DebugLocalRead()); + } + } + + /** + * Remove the given instruction. + * + * <p>The instruction will be removed and uses of its in-values removed. + * + * <p>If the current instruction produces an out-value this out value must not have any users. + */ + public void removeAndDetachInValues(Instruction target) { + assert target.outValue() == null || !target.outValue().isUsed(); + removeIgnoreValues(target.detachInValues()); + } + + /** Removes without doing any validation of in / out values. */ + public void removeIgnoreValues(Instruction target) { + assert linearScanFinds(target); + target.block = null; + Instruction prev = target.prev; + Instruction next = target.next; + if (head == target) { + head = next; + } + if (tail == target) { + tail = prev; + } + if (prev != null) { + prev.next = next; + } + if (next != null) { + next.prev = prev; + } + size -= 1; + } + + /** Removes all instructions. Instructions removed in this way may not be reused. */ + public void clear() { + head = null; + tail = null; + size = 0; + } + + // Non-assert uses should check instruction.block for containment. */ + private boolean linearScanFinds(Instruction instruction) { + for (Instruction cur = head; cur != null; cur = cur.next) { + if (cur == instruction) { + return true; + } + } + return false; + } + + public Stream<Instruction> stream() { + return StreamSupport.stream(new SpliteratorImpl(), false); + } + + @Override + public BasicBlockInstructionListIterator iterator() { + return new BasicBlockInstructionListIterator(ownerBlock); + } + + public BasicBlockInstructionListIterator iterator(Instruction firstInstructionToReturn) { + return new BasicBlockInstructionListIterator(ownerBlock, firstInstructionToReturn); + } + + private class SpliteratorImpl implements Spliterator<Instruction> { + Instruction next = head; + + @Override + public boolean tryAdvance(Consumer<? super Instruction> action) { + if (next == null) { + return false; + } + action.accept(next); + next = next.next; + return true; + } + + @Override + public Spliterator<Instruction> trySplit() { + return null; + } + + @Override + public long estimateSize() { + return size; + } + + @Override + public long getExactSizeIfKnown() { + return size; + } + + @Override + public int characteristics() { + return SIZED | DISTINCT | NONNULL; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java index c1e8aa6..b9c54f1 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -98,7 +98,7 @@ next(); } - /** See {@link #replaceCurrentInstruction(Instruction, Set)}. */ + /** See {@link #replaceCurrentInstruction(Instruction, AffectedValues)}. */ default void replaceCurrentInstruction(Instruction newInstruction) { replaceCurrentInstruction(newInstruction, null); } @@ -119,7 +119,7 @@ * @param newInstruction the instruction to insert instead of the current. * @param affectedValues if non-null, all users of the out value will be added to this set. */ - void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues); + void replaceCurrentInstruction(Instruction newInstruction, AffectedValues affectedValues); // Do not show a deprecation warning for InstructionListIterator.remove(). @SuppressWarnings("deprecation") @@ -229,7 +229,7 @@ void replaceCurrentInstructionWithNullCheck(AppView<?> appView, Value object); void replaceCurrentInstructionWithStaticGet( - AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues); + AppView<?> appView, IRCode code, DexField field, AffectedValues affectedValues); void replaceCurrentInstructionWithThrow( AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java index 4406d9d..d16fdc6 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java +++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.code; +import static com.google.common.base.Predicates.alwaysTrue; + import com.android.tools.r8.cf.LoadStoreHelper; import com.android.tools.r8.dex.Constants; import com.android.tools.r8.dex.code.DexInstruction; @@ -19,7 +21,10 @@ import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.conversion.DexBuilder; +import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; import com.android.tools.r8.utils.BooleanUtils; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; import java.util.List; import java.util.Set; @@ -157,9 +162,10 @@ } protected int getRegisterForInvokeRange(DexBuilder builder, Value argument) { - return builder.getOptions().getTestingOptions().enableLiveIntervalsSplittingForInvokeRange - ? builder.allocatedRegister(argument, getNumber()) - : builder.argumentOrAllocateRegister(argument, getNumber()); + if (argumentsAreConsecutivePinnedInputArguments(builder.getRegisterAllocator())) { + return builder.getArgumentRegister(argument); + } + return builder.allocatedRegister(argument, getNumber()); } protected void addInvokeAndMoveResult(DexInstruction instruction, DexBuilder builder) { @@ -223,6 +229,15 @@ } private boolean argumentsAreConsecutiveInputArguments() { + return argumentsAreConsecutiveInputArgumentsThatMatches(alwaysTrue()); + } + + private boolean argumentsAreConsecutivePinnedInputArguments( + LinearScanRegisterAllocator registerAllocator) { + return argumentsAreConsecutiveInputArgumentsThatMatches(registerAllocator::isPinnedArgument); + } + + private boolean argumentsAreConsecutiveInputArgumentsThatMatches(Predicate<Value> predicate) { if (arguments().isEmpty()) { return false; } @@ -237,28 +252,24 @@ } current = next; } - return true; + return Iterables.all(arguments(), predicate); } + // Used to decide if this invoke should be emitted as invoke/range. protected boolean needsRangedInvoke(DexBuilder builder) { + if (arguments().size() == 1) { + // Prefer invoke-range since this does not impose any constraints on the operand register. + return true; + } if (requiredArgumentRegisters() > 5) { // No way around using an invoke-range instruction. return true; } - // By using an invoke-range instruction when there is only one argument, we avoid having to - // satisfy the constraint that the argument register(s) must fit in 4 bits. - boolean registersGuaranteedToBeConsecutive = - arguments().size() == 1 || argumentsAreConsecutiveInputArguments(); - if (!registersGuaranteedToBeConsecutive) { - // No way that we will need an invoke-range. - return false; + if (argumentsAreConsecutivePinnedInputArguments(builder.getRegisterAllocator())) { + // Use the arguments from their input registers. + return true; } - // If we could use an invoke-range instruction, but all the registers fit in 4 bits, then we - // use a non-range invoke. - assert verifyInvokeRangeArgumentsAreConsecutive(builder); - int registerStart = getRegisterForInvokeRange(builder, getFirstArgument()); - int registerEnd = registerStart + requiredArgumentRegisters() - 1; - return registerEnd > Constants.U4BIT_MAX; + return false; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java index 0885e72..99b0a1b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -64,7 +64,7 @@ } @Override - public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) { + public void replaceCurrentInstruction(Instruction newInstruction, AffectedValues affectedValues) { currentBlockIterator.replaceCurrentInstruction(newInstruction, affectedValues); } @@ -133,7 +133,7 @@ @Override public void replaceCurrentInstructionWithStaticGet( - AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) { + AppView<?> appView, IRCode code, DexField field, AffectedValues affectedValues) { currentBlockIterator.replaceCurrentInstructionWithStaticGet( appView, code, field, affectedValues); } @@ -322,7 +322,7 @@ if (target == null || target.size() < 2) { return null; } - return target.getInstructions().get(target.size() - 2); + return target.getLastInstruction().getPrev(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java index 025df81..430f8a4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java +++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -42,14 +42,6 @@ return inValues.get(0); } - public boolean isEnter() { - return type == MonitorType.ENTER; - } - - public boolean isExit() { - return type == MonitorType.EXIT; - } - @Override public void buildDex(DexBuilder builder) { // If the monitor object is an argument, we use the argument register for all the monitor @@ -96,7 +88,7 @@ @Override public boolean isMonitorEnter() { - return isEnter(); + return type == MonitorType.ENTER; } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java index 2d4b2ee..480be85 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -41,6 +41,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Inc; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionList; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InvokeDirect; import com.android.tools.r8.ir.code.JumpInstruction; @@ -69,7 +70,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -79,7 +79,6 @@ private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2; private static final int SUFFIX_SHARING_OVERHEAD = 30; - private static final int IINC_PATTERN_SIZE = 4; public final AppView<?> appView; private final ProgramMethod method; @@ -234,15 +233,13 @@ Set<UninitializedThisLocalRead> uninitializedThisLocalReads = Sets.newIdentityHashSet(); for (BasicBlock exitBlock : code.blocks) { if (exitBlock.exit().isThrow() && !exitBlock.hasCatchHandlers()) { - LinkedList<Instruction> instructions = exitBlock.getInstructions(); - Instruction throwing = instructions.removeLast(); + InstructionList instructions = exitBlock.getInstructions(); + Instruction throwing = instructions.getLast(); assert throwing.isThrow(); UninitializedThisLocalRead read = new UninitializedThisLocalRead(code.getThis()); read.setPosition(throwing.getPosition()); uninitializedThisLocalReads.add(read); - read.setBlock(exitBlock); - instructions.addLast(read); - instructions.addLast(throwing); + instructions.addBefore(read, throwing); } } return uninitializedThisLocalReads; @@ -322,7 +319,6 @@ it.previous(); Value constValue = code.createValue(inValue.getType()); Instruction newInstruction = new ConstNumber(constValue, -1); - newInstruction.setBlock(block); newInstruction.setPosition(current.getPosition()); it.add(newInstruction); it.next(); @@ -473,44 +469,43 @@ @SuppressWarnings("ReferenceEquality") private void rewriteIincPatterns() { for (BasicBlock block : code.blocks) { - InstructionListIterator it = block.listIterator(code); - // Test that we have enough instructions for iinc. - while (IINC_PATTERN_SIZE <= block.getInstructions().size() - it.nextIndex()) { - Instruction loadOrConst1 = it.next(); - if (!loadOrConst1.isLoad() && !loadOrConst1.isConstNumber()) { + InstructionList instructions = block.getInstructions(); + for (Instruction ins = instructions.getFirst(); ins != null; ins = ins.getNext()) { + boolean isLoad = ins.isLoad(); + if (!isLoad && !ins.isConstNumber()) { continue; } Load load; ConstNumber constNumber; - if (loadOrConst1.isLoad()) { - load = loadOrConst1.asLoad(); - constNumber = it.next().asConstNumber(); + Instruction nextInstruction = ins.getNext(); + if (isLoad) { + load = ins.asLoad(); + constNumber = nextInstruction.asConstNumber(); } else { - load = it.next().asLoad(); - constNumber = loadOrConst1.asConstNumber(); + load = nextInstruction.asLoad(); + constNumber = ins.asConstNumber(); } - Instruction add = it.next().asAdd(); - Instruction store = it.next().asStore(); - // Reset pointer to load. - it.previous(); - it.previous(); - it.previous(); - it.previous(); if (load == null || constNumber == null - || add == null - || store == null || constNumber.getOutType() != TypeElement.getInt()) { - it.next(); + continue; + } + Instruction add = nextInstruction.getNext(); + if (add == null) { + break; + } + Instruction store = add.getNext(); + if (store == null) { + break; + } + if (!add.isAdd() || !store.isStore()) { continue; } int increment = constNumber.getIntValue(); if (increment < Byte.MIN_VALUE || Byte.MAX_VALUE < increment) { - it.next(); continue; } if (getLocalRegister(load.src()) != getLocalRegister(store.outValue())) { - it.next(); continue; } Position position = add.getPosition(); @@ -519,16 +514,15 @@ || position != store.getPosition()) { continue; } - it.removeInstructionIgnoreOutValue(); - it.next(); - it.removeInstructionIgnoreOutValue(); - it.next(); - it.removeInstructionIgnoreOutValue(); - it.next(); + Inc inc = new Inc(store.outValue(), load.inValues().get(0), increment); inc.setPosition(position); - inc.setBlock(block); - it.set(inc); + instructions.addBefore(inc, store); + + load.removeIgnoreValues(); + constNumber.removeIgnoreValues(); + add.removeIgnoreValues(); + store.removeIgnoreValues(); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java index bdeeb96..6f835bd 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -58,6 +58,7 @@ import com.android.tools.r8.ir.code.If; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; +import com.android.tools.r8.ir.code.InstructionList; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.IntSwitch; import com.android.tools.r8.ir.code.JumpInstruction; @@ -67,13 +68,13 @@ import com.android.tools.r8.ir.code.Return; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser; +import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; import com.android.tools.r8.ir.regalloc.RegisterAllocator; import com.android.tools.r8.lightir.ByteUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.InternalOutputMode; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; @@ -99,7 +100,7 @@ private final BytecodeMetadata.Builder<DexInstruction> bytecodeMetadataBuilder; // The register allocator providing register assignments for the code to build. - private final RegisterAllocator registerAllocator; + private final LinearScanRegisterAllocator registerAllocator; private final InternalOptions options; private final MethodConversionOptions conversionOptions; @@ -138,7 +139,7 @@ public DexBuilder( IRCode ir, BytecodeMetadataProvider bytecodeMetadataProvider, - RegisterAllocator registerAllocator, + LinearScanRegisterAllocator registerAllocator, InternalOptions options) { this( ir, @@ -158,7 +159,7 @@ this.appView = registerAllocator.getAppView(); this.ir = ir; this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider); - this.registerAllocator = registerAllocator; + this.registerAllocator = (LinearScanRegisterAllocator) registerAllocator; this.options = options; this.conversionOptions = conversionOptions; if (isBuildingForComparison()) { @@ -664,6 +665,10 @@ return registerAllocator.getArgumentOrAllocateRegisterForValue(value, instructionNumber); } + public int getArgumentRegister(Value value) { + return registerAllocator.getArgumentRegisterForValue(value); + } + public void addGoto(com.android.tools.r8.ir.code.Goto jump) { if (jump.getTarget() != nextBlock) { add(jump, new GotoInfo(jump)); @@ -942,8 +947,7 @@ item = tryItems.get(i); coalescedTryItems.add(item); // Trim the range start for non-throwing instructions when starting a new range. - List<com.android.tools.r8.ir.code.Instruction> instructions = blocksWithHandlers.get(i) - .getInstructions(); + InstructionList instructions = blocksWithHandlers.get(i).getInstructions(); for (com.android.tools.r8.ir.code.Instruction insn : instructions) { if (insn.instructionTypeCanThrow()) { item.start = getInfo(insn).getOffset(); @@ -1028,10 +1032,9 @@ private int trimEnd(BasicBlock block) { // Trim the range end for non-throwing instructions when end has been computed. - List<com.android.tools.r8.ir.code.Instruction> instructions = block.getInstructions(); - for (com.android.tools.r8.ir.code.Instruction insn : Lists.reverse(instructions)) { - if (insn.instructionTypeCanThrow()) { - Info info = getInfo(insn); + for (Instruction ins = block.getLastInstruction(); ins != null; ins = ins.getPrev()) { + if (ins.instructionTypeCanThrow()) { + Info info = getInfo(ins); return info.getOffset() + info.getSize(); } } @@ -1078,7 +1081,7 @@ return options; } - public RegisterAllocator getRegisterAllocator() { + public LinearScanRegisterAllocator getRegisterAllocator() { return registerAllocator; }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index 2c84747..8e0b85a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -282,9 +282,8 @@ } } - public static class BlockInfo { - - BasicBlock block = new BasicBlock(); + public class BlockInfo { + BasicBlock block = new BasicBlock(metadata); IntSet normalPredecessors = new IntArraySet(); IntSet normalSuccessors = new IntArraySet(); IntSet exceptionalPredecessors = new IntArraySet(); @@ -697,14 +696,13 @@ // Insert definitions for all uninitialized local values. if (uninitializedDebugLocalValues != null) { Position position = entryBlock.getPosition(); - InstructionListIterator it = entryBlock.listIterator(metadata); + InstructionListIterator it = entryBlock.listIterator(); it.nextUntil(i -> !i.isArgument()); it.previous(); for (List<Value> values : uninitializedDebugLocalValues.values()) { for (Value value : values) { if (value.isUsed()) { Instruction def = new DebugLocalUninitialized(value); - def.setBlock(entryBlock); def.setPosition(position); it.add(def); } @@ -797,7 +795,7 @@ return; } for (BasicBlock block : blocks) { - InstructionListIterator it = block.listIterator(metadata); + InstructionListIterator it = block.listIterator(); Position current = null; while (it.hasNext()) { Instruction instruction = it.next(); @@ -1760,8 +1758,7 @@ } public void addMoveResult(int dest) { - List<Instruction> instructions = currentBlock.getInstructions(); - Invoke invoke = instructions.get(instructions.size() - 1).asInvoke(); + Invoke invoke = currentBlock.getInstructions().getLast().asInvoke(); assert invoke.outValue() == null; assert invoke.instructionTypeCanThrow(); DexType outType = invoke.getReturnType(); @@ -2410,7 +2407,7 @@ Set<BasicBlock> moveExceptionTargets = Sets.newIdentityHashSet(); catchHandlers.forEach( (exceptionType, targetOffset) -> { - BasicBlock header = new BasicBlock(); + BasicBlock header = new BasicBlock(currentBlock.getMetadata()); header.incrementUnfilledPredecessorCount(); ssaWorklist.add( new MoveExceptionWorklistItem( @@ -2669,7 +2666,7 @@ } private static BasicBlock createSplitEdgeBlock(BasicBlock source, BasicBlock target) { - BasicBlock splitBlock = new BasicBlock(); + BasicBlock splitBlock = new BasicBlock(source.getMetadata()); splitBlock.incrementUnfilledPredecessorCount(); splitBlock.getMutablePredecessors().add(source); splitBlock.getMutableSuccessors().add(target);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java index edac43f..c971967 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -16,7 +16,6 @@ import com.android.tools.r8.ir.optimize.PeepholeOptimizer; import com.android.tools.r8.ir.optimize.RuntimeWorkaroundCodeRewriter; import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; -import com.android.tools.r8.ir.regalloc.RegisterAllocator; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; @@ -46,7 +45,7 @@ workaroundBugs(code, timing); code.traceBlocks(); // Perform register allocation. - RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing); + LinearScanRegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing); return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build(); } @@ -66,7 +65,7 @@ } @SuppressWarnings("UnusedVariable") - private RegisterAllocator performRegisterAllocation( + private LinearScanRegisterAllocator performRegisterAllocation( IRCode code, DexEncodedMethod method, Timing timing) { // Always perform dead code elimination before register allocation. The register allocator // does not allow dead code (to make sure that we do not waste registers for unneeded values).
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index e8b022d..e9b1aa4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -1079,7 +1079,7 @@ AffectedValues affectedValues = new AffectedValues(); for (UnusedArgument unusedArgument : unusedArguments) { InstructionListIterator instructionIterator = - unusedArgument.getBlock().listIterator(code, unusedArgument); + unusedArgument.getBlock().listIterator(code, unusedArgument.getNext()); if (unusedArgument.outValue().hasAnyUsers()) { // This is an unused argument with a default value. The unused argument is an operand of the // phi. This use is eliminated after constant propagation + branch pruning. We eliminate the
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java index 221adce..990257e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
@@ -28,6 +28,7 @@ import com.android.tools.r8.ir.code.IfType; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; +import com.android.tools.r8.ir.code.InstructionList; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.IntSwitch; import com.android.tools.r8.ir.code.InvokeStatic; @@ -525,21 +526,20 @@ ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber; BasicBlock phiBlock = phi.getBlock(); Position phiPosition = phiBlock.getPosition(); - int insertIndex = 0; + InstructionList instructions = phiBlock.getInstructions(); + Instruction prevHead = instructions.getFirst(); if (cstToUse.getBlock() == trueBlock || cstToUse.getBlock() == falseBlock) { // The constant belongs to the block to remove, create a new one. cstToUse = ConstNumber.copyOf(code, cstToUse); - cstToUse.setBlock(phiBlock); cstToUse.setPosition(phiPosition); - phiBlock.getInstructions().add(insertIndex++, cstToUse); + instructions.addBefore(cstToUse, prevHead); } phi.replaceUsers(newOutValue); Instruction newInstruction = Xor.create(NumericType.INT, newOutValue, testValue, cstToUse.outValue()); - newInstruction.setBlock(phiBlock); // The xor is replacing a phi so it does not have an actual position. newInstruction.setPosition(phiPosition); - phiBlock.listIterator(code, insertIndex).add(newInstruction); + instructions.addBefore(newInstruction, prevHead); deadPhis++; } } @@ -565,7 +565,7 @@ int instructionSize = b.getInstructions().size(); if (b.exit().isGoto() && (instructionSize == 2 || instructionSize == 3)) { - Instruction constInstruction = b.getInstructions().get(instructionSize - 2); + Instruction constInstruction = b.getInstructions().getLast().getPrev(); if (constInstruction.isConstNumber()) { if (!constInstruction.asConstNumber().isIntegerOne() && !constInstruction.asConstNumber().isIntegerZero()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java index a213f7f..2bb1a5a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
@@ -143,7 +143,7 @@ ConstNumber newConstant = ConstNumber.copyOf(code, constValue.definition.asConstNumber()); newConstant.setPosition(currentInstruction.getPosition()); - newConstant.setBlock(currentInstruction.getBlock()); + currentInstruction.getBlock(); currentInstruction.replaceValue(constValue, newConstant.outValue()); constValue.removeUser(currentInstruction); instructionIterator.previous(); @@ -274,9 +274,7 @@ for (Instruction user : constantValue.uniqueUsers()) { ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber); newCstNum.setPosition(user.getPosition()); - InstructionListIterator iterator = user.getBlock().listIterator(code, user); - iterator.previous(); - iterator.add(newCstNum); + user.getBlock().getInstructions().addBefore(newCstNum, user); user.replaceValue(constantValue, newCstNum.outValue()); } constantValue.clearUsers();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java index e408515..f2f808e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
@@ -517,14 +517,13 @@ assert !instructionIterator.getBlock().hasCatchHandlers(); for (Value inValue : copy.asNewArrayFilled().inValues()) { instructionIterator.add(inValue.getDefinition()); - inValue.getDefinition().setBlock(instructionIterator.getBlock()); + inValue.getDefinition(); inValue.getDefinition().setPosition(newArrayEmpty.getPosition()); } } else { assert false; return elementValue; } - copy.setBlock(instructionIterator.getBlock()); copy.setPosition(newArrayEmpty.getPosition()); instructionIterator.add(copy); addToRemove(elementValue.getDefinition());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java index 1e327f8..69fb225 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
@@ -413,11 +413,11 @@ private void patchControlFlow(IRCode code, BasicBlock comparisonBlock) { assert loopExit.getPhis().isEmpty(); // Edges should be split. - comparisonBlock.replaceLastInstruction(new Goto(loopBodyEntry), code); + comparisonBlock.replaceLastInstruction(new Goto(), code); comparisonBlock.removeSuccessor(loopExit); backPredecessor.replaceSuccessor(comparisonBlock, loopExit); - backPredecessor.replaceLastInstruction(new Goto(loopExit), code); + backPredecessor.replaceLastInstruction(new Goto(), code); comparisonBlock.removePredecessor(backPredecessor); loopExit.replacePredecessor(comparisonBlock, backPredecessor); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java index 43d249f..7b74732 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java
@@ -23,7 +23,6 @@ import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.WorkList; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.util.ArrayDeque; import java.util.Deque; @@ -96,15 +95,14 @@ return; } // Change the instruction order and continue the hoisting. - List<Instruction> newInstructionOrder = - ImmutableList.<Instruction>builderWithExpectedSize(constants.size() + 2) - .addAll(constants) - .add(invoke) - .add(previousInstruction) - .build(); - instructionIterator.next(); - instructionIterator.set(newInstructionOrder); - IteratorUtils.skip(instructionIterator, -newInstructionOrder.size() - 1); + int numInstructions = constants.size() + 2; + for (int i = 0; i < numInstructions; ++i) { + instructionIterator.next().removeIgnoreValues(); + } + instructionIterator.addAll(constants); + instructionIterator.add(invoke); + instructionIterator.add(previousInstruction); + IteratorUtils.skip(instructionIterator, -numInstructions); } } @@ -121,8 +119,10 @@ } // Remove the constants and the invoke from the block. - constants.forEach(constant -> block.getInstructions().removeFirst()); - block.getInstructions().removeFirst(); + int numToRemove = constants.size() + 1; + for (int i = 0; i < numToRemove; ++i) { + block.entry().removeIgnoreValues(); + } // Add the constants and the invoke before the exit instruction in the predecessor block. InstructionListIterator predecessorInstructionIterator =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java index f031b5c..07b007c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
@@ -103,11 +103,10 @@ if (ifInstruction.isZeroTest()) { changed |= - replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, code, dominatorTree); + replaceDominatedConstNumbers(0, lhs, trueTarget, constantsByValue, dominatorTree); if (lhs.knownToBeBoolean()) { changed |= - replaceDominatedConstNumbers( - 1, lhs, falseTarget, constantsByValue, code, dominatorTree); + replaceDominatedConstNumbers(1, lhs, falseTarget, constantsByValue, dominatorTree); } } else { assert rhs != null; @@ -119,7 +118,6 @@ rhs, trueTarget, constantsByValue, - code, dominatorTree); if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { changed |= @@ -128,7 +126,6 @@ rhs, falseTarget, constantsByValue, - code, dominatorTree); } } else { @@ -140,7 +137,6 @@ lhs, trueTarget, constantsByValue, - code, dominatorTree); if (lhs.knownToBeBoolean() && rhs.knownToBeBoolean()) { changed |= @@ -149,7 +145,6 @@ lhs, falseTarget, constantsByValue, - code, dominatorTree); } } @@ -202,7 +197,6 @@ Value newValue, BasicBlock dominator, LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValueSupplier, - IRCode code, LazyBox<DominatorTree> dominatorTree) { if (newValue.hasLocalInfo()) { // We cannot replace a constant with a value that has local info, because that could change @@ -254,7 +248,7 @@ if (dominatorTree.computeIfAbsent().dominatedBy(block, dominator)) { if (newValue.getType().lessThanOrEqual(value.getType(), appView)) { value.replaceUsers(newValue); - block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead(); + constNumber.removeOrReplaceByDebugLocalRead(); constantWithValueIterator.remove(); changed = true; } else if (value.getType().isNullType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java index 74fa808..4eb842c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java
@@ -280,7 +280,7 @@ newBlocksWithStrings.add(newBlock); if (previous == null) { // Replace the string-switch instruction by a goto instruction. - block.exit().replace(new Goto(newBlock), code); + block.exit().replace(new Goto(), code); block.link(newBlock); } else { // Set the fallthrough block for the previously added if-instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java index 4a1a1d4..5b95f17 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java
@@ -23,7 +23,6 @@ class SwitchCaseEliminator { private final BasicBlock block; - private final BasicBlock defaultTarget; private final InstructionListIterator iterator; private final Switch theSwitch; @@ -35,7 +34,6 @@ SwitchCaseEliminator(Switch theSwitch, InstructionListIterator iterator) { this.block = theSwitch.getBlock(); - this.defaultTarget = theSwitch.fallthroughBlock(); this.iterator = iterator; this.theSwitch = theSwitch; } @@ -147,8 +145,7 @@ private void replaceSwitchByGoto() { assert !hasAlwaysHitCase() || alwaysHitTarget != null; - BasicBlock target = hasAlwaysHitCase() ? alwaysHitTarget : defaultTarget; - iterator.replaceCurrentInstruction(new Goto(target)); + iterator.replaceCurrentInstruction(new Goto()); } private void replaceSwitchByOptimizedSwitch(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java index 4a2d298..3b1d253 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -115,7 +115,7 @@ typeAnalysis -> typeAnalysis.setKeepRedundantBlocksAfterAssumeRemoval(true)); } if (block.size() != blockSizeBeforeAssumeRemoval) { - it = previous != null ? block.listIterator(code, previous) : block.listIterator(code); + it = block.listIterator(code, previous != null ? previous.getNext() : block.entry()); } } } else if (current.isInstanceOf()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java index d002613..92fd99c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -495,7 +495,7 @@ dexItemFactory.createMethod(configuration.getAssertionHandler()), null, ImmutableList.of(throwInstruction.exception()))); - Goto gotoBlockAfterAssertion = new Goto(throwingBlock); + Goto gotoBlockAfterAssertion = new Goto(); gotoBlockAfterAssertion.setPosition(throwInstruction.getPosition()); throwingBlock.link(throwSuccessorAfterHandler.get(throwInstruction)); iterator.add(gotoBlockAfterAssertion);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java index b57fd9b..cf599a6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -7,12 +7,12 @@ import com.android.tools.r8.ir.code.CatchHandlers; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionList; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.MethodConversionOptions; import com.android.tools.r8.ir.regalloc.RegisterAllocator; import com.google.common.base.Equivalence; import java.util.Arrays; -import java.util.Iterator; import java.util.List; class BasicBlockInstructionsEquivalence extends Equivalence<BasicBlock> { @@ -30,19 +30,19 @@ } private boolean hasIdenticalInstructions(BasicBlock first, BasicBlock second) { - List<Instruction> instructions0 = first.getInstructions(); - List<Instruction> instructions1 = second.getInstructions(); + InstructionList instructions0 = first.getInstructions(); + InstructionList instructions1 = second.getInstructions(); if (instructions0.size() != instructions1.size()) { return false; } - Iterator<Instruction> it0 = instructions0.iterator(); - Iterator<Instruction> it1 = instructions1.iterator(); - while (it0.hasNext()) { - Instruction i0 = it0.next(); - Instruction i1 = it1.next(); + Instruction i0 = instructions0.getFirstOrNull(); + Instruction i1 = instructions1.getFirstOrNull(); + while (i0 != null) { if (!i0.identicalAfterRegisterAllocation(i1, allocator, conversionOptions)) { return false; } + i0 = i0.getNext(); + i1 = i1.getNext(); } if (!allocator.hasEqualTypesAtEntry(first, second)) { @@ -93,21 +93,21 @@ } private int computeHash(BasicBlock basicBlock) { - List<Instruction> instructions = basicBlock.getInstructions(); + InstructionList instructions = basicBlock.getInstructions(); int hash = instructions.size(); int i = 0; - for (Instruction instruction : instructions) { + for (Instruction inst = instructions.getFirstOrNull(); inst != null; inst = inst.getNext()) { if (++i > MAX_HASH_INSTRUCTIONS) { break; } int hashPart = 0; - if (instruction.outValue() != null && instruction.outValue().needsRegister()) { - hashPart += allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber()); + if (inst.outValue() != null && inst.outValue().needsRegister()) { + hashPart += allocator.getRegisterForValue(inst.outValue(), inst.getNumber()); } - for (Value inValue : instruction.inValues()) { + for (Value inValue : inst.inValues()) { hashPart = hashPart << 4; if (inValue.needsRegister()) { - hashPart += allocator.getRegisterForValue(inValue, instruction.getNumber()); + hashPart += allocator.getRegisterForValue(inValue, inst.getNumber()); } } hash = hash * 3 + hashPart;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java index aa4bafe..f204dae 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -14,7 +14,6 @@ import com.android.tools.r8.ir.code.DebugLocalsChange; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.Position; @@ -230,12 +229,14 @@ Instruction instruction = it.next(); if (instruction.isMove()) { Move move = instruction.asMove(); + int dst = allocator.getRegisterForValue(move.dest(), move.getNumber()); if (unneededMoves.contains(move)) { - int dst = allocator.getRegisterForValue(move.dest(), move.getNumber()); int src = allocator.getRegisterForValue(move.src(), move.getNumber()); int mappedSrc = mapping.getOrDefault(src, src); mapping.put(dst, mappedSrc); it.removeInstructionIgnoreOutValue(); + } else { + mapping.remove(dst); } } else if (instruction.isDebugLocalsChange()) { DebugLocalsChange change = instruction.asDebugLocalsChange(); @@ -257,15 +258,13 @@ IntSet clobberedRegisters = new IntOpenHashSet(); // Backwards instruction scan collecting the registers used by actual instructions. boolean inEntrySpillMoves = false; - InstructionIterator it = block.iterator(block.getInstructions().size()); - while (it.hasPrevious()) { - Instruction instruction = it.previous(); - if (instruction == postSpillLocalsChange) { + for (Instruction ins = block.getLastInstruction(); ins != null; ins = ins.getPrev()) { + if (ins == postSpillLocalsChange) { inEntrySpillMoves = true; } // If this is a move in the block-entry spill moves check if it is unneeded. - if (inEntrySpillMoves && instruction.isMove()) { - Move move = instruction.asMove(); + if (inEntrySpillMoves && ins.isMove()) { + Move move = ins.asMove(); int dst = allocator.getRegisterForValue(move.dest(), move.getNumber()); int src = allocator.getRegisterForValue(move.src(), move.getNumber()); if (!usedRegisters.contains(dst) && !clobberedRegisters.contains(src)) { @@ -273,18 +272,17 @@ continue; } } - if (instruction.outValue() != null && instruction.outValue().needsRegister()) { - int register = - allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber()); + if (ins.outValue() != null && ins.outValue().needsRegister()) { + int register = allocator.getRegisterForValue(ins.outValue(), ins.getNumber()); // The register is defined anew, so uses before this are on distinct values. usedRegisters.remove(register); // Mark it clobbered to avoid any uses in locals after this point to become invalid. clobberedRegisters.add(register); } - if (!instruction.inValues().isEmpty()) { - for (Value inValue : instruction.inValues()) { + if (!ins.inValues().isEmpty()) { + for (Value inValue : ins.inValues()) { if (inValue.needsRegister()) { - int register = allocator.getRegisterForValue(inValue, instruction.getNumber()); + int register = allocator.getRegisterForValue(inValue, ins.getNumber()); // Record the register as being used. usedRegisters.add(register); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index 7a18075..a4e941c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -252,10 +252,7 @@ for (Instruction oldInstruction : entry.getValue()) { if (oldInstruction != newInstruction) { oldInstruction.outValue().replaceUsers(newInstruction.outValue()); - oldInstruction - .getBlock() - .listIterator(code, oldInstruction) - .removeOrReplaceByDebugLocalRead(); + oldInstruction.removeOrReplaceByDebugLocalRead(); // If the removed instruction is an insertion point for another constant, then record that // the constant should instead be inserted at the point where the removed instruction has
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index d73ab2a..add17fb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -660,7 +660,7 @@ code.prepareBlocksForCatchHandlers(); // Create a block for holding the monitor-exit instruction. - BasicBlock monitorExitBlock = new BasicBlock(); + BasicBlock monitorExitBlock = new BasicBlock(code.metadata()); monitorExitBlock.setNumber(code.getNextBlockNumber()); // For each block in the code that may throw, add a catch-all handler targeting the
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java index 84fe7f8..3f22277 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -11,7 +11,7 @@ import com.android.tools.r8.ir.code.Goto; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InstructionIterator; +import com.android.tools.r8.ir.code.InstructionList; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Value; @@ -26,7 +26,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.IdentityHashMap; -import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -157,17 +156,15 @@ // Remove the instruction from the normal successors. for (BasicBlock normalSuccessor : normalSuccessors) { - normalSuccessor.getInstructions().removeFirst(); + normalSuccessor.entry().removeIgnoreValues(); } // Move the instruction into the predecessor. if (instruction.isJumpInstruction()) { // Replace jump instruction in predecessor with the jump instruction from the normal // successors. - LinkedList<Instruction> instructions = block.getInstructions(); - instructions.removeLast(); - instructions.add(instruction); - instruction.setBlock(block); + block.getLastInstruction().removeIgnoreValues(); + block.getInstructions().addLast(instruction); // Take a copy of the old normal successors before performing a destructive update. List<BasicBlock> oldNormalSuccessors = new ArrayList<>(normalSuccessors); @@ -191,8 +188,7 @@ } } else { // Insert instruction before the jump instruction in the predecessor. - block.getInstructions().listIterator(block.getInstructions().size() - 1).add(instruction); - instruction.setBlock(block); + block.getInstructions().addBefore(instruction, block.getLastInstruction()); // Update locals-at-entry if needed. if (instruction.isDebugLocalsChange()) { @@ -238,7 +234,7 @@ BasicBlock normalExit = null; List<BasicBlock> normalExits = code.computeNormalExitBlocks(); if (normalExits.size() > 1) { - normalExit = new BasicBlock(); + normalExit = new BasicBlock(code.metadata()); normalExit.getMutablePredecessors().addAll(normalExits); blocks = new ArrayList<>(code.blocks); blocks.add(normalExit); @@ -259,8 +255,7 @@ if (pred.exit().isGoto() && pred.getSuccessors().size() == 1 && pred.getInstructions().size() > 1) { - List<Instruction> instructions = pred.getInstructions(); - Instruction lastInstruction = instructions.get(instructions.size() - 2); + Instruction lastInstruction = pred.getLastInstruction().getPrev(); List<BasicBlock> value = lastInstructionToBlocks.computeIfAbsent( equivalence.wrap(lastInstruction), (k) -> new ArrayList<>()); value.add(pred); @@ -297,7 +292,7 @@ } BasicBlock newBlock = createAndInsertBlockForSuffix( - code.getNextBlockNumber(), + code, commonSuffixSize, predsWithSameLastInstruction, block == normalExit ? null : block, @@ -318,7 +313,7 @@ } private static BasicBlock createAndInsertBlockForSuffix( - int blockNumber, + IRCode code, int suffixSize, List<BasicBlock> preds, BasicBlock successorBlock, @@ -326,49 +321,57 @@ BasicBlock first = preds.get(0); assert (successorBlock != null && first.exit().isGoto()) || (successorBlock == null && first.exit().isReturn()); - BasicBlock newBlock = new BasicBlock(); - newBlock.setNumber(blockNumber); - InstructionIterator from = first.iterator(first.getInstructions().size()); + BasicBlock newBlock = new BasicBlock(code.metadata()); + newBlock.setNumber(code.getNextBlockNumber()); Int2ReferenceMap<DebugLocalInfo> newBlockEntryLocals = null; if (first.getLocalsAtEntry() != null) { newBlockEntryLocals = new Int2ReferenceOpenHashMap<>(first.getLocalsAtEntry()); int prefixSize = first.getInstructions().size() - suffixSize; - InstructionIterator it = first.iterator(); + Instruction instruction = first.entry(); for (int i = 0; i < prefixSize; i++) { - Instruction instruction = it.next(); if (instruction.isDebugLocalsChange()) { instruction.asDebugLocalsChange().apply(newBlockEntryLocals); } + instruction = instruction.getNext(); } } allocator.addNewBlockToShareIdenticalSuffix(newBlock, suffixSize, preds); boolean movedThrowingInstruction = false; + Instruction instruction = first.getLastInstruction(); for (int i = 0; i < suffixSize; i++) { - Instruction instruction = from.previous(); movedThrowingInstruction = movedThrowingInstruction || instruction.instructionTypeCanThrow(); - newBlock.getInstructions().addFirst(instruction); - instruction.setBlock(newBlock); + instruction = instruction.getPrev(); } + newBlock + .getInstructions() + .severFrom(instruction == null ? first.entry() : instruction.getNext()); if (movedThrowingInstruction && first.hasCatchHandlers()) { newBlock.transferCatchHandlers(first); } + for (BasicBlock pred : preds) { - Position lastPosition = pred.getPosition(); - LinkedList<Instruction> instructions = pred.getInstructions(); - for (int i = 0; i < suffixSize; i++) { - instructions.removeLast(); + Position lastPosition; + InstructionList instructions = pred.getInstructions(); + if (pred == first) { + // Already removed from first via severFrom(). + lastPosition = newBlock.getPosition(); + } else { + lastPosition = pred.getPosition(); + for (int i = 0; i < suffixSize; i++) { + instructions.removeIgnoreValues(instructions.getLast()); + } } - for (Instruction instruction : pred.getInstructions()) { - if (instruction.getPosition().isSome()) { - lastPosition = instruction.getPosition(); + for (Instruction ins = instructions.getLastOrNull(); ins != null; ins = ins.getPrev()) { + if (ins.getPosition().isSome()) { + lastPosition = ins.getPosition(); + break; } } Goto jump = new Goto(); - jump.setBlock(pred); jump.setPosition(lastPosition); - instructions.add(jump); + instructions.addLast(jump); newBlock.getMutablePredecessors().add(pred); if (successorBlock != null) { pred.replaceSuccessor(successorBlock, newBlock); @@ -411,15 +414,15 @@ if (!Objects.equals(localsAtBlockExit(block0), localsAtBlockExit(block1))) { return 0; } - InstructionIterator it0 = block0.iterator(block0.getInstructions().size()); - InstructionIterator it1 = block1.iterator(block1.getInstructions().size()); + Instruction i0 = block0.getLastInstruction(); + Instruction i1 = block1.getLastInstruction(); int suffixSize = 0; - while (it0.hasPrevious() && it1.hasPrevious()) { - Instruction i0 = it0.previous(); - Instruction i1 = it1.previous(); + while (i0 != null && i1 != null) { if (!i0.identicalAfterRegisterAllocation(i1, allocator, code.getConversionOptions())) { return suffixSize; } + i0 = i0.getPrev(); + i1 = i1.getPrev(); suffixSize++; } return suffixSize; @@ -464,9 +467,8 @@ assert !otherPred.getPredecessors().contains(pred); otherPred.getMutablePredecessors().add(pred); Goto exit = new Goto(); - exit.setBlock(pred); exit.setPosition(otherPred.getPosition()); - pred.getInstructions().add(exit); + pred.getInstructions().addLast(exit); } else { blockToIndex.put(wrapper, predIndex); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java index 9a5e3b9..ec3a5a4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/PhiOptimizations.java
@@ -7,7 +7,6 @@ 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.InstructionIterator; import com.android.tools.r8.ir.code.Load; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Store; @@ -86,10 +85,8 @@ private static int getStackHeightAtInstructionBackwards(Instruction instruction) { int stackHeight = 0; BasicBlock block = instruction.getBlock(); - InstructionIterator it = block.iterator(block.getInstructions().size() - 1); - while (it.hasPrevious()) { - Instruction current = it.previous(); - if (current == instruction) { + for (Instruction ins = block.exit().getPrev(); ins != null; ins = ins.getPrev()) { + if (ins == instruction) { break; } stackHeight -= PeepholeHelper.numberOfValuesPutOnStack(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java index aa435cf..543d157 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -406,7 +406,7 @@ } else if (instruction.isInitClass()) { handleInitClass(it, instruction.asInitClass()); } else if (instruction.isMonitor()) { - if (instruction.asMonitor().isEnter()) { + if (instruction.isMonitorEnter()) { killAllNonFinalActiveFields(); } } else if (instruction.isInvokeDirect()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java index bcb57ce..06f3c5f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
@@ -65,7 +65,6 @@ // Split out the last recursive call in its own block. InstructionListIterator splitIterator = lastSelfRecursiveCall.getBlock().listIterator(code, lastSelfRecursiveCall); - splitIterator.previous(); BasicBlock newBlock = splitIterator.split(code, 1); // Generate rethrow block. DexType guard = appView.dexItemFactory().throwableType; @@ -251,7 +250,6 @@ && target.getNormalPredecessors().size() > 1 && target.getNormalSuccessors().size() > 1) { Instruction fixit = new AlwaysMaterializingNop(); - fixit.setBlock(handler); fixit.setPosition(handler.getPosition()); handler.getInstructions().addFirst(fixit); } @@ -362,12 +360,10 @@ // Forced definition of const-zero Value fixitValue = code.createValue(TypeElement.getInt()); Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue); - fixitDefinition.setBlock(addBefore.getBlock()); fixitDefinition.setPosition(addBefore.getPosition()); it.add(fixitDefinition); // Forced user of the forced definition to ensure it has a user and thus live range. Instruction fixitUser = new AlwaysMaterializingUser(fixitValue); - fixitUser.setBlock(addBefore.getBlock()); fixitUser.setPosition(addBefore.getPosition()); it.add(fixitUser); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java index 0e185da..a7cf6e1 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -14,7 +14,6 @@ import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Phi.RegisterReadType; @@ -125,14 +124,9 @@ @SuppressWarnings("ReferenceEquality") private Value getValueDefinedInTheBlock(BasicBlock block, Instruction stopAt) { - InstructionIterator iterator = - stopAt == null ? block.iterator(block.getInstructions().size()) : block.iterator(stopAt); - Instruction valueProducingInsn = null; - while (iterator.hasPrevious()) { - Instruction instruction = iterator.previous(); - assert instruction != null; - + Instruction instruction = stopAt != null ? stopAt : block.getLastInstruction(); + do { if (instruction == root || (instruction.isInstancePut() && instruction.asInstancePut().getField() == field @@ -140,7 +134,8 @@ valueProducingInsn = instruction; break; } - } + instruction = instruction.getPrev(); + } while (instruction != null); if (valueProducingInsn == null) { return null; @@ -151,7 +146,7 @@ assert root == valueProducingInsn; if (defaultValue == null) { - InstructionListIterator it = block.listIterator(code, root); + InstructionListIterator it = block.listIterator(code, root.getNext()); // If we met newInstance it means that default value is supposed to be used. if (field.type.isPrimitiveType()) { defaultValue =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index 403719e..da709f6 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -36,7 +36,6 @@ import com.android.tools.r8.ir.code.AliasedValueConfiguration; import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration; import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.BasicBlockInstructionListIterator; import com.android.tools.r8.ir.code.BasicBlockIterator; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.IRCode; @@ -634,7 +633,7 @@ if (user.isInstanceOf()) { InstanceOf instanceOf = user.asInstanceOf(); InstructionListIterator instructionIterator = - user.getBlock().listIterator(code, instanceOf); + user.getBlock().listIterator(code, instanceOf.getNext()); instructionIterator.replaceCurrentInstructionWithConstBoolean( code, appView.appInfo().isSubtype(eligibleClass.getType(), instanceOf.type())); continue; @@ -802,8 +801,7 @@ affectedValues.addAll(newValue.affectedValues()); } if (replacement != null) { - BasicBlockInstructionListIterator it = fieldRead.getBlock().listIterator(code, fieldRead); - it.replaceCurrentInstruction(replacement, affectedValues); + fieldRead.replace(replacement, affectedValues); } else { removeInstruction(fieldRead); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java index 8b566cf..5452d6e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -296,8 +296,7 @@ } if (ordinalToTargetMap.isEmpty()) { - switchInsn.replace( - Goto.builder().setTarget(block.getUniqueNormalSuccessor()).build(), code); + switchInsn.replace(new Goto(), code); } else { int[] keys = ordinalToTargetMap.keySet().toIntArray(); Arrays.sort(keys);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java index caf6e7e..0a54d91 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
@@ -1305,16 +1305,12 @@ returnValue = null; } Invoke outlineInvoke = new InvokeStatic(outlineMethod, returnValue, in); - outlineInvoke.setBlock(lastInstruction.getBlock()); + lastInstruction.getBlock(); outlineInvoke.setPosition( positionBuilder.hasOutlinePositions() ? positionBuilder.build() : Position.syntheticNone()); - InstructionListIterator endIterator = - lastInstruction.getBlock().listIterator(code, lastInstruction); - Instruction instructionBeforeEnd = endIterator.previous(); - assert instructionBeforeEnd == lastInstruction; - endIterator.set(outlineInvoke); + lastInstruction.replace(outlineInvoke); if (outlineInvoke.hasOutValue() && returnValue.getType().isReferenceType() && returnValue.getType().nullability().isDefinitelyNotNull()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java index 5fef766..c0ea809 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
@@ -58,18 +58,12 @@ InstructionListIterator it, List<Instruction> instructions) { assert !instructions.isEmpty(); for (Instruction instruction : instructions) { + // Add duplicate users to offset the users being removed by removeOrReplaceByDebugLocalRead(). for (Value inValue : instruction.inValues()) { inValue.addUser(instruction); } + instruction.removeOrReplaceByDebugLocalRead(); it.add(instruction); } - Instruction current = it.nextUntil(i -> i == instructions.get(0)); - for (int i = 0; i < instructions.size(); i++) { - assert current == instructions.get(i); - it.removeOrReplaceByDebugLocalRead(); - current = it.next(); - } - it.previousUntil(i -> i == instructions.get(instructions.size() - 1)); - it.next(); } }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/InsertMovesResult.java b/src/main/java/com/android/tools/r8/ir/regalloc/InsertMovesResult.java new file mode 100644 index 0000000..574ebee --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/regalloc/InsertMovesResult.java
@@ -0,0 +1,25 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +public class InsertMovesResult { + + private final LinearScanRegisterAllocator allocator; + private final int numberOfParallelMoveTemporaryRegisters; + + public InsertMovesResult( + LinearScanRegisterAllocator allocator, int numberOfParallelMoveTemporaryRegisters) { + this.allocator = allocator; + this.numberOfParallelMoveTemporaryRegisters = numberOfParallelMoveTemporaryRegisters; + } + + public int getNumberOfParallelMoveTemporaryRegisters() { + return numberOfParallelMoveTemporaryRegisters; + } + + public void revert() { + allocator.removeSpillAndPhiMoves(); + allocator.removeParallelMoveTemporaryRegisters(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java index 9e54599..7216583 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA; import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER; +import static com.google.common.base.Predicates.alwaysTrue; import com.android.tools.r8.cf.FixedLocalValue; import com.android.tools.r8.dex.Constants; @@ -29,6 +30,7 @@ import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.Move; +import com.android.tools.r8.ir.code.MoveException; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.Or; import com.android.tools.r8.ir.code.Phi; @@ -41,7 +43,9 @@ import com.android.tools.r8.ir.regalloc.RegisterPositions.RegisterType; import com.android.tools.r8.utils.ArrayUtils; import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.IntObjPredicate; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.LinkedHashSetUtils; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.SetUtils; @@ -60,10 +64,14 @@ import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntRBTreeSet; import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSortedSet; import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -75,8 +83,6 @@ import java.util.Map; import java.util.PriorityQueue; import java.util.Set; -import java.util.TreeSet; -import java.util.function.BiPredicate; import java.util.function.Predicate; /** @@ -195,13 +201,11 @@ private Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets; // The value of the first argument, or null if the method has no arguments. protected Value firstArgumentValue; - // The value of the last argument, or null if the method has no arguments. - private Value lastArgumentValue; // The current register allocation mode. private ArgumentReuseMode mode; // The set of registers that are free for allocation. - private TreeSet<Integer> freeRegisters = new TreeSet<>(); + private IntSortedSet freeRegisters = new IntRBTreeSet(); // The max register number used. private int maxRegisterNumber = -1; @@ -252,6 +256,10 @@ && isDedicatedMoveExceptionRegisterInLastLocalRegister()); } + private boolean isDedicatedMoveExceptionRegister(int register) { + return hasDedicatedMoveExceptionRegister() && register == getMoveExceptionRegister(); + } + private boolean isDedicatedMoveExceptionRegisterInFirstLocalRegister() { assert hasDedicatedMoveExceptionRegister(); if (mode.is4Bit() || mode.is16Bit()) { @@ -349,7 +357,6 @@ while (it.hasNext()) { Instruction instruction = it.next(); if (instruction.isDebugLocalRead()) { - instruction.clearDebugValues(); it.remove(); } } @@ -668,14 +675,46 @@ // Compute a table that for each register numbers contains the number of previous register // numbers that were unused. This table is then used to slide down the actual registers // used to fill the gaps. - private boolean computeUnusedRegisters() { + private void computeUnusedRegisters() { + unusedRegisters = internalComputeUnusedRegisters(); + } + + private void recomputeUnusedRegisters() { + int[] newUnusedRegisters = internalComputeUnusedRegisters(); + assert verifyNoUsesOfPreviouslyUnusedRegisters(newUnusedRegisters); + unusedRegisters = newUnusedRegisters; + } + + private int[] internalComputeUnusedRegisters() { if (mode.is4Bit() || registersUsed() == 0) { - return false; + return null; } // Compute the table based on the set of used registers. IntSet usedRegisters = computeUsedRegisters(); - unusedRegisters = computeUnusedRegistersFromUsedRegisters(usedRegisters); - return ArrayUtils.lastOrDefault(unusedRegisters, 0) > 0; + return computeUnusedRegistersFromUsedRegisters(usedRegisters); + } + + private boolean verifyNoChangesToUnusedRegisters() { + assert Arrays.equals(unusedRegisters, internalComputeUnusedRegisters()); + return true; + } + + private boolean verifyNoUsesOfPreviouslyUnusedRegisters(int[] newUnusedRegisters) { + // We only recompute the unused registers when at least one argument live intervals was unsplit, + // thus we always compute a non-null unused registers result. + assert unusedRegisters != null; + assert newUnusedRegisters != null; + assert unusedRegisters.length == newUnusedRegisters.length; + int previousNumberOfUnusedRegisters = 0; + int previousNumberOfNewUnusedRegisters = 0; + for (int i = 0; i < unusedRegisters.length; i++) { + boolean wasRegisterUnused = previousNumberOfUnusedRegisters != unusedRegisters[i]; + boolean isRegisterUnused = previousNumberOfNewUnusedRegisters != newUnusedRegisters[i]; + assert !wasRegisterUnused || isRegisterUnused; + previousNumberOfUnusedRegisters = unusedRegisters[i]; + previousNumberOfNewUnusedRegisters = newUnusedRegisters[i]; + } + return true; } private IntSet computeUsedRegisters() { @@ -689,8 +728,10 @@ } // Additionally, we have used temporary registers for parallel move scheduling, those // are used as well. - for (int i = firstParallelMoveTemporary; i < maxRegisterNumber + 1; i++) { - usedRegisters.add(i); + if (firstParallelMoveTemporary != NO_REGISTER) { + for (int i = firstParallelMoveTemporary; i < maxRegisterNumber + 1; i++) { + usedRegisters.add(i); + } } return usedRegisters; } @@ -704,12 +745,13 @@ } private int[] computeUnusedRegistersFromUsedRegisters(IntSet usedRegisters) { - assert firstParallelMoveTemporary != NO_REGISTER; int firstLocalRegister = numberOfArgumentRegisters + getMoveExceptionOffsetForLocalRegisters(); assert verifyRegistersBeforeFirstLocalRegisterAreUsed(firstLocalRegister, usedRegisters); - int numberOfParallelMoveTemporaryRegisters = registersUsed() - firstParallelMoveTemporary; + int registersUsed = unadjustedRegistersUsed(); + int numberOfParallelMoveTemporaryRegisters = + firstParallelMoveTemporary != NO_REGISTER ? registersUsed - firstParallelMoveTemporary : 0; int numberOfLocalRegisters = - registersUsed() - firstLocalRegister - numberOfParallelMoveTemporaryRegisters; + registersUsed - firstLocalRegister - numberOfParallelMoveTemporaryRegisters; int unused = 0; int[] unusedRegisters = new int[numberOfLocalRegisters]; for (int i = 0; i < numberOfLocalRegisters; i++) { @@ -742,6 +784,10 @@ return numberOfRegister; } + private int unadjustedRegistersUsed() { + return maxRegisterNumber + 1; + } + @Override public int getRegisterForValue(Value value, int instructionNumber) { if (value.isFixedRegisterValue()) { @@ -763,13 +809,19 @@ @Override public int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber) { - if (value.isArgument()) { - return getRegisterForIntervals(value.getLiveIntervals()); + if (isPinnedArgument(value)) { + return getArgumentRegisterForValue(value); } return getRegisterForValue(value, instructionNumber); } @Override + public int getArgumentRegisterForValue(Value value) { + assert value.isArgument(); + return getRegisterForIntervals(value.getLiveIntervals().getSplitParent()); + } + + @Override public InternalOptions options() { return appView.options(); } @@ -807,7 +859,7 @@ this.mode = mode; if (retry) { - clearRegisterAssignments(mode); + clearRegisterAssignments(); removeSpillAndPhiMoves(); } @@ -815,15 +867,55 @@ boolean succeeded = performLinearScan(mode); if (succeeded) { - insertMoves(); - // Now that we know the max register number we can compute whether it is safe to use - // argument registers in place. If it is, we redo move insertion to get rid of the moves - // caused by splitting of the argument registers. - if (unsplitArguments()) { - removeSpillAndPhiMoves(); - insertMoves(); - } + InsertMovesResult insertMovesResult = insertMoves(); + + // We only compute unused information for local registers (temporary registers for parallel + // move scheduling and argument registers are never unused). Therefore, we can already compute + // unused registers now. This can help lead to more aggressive argument unsplitting, since + // this effectively lowers the real argument registers. computeUnusedRegisters(); + + // After having finished move insertion (which can allocate temporary registers for parallel + // move scheduling), we now know the final registers of the arguments. If we have moved some + // arguments down to low registers, but the input argument register itself ended up being in a + // low register, then we can avoid the move into a low register by just using the argument + // register directly. This is achieved by updating the register assignment to argument split + // live intervals. + UnsplitArgumentsResult unsplitArgumentsResult = unsplitArguments(); + if (unsplitArgumentsResult != null) { + // If any changes were made, we need to redo move insertion. + insertMovesResult.revert(); + InsertMovesResult newInsertMovesResult = insertMoves(); + int iterations = 0; + + // In some cases, the new move insertion may lead to more temporary registers being used for + // parallel move scheduling. This is rare (e.g., never happens when compiling JetNews). If + // that happened, the argument registers are now in higher registers, meaning we may have + // invalidated the argument unsplitting. We therefore (partially) revert the argument + // unsplitting and redo move insertion. + while (newInsertMovesResult.getNumberOfParallelMoveTemporaryRegisters() + > insertMovesResult.getNumberOfParallelMoveTemporaryRegisters()) { + assert iterations < 5; + boolean changed = unsplitArgumentsResult.revertPartial(); + if (changed) { + // We invalidated the unsplit arguments optimization (or some of it). Redo move + // insertion and check again. + insertMovesResult = newInsertMovesResult; + insertMovesResult.revert(); + newInsertMovesResult = insertMoves(); + } else { + // Although we used more parallel move temporary registers this did not invalidate the + // unsplit arguments result. + break; + } + iterations++; + } + if (unsplitArgumentsResult.isFullyReverted()) { + assert verifyNoChangesToUnusedRegisters(); + } else { + recomputeUnusedRegisters(); + } + } } else { assert mode.is4Bit(); } @@ -879,6 +971,8 @@ assert !result.is8Bit() || highestUsedRegister() <= Constants.U8BIT_MAX; assert !result.is16Bit() || highestUsedRegister() <= Constants.U16BIT_MAX; + new MoveSorter(code).sortMovesForSuffixSharing(); + return result; } @@ -886,46 +980,40 @@ // we can get the argument into low enough registers at uses that require low numbers. After // register allocation we can check if it is safe to just use the argument register itself // for all uses and thereby avoid moving argument values around. - private boolean unsplitArguments() { + private UnsplitArgumentsResult unsplitArguments() { if (mode.is4Bit()) { - return false; + return null; } - boolean argumentRegisterUnsplit = false; + Reference2IntMap<LiveIntervals> originalRegisterAssignment = new Reference2IntOpenHashMap<>(); + originalRegisterAssignment.defaultReturnValue(NO_REGISTER); for (Value current = firstArgumentValue; current != null; current = current.getNextConsecutive()) { LiveIntervals intervals = current.getLiveIntervals(); + int conservativeRealRegisterEnd = realRegisterNumberFromAllocated(intervals.getRegisterEnd()); assert !mode.hasRegisterConstraint(intervals) || (mode.is8BitRefinement() && intervals.getRegisterEnd() < numberOf4BitArgumentRegisters); - boolean canUseArgumentRegister = true; - boolean couldUseArgumentRegister = true; for (LiveIntervals child : intervals.getSplitChildren()) { - int registerConstraint = child.getRegisterLimit(); - if (registerConstraint < Constants.U16BIT_MAX) { - couldUseArgumentRegister = false; - - if (registerConstraint < highestUsedRegister()) { - canUseArgumentRegister = false; - break; - } - } - } - if (canUseArgumentRegister && !couldUseArgumentRegister) { - // Only return true if there is a constrained use where it turns out that we can use the - // original argument register. This way we will not unnecessarily redo move insertion. - argumentRegisterUnsplit = true; - for (LiveIntervals child : intervals.getSplitChildren()) { + if (!child.isInvokeRangeIntervals() + && conservativeRealRegisterEnd <= child.getRegisterLimit() + && child.getRegister() != intervals.getRegister()) { + originalRegisterAssignment.put(child, child.getRegister()); child.clearRegisterAssignment(); child.setRegister(intervals.getRegister()); - child.setSpilled(false); + // If the child could be spilled then we would need to unset it here + update + // UnsplitArgumentsResult#revertPartial to account for this. + assert !child.isSpilled(); } } } - return argumentRegisterUnsplit; + if (!originalRegisterAssignment.isEmpty()) { + return new UnsplitArgumentsResult(this, originalRegisterAssignment); + } + return null; } - private void removeSpillAndPhiMoves() { + void removeSpillAndPhiMoves() { for (BasicBlock block : code.blocks) { InstructionListIterator it = block.listIterator(code); while (it.hasNext()) { @@ -937,6 +1025,13 @@ } } + void removeParallelMoveTemporaryRegisters() { + if (firstParallelMoveTemporary != NO_REGISTER) { + maxRegisterNumber = firstParallelMoveTemporary - 1; + firstParallelMoveTemporary = NO_REGISTER; + } + } + private static boolean isSpillInstruction(Instruction instruction) { Value outValue = instruction.outValue(); if (outValue != null && outValue.isFixedRegisterValue()) { @@ -951,20 +1046,22 @@ return false; } - private void clearRegisterAssignments(ArgumentReuseMode mode) { + private void clearRegisterAssignments() { freeRegisters.clear(); maxRegisterNumber = -1; active.clear(); expiredHere.clear(); + firstParallelMoveTemporary = NO_REGISTER; inactive.clear(); unhandled.clear(); moveExceptionIntervals.clear(); for (LiveIntervals intervals : liveIntervals) { - if (mode.is8BitRefinement() || mode.is8BitRetry() || mode.is16Bit()) { - intervals.undoSplits(); + intervals.undoSplits(); + if (intervals.hasRegister()) { intervals.setSpilled(false); } intervals.clearRegisterAssignment(); + intervals.unsetIsInvokeRangeIntervals(); } } @@ -1017,8 +1114,8 @@ unhandled.addAll(liveIntervals); processArgumentLiveIntervals(); - allocateRegistersForMoveExceptionIntervals(); - splitLiveIntervalsForInvokeRange(); + boolean hasInvokeRangeLiveIntervals = splitLiveIntervalsForInvokeRange(); + allocateRegistersForMoveExceptionIntervals(hasInvokeRangeLiveIntervals); // Go through each unhandled live interval and find a register for it. while (!unhandled.isEmpty()) { @@ -1028,14 +1125,11 @@ setHintForDestRegOfCheckCast(unhandledInterval); setHintToPromote2AddrInstruction(unhandledInterval); - // If this interval value is the src of an argument move. Fix the registers for the - // consecutive arguments now and add hints to the move sources. This looks forward - // and propagate hints backwards to avoid many moves in connection with ranged invokes. - if (options().getTestingOptions().enableLiveIntervalsSplittingForInvokeRange) { - allocateRegistersForInvokeRangeSplits(unhandledInterval); - } else { - allocateArgumentIntervalsWithSrc(unhandledInterval); - } + // If this interval value has an invoke/rangerange user, then fix the registers for the + // consecutive arguments now and add hints to the live intervals leading up to this + // invoke/range. This looks forward and propagate hints backwards to avoid many moves in + // connection with ranged invokes. + allocateRegistersForInvokeRangeSplits(unhandledInterval); if (unhandledInterval.getRegister() != NO_REGISTER) { // The value itself is in the chain that has now gotten registers allocated. continue; @@ -1044,15 +1138,13 @@ advanceStateToLiveIntervals(unhandledInterval); // Perform the actual allocation. - if (unhandledInterval.isLinked() && !unhandledInterval.isArgumentInterval()) { - assert !options().getTestingOptions().enableLiveIntervalsSplittingForInvokeRange; - allocateLinkedIntervals(unhandledInterval, false); - } else if (!allocateSingleInterval(unhandledInterval)) { + if (!allocateSingleInterval(unhandledInterval)) { return false; } expiredHere.clear(); } + assert invariantsHold(mode); return true; } @@ -1061,7 +1153,7 @@ argumentValue != null; argumentValue = argumentValue.getNextConsecutive()) { LiveIntervals argumentInterval = argumentValue.getLiveIntervals(); - assert argumentInterval.getRegister() != NO_REGISTER; + assert argumentInterval.hasRegister(); unhandled.remove(argumentInterval); if (!mode.hasRegisterConstraint(argumentInterval)) { // All the argument intervals are active in the beginning and have preallocated registers. @@ -1100,7 +1192,7 @@ } } - private void allocateRegistersForMoveExceptionIntervals() { + private void allocateRegistersForMoveExceptionIntervals(boolean hasInvokeRangeLiveIntervals) { // We have to be careful when it comes to the register allocated for a move exception // instruction. For move exception instructions there is no place to put spill or // restore moves. The move exception instruction has to be the first instruction in a @@ -1109,50 +1201,68 @@ // When we allow argument reuse we do not allow any splitting, therefore we cannot get into // trouble with move exception registers. When argument reuse is disallowed we block a fixed // register to be used only by move exception instructions. - if (mode.is8Bit() || mode.is16Bit()) { - // Force all move exception ranges to start out with the exception in a fixed register. - for (BasicBlock block : code.blocks) { - Instruction instruction = block.entry(); - if (instruction.isMoveException()) { - LiveIntervals intervals = instruction.outValue().getLiveIntervals(); - unhandled.remove(intervals); - moveExceptionIntervals.add(intervals); - intervals.setRegister(getMoveExceptionRegister()); - } + if (mode.is4Bit() && !hasInvokeRangeLiveIntervals) { + return; + } + // Force all move exception ranges to start out with the exception in a fixed register. + for (BasicBlock block : code.blocks(block -> block.entry().isMoveException())) { + MoveException moveException = block.entry().asMoveException(); + LiveIntervals intervals = moveException.outValue().getLiveIntervals(); + if (intervals.getValue().hasAnyUsers()) { + LiveIntervals split = intervals.splitAfter(intervals.getValue().getDefinition()); + unhandled.add(split); } - if (hasDedicatedMoveExceptionRegister()) { - int moveExceptionRegister = getMoveExceptionRegister(); - assert moveExceptionRegister == maxRegisterNumber + 1; - increaseCapacity(moveExceptionRegister, true); + if (intervals.getStart() < moveException.getNumber()) { + intervals = intervals.splitBefore(moveException); + } else { + unhandled.remove(intervals); } - // Split their live ranges which will force another register if used. - for (LiveIntervals intervals : moveExceptionIntervals) { - if (intervals.getUses().size() > 1) { - LiveIntervals split = - intervals.splitBefore(intervals.getFirstUse() + INSTRUCTION_NUMBER_DELTA); - unhandled.add(split); - } - } - for (LiveIntervals intervals : moveExceptionIntervals) { - assert intervals.getRegisterLimit() == Constants.U8BIT_MAX; - } + moveExceptionIntervals.add(intervals); + intervals.setRegister(getMoveExceptionRegister()); + } + if (hasDedicatedMoveExceptionRegister()) { + int moveExceptionRegister = getMoveExceptionRegister(); + assert moveExceptionRegister == maxRegisterNumber + 1; + increaseCapacity(moveExceptionRegister, false); } } - private void splitLiveIntervalsForInvokeRange() { - if (!options().getTestingOptions().enableLiveIntervalsSplittingForInvokeRange) { - return; - } + private boolean splitLiveIntervalsForInvokeRange() { + boolean hasInvokeRangeLiveIntervals = false; for (LiveIntervals intervals : liveIntervals) { Value value = intervals.getValue(); - for (Invoke invoke : value.<Invoke>uniqueUsers(this::isInvokeRange)) { + for (Invoke invoke : value.<Invoke>uniqueUsers(this::needsInvokeRangeLiveIntervals)) { LiveIntervals overlappingIntervals = intervals.getSplitCovering(invoke.getNumber()); - LiveIntervals invokeRangeIntervals = overlappingIntervals.splitBefore(invoke); - if (invoke.getNumber() < invokeRangeIntervals.getEnd()) { - invokeRangeIntervals.splitAfter(invoke); + LiveIntervals invokeRangeIntervals; + if (overlappingIntervals.getStart() == toGapPosition(invoke.getNumber())) { + invokeRangeIntervals = overlappingIntervals; + } else { + invokeRangeIntervals = overlappingIntervals.splitBefore(invoke); + unhandled.add(invokeRangeIntervals); } + invokeRangeIntervals.setIsInvokeRangeIntervals(); + if (invoke.getNumber() + 1 < invokeRangeIntervals.getEnd()) { + LiveIntervals successorIntervals = invokeRangeIntervals.splitAfter(invoke); + unhandled.add(successorIntervals); + } + hasInvokeRangeLiveIntervals = true; } } + return hasInvokeRangeLiveIntervals; + } + + private boolean needsInvokeRangeLiveIntervals(Instruction instruction) { + Invoke invoke = instruction.asInvoke(); + if (invoke == null || invoke.requiredArgumentRegisters() <= 5) { + return false; + } + if (argumentsAreAlreadyLinked(invoke) + && Iterables.all( + invoke.arguments(), + argument -> isPinnedArgumentRegister(argument.getLiveIntervals()))) { + return false; + } + return true; } private void advanceStateToLiveIntervals(LiveIntervals unhandledInterval) { @@ -1200,7 +1310,7 @@ } private boolean invariantsHold(ArgumentReuseMode mode) { - TreeSet<Integer> computedFreeRegisters = new TreeSet<>(); + IntSortedSet computedFreeRegisters = new IntRBTreeSet(); for (int register = 0; register <= maxRegisterNumber; ++register) { computedFreeRegisters.add(register); } @@ -1212,22 +1322,18 @@ computedFreeRegisters.remove(register); }); } - if (mode.is8Bit() || mode.is16Bit()) { - // Each time an argument interval is active, we currently require that it is present in its - // original, incoming argument register. - for (LiveIntervals activeIntervals : active) { - if (activeIntervals.isArgumentInterval() - && activeIntervals != activeIntervals.getSplitParent()) { - LiveIntervals parent = activeIntervals.getSplitParent(); - if (parent.getRegister() != activeIntervals.getRegister()) { - activeIntervals - .getSplitParent() - .forEachRegister( - register -> { - assert computedFreeRegisters.contains(register); - computedFreeRegisters.remove(register); - }); - } + // All active argument intervals that are pinned must be present in its original, incoming + // argument register. + for (LiveIntervals activeIntervals : active) { + if (isPinnedArgumentRegister(activeIntervals)) { + assert !mode.is4Bit() || activeIntervals.getValue().isThis(); + LiveIntervals parent = activeIntervals.getSplitParent(); + if (parent.getRegister() != activeIntervals.getRegister()) { + parent.forEachRegister( + register -> { + assert computedFreeRegisters.contains(register); + computedFreeRegisters.remove(register); + }); } } } @@ -1265,14 +1371,17 @@ for (Value argumentValue = firstArgumentValue; argumentValue != null; argumentValue = argumentValue.getNextConsecutive()) { - assert !interval.hasConflictingRegisters(argumentValue.getLiveIntervals()) - || !argumentValue.getLiveIntervals().anySplitOverlaps(interval); + LiveIntervals argumentIntervals = argumentValue.getLiveIntervals(); + assert interval.getSplitParent() == argumentIntervals + || !isPinnedArgumentRegister(argumentIntervals) + || !interval.hasConflictingRegisters(argumentIntervals) + || !argumentIntervals.anySplitOverlaps(interval); } return true; } private void setHintForDestRegOfCheckCast(LiveIntervals unhandledInterval) { - if (unhandledInterval.getHint() != null) { + if (unhandledInterval.hasHint()) { return; } Value value = unhandledInterval.getValue(); @@ -1292,7 +1401,7 @@ * that is the left interval or the right interval if possible when intervals do not overlap. */ private void setHintToPromote2AddrInstruction(LiveIntervals unhandledInterval) { - if (unhandledInterval.getHint() != null) { + if (unhandledInterval.hasHint()) { return; } Value value = unhandledInterval.getValue(); @@ -1320,90 +1429,21 @@ * allocated and have been moved from unhandled to inactive. The move sources have their hints * updated. The rest of the register allocation state is unchanged. */ - // TODO(b/270398965): Replace LinkedList. @SuppressWarnings("JdkObsolete") - private void allocateArgumentIntervalsWithSrc(LiveIntervals srcInterval) { - Value value = srcInterval.getValue(); - for (Instruction instruction : value.uniqueUsers()) { - // If there is a move user that is an argument move, we allocate the consecutive - // registers for the argument intervals and propagate the selected registers back as - // hints to the sources. - if (instruction.isMove() && instruction.asMove().dest().isLinked()) { - Move move = instruction.asMove(); - Value dest = move.dest(); - LiveIntervals destIntervals = dest.getLiveIntervals(); - if (destIntervals.getRegister() == NO_REGISTER) { - // Save the current register allocation state so we can restore it at the end. - TreeSet<Integer> savedFreeRegisters = new TreeSet<>(freeRegisters); - int savedMaxRegisterNumber = maxRegisterNumber; - List<LiveIntervals> savedInactive = new LinkedList<>(inactive); - - // Add all the active intervals to the inactive set. When allocating linked intervals we - // check all inactive intervals and exclude the registers for overlapping inactive - // intervals. - for (LiveIntervals active : active) { - // TODO(ager): We could allow the use of all the currently active registers for the - // ranged invoke (by adding the registers for all the active intervals to freeRegisters - // here). That could lead to lower register pressure. However, it would also often mean - // that we cannot allocate the right argument register to the current unhandled - // interval. Size measurements on GMSCore indicate that blocking the current active - // registers works the best for code size. - if (active.isArgumentInterval()) { - // Allow the ranged invoke to use argument registers if free. This improves register - // allocation for bridge methods that forwards all of their arguments after check-cast - // checks on their types. - freeOccupiedRegistersForIntervals(active); - } - inactive.add(active); - } - - // Allocate the argument intervals. - unhandled.remove(destIntervals); - // Since we are going to do a look-ahead, there may be argument live interval splits, - // which are currently unhandled, but would be inactive at the invoke-range instruction. - // Thus, the implementation of allocateLinkedIntervals needs to exclude the argument - // registers for which there exists a split that overlaps with one of the inputs to the - // invoke-range instruction. We handle this situation by setting the following flag. - boolean excludeUnhandledOverlappingArgumentIntervals = !mode.is4Bit(); - unhandled.add(srcInterval); - allocateLinkedIntervals(destIntervals, excludeUnhandledOverlappingArgumentIntervals); - active.remove(destIntervals); - unhandled.remove(srcInterval); - // Restore the register allocation state. - freeRegisters = savedFreeRegisters; - // In case maxRegisterNumber has changed, update freeRegisters. - for (int i = savedMaxRegisterNumber + 1; i <= maxRegisterNumber; i++) { - freeRegisters.add(i); - } - - inactive = savedInactive; - // Move all the argument intervals to the inactive set. - LiveIntervals current = destIntervals.getStartOfConsecutive(); - while (current != null) { - assert !inactive.contains(current); - assert !active.contains(current); - assert !unhandled.contains(current); - inactive.add(current); - current = current.getNextConsecutive(); - } - } - } - } - } - private void allocateRegistersForInvokeRangeSplits(LiveIntervals unhandledIntervals) { - // Since we are going to do a look-ahead, there may be argument live interval splits, - // which are currently unhandled, but would be inactive at the invoke-range instruction. - // Thus, the implementation of allocateLinkedIntervals needs to exclude the argument - // registers for which there exists a split that overlaps with one of the inputs to the - // invoke-range instruction. We handle this situation by setting the following flag. - boolean excludeUnhandledOverlappingArgumentIntervals = !mode.is4Bit(); - Value value = unhandledIntervals.getValue(); - for (Invoke invoke : value.<Invoke>uniqueUsers(this::isInvokeRange)) { + for (Invoke invoke : value.<Invoke>uniqueUsers(this::needsInvokeRangeLiveIntervals)) { LiveIntervals overlappingIntervals = unhandledIntervals.getSplitParent().getSplitCovering(invoke); - if (overlappingIntervals.getRegister() != NO_REGISTER) { + if (overlappingIntervals.hasRegister()) { + assert invoke.arguments().stream() + .allMatch( + invokeArgument -> { + LiveIntervals overlappingInvokeArgumentIntervals = + invokeArgument.getLiveIntervals().getSplitCovering(invoke); + assert overlappingInvokeArgumentIntervals.hasRegister(); + return true; + }); continue; } List<LiveIntervals> intervalsList = @@ -1412,105 +1452,146 @@ invokeArgument -> { LiveIntervals overlappingInvokeArgumentIntervals = invokeArgument.getLiveIntervals().getSplitCovering(invoke); - assert overlappingIntervals.getRegister() == NO_REGISTER; - assert overlappingIntervals.getStart() == invoke.getNumber() - 1; - assert overlappingIntervals.getEnd() == invoke.getNumber(); + assert !overlappingInvokeArgumentIntervals.hasRegister(); + assert overlappingInvokeArgumentIntervals.getStart() == invoke.getNumber() - 1; + assert overlappingInvokeArgumentIntervals.getEnd() == invoke.getNumber() + || overlappingInvokeArgumentIntervals.getEnd() == invoke.getNumber() + 1; return overlappingInvokeArgumentIntervals; }); - allocateLinkedIntervals( - overlappingIntervals, excludeUnhandledOverlappingArgumentIntervals, intervalsList); - } - } - private void allocateLinkedIntervals( - LiveIntervals unhandledInterval, boolean excludeUnhandledOverlappingArgumentIntervals) { - List<LiveIntervals> intervalsList = new ArrayList<>(); - for (LiveIntervals intervals = unhandledInterval.getStartOfConsecutive(); - intervals != null; - intervals = intervals.getNextConsecutive()) { - intervalsList.add(intervals); - } - allocateLinkedIntervals( - unhandledInterval, excludeUnhandledOverlappingArgumentIntervals, intervalsList); - } + // Save the current register allocation state so we can restore it at the end. + IntSortedSet savedFreeRegisters = new IntRBTreeSet(freeRegisters); + int savedMaxRegisterNumber = maxRegisterNumber; - private void allocateLinkedIntervals( - LiveIntervals unhandledInterval, - boolean excludeUnhandledOverlappingArgumentIntervals, - List<LiveIntervals> intervalsList) { - LiveIntervals start = ListUtils.first(intervalsList); - - // Exclude the registers that overlap the start of one of the live ranges we are - // going to assign registers to now. - IntSet excludedRegisters = new IntArraySet(); - for (LiveIntervals inactiveIntervals : inactive) { - if (Iterables.any(intervalsList, inactiveIntervals::overlaps)) { - excludeRegistersForInterval(inactiveIntervals, excludedRegisters); - } - } - if (excludeUnhandledOverlappingArgumentIntervals) { - // Exclude the argument registers for which there exists a split that overlaps with one of - // the inputs to the invoke-range instruction. - for (Value argument = firstArgumentValue; - argument != null; - argument = argument.getNextConsecutive()) { - LiveIntervals argumentLiveIntervals = argument.getLiveIntervals(); - if (liveIntervalsHasUnhandledSplitOverlappingAnyOf(argumentLiveIntervals, intervalsList)) { - excludeRegistersForInterval(argumentLiveIntervals, excludedRegisters); + // Simulate adding all the active intervals to the inactive set by blocking their register if + // they overlap with any of the invoke/range intervals. + for (LiveIntervals active : active) { + // We could allow the use of all the currently active registers for the ranged invoke (by + // adding the registers for all the active intervals to freeRegisters here). That could lead + // to lower register pressure. However, it would also often mean that we cannot allocate the + // right argument register to the current unhandled interval. Size measurements on GMSCore + // indicate that blocking the current active registers works the best for code size. + if (Iterables.any(intervalsList, active::overlaps)) { + excludeRegistersForInterval(active); + } else if (active.isArgumentInterval()) { + // Allow the ranged invoke to use argument registers if free. This improves register + // allocation for bridge methods that forwards all of their arguments after check-cast + // checks on their types. + freeOccupiedRegistersForIntervals(active); } } + + unhandled.removeAll(intervalsList); + allocateLinkedIntervals(intervalsList, invoke); + + // Restore the register allocation state. + freeRegisters = savedFreeRegisters; + // In case maxRegisterNumber has changed, update freeRegisters. + for (int i = savedMaxRegisterNumber + 1; i <= maxRegisterNumber; i++) { + freeRegisters.add(i); + } + // Move all the argument intervals to the inactive set. + inactive.addAll(intervalsList); } - // Exclude move exception register if the first interval overlaps a move exception interval. - // It is not necessary to check the remaining consecutive intervals, since we always use - // register 0 (after remapping) for the argument register. - if (hasDedicatedMoveExceptionRegister()) { - boolean canUseMoveExceptionRegisterForLinkedIntervals = - isDedicatedMoveExceptionRegisterInFirstLocalRegister() - && !overlapsMoveExceptionInterval(start); - if (!canUseMoveExceptionRegisterForLinkedIntervals - && freeRegisters.remove(getMoveExceptionRegister())) { - excludedRegisters.add(getMoveExceptionRegister()); + } + + private void allocateLinkedIntervals(List<LiveIntervals> intervalsList, Invoke invoke) { + LiveIntervals start = ListUtils.first(intervalsList); + + boolean consecutiveArguments = + IterableUtils.allWithPrevious( + intervalsList, + (current, previous) -> + previous == null + || current.getSplitParent().getPreviousConsecutive() + == previous.getSplitParent()); + boolean consecutivePinnedArguments = + consecutiveArguments && Iterables.all(intervalsList, this::isPinnedArgumentRegister); + + int nextRegister; + if (consecutivePinnedArguments) { + // We can use the arguments from their input registers. + nextRegister = start.getSplitParent().getRegister(); + } else { + // Ensure that there is a free register for the out value (or two consecutive registers if + // wide). + int numberOfRegisters = getNumberOfRequiredRegisters(intervalsList); + int numberOfOutRegisters = invoke.hasOutValue() ? invoke.outValue().requiredRegisters() : 0; + if (numberOfOutRegisters > 0 + && numberOfRegisters + numberOfOutRegisters - 1 > Constants.U4BIT_MAX) { + int firstLocalRegister = numberOfArgumentRegisters; + if (hasDedicatedMoveExceptionRegister() + && isDedicatedMoveExceptionRegisterInFirstLocalRegister()) { + firstLocalRegister++; + } + ensureCapacity(firstLocalRegister + numberOfOutRegisters - 1); + for (int i = 0; i < numberOfOutRegisters; i++) { + freeRegisters.remove(firstLocalRegister + i); + } + } + + // Exclude the registers that overlap the start of one of the live ranges we are going to + // assign registers to now. + for (LiveIntervals inactiveIntervals : inactive) { + if (Iterables.any(intervalsList, inactiveIntervals::overlaps)) { + excludeRegistersForInterval(inactiveIntervals); + } + } + + if (consecutiveArguments + && registerRangeIsFree(start.getSplitParent().getRegister(), numberOfRegisters)) { + // For consecutive arguments we always to use the input argument registers, if they are + // free. + nextRegister = start.getSplitParent().getRegister(); + } else { + // Exclude the pinned argument registers for which there exists a split that overlaps with + // one of the inputs to the invoke-range instruction. + for (Value argument = firstArgumentValue; + argument != null; + argument = argument.getNextConsecutive()) { + LiveIntervals argumentLiveIntervals = argument.getLiveIntervals(); + if (isPinnedArgumentRegister(argumentLiveIntervals) + && liveIntervalsOverlappingAnyOf(argumentLiveIntervals, intervalsList)) { + excludeRegistersForInterval(argumentLiveIntervals); + } + } + // Exclude move exception register if the first interval overlaps a move exception interval. + // It is not necessary to check the remaining consecutive intervals, since we always use + // register 0 (after remapping) for the argument register. + if (hasDedicatedMoveExceptionRegister()) { + boolean canUseMoveExceptionRegisterForLinkedIntervals = + isDedicatedMoveExceptionRegisterInFirstLocalRegister() + && (!start.isLiveAtMoveExceptionEntry() || !overlapsMoveExceptionInterval(start)); + if (!canUseMoveExceptionRegisterForLinkedIntervals) { + freeRegisters.remove(getMoveExceptionRegister()); + } + } + // Select registers. + nextRegister = getFreeConsecutiveRegisters(numberOfRegisters); } } - // Select registers. - int numberOfRegisters = getNumberOfRequiredRegisters(intervalsList); - int nextRegister = getFreeConsecutiveRegisters(numberOfRegisters); + + // Assign registers. for (LiveIntervals current : intervalsList) { current.setRegister(nextRegister); assert verifyRegisterAssignmentNotConflictingWithArgument(current); - // Propagate hints to the move sources. - Value value = current.getValue(); - if (value.isDefinedByInstructionSatisfying(Instruction::isMove)) { - Move move = value.getDefinition().asMove(); - LiveIntervals intervals = move.src().getLiveIntervals(); - intervals.setHint(current, unhandled); - } - if (current != unhandledInterval) { - // Only the start of unhandledInterval has been reached at this point. All other live - // intervals in the chain have been assigned registers but their start has not yet been - // reached. Therefore, they belong in the inactive set. - unhandled.remove(current); - inactive.add(current); - } nextRegister += current.requiredRegisters(); } - assert unhandledInterval.getRegister() != NO_REGISTER; - takeFreeRegistersForIntervals(unhandledInterval); - active.add(unhandledInterval); - // Include the registers for inactive ranges that we had to exclude for this allocation. - freeRegisters.addAll(excludedRegisters); - - if (options().getTestingOptions().enableLiveIntervalsSplittingForInvokeRange) { - for (LiveIntervals intervals : intervalsList) { - LiveIntervals parentIntervals = intervals.getSplitParent(); - parentIntervals.setHint(intervals, unhandled); - for (LiveIntervals siblingIntervals : parentIntervals.getSplitChildren()) { - if (siblingIntervals != intervals && !siblingIntervals.hasRegister()) { - siblingIntervals.setHint(intervals, unhandled); - } + // Add hints. + for (LiveIntervals intervals : intervalsList) { + LiveIntervals parentIntervals = intervals.getSplitParent(); + parentIntervals.setHint(intervals, unhandled); + for (LiveIntervals siblingIntervals : parentIntervals.getSplitChildren()) { + if (siblingIntervals != intervals && !siblingIntervals.hasRegister()) { + siblingIntervals.setHint(intervals, unhandled); } } + Value value = intervals.getValue(); + if (value.isDefinedByInstructionSatisfying(Instruction::isMove)) { + Move move = value.getDefinition().asMove(); + move.src().getLiveIntervals().setHint(intervals, unhandled); + } } } @@ -1522,13 +1603,13 @@ return requiredRegisters; } - // Returns true if intervals has an unhandled split, which overlaps with chain or any of its - // consecutives. - private boolean liveIntervalsHasUnhandledSplitOverlappingAnyOf( + // Returns true if intervals has a split, which overlaps with any of the live intervals in the + // given list. + private boolean liveIntervalsOverlappingAnyOf( LiveIntervals intervals, List<LiveIntervals> intervalsList) { assert intervals == intervals.getSplitParent(); for (LiveIntervals split : intervals.getSplitChildren()) { - if (unhandled.contains(split) && Iterables.any(intervalsList, split::overlaps)) { + if (Iterables.any(intervalsList, split::overlaps)) { return true; } } @@ -1554,7 +1635,7 @@ return intervals.getSplitParent().getRegister(); } - TreeSet<Integer> previousFreeRegisters = new TreeSet<>(freeRegisters); + IntSortedSet previousFreeRegisters = new IntRBTreeSet(freeRegisters); int previousMaxRegisterNumber = maxRegisterNumber; freeRegisters.removeAll(expiredHere); if (excludedRegisters != null) { @@ -1657,7 +1738,8 @@ // Check for overlap with the move exception interval. boolean overlapsMoveExceptionInterval = - hasDedicatedMoveExceptionRegister() + intervals.isLiveAtMoveExceptionEntry() + && hasDedicatedMoveExceptionRegister() && (register == getMoveExceptionRegister() || (intervals.getType().isWide() && register + 1 == getMoveExceptionRegister())) && overlapsMoveExceptionInterval(intervals); @@ -1712,7 +1794,7 @@ // Is the array-get array register the same as the first register we are // allocating for the result? - private boolean isArrayGetArrayRegister(LiveIntervals intervals, int register) { + private boolean isArrayGetArrayRegister(int register, LiveIntervals intervals) { assert needsArrayGetWideWorkaround(intervals); Value array = intervals.getValue().definition.asArrayGet().array(); int arrayReg = @@ -1752,7 +1834,7 @@ // Is one of the cmp-long argument registers the same as the register we are // allocating for the result? - private boolean isSingleResultOverlappingLongOperands(LiveIntervals intervals, int register) { + private boolean isSingleResultOverlappingLongOperands(int register, LiveIntervals intervals) { assert needsSingleResultOverlappingLongOperandsWorkaround(intervals); if (intervals.getValue().definition.isCmp()) { Value left = intervals.getValue().definition.asCmp().leftValue(); @@ -1818,7 +1900,7 @@ } private boolean isLongResultOverlappingLongOperands( - LiveIntervals unhandledInterval, int register) { + int register, LiveIntervals unhandledInterval) { assert needsLongResultOverlappingLongOperandsWorkaround(unhandledInterval); Value left = unhandledInterval.getValue().definition.asBinop().leftValue(); Value right = unhandledInterval.getValue().definition.asBinop().rightValue(); @@ -1897,7 +1979,7 @@ // Just use the argument register if an argument split has no register constraint. That will // avoid move generation for the argument. - if (unhandledInterval.isArgumentInterval()) { + if (isPinnedArgumentRegister(unhandledInterval)) { if (registerConstraint == Constants.U16BIT_MAX || (mode.is8Bit() && registerConstraint == Constants.U8BIT_MAX)) { int argumentRegister = unhandledInterval.getSplitParent().getRegister(); @@ -2014,7 +2096,19 @@ freePositions.setBlocked(0); } - if (!mode.is4Bit()) { + if (mode.is4Bit()) { + // We may block the receiver register. + if (firstArgumentValue != null + && isPinnedArgumentRegister(firstArgumentValue.getLiveIntervals())) { + firstArgumentValue.getLiveIntervals().forEachRegister(freePositions::setBlocked); + } + // But not any of the other argument registers. + for (Value argument = firstArgumentValue; + argument != null; + argument = argument.getNextConsecutive()) { + assert !isPinnedArgumentRegister(argument.getLiveIntervals()) || argument.isThis(); + } + } else { // Generally argument reuse is not allowed and we block all the argument registers so that // arguments are never free. // @@ -2055,10 +2149,12 @@ // place to put a spill move (because the move exception instruction has to be the // first instruction in the handler block). if (hasDedicatedMoveExceptionRegister()) { - if (unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX + if (!mode.is4Bit() + && unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX && isDedicatedMoveExceptionRegisterInLastLocalRegister()) { freePositions.setBlocked(getMoveExceptionRegister()); - } else if (overlapsMoveExceptionInterval(unhandledInterval)) { + } else if (unhandledInterval.isLiveAtMoveExceptionEntry() + && overlapsMoveExceptionInterval(unhandledInterval)) { int moveExceptionRegister = getMoveExceptionRegister(); if (moveExceptionRegister <= registerConstraint) { freePositions.setBlocked(moveExceptionRegister); @@ -2107,17 +2203,50 @@ // Attempt to use the register hint for the unhandled interval in order to avoid generating // moves. - private boolean useRegisterHint(LiveIntervals unhandledInterval, int registerConstraint, - RegisterPositions freePositions, boolean needsRegisterPair) { + private boolean useRegisterHint( + LiveIntervals unhandledInterval, + int registerConstraint, + RegisterPositions freePositions, + boolean needsRegisterPair) { // If the unhandled interval has a hint we give it that register if it is available without // spilling. For phis we also use the hint before looking at the operand registers. The // phi could have a hint from an argument moves which it seems more important to honor in // practice. - Integer hint = unhandledInterval.getHint(); - if (hint != null) { - if (tryHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair, hint)) { - return true; - } + IntSet triedHints = new IntArraySet(); + if (unhandledInterval.hasHint() + && triedHints.add(unhandledInterval.getHint()) + && tryHint( + unhandledInterval, + registerConstraint, + freePositions, + needsRegisterPair, + unhandledInterval.getHint())) { + return true; + } + + LiveIntervals previousSplit = unhandledInterval.getPreviousSplit(); + if (previousSplit != null + && triedHints.add(previousSplit.getRegister()) + && tryHint( + unhandledInterval, + registerConstraint, + freePositions, + needsRegisterPair, + previousSplit.getRegister())) { + return true; + } + + LiveIntervals nextSplit = unhandledInterval.getNextSplit(); + if (nextSplit != null + && nextSplit.hasRegister() + && triedHints.add(nextSplit.getRegister()) + && tryHint( + unhandledInterval, + registerConstraint, + freePositions, + needsRegisterPair, + nextSplit.getRegister())) { + return true; } // If there is no hint or it cannot be applied we search for a good register for phis using @@ -2131,12 +2260,11 @@ for (int i = 0; i < operands.size(); i++) { LiveIntervals intervals = operands.get(i).getLiveIntervals(); if (intervals.hasSplits()) { - BasicBlock pred = phi.getBlock().getPredecessors().get(i); + BasicBlock pred = phi.getBlock().getPredecessor(i); intervals = intervals.getSplitCovering(pred.exit().getNumber()); } - int operandRegister = intervals.getRegister(); - if (operandRegister != NO_REGISTER) { - map.add(operandRegister); + if (intervals.hasRegister()) { + map.add(intervals.getRegister()); } } for (Multiset.Entry<Integer> entry : Multisets.copyHighestCountFirst(map).entrySet()) { @@ -2165,7 +2293,7 @@ return false; } if (freePositions.isBlocked(register, needsRegisterPair)) { - return tryAllocateBlockedHint(unhandledInterval); + return tryAllocateBlockedHint(unhandledInterval, register); } int freePosition = freePositions.get(register); if (needsRegisterPair) { @@ -2176,39 +2304,80 @@ } // Check for overlapping long registers issue. if (needsLongResultOverlappingLongOperandsWorkaround(unhandledInterval) - && isLongResultOverlappingLongOperands(unhandledInterval, register)) { + && isLongResultOverlappingLongOperands(register, unhandledInterval)) { return false; } // Check for aget-wide bug in recent Art VMs. if (needsArrayGetWideWorkaround(unhandledInterval) - && isArrayGetArrayRegister(unhandledInterval, register)) { + && isArrayGetArrayRegister(register, unhandledInterval)) { return false; } assignFreeRegisterToUnhandledInterval(unhandledInterval, register); return true; } - private boolean tryAllocateBlockedHint(LiveIntervals unhandledInterval) { + private boolean tryAllocateBlockedHint(LiveIntervals unhandledInterval, int candidate) { if (!options().getTestingOptions().enableRegisterHintsForBlockedRegisters) { return false; } LiveIntervals nextSplit = unhandledInterval.getNextSplit(); - int candidate = nextSplit != null ? nextSplit.getRegister() : NO_REGISTER; - if (candidate == NO_REGISTER || unhandledInterval.getEnd() != nextSplit.getStart()) { + int alternativeHint = nextSplit != null ? nextSplit.getRegister() : NO_REGISTER; + if (candidate != alternativeHint) { return false; } - // Find the value occupying the register of interest. + if (needsArrayGetWideWorkaround(unhandledInterval) + || needsLongResultOverlappingLongOperandsWorkaround(unhandledInterval)) { + return false; + } + if (isArgumentRegister(candidate)) { + for (Value argument = firstArgumentValue; + argument != null; + argument = argument.getNextConsecutive()) { + if (isPinnedArgument(argument)) { + return false; + } + } + } + if (isDedicatedMoveExceptionRegister(candidate)) { + return false; + } + if (!getLiveIntervalsWithRegister( + inactive, unhandledInterval, candidate, unhandledInterval::overlaps) + .isEmpty()) { + return false; + } + // Find the value occupying the register of interest. Note that the current live intervals may + // be blocked by an inactive (overlapping) live intervals. Collection<LiveIntervals> blockingIntervals = - getLiveIntervalsWithRegister(unhandledInterval, candidate); + getLiveIntervalsWithRegister(active, unhandledInterval, candidate); assert !blockingIntervals.isEmpty(); - if (blockingIntervals.size() > 1) { + if (blockingIntervals.size() != 1) { + // Validate that not finding any blocking live intervals means the current live intervals is + // blocked by an inactive live intervals. return false; } LiveIntervals blockingInterval = blockingIntervals.iterator().next(); + if (unhandledInterval.getType().isWide()) { + if (blockingInterval.getRegister() != candidate || !blockingInterval.getType().isWide()) { + // Conservatively bail out. It could be that the low-half of the register pair is blocked by + // an inactive live intervals. + return false; + } + } + if (isArgumentRegister(candidate) && isPinnedArgumentRegister(blockingInterval)) { + return false; + } + if (toInstructionPosition(blockingInterval.getStart()) + == toInstructionPosition(unhandledInterval.getStart())) { + return false; + } if (hasConstrainedUseInRange( blockingInterval, unhandledInterval.getStart(), unhandledInterval.getEnd())) { return false; } + if (!expiredHere.isEmpty()) { + return false; + } LiveIntervals split = blockingInterval.splitBefore(unhandledInterval.getStart()); freeOccupiedRegistersForIntervals(blockingInterval); assignFreeRegisterToUnhandledInterval(unhandledInterval, blockingInterval.getRegister()); @@ -2217,12 +2386,20 @@ return true; } - private Collection<LiveIntervals> getLiveIntervalsWithRegister( - LiveIntervals unhandledInterval, int register) { + private static Collection<LiveIntervals> getLiveIntervalsWithRegister( + List<LiveIntervals> intervalsList, LiveIntervals unhandledInterval, int register) { + return getLiveIntervalsWithRegister(intervalsList, unhandledInterval, register, alwaysTrue()); + } + + private static Collection<LiveIntervals> getLiveIntervalsWithRegister( + List<LiveIntervals> intervalsList, + LiveIntervals unhandledInterval, + int register, + Predicate<LiveIntervals> predicate) { LiveIntervals intervalsWithRegister = null; boolean isWide = unhandledInterval.getType().isWide(); - for (LiveIntervals intervals : active) { - if (!intervals.usesRegister(register, isWide)) { + for (LiveIntervals intervals : intervalsList) { + if (!intervals.usesRegister(register, isWide) || !predicate.test(intervals)) { continue; } if (!isWide || intervals.usesBothRegisters(register, register + 1)) { @@ -2233,6 +2410,9 @@ } intervalsWithRegister = intervals; } + if (intervalsWithRegister != null) { + return Collections.singleton(intervalsWithRegister); + } return Collections.emptyList(); } @@ -2257,14 +2437,14 @@ // phi and do not have hints yet. for (Phi phi : value.uniquePhiUsers()) { LiveIntervals phiIntervals = phi.getLiveIntervals(); - if (phiIntervals.getHint() == null) { + if (!phiIntervals.hasHint()) { phiIntervals.setHint(intervals, unhandled); for (int i = 0; i < phi.getOperands().size(); i++) { Value operand = phi.getOperand(i); LiveIntervals operandIntervals = operand.getLiveIntervals(); BasicBlock pred = phi.getBlock().getPredecessors().get(i); operandIntervals = operandIntervals.getSplitCovering(pred.exit().getNumber()); - if (operandIntervals.getHint() == null) { + if (!operandIntervals.hasHint()) { operandIntervals.setHint(intervals, unhandled); } } @@ -2343,7 +2523,7 @@ private int handleWorkaround( Predicate<LiveIntervals> workaroundNeeded, - BiPredicate<LiveIntervals, Integer> workaroundNeededForCandidate, + IntObjPredicate<LiveIntervals> workaroundNeededForCandidate, int candidate, LiveIntervals unhandledInterval, int registerConstraint, @@ -2352,7 +2532,7 @@ RegisterType type) { if (workaroundNeeded.test(unhandledInterval)) { int lastCandidate = candidate; - while (workaroundNeededForCandidate.test(unhandledInterval, candidate)) { + while (workaroundNeededForCandidate.test(candidate, unhandledInterval)) { // Make the unusable register unavailable for allocation and try again. freePositions.setBlockedTemporarily(candidate); candidate = @@ -2470,7 +2650,8 @@ // Disallow reuse of the move exception register if we have reserved one. if (hasDedicatedMoveExceptionRegister()) { - if (unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX + if (!mode.is4Bit() + && unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX && isDedicatedMoveExceptionRegisterInLastLocalRegister()) { usePositions.setBlocked(getMoveExceptionRegister()); } else if (overlapsMoveExceptionInterval(unhandledInterval)) { @@ -2480,10 +2661,8 @@ // Treat active and inactive linked argument intervals as pinned. They cannot be given another // register at their uses. - blockLinkedRegisters( - active, unhandledInterval, registerConstraint, usePositions, blockedPositions); - blockLinkedRegisters(inactive, unhandledInterval, registerConstraint, usePositions, - blockedPositions); + blockInvokeRangeIntervals( + unhandledInterval, registerConstraint, usePositions, blockedPositions); // Get the register (pair) that has the highest use position. boolean needsRegisterPair = unhandledInterval.getType().isWide(); @@ -2789,31 +2968,42 @@ } } - private void blockLinkedRegisters( - List<LiveIntervals> intervalsList, LiveIntervals interval, int registerConstraint, - RegisterPositions usePositions, RegisterPositions blockedPositions) { - for (LiveIntervals other : intervalsList) { - if (other.isLinked()) { - int register = other.getRegister(); - if (register <= registerConstraint && other.overlaps(interval)) { - for (int i = 0; i < other.requiredRegisters(); i++) { - if (register + i <= registerConstraint) { - int firstUse = other.firstUseAfter(interval.getStart()); - if (firstUse < blockedPositions.get(register + i)) { - blockedPositions.set(register + i, firstUse, other); - // If we start blocking registers other than linked arguments, we might need to - // explicitly update the use positions as well as blocked positions. - assert usePositions.isBlocked(register + i) - || usePositions.get(register + i) <= blockedPositions.get(register + i); + private void blockInvokeRangeIntervals( + LiveIntervals unhandledInterval, + int registerConstraint, + RegisterPositions usePositions, + RegisterPositions blockedPositions) { + // TODO(b/302281605): The only way there can be active invoke-range intervals is if we have a + // live intervals that have been split right before the invoke range instruction. If we had a + // mapping from instruction number to the invoke range instruction, we could find the invoke + // range live intervals directly without scanning all active intervals. Moreover, we could + // avoid checking if the intervals overlap, since they clearly do. + for (LiveIntervals intervals : Iterables.concat(active, inactive)) { + if (!intervals.isInvokeRangeIntervals()) { + continue; + } + int registerStart = intervals.getRegister(); + if (registerStart <= registerConstraint && intervals.overlaps(unhandledInterval)) { + intervals.forEachRegister( + register -> { + if (register <= registerConstraint) { + int firstUse = intervals.firstUseAfter(unhandledInterval.getStart()); + if (firstUse < blockedPositions.get(register)) { + blockedPositions.set(register, firstUse, intervals); + // If we start blocking registers other than linked arguments, we might need to + // explicitly update the use positions as well as blocked positions. + assert usePositions.isBlocked(register) + || usePositions.get(register) <= blockedPositions.get(register); + } } - } - } - } + }); } } } - private void insertMoves() { + // Returns the number of added parallel move temporary registers. + private InsertMovesResult insertMoves() { + assert firstParallelMoveTemporary == NO_REGISTER; computeRematerializableBits(); SpillMoveSet spillMoves = new SpillMoveSet(this, code, appView); @@ -2826,7 +3016,7 @@ split != null; split = sortedChildren.poll()) { int position = split.getStart(); - if (!isPinnedArgumentRegister(split)) { + if (!canSkipArgumentMove(split)) { spillMoves.addSpillOrRestoreMove(toGapPosition(position), split, current); } current = split; @@ -2835,8 +3025,14 @@ } resolveControlFlow(spillMoves); - firstParallelMoveTemporary = maxRegisterNumber + 1; - maxRegisterNumber += spillMoves.scheduleAndInsertMoves(maxRegisterNumber + 1); + int firstParallelMoveTemporaryRegister = maxRegisterNumber + 1; + int numberOfParallelMoveTemporaryRegisters = + spillMoves.scheduleAndInsertMoves(firstParallelMoveTemporaryRegister); + if (numberOfParallelMoveTemporaryRegisters > 0) { + firstParallelMoveTemporary = firstParallelMoveTemporaryRegister; + maxRegisterNumber += numberOfParallelMoveTemporaryRegisters; + } + return new InsertMovesResult(this, numberOfParallelMoveTemporaryRegisters); } private void computeRematerializableBits() { @@ -2881,7 +3077,7 @@ LiveIntervals parentInterval = value.getLiveIntervals(); LiveIntervals fromIntervals = parentInterval.getSplitCovering(fromInstruction); LiveIntervals toIntervals = parentInterval.getSplitCovering(toInstruction); - if (isPinnedArgumentRegister(toIntervals)) { + if (canSkipArgumentMove(toIntervals)) { // No need to add resolution moves to pinned argument registers. continue; } @@ -2910,19 +3106,43 @@ } } + public boolean isPinnedArgument(Value value) { + return value.isArgument() && isPinnedArgumentRegister(value.getLiveIntervals()); + } + boolean isPinnedArgumentRegister(LiveIntervals intervals) { if (!intervals.isArgumentInterval()) { return false; } - assert intervals.getRegister() != NO_REGISTER; + LiveIntervals parentIntervals = intervals.getSplitParent(); + assert parentIntervals.hasRegister(); + if (mode.is4Bit()) { + // We don't pin argument registers in 4 bit mode, unless we have to. + if (options().shouldCompileMethodInDebugMode(code.context()) + || options().canHaveThisTypeVerifierBug() + || options().canHaveThisJitCodeDebuggingBug()) { + return parentIntervals.getValue().isThis(); + } + return false; + } + return true; + } + + public boolean isArgumentRegister(int register) { + return register < numberOfArgumentRegisters; + } + + boolean canSkipArgumentMove(LiveIntervals intervals) { + if (!isPinnedArgumentRegister(intervals)) { + return false; + } + assert intervals.hasRegister(); if (intervals.getRegister() >= numberOfArgumentRegisters) { return false; } - if (mode.is8BitRefinement()) { - // An 8 bit argument register could be moved to a 4 bit argument register. - if (intervals.getRegister() != intervals.getSplitParent().getRegister()) { - return false; - } + // An argument register could be moved to another argument register. + if (intervals.getRegister() != intervals.getSplitParent().getRegister()) { + return false; } return true; } @@ -2976,23 +3196,27 @@ private void computeLiveRanges() { computeLiveRanges(appView, code, liveAtEntrySets, liveIntervals); + boolean hasMoveException = false; + for (BasicBlock block : code.blocks(block -> block.entry().isMoveException())) { + for (Value value : liveAtEntrySets.get(block).liveValues) { + value.getLiveIntervals().setIsLiveAtMoveExceptionEntry(); + } + hasMoveException = true; + } // Art VMs before Android M assume that the register for the receiver never changes its value. // This assumption is used during verification. Allowing the receiver register to be // overwritten can therefore lead to verification errors. If we could be targeting one of these // VMs we block the receiver register throughout the method. if ((options().canHaveThisTypeVerifierBug() || options().canHaveThisJitCodeDebuggingBug()) - && !code.method().accessFlags.isStatic()) { - for (Instruction instruction : code.entryBlock().getInstructions()) { - if (instruction.isArgument() && instruction.outValue().isThis()) { - Value thisValue = instruction.outValue(); - LiveIntervals thisIntervals = thisValue.getLiveIntervals(); - thisIntervals.getRanges().clear(); - thisIntervals.addRange(new LiveRange(0, code.getNextInstructionNumber())); - for (LiveAtEntrySets values : liveAtEntrySets.values()) { - values.liveValues.add(thisValue); - } - return; - } + && !code.method().getAccessFlags().isStatic()) { + LiveIntervals thisIntervals = firstArgumentValue.getLiveIntervals(); + thisIntervals.getRanges().clear(); + thisIntervals.addRange(new LiveRange(0, code.getNextInstructionNumber())); + for (LiveAtEntrySets values : liveAtEntrySets.values()) { + values.liveValues.add(firstArgumentValue); + } + if (hasMoveException) { + thisIntervals.setIsLiveAtMoveExceptionEntry(); } } } @@ -3029,9 +3253,9 @@ } } LinkedHashSetUtils.addAll(live, phiOperands); - List<Instruction> instructions = block.getInstructions(); + int numInstructionsDelta = block.getInstructions().size() * INSTRUCTION_NUMBER_DELTA; for (Value value : live) { - int end = block.entry().getNumber() + instructions.size() * INSTRUCTION_NUMBER_DELTA; + int end = block.entry().getNumber() + numInstructionsDelta; // Make sure that phi operands do not overlap the phi live range. The phi operand is // not live until the next instruction, but only until the gap before the next instruction // where the phi value takes over. @@ -3040,10 +3264,8 @@ } addLiveRange(value, block, end, liveIntervals, code); } - InstructionIterator iterator = block.iterator(block.getInstructions().size()); - while (iterator.hasPrevious()) { - Instruction instruction = iterator.previous(); - Value definition = instruction.outValue(); + for (Instruction ins = block.getLastInstruction(); ins != null; ins = ins.getPrev()) { + Value definition = ins.outValue(); if (definition != null) { // For instructions that define values which have no use create a live range covering // the instruction. This will typically be instructions that can have side effects even @@ -3056,23 +3278,23 @@ addLiveRange( definition, block, - instruction.getNumber() + INSTRUCTION_NUMBER_DELTA - 1, + ins.getNumber() + INSTRUCTION_NUMBER_DELTA - 1, liveIntervals, code); - assert !code.getConversionOptions().isGeneratingClassFiles() || instruction.isArgument() + assert !code.getConversionOptions().isGeneratingClassFiles() || ins.isArgument() : "Arguments should be the only potentially unused local in CF"; } live.remove(definition); } - for (Value use : instruction.inValues()) { + for (Value use : ins.inValues()) { if (use.needsRegister()) { - assert unconstrainedForCf(instruction.maxInValueRegister(), code); + assert unconstrainedForCf(ins.maxInValueRegister(), code); if (!live.contains(use)) { live.add(use); - addLiveRange(use, block, instruction.getNumber(), liveIntervals, code); + addLiveRange(use, block, ins.getNumber(), liveIntervals, code); } if (code.getConversionOptions().isGeneratingDex()) { - int inConstraint = instruction.maxInValueRegister(); + int inConstraint = ins.maxInValueRegister(); LiveIntervals useIntervals = use.getLiveIntervals(); // Arguments are always kept in their original, incoming register. For every // unconstrained use of an argument we therefore use its incoming register. @@ -3086,9 +3308,9 @@ // it in the argument register, the register allocator would use two registers for the // argument but in reality only use one. boolean isUnconstrainedArgumentUse = - use.isArgument() && inConstraint == Constants.U16BIT_MAX; + use.isArgument() && inConstraint == Constants.U16BIT_MAX && !isInvokeRange(ins); if (!isUnconstrainedArgumentUse) { - useIntervals.addUse(new LiveIntervalsUse(instruction.getNumber(), inConstraint)); + useIntervals.addUse(new LiveIntervalsUse(ins.getNumber(), inConstraint)); } } } @@ -3100,24 +3322,20 @@ // 'r1 <- check-cast r0' maps to 'move r1, r0; check-cast r1' and when that // happens r1 could be clobbered on the exceptional edge if r1 initially contained // a value that is used in the exceptional code. - if (instruction.instructionTypeCanThrow()) { + if (ins.instructionTypeCanThrow()) { for (Value use : liveAtThrowingInstruction) { if (use.needsRegister() && !live.contains(use)) { live.add(use); addLiveRange( - use, - block, - getLiveRangeEndOnExceptionalFlow(instruction, use), - liveIntervals, - code); + use, block, getLiveRangeEndOnExceptionalFlow(ins, use), liveIntervals, code); } } } if (appView.options().debug || code.context().isReachabilitySensitive()) { // In debug mode, or if the method is reachability sensitive, extend the live range // to cover the full scope of a local variable (encoded as debug values). - int number = instruction.getNumber(); - List<Value> sortedDebugValues = new ArrayList<>(instruction.getDebugValues()); + int number = ins.getNumber(); + List<Value> sortedDebugValues = new ArrayList<>(ins.getDebugValues()); sortedDebugValues.sort(Value::compareTo); for (Value use : sortedDebugValues) { assert use.needsRegister(); @@ -3250,121 +3468,30 @@ newArgument.addUser(invoke); } - private void generateArgumentMoves(Invoke invoke, InstructionListIterator insertAt) { - // If the invoke instruction require more than 5 registers we link the inputs because they - // need to be in consecutive registers. - if (invoke.requiredArgumentRegisters() > 5 && !argumentsAreAlreadyLinked(invoke)) { - List<Value> arguments = invoke.arguments(); - Value previous = null; - - PriorityQueue<Move> insertAtDefinition = null; - if (invoke.requiredArgumentRegisters() > 16) { - insertAtDefinition = - new PriorityQueue<>( - (x, y) -> x.src().definition.getNumber() - y.src().definition.getNumber()); - - // Number the instructions in this basic block such that we can order the moves according - // to the positions of the instructions that define the srcs of the moves. Note that this - // is a local numbering of the instructions. These instruction numbers will be recomputed - // just before the liveness analysis. - BasicBlock block = invoke.getBlock(); - if (block.entry().getNumber() == -1) { - block.numberInstructions(0); - } + private void ensureUniqueArgumentsToInvokeRangeInstructions( + Invoke invoke, InstructionListIterator instructionIterator) { + Set<Value> seen = Sets.newIdentityHashSet(); + for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) { + Value argument = invoke.getArgument(argumentIndex); + if (seen.add(argument)) { + continue; } - - for (int i = 0; i < arguments.size(); i++) { - Value argument = arguments.get(i); - Value newArgument = argument; - // In debug mode, we have debug instructions that are also moves. Do not generate another - // move if there already is a move instruction that we can use. We generate moves if: - // - // 1. the argument is not defined by a move, - // - // 2. the argument is already linked or would cause a cycle if linked, or - // - // 3. the argument has a register constraint (the argument moves are there to make the - // input value to a ranged invoke unconstrained.) - if (argument.definition == null || - !argument.definition.isMove() || - argument.isLinked() || - argument == previous || - argument.hasRegisterConstraint()) { - newArgument = createValue(argument.getType()); - Move move = new Move(newArgument, argument); - move.setBlock(invoke.getBlock()); - replaceArgument(invoke, i, newArgument); - - boolean argumentIsDefinedInSameBlock = - argument.definition != null && argument.definition.getBlock() == invoke.getBlock(); - if (invoke.requiredArgumentRegisters() > 16 && argumentIsDefinedInSameBlock) { - // Heuristic: Insert the move immediately after the argument. This increases the - // likelyhood that we will be able to move the argument directly into the register it - // needs to be in for the ranged invoke. - // - // If we instead were to insert the moves immediately before the ranged invoke when - // there are many arguments, then there is a high risk that we will need to spill the - // arguments before they get moved to the correct register right before the invoke. - assert move.src().definition.getNumber() >= 0; - insertAtDefinition.add(move); - move.setPosition(argument.definition.getPosition()); - } else { - insertAt.add(move); - move.setPosition(invoke.getPosition()); - } - } - if (previous != null) { - previous.linkTo(newArgument); - } - previous = newArgument; - } - - if (insertAtDefinition != null && !insertAtDefinition.isEmpty()) { - generateArgumentMovesAtDefinitions(invoke, insertAtDefinition, insertAt); - } + Value newArgument = createValue(argument.getType()); + Move move = new Move(newArgument, argument); + move.setPosition(invoke.getPosition()); + replaceArgument(invoke, argumentIndex, newArgument); + instructionIterator.add(move); } } - private void generateArgumentMovesAtDefinitions( - Invoke invoke, PriorityQueue<Move> insertAtDefinition, InstructionListIterator insertAt) { - Move move = insertAtDefinition.poll(); - // Rewind instruction iterator to the position where the first move needs to be inserted. - Instruction previousDefinition = - move.src().isArgument() ? lastArgumentValue.definition : move.src().definition; - while (insertAt.peekPrevious() != previousDefinition) { - insertAt.previous(); - } - // Insert the instructions one by one after their definition. - insertAt.add(move); - while (!insertAtDefinition.isEmpty()) { - move = insertAtDefinition.poll(); - Instruction currentDefinition = - move.src().isArgument() ? lastArgumentValue.definition : move.src().definition; - assert currentDefinition.getNumber() >= previousDefinition.getNumber(); - if (currentDefinition.getNumber() > previousDefinition.getNumber()) { - // Move the instruction iterator forward to where this move needs to be inserted. - while (insertAt.peekPrevious() != currentDefinition) { - insertAt.next(); - } - } - insertAt.add(move); - // Update state. - previousDefinition = currentDefinition; - } - // Move the instruction iterator forward to its old position. - while (insertAt.peekNext() != invoke) { - insertAt.next(); - } - } - - private boolean isInvokeRange(Instruction instruction) { + private static boolean isInvokeRange(Instruction instruction) { Invoke invoke = instruction.asInvoke(); return invoke != null && invoke.requiredArgumentRegisters() > 5 && !argumentsAreAlreadyLinked(invoke); } - private boolean argumentsAreAlreadyLinked(Invoke invoke) { + private static boolean argumentsAreAlreadyLinked(Invoke invoke) { Iterator<Value> it = invoke.arguments().iterator(); Value current = it.next(); while (it.hasNext()) { @@ -3399,7 +3526,6 @@ last.getLiveIntervals().link(next.getLiveIntervals()); last = next; } - lastArgumentValue = last; } } @@ -3411,18 +3537,15 @@ } private void insertRangeInvokeMoves() { - if (options().getTestingOptions().enableLiveIntervalsSplittingForInvokeRange) { - return; - } for (BasicBlock block : code.blocks) { InstructionListIterator it = block.listIterator(code); while (it.hasNext()) { Instruction instruction = it.next(); - if (instruction.isInvoke()) { + if (isInvokeRange(instruction)) { // Rewind so moves are inserted before the invoke. it.previous(); // Generate the argument moves. - generateArgumentMoves(instruction.asInvoke(), it); + ensureUniqueArgumentsToInvokeRangeInstructions(instruction.asInvoke(), it); // Move past the move again. it.next(); } @@ -3480,10 +3603,10 @@ private int getFreeConsecutiveRegisters(int numberOfRegisters, boolean prioritizeSmallRegisters) { int oldMaxRegisterNumber = maxRegisterNumber; - TreeSet<Integer> freeRegistersWithDesiredOrdering = this.freeRegisters; + IntSortedSet freeRegistersWithDesiredOrdering = freeRegisters; if (prioritizeSmallRegisters) { freeRegistersWithDesiredOrdering = - new TreeSet<>( + new IntRBTreeSet( (Integer x, Integer y) -> { boolean xIsArgument = x < numberOfArgumentRegisters; boolean yIsArgument = y < numberOfArgumentRegisters; @@ -3498,10 +3621,10 @@ // Otherwise use their normal ordering. return x - y; }); - freeRegistersWithDesiredOrdering.addAll(this.freeRegisters); + freeRegistersWithDesiredOrdering.addAll(freeRegisters); } - Iterator<Integer> freeRegistersIterator = freeRegistersWithDesiredOrdering.iterator(); + IntIterator freeRegistersIterator = freeRegistersWithDesiredOrdering.iterator(); int first = getNextFreeRegister(freeRegistersIterator); int current = first; while (current - first + 1 != numberOfRegisters) { @@ -3546,27 +3669,21 @@ return true; } - private int getNextFreeRegister(Iterator<Integer> freeRegistersIterator) { + private int getNextFreeRegister(IntIterator freeRegistersIterator) { if (freeRegistersIterator.hasNext()) { - return freeRegistersIterator.next(); + return freeRegistersIterator.nextInt(); } return ++maxRegisterNumber; } - private void excludeRegistersForInterval(LiveIntervals intervals, IntSet excluded) { - int register = intervals.getRegister(); - assert register != NO_REGISTER; - - for (int i = 0; i < intervals.requiredRegisters(); i++) { - if (freeRegisters.remove(register + i)) { - excluded.add(register + i); - } - } - - if (intervals.isArgumentInterval() && intervals != intervals.getSplitParent()) { + private void excludeRegistersForInterval(LiveIntervals intervals) { + assert intervals.hasRegister(); + intervals.forEachRegister(freeRegisters::remove); + if (isPinnedArgumentRegister(intervals) && !intervals.isSplitParent()) { LiveIntervals parent = intervals.getSplitParent(); - if (parent.getRegister() != register) { - excludeRegistersForInterval(parent, excluded); + assert parent.hasRegister(); + if (parent.getRegister() != intervals.getRegister()) { + parent.forEachRegister(freeRegisters::remove); } } } @@ -3580,7 +3697,7 @@ freeRegisters.add(register + 1); } - if (intervals.isArgumentInterval() && intervals != intervals.getSplitParent()) { + if (isPinnedArgumentRegister(intervals) && !intervals.isSplitParent()) { LiveIntervals parent = intervals.getSplitParent(); if (parent.getRegister() != intervals.getRegister()) { freeOccupiedRegistersForIntervals(intervals.getSplitParent()); @@ -3599,7 +3716,7 @@ private void takeFreeRegistersForIntervals(LiveIntervals intervals) { takeFreeRegisters(intervals.getRegister(), intervals.getType().isWide()); - if (intervals.isArgumentInterval() && intervals != intervals.getSplitParent()) { + if (isPinnedArgumentRegister(intervals) && !intervals.isSplitParent()) { LiveIntervals parent = intervals.getSplitParent(); if (parent.getRegister() != intervals.getRegister()) { takeFreeRegistersForIntervals(parent); @@ -3608,8 +3725,17 @@ } private boolean registerIsFree(int register) { - return freeRegisters.contains(register) - || (hasDedicatedMoveExceptionRegister() && register == getMoveExceptionRegister()); + return freeRegisters.contains(register) || isDedicatedMoveExceptionRegister(register); + } + + private boolean registerRangeIsFree(int register, int requiredRegisters) { + for (int i = 0; i < requiredRegisters; i++) { + assert !isDedicatedMoveExceptionRegister(register + i); + if (!freeRegisters.contains(register + i)) { + return false; + } + } + return true; } // Note: treats a register as free if it is in the set of free registers, or it is the dedicated
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java index 1b6c53d..64d42b0 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -38,11 +38,12 @@ private boolean sortedChildren = false; private List<LiveRange> ranges = new ArrayList<>(); private final TreeSet<LiveIntervalsUse> uses = new TreeSet<>(); - private int numberOfConsecutiveRegisters = -1; private int register = NO_REGISTER; - private Integer hint; + private int hint = NO_REGISTER; private boolean spilled = false; + private boolean isInvokeRangeIntervals = false; private boolean usedInMonitorOperations = false; + private boolean liveAtMoveExceptionEntry = false; // Only registers up to and including the registerLimit are allowed for this interval. private int registerLimit = U16BIT_MAX; @@ -87,6 +88,7 @@ } public void setHint(LiveIntervals intervals, PriorityQueue<LiveIntervals> unhandled) { + assert intervals.hasRegister(); // Do not set hints if they cannot be used anyway. if (!overlaps(intervals)) { // The hint is used in sorting the unhandled intervals. Therefore, if the hint changes @@ -99,7 +101,12 @@ } } - public Integer getHint() { + public boolean hasHint() { + return hint != NO_REGISTER; + } + + public int getHint() { + assert hasHint(); return hint; } @@ -132,7 +139,6 @@ } public void link(LiveIntervals next) { - assert numberOfConsecutiveRegisters == -1; nextConsecutive = next; next.previousConsecutive = this; } @@ -146,23 +152,29 @@ return definition != null && definition.isArgument(); } - public LiveIntervals getStartOfConsecutive() { - LiveIntervals current = this; - while (current.previousConsecutive != null) { - current = current.previousConsecutive; - } - return current; + public boolean isSplitParent() { + return this == splitParent; } public LiveIntervals getNextConsecutive() { return nextConsecutive; } + public LiveIntervals getPreviousSplit() { + if (this == splitParent) { + return null; + } + splitParent.sortSplitChildrenIfNeeded(); + int i = splitParent.getSplitChildren().indexOf(this) - 1; + return i >= 0 ? splitParent.getSplitChildren().get(i) : splitParent; + } + public LiveIntervals getNextSplit() { + splitParent.sortSplitChildrenIfNeeded(); if (this == splitParent) { return Iterables.getFirst(splitChildren, null); } - int i = splitParent.getSplitChildren().indexOf(this); + int i = splitParent.getSplitChildren().indexOf(this) + 1; return i < splitParent.getSplitChildren().size() ? splitParent.getSplitChildren().get(i) : null; } @@ -170,32 +182,12 @@ return previousConsecutive; } - public int numberOfConsecutiveRegisters() { - LiveIntervals start = getStartOfConsecutive(); - if (start.numberOfConsecutiveRegisters != -1) { - assert start.numberOfConsecutiveRegisters == computeNumberOfConsecutiveRegisters(); - return start.numberOfConsecutiveRegisters; - } - return computeNumberOfConsecutiveRegisters(); - } - - private int computeNumberOfConsecutiveRegisters() { - LiveIntervals start = getStartOfConsecutive(); - int result = 0; - for (LiveIntervals current = start; - current != null; - current = current.nextConsecutive) { - result += current.requiredRegisters(); - } - start.numberOfConsecutiveRegisters = result; - return result; - } - public boolean hasSplits() { return splitChildren.size() != 0; } private void sortSplitChildrenIfNeeded() { + assert isSplitParent(); if (!sortedChildren) { splitChildren.sort(Comparator.comparingInt(LiveIntervals::getEnd)); sortedSplitChildrenEnds.clear(); @@ -304,6 +296,29 @@ register = n; } + public boolean isInvokeRangeIntervals() { + return isInvokeRangeIntervals; + } + + public void setIsInvokeRangeIntervals() { + assert !isInvokeRangeIntervals; + isInvokeRangeIntervals = true; + } + + public void unsetIsInvokeRangeIntervals() { + assert isSplitParent(); + isInvokeRangeIntervals = false; + } + + public boolean isLiveAtMoveExceptionEntry() { + return splitParent.liveAtMoveExceptionEntry; + } + + public void setIsLiveAtMoveExceptionEntry() { + assert isSplitParent(); + liveAtMoveExceptionEntry = true; + } + private int computeMaxNonSpilledRegister() { assert splitParent == this; assert maxNonSpilledRegister == NO_REGISTER; @@ -353,7 +368,7 @@ public void clearRegisterAssignment() { register = NO_REGISTER; - hint = null; + hint = NO_REGISTER; } public boolean overlapsPosition(int position) { @@ -456,7 +471,7 @@ start = toGapPosition(start); LiveIntervals splitChild = new LiveIntervals(splitParent); splitParent.splitChildren.add(splitChild); - splitParent.sortedChildren = false; + splitParent.sortedChildren = splitParent.splitChildren.size() == 1; List<LiveRange> beforeSplit = new ArrayList<>(); List<LiveRange> afterSplit = new ArrayList<>(); if (start == getEnd()) { @@ -593,14 +608,14 @@ if (startDiff != 0) return startDiff; // Then sort by register number of hints to make sure that a phi // does not take a low register that is the hint for another phi. - if (hint != null && other.hint != null) { - int registerDiff = hint - other.hint; + if (hasHint() && other.hasHint()) { + int registerDiff = getHint() - other.getHint(); if (registerDiff != 0) return registerDiff; } // Intervals with hints go first so intervals without hints // do not take registers from intervals with hints. - if (hint != null && other.hint == null) return -1; - if (hint == null && other.hint != null) return 1; + if (hasHint() && !other.hasHint()) return -1; + if (!hasHint() && other.hasHint()) return 1; // Tie-breaker: no values have equal numbers. int result = value.getNumber() - other.value.getNumber(); assert result != 0; @@ -610,10 +625,6 @@ @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("(cons "); - // Use the field here to avoid toString to have side effects. - builder.append(numberOfConsecutiveRegisters); - builder.append("): "); for (LiveRange range : getRanges()) { builder.append(range); builder.append(" ");
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/MoveSorter.java b/src/main/java/com/android/tools/r8/ir/regalloc/MoveSorter.java new file mode 100644 index 0000000..dacade5 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/regalloc/MoveSorter.java
@@ -0,0 +1,275 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import static com.android.tools.r8.utils.MapUtils.ignoreKey; + +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.ConstNumber; +import com.android.tools.r8.ir.code.FixedRegisterValue; +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.Move; +import com.android.tools.r8.utils.ListUtils; +import com.google.common.base.Equivalence; +import com.google.common.base.Equivalence.Wrapper; +import com.google.common.collect.Iterables; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class MoveSorter { + + private static final Equivalence<Instruction> equivalence = new MoveEquivalence(); + + private final IRCode code; + + public MoveSorter(IRCode code) { + this.code = code; + } + + public void sortMovesForSuffixSharing() { + for (BasicBlock block : code.blocks(this::hasTwoPredecessorsWithUniqueSuccessor)) { + // First compute the set of moves that are in the suffix of both predecessor blocks. These are + // the candidates for suffix sharing. + Set<Wrapper<Instruction>> suffixSharingCandidates = getSuffixSharingCandidates(block); + if (suffixSharingCandidates.isEmpty()) { + continue; + } + // Compute a one-to-many map where an edge x -> y means that the instruction x cannot be moved + // past instruction y. + Map<Wrapper<Instruction>, Set<Instruction>> blockedBy = + computeBlockedBy(block, suffixSharingCandidates); + // Compute which instructions can be shared in the successor. + Set<Wrapper<Instruction>> sharedSuffix = + blockedBy.isEmpty() + ? suffixSharingCandidates + : computeSharedSuffix(suffixSharingCandidates, blockedBy); + if (!sharedSuffix.isEmpty()) { + for (BasicBlock predecessor : block.getPredecessors()) { + sortSuffix(predecessor, sharedSuffix); + } + } + } + } + + Set<Wrapper<Instruction>> getSuffixSharingCandidates(BasicBlock block) { + Set<Wrapper<Instruction>> firstSuffix = new HashSet<>(); + for (Instruction instruction = block.getPredecessor(0).exit().getPrev(); + instruction != null && isMove(instruction); + instruction = instruction.getPrev()) { + firstSuffix.add(equivalence.wrap(instruction)); + } + if (firstSuffix.isEmpty()) { + return firstSuffix; + } + Set<Wrapper<Instruction>> suffixSharingCandidates = new HashSet<>(); + for (Instruction instruction = block.getPredecessor(1).exit().getPrev(); + instruction != null && isMove(instruction); + instruction = instruction.getPrev()) { + Wrapper<Instruction> wrapper = equivalence.wrap(instruction); + if (firstSuffix.contains(wrapper)) { + suffixSharingCandidates.add(wrapper); + } + } + return suffixSharingCandidates; + } + + Map<Wrapper<Instruction>, Set<Instruction>> computeBlockedBy( + BasicBlock block, Set<Wrapper<Instruction>> suffixSharingCandidates) { + Map<Wrapper<Instruction>, Set<Instruction>> blockedBy = new HashMap<>(); + for (BasicBlock predecessor : block.getPredecessors()) { + addBlockedBy(predecessor, suffixSharingCandidates, blockedBy); + } + return blockedBy; + } + + void addBlockedBy( + BasicBlock block, + Set<Wrapper<Instruction>> suffixSharingCandidates, + Map<Wrapper<Instruction>, Set<Instruction>> blockedBy) { + // Find the first move. + Instruction instruction = block.exit().getPrev(); + assert instruction != null; + assert isMove(instruction); + while (instruction.hasPrev() && isMove(instruction.getPrev())) { + instruction = instruction.getPrev(); + } + + List<Instruction> seen = ListUtils.newArrayList(instruction); + for (instruction = instruction.getNext(); + !instruction.isExit(); + instruction = instruction.getNext()) { + // Record if the current instruction is blocking any of the previously seen instructions. + assert isMove(instruction); + for (Instruction previous : seen) { + if (isBlockedBy(previous, instruction)) { + blockedBy + .computeIfAbsent(equivalence.wrap(previous), ignoreKey(HashSet::new)) + .add(instruction); + } + } + // Only add the current instruction to the seen set if it is a candidate for suffix sharing. + // Otherwise, we can't move it to the successor so we don't need worry about whether it is + // blocked. + if (suffixSharingCandidates.contains(equivalence.wrap(instruction))) { + seen.add(instruction); + } + } + } + + Set<Wrapper<Instruction>> computeSharedSuffix( + Set<Wrapper<Instruction>> suffixSharingCandidates, + Map<Wrapper<Instruction>, Set<Instruction>> blockedBy) { + Set<Wrapper<Instruction>> sharedSuffix = new HashSet<>(); + while (true) { + Wrapper<Instruction> unblockedMove = + removeUnblockedMove(suffixSharingCandidates, blockedBy, sharedSuffix); + if (unblockedMove == null) { + break; + } + sharedSuffix.add(unblockedMove); + } + return sharedSuffix; + } + + Wrapper<Instruction> removeUnblockedMove( + Set<Wrapper<Instruction>> suffixSharingCandidates, + Map<Wrapper<Instruction>, Set<Instruction>> blockedBy, + Set<Wrapper<Instruction>> sharedSuffix) { + Iterator<Wrapper<Instruction>> iterator = suffixSharingCandidates.iterator(); + while (iterator.hasNext()) { + Wrapper<Instruction> candidate = iterator.next(); + Set<Instruction> blockingSet = blockedBy.getOrDefault(candidate, Collections.emptySet()); + // Disregard blocking moves that have already been moved to the successor. + blockingSet.removeIf(blockingMove -> sharedSuffix.contains(equivalence.wrap(blockingMove))); + if (blockingSet.isEmpty()) { + iterator.remove(); + return candidate; + } + } + return null; + } + + void sortSuffix(BasicBlock predecessor, Set<Wrapper<Instruction>> sharedSuffix) { + InstructionListIterator iterator = + predecessor.listIterator(code, predecessor.getInstructions().size() - 1); + Deque<Instruction> removedInstructions = new ArrayDeque<>(); + while (iterator.hasPrevious()) { + Instruction instruction = iterator.previous(); + if (isMove(instruction)) { + if (sharedSuffix.contains(equivalence.wrap(instruction))) { + removedInstructions.addFirst(instruction); + iterator.removeInstructionIgnoreOutValue(); + } + } else { + break; + } + } + assert removedInstructions.size() == sharedSuffix.size(); + predecessor + .listIterator(code, predecessor.getInstructions().size() - 1) + .addAll(removedInstructions); + } + + private boolean hasTwoPredecessorsWithUniqueSuccessor(BasicBlock block) { + return block.getPredecessors().size() == 2 + && Iterables.all( + block.getPredecessors(), + predecessor -> predecessor.hasUniqueSuccessor() && predecessor.exit().isGoto()); + } + + private boolean isBlockedBy(Instruction instruction, Instruction laterInstruction) { + // Check if either of the two instructions write the operand of the other instruction. + if (instruction.isMove()) { + FixedRegisterValue inValue = instruction.getFirstOperand().asFixedRegisterValue(); + FixedRegisterValue laterOutValue = laterInstruction.outValue().asFixedRegisterValue(); + if (laterOutValue.usesRegister(inValue)) { + return true; + } + } + if (laterInstruction.isMove()) { + FixedRegisterValue outValue = instruction.outValue().asFixedRegisterValue(); + FixedRegisterValue laterInValue = laterInstruction.getFirstOperand().asFixedRegisterValue(); + if (outValue.usesRegister(laterInValue)) { + return true; + } + } + return false; + } + + private boolean isMove(Instruction instruction) { + if (instruction.isConstNumber()) { + return instruction.outValue().isFixedRegisterValue(); + } + if (instruction.isMove() && !instruction.isDebugLocalWrite()) { + if (instruction.getFirstOperand().isFixedRegisterValue()) { + assert instruction.outValue().isFixedRegisterValue(); + return true; + } + // This Move instruction has been inserted for dealing with invoke/range. It is not inserted + // by the move scheduler. + } + return false; + } + + private static class MoveEquivalence extends Equivalence<Instruction> { + + @Override + protected boolean doEquivalent(Instruction a, Instruction b) { + if (a.isConstNumber()) { + if (!b.isConstNumber()) { + return false; + } + ConstNumber constNumber = a.asConstNumber(); + ConstNumber other = b.asConstNumber(); + return constNumber.getOutType().equals(other.getOutType()) + && constNumber.getRawValue() == other.getRawValue() + && constNumber.outValue().asFixedRegisterValue().getRegister() + == other.outValue().asFixedRegisterValue().getRegister(); + } else { + assert a.isMove(); + if (!b.isMove()) { + return false; + } + Move move = a.asMove(); + Move other = b.asMove(); + return move.src() + .asFixedRegisterValue() + .outType() + .equals(other.src().asFixedRegisterValue().outType()) + && move.src().asFixedRegisterValue().getRegister() + == other.src().asFixedRegisterValue().getRegister() + && move.outValue().asFixedRegisterValue().getRegister() + == other.outValue().asFixedRegisterValue().getRegister(); + } + } + + @Override + protected int doHash(Instruction instruction) { + if (instruction.isConstNumber()) { + ConstNumber constNumber = instruction.asConstNumber(); + return Objects.hash( + constNumber.getClass(), + constNumber.outValue().asFixedRegisterValue().getRegister(), + constNumber.getRawValue()); + } else { + assert instruction.isMove(); + Move move = instruction.asMove(); + return Objects.hash( + move.getClass(), + move.outValue().asFixedRegisterValue().getRegister(), + move.src().asFixedRegisterValue().getRegister()); + } + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java index 6c2221c..398b633 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.regalloc; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.BasicBlock; @@ -18,6 +19,10 @@ int getRegisterForValue(Value value, int instructionNumber); int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber); + default int getArgumentRegisterForValue(Value value) { + throw new Unreachable(); + } + InternalOptions options(); AppView<?> getAppView();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java index be68587..70d2ab8 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -3,10 +3,15 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.regalloc; +import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER; + import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Instruction; -import java.util.Map; +import com.android.tools.r8.utils.ObjectUtils; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import java.util.Objects; import java.util.Set; +import java.util.function.IntConsumer; // Register moves used by the spilling register allocator. These are used both for spill and // for phi moves and they are moves between actual registers represented by their register number. @@ -27,51 +32,85 @@ public RegisterMove(int dst, TypeElement type, Instruction definition) { assert definition.isOutConstant(); this.dst = dst; - this.src = LiveIntervals.NO_REGISTER; + this.src = NO_REGISTER; this.definition = definition; this.type = type; } - private boolean writes(int register) { - if (type.isWidePrimitive() && (dst + 1) == register) { - return true; + public void forEachDestinationRegister(IntConsumer consumer) { + consumer.accept(dst); + if (isWide()) { + consumer.accept(dst + 1); } - return dst == register; } - @SuppressWarnings("ReferenceEquality") - public boolean isBlocked(Set<RegisterMove> moveSet, Map<Integer, Integer> valueMap) { + public void forEachSourceRegister(IntConsumer consumer) { + if (src != NO_REGISTER) { + consumer.accept(src); + if (isWide()) { + consumer.accept(src + 1); + } + } + } + + public boolean writes(int register, boolean otherIsWide) { + if (dst == register) { + return true; + } + if (isWide() && dst + 1 == register) { + return true; + } + if (otherIsWide && dst == register + 1) { + return true; + } + return false; + } + + public boolean isBlocked( + RegisterMoveScheduler scheduler, Set<RegisterMove> moveSet, Int2IntMap valueMap) { + if (isDestUsedAsTemporary(scheduler)) { + return true; + } for (RegisterMove move : moveSet) { - if (move.src == LiveIntervals.NO_REGISTER) { + if (isIdentical(move) || move.src == NO_REGISTER) { continue; } - if (move != this) { - if (writes(valueMap.get(move.src))) { - return true; - } - if (move.type.isWidePrimitive()) { - if (writes(valueMap.get(move.src) + 1)) { - return true; - } - } + if (writes(valueMap.get(move.src), move.isWide())) { + return true; } } return false; } - @Override - public int hashCode() { - return src + dst * 3 + type.hashCode() * 5 + (definition == null ? 0 : definition.hashCode()); + public boolean isDestUsedAsTemporary(RegisterMoveScheduler scheduler) { + return scheduler.activeTempRegisters.contains(dst) + || (isWide() && scheduler.activeTempRegisters.contains(dst + 1)); + } + + public boolean isIdentical(RegisterMove move) { + return ObjectUtils.identical(this, move); + } + + public boolean isNotIdentical(RegisterMove move) { + return !isIdentical(move); + } + + public boolean isWide() { + return type.isWidePrimitive(); } @Override - @SuppressWarnings("ReferenceEquality") + public int hashCode() { + return src + dst * 3 + type.hashCode() * 5 + Objects.hashCode(definition); + } + + @Override public boolean equals(Object other) { if (!(other instanceof RegisterMove)) { return false; } RegisterMove o = (RegisterMove) other; - return o.src == src && o.dst == dst && o.type == type && o.definition == definition; + return o.src == src && o.dst == dst && type.equals(o.type) && o.definition == definition; } @Override @@ -104,4 +143,16 @@ } return definition.getNumber() - o.definition.getNumber(); } + + @Override + public String toString() { + if (type.isSinglePrimitive()) { + return "move " + dst + ", " + src; + } else if (type.isWidePrimitive()) { + return "move-wide " + dst + ", " + src; + } else { + assert type.isReferenceType(); + return "move-object " + dst + ", " + src; + } + } }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycle.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycle.java new file mode 100644 index 0000000..a1e986d --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycle.java
@@ -0,0 +1,28 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import java.util.TreeSet; + +class RegisterMoveCycle { + + private final TreeSet<RegisterMove> moves; + + // Whether the cycle is closed, i.e., the moves in the cycle are only blocked by moves also + // present in this cycle. + private final boolean closed; + + RegisterMoveCycle(TreeSet<RegisterMove> cycle, boolean closed) { + this.moves = cycle; + this.closed = closed; + } + + public TreeSet<RegisterMove> getMoves() { + return moves; + } + + public boolean isClosed() { + return closed; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycleDetector.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycleDetector.java new file mode 100644 index 0000000..cdb321d --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycleDetector.java
@@ -0,0 +1,141 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import com.android.tools.r8.utils.SetUtils; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +public class RegisterMoveCycleDetector { + + @SuppressWarnings("MixedMutabilityReturnType") + static List<RegisterMoveCycle> getMoveCycles(TreeSet<RegisterMove> moveSet) { + // Although there can be a cycle when there are two moves, we return no cycles, since the + // default move scheduling has the same behavior as the cycle-based move scheduling in this + // case. + if (moveSet.size() <= 2) { + return Collections.emptyList(); + } + List<RegisterMoveCycle> moveCycles = new ArrayList<>(); + Int2ObjectMap<TreeSet<RegisterMove>> readBy = createReadByGraph(moveSet); + Set<RegisterMove> finished = new HashSet<>(); + Deque<RegisterMove> stack = new ArrayDeque<>(); + TreeSet<RegisterMove> stackSet = new TreeSet<>(); + for (RegisterMove move : moveSet) { + if (finished.contains(move)) { + continue; + } + dfs(move, finished, readBy, stack, stackSet, moveCycles); + assert finished.contains(move); + assert stack.isEmpty(); + assert stackSet.isEmpty(); + } + return moveCycles; + } + + private static void dfs( + RegisterMove move, + Set<RegisterMove> finished, + Int2ObjectMap<TreeSet<RegisterMove>> readBy, + Deque<RegisterMove> stack, + TreeSet<RegisterMove> stackSet, + List<RegisterMoveCycle> moveCycles) { + stack.addLast(move); + boolean addedToStack = stackSet.add(move); + assert addedToStack; + // Explore all outgoing edges. The successors of this move are the moves that read the + // destination registers of the current move. + for (RegisterMove successor : getSuccessors(move, readBy)) { + if (finished.contains(successor)) { + // Already fully explored. + continue; + } + if (successor.isIdentical(move)) { + // This move is reading/writing overlapping registers (e.g., move-wide 0, 1). + continue; + } + if (stackSet.contains(successor)) { + moveCycles.add(extractCycle(stack, successor, readBy)); + } else { + dfs(successor, finished, readBy, stack, stackSet, moveCycles); + } + } + RegisterMove removedFromStack = stack.removeLast(); + assert removedFromStack.isIdentical(move); + boolean removedFromStackSet = stackSet.remove(move); + assert removedFromStackSet; + boolean markedFinished = finished.add(move); + assert markedFinished; + } + + // Returns a one-to-many map from registers to the set of moves that read that register. + private static Int2ObjectMap<TreeSet<RegisterMove>> createReadByGraph( + TreeSet<RegisterMove> moveSet) { + Int2ObjectMap<TreeSet<RegisterMove>> readBy = new Int2ObjectOpenHashMap<>(); + for (RegisterMove move : moveSet) { + move.forEachSourceRegister( + register -> { + if (readBy.containsKey(register)) { + readBy.get(register).add(move); + } else { + readBy.put(register, SetUtils.newTreeSet(move)); + } + }); + } + return readBy; + } + + private static Set<RegisterMove> getSuccessors( + RegisterMove move, Int2ObjectMap<TreeSet<RegisterMove>> readBy) { + TreeSet<RegisterMove> successors = readBy.get(move.dst); + if (move.isWide()) { + TreeSet<RegisterMove> additionalSuccessors = readBy.get(move.dst + 1); + if (successors == null) { + successors = additionalSuccessors; + } else if (additionalSuccessors != null) { + successors = new TreeSet<>(successors); + successors.addAll(additionalSuccessors); + } + } + return successors != null ? successors : Collections.emptySet(); + } + + private static RegisterMoveCycle extractCycle( + Deque<RegisterMove> stack, + RegisterMove cycleEntry, + Int2ObjectMap<TreeSet<RegisterMove>> readBy) { + Deque<RegisterMove> cycle = new ArrayDeque<>(); + while (!cycleEntry.isIdentical(cycle.peekFirst())) { + cycle.addFirst(stack.removeLast()); + } + stack.addAll(cycle); + TreeSet<RegisterMove> cycleSet = new TreeSet<>(cycle); + return new RegisterMoveCycle(cycleSet, isClosedCycle(cycleSet, readBy)); + } + + private static boolean isClosedCycle( + TreeSet<RegisterMove> cycle, Int2ObjectMap<TreeSet<RegisterMove>> readBy) { + for (RegisterMove move : cycle) { + for (int i = 0; i < move.type.requiredRegisters(); i++) { + TreeSet<RegisterMove> successors = readBy.get(move.dst + i); + if (successors != null) { + for (RegisterMove successor : successors) { + if (!cycle.contains(successor)) { + return false; + } + } + } + } + } + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java index 5251d17..43c2ed3 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -3,6 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.regalloc; +import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER; +import static com.android.tools.r8.utils.IntConsumerUtils.emptyIntConsumer; +import static com.google.common.base.Predicates.not; + import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Argument; @@ -14,119 +18,152 @@ import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.utils.ObjectUtils; +import com.google.common.collect.Iterables; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntRBTreeSet; import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntSortedSet; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.TreeSet; +import java.util.function.IntConsumer; public class RegisterMoveScheduler { // The set of moves to schedule. - private final Set<RegisterMove> moveSet = new TreeSet<>(); + private final TreeSet<RegisterMove> moveSet = new TreeSet<>(); // Mapping to keep track of which values currently corresponds to each other. // This is initially an identity map but changes as we insert moves. - private final Map<Integer, Integer> valueMap = new HashMap<>(); - // Number of temp registers used to schedule the moves. - private int usedTempRegisters = 0; + private final Int2IntMap valueMap = new Int2IntOpenHashMap(); // Location at which to insert the scheduled moves. private final InstructionListIterator insertAt; // Debug position associated with insertion point. private final Position position; // The first available temporary register. - private final int tempRegister; + private final int firstTempRegister; + private int nextTempRegister; + // Free temporary registers that have been allocated by move scheduling. + private final IntSortedSet freeRegisters = new IntRBTreeSet(); + // Registers that can be used as temporary registers until they are assigned by a move in the + // move set. + private final IntSortedSet freeRegistersUntilAssigned = new IntRBTreeSet(); + // Registers that are the destination register of some move in the move set, but which are + // currently being used as a temporary register for another value. Moves in the move set that + // write a register in this set are blocked until the temporary registers are released. + public final IntSet activeTempRegisters = new IntOpenHashSet(); public RegisterMoveScheduler( - InstructionListIterator insertAt, int tempRegister, Position position) { + InstructionListIterator insertAt, int firstTempRegister, Position position) { this.insertAt = insertAt; - this.tempRegister = tempRegister; + this.firstTempRegister = firstTempRegister; + this.nextTempRegister = firstTempRegister; this.position = position; + this.valueMap.defaultReturnValue(NO_REGISTER); } - public RegisterMoveScheduler(InstructionListIterator insertAt, int tempRegister) { - this(insertAt, tempRegister, Position.none()); + public RegisterMoveScheduler(InstructionListIterator insertAt, int firstTempRegister) { + this(insertAt, firstTempRegister, Position.none()); + } + + private void initializeFreeRegistersUntilAssigned() { + // All registers that are assigned by the move set but not read can be used as temporary + // registers until they are assigned. + assert activeTempRegisters.isEmpty(); + assert freeRegistersUntilAssigned.isEmpty(); + for (RegisterMove move : moveSet) { + move.forEachDestinationRegister(freeRegistersUntilAssigned::add); + } + for (RegisterMove move : moveSet) { + move.forEachSourceRegister(freeRegistersUntilAssigned::remove); + } } public void addMove(RegisterMove move) { moveSet.add(move); - if (move.src != LiveIntervals.NO_REGISTER) { + if (move.src != NO_REGISTER) { valueMap.put(move.src, move.src); } valueMap.put(move.dst, move.dst); } - // TODO(b/270398965): Replace LinkedList. - @SuppressWarnings("JdkObsolete") public void schedule() { assert everyDestinationOnlyWrittenOnce(); + initializeFreeRegistersUntilAssigned(); // Worklist of moves that are ready to be inserted. - Deque<RegisterMove> worklist = new LinkedList<>(); + Deque<RegisterMove> worklist = new ArrayDeque<>(); - // Initialize worklist with the moves that do not interfere with other moves. - Iterator<RegisterMove> iterator = moveSet.iterator(); - while (iterator.hasNext()) { - RegisterMove move = iterator.next(); - if (!move.isBlocked(moveSet, valueMap)) { - worklist.addLast(move); - iterator.remove(); + // If there are destination registers we can use until they are assigned, then instead of + // emitting these unblocked moves, we use them as temporary registers to unblock move cycles. + List<RegisterMoveCycle> moveCycles = RegisterMoveCycleDetector.getMoveCycles(moveSet); + for (RegisterMoveCycle moveCycle : moveCycles) { + for (RegisterMove move : moveCycle.getMoves()) { + assert move.isBlocked(this, moveSet, valueMap) || !moveSet.contains(move); + removeFromFreeRegistersUntilAssigned(move.dst, move.isWide(), emptyIntConsumer()); } + // If the cycle is not closed then some moves in the cycle are blocked by other moves. + // TODO(b/375147902): Try to schedule these other moves before scheduling the cycle to + // unblock the cycle. In JetNews 15% of move cycles are open. + if (moveCycle.isClosed()) { + assert moveSet.containsAll(moveCycle.getMoves()); + assert worklist.isEmpty(); + schedulePartial(moveCycle.getMoves(), worklist); + } + assert activeTempRegisters.isEmpty(); } + // Initialize worklist with the moves that do not interfere with other moves. + enqueueUnblockedMoves(worklist); + schedulePartial(moveSet, worklist); + assert freeRegistersUntilAssigned.isEmpty(); + } + + private void schedulePartial( + TreeSet<RegisterMove> movesToSchedule, Deque<RegisterMove> worklist) { // Process the worklist generating moves. If the worklist becomes empty while the move set // still contains elements we need to use a temporary to break cycles. - while (!worklist.isEmpty() || !moveSet.isEmpty()) { + while (!worklist.isEmpty() || !movesToSchedule.isEmpty()) { while (!worklist.isEmpty()) { - RegisterMove move = worklist.removeFirst(); - assert !move.isBlocked(moveSet, valueMap); - // Insert the move. - Integer generatedDest = createMove(move); - // Update the value map with the information that dest can be used instead of - // src starting now. - if (move.src != LiveIntervals.NO_REGISTER) { - valueMap.put(move.src, generatedDest); + while (!worklist.isEmpty()) { + RegisterMove move = worklist.removeFirst(); + assert !move.isBlocked(this, moveSet, valueMap); + createMove(move); } - // Iterate and find the moves that were blocked because they need to write to - // one of the move src. That is now valid because the move src is preserved in dest. - iterator = moveSet.iterator(); - while (iterator.hasNext()) { - RegisterMove other = iterator.next(); - if (!other.isBlocked(moveSet, valueMap)) { - worklist.addLast(other); - iterator.remove(); - } - } + enqueueUnblockedMoves(worklist, movesToSchedule); } - if (!moveSet.isEmpty()) { + if (!movesToSchedule.isEmpty()) { // The remaining moves are conflicting. Chose a move and unblock it by generating moves to // temporary registers for its destination value(s). - RegisterMove move = pickMoveToUnblock(); + RegisterMove move = pickMoveToUnblock(movesToSchedule); createMoveDestToTemp(move); - worklist.addLast(move); + // TODO(b/375147902): After emitting the newly unblocked move, try to prioritize the moves + // that blocked it, so that we free up the temp register, rather than getting overlapping + // temporary registers. + worklist.add(move); } } } public int getUsedTempRegisters() { - return usedTempRegisters; + return nextTempRegister - firstTempRegister; } private List<RegisterMove> findMovesWithSrc(int src, TypeElement type) { List<RegisterMove> result = new ArrayList<>(); - assert src != LiveIntervals.NO_REGISTER; + assert src != NO_REGISTER; for (RegisterMove move : moveSet) { - if (move.src == LiveIntervals.NO_REGISTER) { + if (move.src == NO_REGISTER) { continue; } int moveSrc = valueMap.get(move.src); if (moveSrc == src) { result.add(move); - } else if (move.type.isWidePrimitive() && (moveSrc + 1) == src) { + } else if (move.isWide() && (moveSrc + 1) == src) { result.add(move); } else if (type.isWidePrimitive() && (moveSrc - 1) == src) { result.add(move); @@ -135,7 +172,7 @@ return result; } - private Integer createMove(RegisterMove move) { + private void createMove(RegisterMove move) { Instruction instruction; if (move.definition != null) { if (move.definition.isArgument()) { @@ -155,14 +192,51 @@ } } } else { + int mappedSrc = valueMap.get(move.src); Value to = new FixedRegisterValue(move.type, move.dst); - Value from = new FixedRegisterValue(move.type, valueMap.get(move.src)); + Value from = new FixedRegisterValue(move.type, mappedSrc); instruction = new Move(to, from); + returnTemporaryRegister(mappedSrc, move.isWide()); } instruction.setPosition(position); insertAt.add(instruction); - return move.dst; + // Update the value map with the information that dest can be used instead of + // src starting now. + if (move.src != NO_REGISTER) { + valueMap.put(move.src, move.dst); + } + removeFromFreeRegistersUntilAssigned(move.dst, move.isWide(), emptyIntConsumer()); + } + + private void enqueueUnblockedMoves(Deque<RegisterMove> worklist) { + // Iterate and find the moves that were blocked because they need to write to one of the move + // src. That is now valid because the move src is preserved in dest. + moveSet.removeIf( + move -> { + if (move.isBlocked(this, moveSet, valueMap)) { + return false; + } + worklist.addLast(move); + return true; + }); + } + + private void enqueueUnblockedMoves( + Deque<RegisterMove> worklist, TreeSet<RegisterMove> movesToSchedule) { + // Iterate and find the moves that were blocked because they need to write to one of the move + // src. That is now valid because the move src is preserved in dest. + movesToSchedule.removeIf( + move -> { + if (move.isBlocked(this, moveSet, valueMap)) { + return false; + } + if (ObjectUtils.notIdentical(moveSet, movesToSchedule)) { + moveSet.remove(move); + } + worklist.addLast(move); + return true; + }); } private void createMoveDestToTemp(RegisterMove move) { @@ -170,34 +244,139 @@ // registers if we are unlucky with the overlap for values that use two registers. List<RegisterMove> movesWithSrc = findMovesWithSrc(move.dst, move.type); assert movesWithSrc.size() > 0; + assert verifyMovesHaveDifferentSources(movesWithSrc); for (RegisterMove moveWithSrc : movesWithSrc) { - // TODO(ager): For now we always use a new temporary register whenever we have to unblock - // a move. The move scheduler can have multiple unblocking temps live at the same time - // and therefore we cannot have just one tempRegister (pair). However, we could check here - // if the previously used tempRegisters is still needed by any of the moves in the move set - // (taking the value map into account). If not, we can reuse the temp register instead - // of generating a new one. - Value to = new FixedRegisterValue(moveWithSrc.type, tempRegister + usedTempRegisters); + // TODO(b/375147902): Maybe seed the move scheduler with a set of registers known to be free + // at this point. + int register = takeFreeRegister(moveWithSrc.isWide()); + Value to = new FixedRegisterValue(moveWithSrc.type, register); Value from = new FixedRegisterValue(moveWithSrc.type, valueMap.get(moveWithSrc.src)); Move instruction = new Move(to, from); instruction.setPosition(position); insertAt.add(instruction); - valueMap.put(moveWithSrc.src, tempRegister + usedTempRegisters); - usedTempRegisters += moveWithSrc.type.requiredRegisters(); + valueMap.put(moveWithSrc.src, register); } } - private RegisterMove pickMoveToUnblock() { - Iterator<RegisterMove> iterator = moveSet.iterator(); - RegisterMove move = null; - // Pick a non-wide move to unblock if possible. - while (iterator.hasNext()) { - move = iterator.next(); - if (!move.type.isWidePrimitive()) { - break; + private int takeFreeRegister(boolean wide) { + int register = takeFreeRegisterFrom(freeRegistersUntilAssigned, wide); + if (register != NO_REGISTER) { + addActiveTempRegisters(register, wide); + return register; + } + register = takeFreeRegisterFrom(freeRegisters, wide); + if (register != NO_REGISTER) { + return register; + } + // We don't have a free register. + register = allocateExtraRegister(); + if (!wide) { + return register; + } + if (freeRegisters.remove(register - 1)) { + return register - 1; + } + allocateExtraRegister(); + return register; + } + + private static int takeFreeRegisterFrom(IntSortedSet freeRegisters, boolean wide) { + for (int freeRegister : freeRegisters) { + if (wide && !freeRegisters.remove(freeRegister + 1)) { + continue; + } + freeRegisters.remove(freeRegister); + return freeRegister; + } + return NO_REGISTER; + } + + private void addActiveTempRegisters(int register, boolean wide) { + boolean addedRegister = activeTempRegisters.add(register); + assert addedRegister; + if (wide) { + boolean addedHighRegister = activeTempRegisters.add(register + 1); + assert addedHighRegister; + } + } + + private void returnTemporaryRegister(int register, boolean wide) { + if (returnActiveTempRegister(register, wide)) { + addFreeRegistersUntilAssigned(register, wide); + } else if (isExtraTemporaryRegister(register)) { + returnExtraTemporaryRegister(register, wide); + } + } + + private boolean returnActiveTempRegister(int register, boolean wide) { + boolean removedRegister = activeTempRegisters.remove(register); + if (wide) { + if (removedRegister) { + boolean removedHighRegister = activeTempRegisters.remove(register + 1); + assert removedHighRegister; + } else { + assert !activeTempRegisters.contains(register + 1); } } - iterator.remove(); + return removedRegister; + } + + private void addFreeRegistersUntilAssigned(int register, boolean wide) { + boolean addedRegister = freeRegistersUntilAssigned.add(register); + assert addedRegister; + if (wide) { + boolean addedHighRegister = freeRegistersUntilAssigned.add(register + 1); + assert addedHighRegister; + } + } + + private void returnExtraTemporaryRegister(int register, boolean wide) { + assert isExtraTemporaryRegister(register); + freeRegisters.add(register); + if (wide) { + assert isExtraTemporaryRegister(register + 1); + freeRegisters.add(register + 1); + } + } + + private void removeFromFreeRegistersUntilAssigned( + int register, boolean wide, IntConsumer changedConsumer) { + if (freeRegistersUntilAssigned.remove(register)) { + changedConsumer.accept(register); + } + if (wide) { + if (freeRegistersUntilAssigned.remove(register + 1)) { + changedConsumer.accept(register + 1); + } + } + } + + private boolean isExtraTemporaryRegister(int register) { + return register >= firstTempRegister; + } + + private int allocateExtraRegister() { + return nextTempRegister++; + } + + private boolean verifyMovesHaveDifferentSources(List<RegisterMove> movesWithSrc) { + IntSet seen = new IntOpenHashSet(); + for (RegisterMove move : movesWithSrc) { + assert seen.add(move.src); + } + return true; + } + + private RegisterMove pickMoveToUnblock(TreeSet<RegisterMove> moves) { + // Pick a non-wide move to unblock if possible. + Iterable<RegisterMove> eligible = + Iterables.filter(moves, move -> !move.isDestUsedAsTemporary(this)); + RegisterMove move = + Iterables.find(eligible, not(RegisterMove::isWide), eligible.iterator().next()); + moves.remove(move); + if (ObjectUtils.notIdentical(moves, moveSet)) { + moveSet.remove(move); + } return move; }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java index 87c059a..0bd5cf8 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -300,7 +300,7 @@ // the arguments are not live, so it is insufficient to check that the destination register // is in the argument register range. for (SpillMove move : moves) { - assert !allocator.isPinnedArgumentRegister(move.to); + assert !allocator.canSkipArgumentMove(move.to); } return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java b/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java new file mode 100644 index 0000000..5b0810d --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java
@@ -0,0 +1,58 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER; + +import com.android.tools.r8.ir.code.Value; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; + +public class UnsplitArgumentsResult { + + private final LinearScanRegisterAllocator allocator; + private final Reference2IntMap<LiveIntervals> originalRegisterAssignment; + + public UnsplitArgumentsResult( + LinearScanRegisterAllocator allocator, + Reference2IntMap<LiveIntervals> originalRegisterAssignment) { + assert originalRegisterAssignment.defaultReturnValue() == NO_REGISTER; + this.allocator = allocator; + this.originalRegisterAssignment = originalRegisterAssignment; + } + + public boolean isFullyReverted() { + return originalRegisterAssignment.isEmpty(); + } + + // Returns true if any changes were made. + public boolean revertPartial() { + boolean changed = false; + for (Value argument = allocator.firstArgumentValue; + argument != null; + argument = argument.getNextConsecutive()) { + for (LiveIntervals child : argument.getLiveIntervals().getSplitChildren()) { + changed |= revertPartial(child); + } + } + return changed; + } + + private boolean revertPartial(LiveIntervals intervals) { + int originalRegister = originalRegisterAssignment.getInt(intervals); + if (originalRegister == NO_REGISTER) { + // This live intervals was not affected by the unsplit arguments optimization. + return false; + } + int conservativeRealRegisterEnd = + allocator.realRegisterNumberFromAllocated(intervals.getRegisterEnd()); + if (conservativeRealRegisterEnd <= intervals.getRegister()) { + return false; + } + // Apply revert. + intervals.clearRegisterAssignment(); + intervals.setRegister(originalRegister); + originalRegisterAssignment.removeInt(intervals); + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java index 27f4575..fd08861 100644 --- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java +++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -356,7 +356,7 @@ return blocks.computeIfAbsent( instructionIndex, k -> { - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(irMetadata); block.setNumber(basicBlockNumberGenerator.next()); return block; }); @@ -424,9 +424,7 @@ int index = toInstructionIndexInIR(peekNextInstructionIndex()); advanceInstructionState(); instruction.setPosition(currentPosition); - currentBlock.getInstructions().add(instruction); - irMetadata.record(instruction); - instruction.setBlock(currentBlock); + currentBlock.getInstructions().addLast(instruction); int[] debugEndIndices = code.getDebugLocalEnds(index); if (debugEndIndices != null) { for (int encodedDebugEndIndex : debugEndIndices) { @@ -471,9 +469,7 @@ // which would otherwise advance the state. Argument argument = new Argument(dest, currentBlock.size(), isBooleanType); argument.setPosition(currentPosition); - currentBlock.getInstructions().add(argument); - irMetadata.record(argument); - argument.setBlock(currentBlock); + currentBlock.getInstructions().addLast(argument); return argument; }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java index 1fdfb27..36ca4f0 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java
@@ -23,7 +23,6 @@ import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.BasicBlockInstructionIterator; import com.android.tools.r8.ir.code.CheckCast; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.ConstString; @@ -142,9 +141,8 @@ } } - AnalysisContinuation applyInstructions(BasicBlockInstructionIterator instructionIterator) { - while (instructionIterator.hasNext()) { - Instruction instruction = instructionIterator.next(); + AnalysisContinuation applyInstructions(Instruction instruction) { + for (; instruction != null; instruction = instruction.getNext()) { assert !instruction.hasOutValue() || !isMaybeInstance(instruction.outValue()); AnalysisContinuation continuation; // TODO(b/339210038): Extend this to many other instructions, such as ConstClass, @@ -354,11 +352,9 @@ } addBlockToStack(block); addInstanceAlias(getNewInstance().outValue()); - BasicBlockInstructionIterator instructionIterator = block.iterator(uniqueConstructorInvoke); // Start the analysis from the invoke-direct instruction. This is important if we can tell // that the constructor definitely writes some fields. - instructionIterator.previous(); - return applyInstructions(instructionIterator).toTraversalContinuation(); + return applyInstructions(uniqueConstructorInvoke).toTraversalContinuation(); } } @@ -379,7 +375,7 @@ } addBlockToStack(block); applyPhis(block); - AnalysisContinuation continuation = applyInstructions(block.iterator()); + AnalysisContinuation continuation = applyInstructions(block.entry()); assert continuation.isAbortOrContinue(); return TraversalContinuation.breakIf(continuation.isAbort()); }
diff --git a/src/main/java/com/android/tools/r8/shaking/EmptyEnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EmptyEnqueuerDeferredTracing.java index 2faf20b..8872ed2 100644 --- a/src/main/java/com/android/tools/r8/shaking/EmptyEnqueuerDeferredTracing.java +++ b/src/main/java/com/android/tools/r8/shaking/EmptyEnqueuerDeferredTracing.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind; import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata; +import com.android.tools.r8.utils.Timing; import java.util.concurrent.ExecutorService; public class EmptyEnqueuerDeferredTracing extends EnqueuerDeferredTracing { @@ -25,7 +26,7 @@ } @Override - public boolean enqueueWorklistActions(EnqueuerWorklist worklist) { + public boolean enqueueWorklistActions(EnqueuerWorklist worklist, Timing timing) { return false; }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index 30d420d..cb5e723 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -97,7 +97,9 @@ import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker; import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry; import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension; +import com.android.tools.r8.ir.analysis.type.ClassTypeElement; import com.android.tools.r8.ir.code.ArrayPut; +import com.android.tools.r8.ir.code.ConstClass; import com.android.tools.r8.ir.code.ConstantValueUtils; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; @@ -1127,7 +1129,7 @@ } public void traceResourceValue(int value) { - appView.getResourceShrinkerState().trace(value); + appView.getResourceShrinkerState().trace(value, "from dex"); } public void traceReflectiveFieldWrite(ProgramField field, ProgramMethod context) { @@ -1516,6 +1518,11 @@ MethodResolutionResult resolutionResult = handleInvokeOfDirectTarget(invokedMethod, context, reason); analyses.traceInvokeDirect(invokedMethod, resolutionResult, context); + + if (invokedMethod.equals(appView.dexItemFactory().javaUtilEnumMapMembers.constructor)) { + // EnumMap uses reflection. + pendingReflectiveUses.add(context); + } } void traceInvokeInterface( @@ -1570,16 +1577,14 @@ identifierNameStrings.add(invokedMethod); // Revisit the current method to implicitly add -keep rule for items with reflective access. pendingReflectiveUses.add(context); - } - // See comment in handleJavaLangEnumValueOf. - if (invokedMethod == dexItemFactory.enumMembers.valueOf) { + } else if (invokedMethod == dexItemFactory.enumMembers.valueOf + || dexItemFactory.javaUtilEnumSetMembers.isFactoryMethod(invokedMethod)) { + // See comment in handleEnumValueOfOrCollectionInstantiation. pendingReflectiveUses.add(context); - } - // Handling of application services. - if (dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) { + } else if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) { pendingReflectiveUses.add(context); - } - if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) { + } else if (dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) { + // Handling of application services. pendingReflectiveUses.add(context); } markTypeAsLive(invokedMethod.getHolderType(), context); @@ -4187,7 +4192,7 @@ } } - private void synthesize() throws ExecutionException { + private void synthesize(Timing timing) throws ExecutionException { if (!mode.isInitialTreeShaking()) { return; } @@ -4195,20 +4200,20 @@ // In particular these additions are order independent, i.e., it does not matter which are // registered first and no dependencies may exist among them. SyntheticAdditions additions = new SyntheticAdditions(appView.createProcessorContext()); - desugar(additions); - synthesizeInterfaceMethodBridges(); + timing.time("Desugar", () -> desugar(additions)); + timing.time("Synthesize interface method bridges", this::synthesizeInterfaceMethodBridges); if (additions.isEmpty()) { return; } // Commit the pending synthetics and recompute subtypes. - appInfo = appInfo.rebuildWithClassHierarchy(app -> app); + appInfo = timing.time("Rebuild AppInfo", () -> appInfo.rebuildWithClassHierarchy(app -> app)); appView.setAppInfo(appInfo); - subtypingInfo = SubtypingInfo.create(appView); + subtypingInfo = timing.time("Create SubtypingInfo", () -> SubtypingInfo.create(appView)); // Finally once all synthesized items "exist" it is now safe to continue tracing. The new work // items are enqueued and the fixed point will continue once this subroutine returns. - additions.enqueueWorkItems(this); + timing.time("Enqueue work items", () -> additions.enqueueWorkItems(this)); } private boolean mustMoveToInterfaceCompanionMethod(ProgramMethod method) { @@ -4638,7 +4643,9 @@ private void trace(ExecutorService executorService, Timing timing) throws ExecutionException { timing.begin("Grow the tree."); try { + int round = 1; while (true) { + timing.begin("Compute fixpoint #" + round++); long numberOfLiveItems = getNumberOfLiveItems(); while (worklist.hasNext()) { EnqueuerAction action = worklist.poll(); @@ -4651,38 +4658,49 @@ timing.time("Conditional rules", () -> applicableRules.evaluateConditionalRules(this)); assert getNumberOfLiveItems() == numberOfLiveItemsAfterProcessing; if (worklist.hasNext()) { + timing.end(); continue; } } // Process all deferred annotations. + timing.begin("Process deferred annotations"); processDeferredAnnotations(deferredAnnotations, AnnotatedKind::from); processDeferredAnnotations( deferredParameterAnnotations, annotatedItem -> AnnotatedKind.PARAMETER); + timing.end(); // Continue fix-point processing while there are additional work items to ensure items that // are passed to Java reflections are traced. if (!pendingReflectiveUses.isEmpty()) { + timing.begin("Handle reflective behavior"); pendingReflectiveUses.forEach(this::handleReflectiveBehavior); pendingReflectiveUses.clear(); + timing.end(); } if (worklist.hasNext()) { + timing.end(); continue; } // Allow deferred tracing to enqueue worklist items. - if (deferredTracing.enqueueWorklistActions(worklist)) { + if (deferredTracing.enqueueWorklistActions(worklist, timing)) { assert worklist.hasNext(); + timing.end(); continue; } // Notify each analysis that a fixpoint has been reached, and give each analysis an // opportunity to add items to the worklist. - analyses.notifyFixpoint(this, worklist, executorService, timing); + timing.time( + "Notify analyses", + () -> analyses.notifyFixpoint(this, worklist, executorService, timing)); if (worklist.hasNext()) { + timing.end(); continue; } + timing.begin("Process delayed root set items"); for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) { if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) { @@ -4690,26 +4708,31 @@ delayedRootSetActionItem.asInterfaceMethodSyntheticBridgeAction()); } } + timing.end(); - synthesize(); + timing.time("Synthesize", () -> synthesize(timing)); + timing.begin("Delayed interface method synthetic bridges"); ConsequentRootSet consequentRootSet = computeDelayedInterfaceMethodSyntheticBridges(); addConsequentRootSet(consequentRootSet); rootSet .getDependentMinimumKeepInfo() .merge(consequentRootSet.getDependentMinimumKeepInfo()); rootSet.delayedRootSetActionItems.clear(); + timing.end(); if (worklist.hasNext()) { + timing.end(); continue; } // Reached the fixpoint. + timing.end(); break; } if (mode.isInitialTreeShaking()) { - postProcessingDesugaring(); + timing.time("Post processing desugaring", this::postProcessingDesugaring); } } finally { timing.end(); @@ -5129,8 +5152,10 @@ handleJavaLangReflectConstructorNewInstance(method, invoke); return; } - if (invokedMethod == dexItemFactory.enumMembers.valueOf) { - handleJavaLangEnumValueOf(method, invoke); + if (invokedMethod == dexItemFactory.enumMembers.valueOf + || invokedMethod == dexItemFactory.javaUtilEnumMapMembers.constructor + || dexItemFactory.javaUtilEnumSetMembers.isFactoryMethod(invokedMethod)) { + handleEnumValueOfOrCollectionInstantiation(method, invoke); return; } if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) { @@ -5462,17 +5487,44 @@ } } - private void handleJavaLangEnumValueOf(ProgramMethod method, InvokeMethod invoke) { + private void handleEnumValueOfOrCollectionInstantiation( + ProgramMethod context, InvokeMethod invoke) { + if (invoke.inValues().isEmpty()) { + // Should never happen. + return; + } + // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly // access the values() method of the enum class passed as the first argument. The method // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will // call this method. - if (invoke.inValues().get(0).isConstClass()) { - DexType type = invoke.inValues().get(0).definition.asConstClass().getType(); - DexProgramClass clazz = getProgramClassOrNull(type, method); - if (clazz != null && clazz.isEnum()) { - markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method)); + // Likewise, EnumSet and EnumMap call values() on the passed in Class. + Value firstArg = invoke.getFirstNonReceiverArgument(); + if (firstArg.isPhi()) { + return; + } + DexType type; + if (invoke + .getInvokedMethod() + .getParameter(0) + .isIdenticalTo(appView.dexItemFactory().classType)) { + // EnumMap.<init>(), EnumSet.noneOf(), EnumSet.allOf(), Enum.valueOf(). + ConstClass constClass = firstArg.definition.asConstClass(); + if (constClass == null || !constClass.getType().isClassType()) { + return; } + type = constClass.getType(); + } else { + // EnumSet.of(), EnumSet.range() + ClassTypeElement typeElement = firstArg.getType().asClassType(); + if (typeElement == null) { + return; + } + type = typeElement.getClassType(); + } + DexProgramClass clazz = getProgramClassOrNull(type, context); + if (clazz != null && clazz.isEnum()) { + markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(context)); } }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java index d7369fbd..1955dfe 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata; import com.android.tools.r8.shaking.Enqueuer.Mode; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Timing; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -56,7 +57,7 @@ * * @return true if any worklist items were enqueued. */ - public abstract boolean enqueueWorklistActions(EnqueuerWorklist worklist); + public abstract boolean enqueueWorklistActions(EnqueuerWorklist worklist, Timing timing); /** * Called when tree shaking has ended, to allow rewriting the application according to the tracing
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index 1c3889d..bf3e76d 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -229,15 +229,19 @@ } @Override - public boolean enqueueWorklistActions(EnqueuerWorklist worklist) { - return deferredEnqueuerActions.removeIf( - (field, worklistActions) -> { - if (isEligibleForPruning(field)) { - return false; - } - worklist.enqueueAll(worklistActions); - return true; - }); + public boolean enqueueWorklistActions(EnqueuerWorklist worklist, Timing timing) { + timing.begin("Process deferred tracing"); + boolean changed = + deferredEnqueuerActions.removeIf( + (field, worklistActions) -> { + if (isEligibleForPruning(field)) { + return false; + } + worklist.enqueueAll(worklistActions); + return true; + }); + timing.end(); + return changed; } @Override
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java index a5aadef..0cf82dc 100644 --- a/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java +++ b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java
@@ -4,183 +4,27 @@ package com.android.tools.r8.utils; +import com.android.tools.r8.BaseCompilerCommand; +import com.android.tools.r8.utils.compiledump.ArtProfileDumpUtils; import java.io.IOException; -import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; +import java.util.Map.Entry; public class CompileDumpBase { - static void setEnableExperimentalMissingLibraryApiModeling( - Object builder, boolean enableMissingLibraryApiModeling) { - getReflectiveBuilderMethod( - builder, "setEnableExperimentalMissingLibraryApiModeling", boolean.class) - .accept(new Object[] {enableMissingLibraryApiModeling}); - } - - static void setAndroidPlatformBuild(Object builder, boolean androidPlatformBuild) { - getReflectiveBuilderMethod(builder, "setAndroidPlatformBuild", boolean.class) - .accept(new Object[] {androidPlatformBuild}); - } - - static void setIsolatedSplits(Object builder, boolean isolatedSplits) { - getReflectiveBuilderMethod(builder, "setEnableIsolatedSplits", boolean.class) - .accept(new Object[] {isolatedSplits}); - } - - static void setupResourceShrinking( - Path androidResourcesInput, Path androidResourcesOutput, Object builder) { - try { - Class<?> androidResourceProvider = - Class.forName("com.android.tools.r8.AndroidResourceProvider"); - Class<?> androidResourceConsumer = - Class.forName("com.android.tools.r8.AndroidResourceConsumer"); - getReflectiveBuilderMethod(builder, "setAndroidResourceProvider", androidResourceProvider) - .accept(new Object[] {createAndroidResourceProvider(androidResourcesInput)}); - getReflectiveBuilderMethod(builder, "setAndroidResourceConsumer", androidResourceConsumer) - .accept(new Object[] {createAndroidResourceConsumer(androidResourcesOutput)}); - } catch (ClassNotFoundException e) { - // Ignore + static void addArtProfilesForRewriting( + BaseCompilerCommand.Builder<?, ?> builder, Map<Path, Path> artProfileFiles) { + for (Entry<Path, Path> inputOutput : artProfileFiles.entrySet()) { + runIgnoreMissing( + () -> + ArtProfileDumpUtils.addArtProfileForRewriting( + inputOutput.getKey(), inputOutput.getValue(), builder), + "Unable to setup art profile rewriting for " + inputOutput.getKey()); } } - static void addArtProfilesForRewriting(Object builder, Map<Path, Path> artProfileFiles) { - try { - Class<?> artProfileProviderClass = - Class.forName("com.android.tools.r8.profile.art.ArtProfileProvider"); - Class<?> artProfileConsumerClass = - Class.forName("com.android.tools.r8.profile.art.ArtProfileConsumer"); - artProfileFiles.forEach( - (artProfile, residualArtProfile) -> - getReflectiveBuilderMethod( - builder, - "addArtProfileForRewriting", - artProfileProviderClass, - artProfileConsumerClass) - .accept( - new Object[] { - createArtProfileProvider(artProfile), - createResidualArtProfileConsumer(residualArtProfile) - })); - } catch (ClassNotFoundException e) { - // Ignore. - } - } - - static void addStartupProfileProviders(Object builder, List<Path> startupProfileFiles) { - getReflectiveBuilderMethod(builder, "addStartupProfileProviders", Collection.class) - .accept(new Object[] {createStartupProfileProviders(startupProfileFiles)}); - } - - static Object callReflectiveDumpUtilsMethodWithPath(Path path, String method) { - Object[] returnObject = new Object[1]; - boolean found = - callReflectiveUtilsMethod( - method, - new Class<?>[] {Path.class}, - fn -> returnObject[0] = fn.apply(new Object[] {path})); - if (!found) { - System.out.println( - "Unable to call invoke method on path " - + path - + ". " - + "Method " - + method - + "() was not found."); - return null; - } - return returnObject[0]; - } - - static Object createAndroidResourceProvider(Path resourceInput) { - return callReflectiveDumpUtilsMethodWithPath( - resourceInput, "createAndroidResourceProviderFromDumpFile"); - } - - static Object createAndroidResourceConsumer(Path resourceOutput) { - return callReflectiveDumpUtilsMethodWithPath( - resourceOutput, "createAndroidResourceConsumerFromDumpFile"); - } - - static Object createArtProfileProvider(Path artProfile) { - return callReflectiveDumpUtilsMethodWithPath( - artProfile, "createArtProfileProviderFromDumpFile"); - } - - static Object createResidualArtProfileConsumer(Path residualArtProfile) { - return callReflectiveDumpUtilsMethodWithPath( - residualArtProfile, "createResidualArtProfileConsumerFromDumpFile"); - } - - static Collection<Object> createStartupProfileProviders(List<Path> startupProfileFiles) { - List<Object> startupProfileProviders = new ArrayList<>(); - for (Path startupProfileFile : startupProfileFiles) { - boolean found = - callReflectiveUtilsMethod( - "createStartupProfileProviderFromDumpFile", - new Class<?>[] {Path.class}, - fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile}))); - if (!found) { - System.out.println( - "Unable to add startup profiles as input. " - + "Method createStartupProfileProviderFromDumpFile() was not found."); - break; - } - } - return startupProfileProviders; - } - - static Consumer<Object[]> getReflectiveBuilderMethod( - Object builder, String setter, Class<?>... parameters) { - try { - Method declaredMethod = builder.getClass().getMethod(setter, parameters); - return args -> { - try { - declaredMethod.invoke(builder, args); - } catch (Exception e) { - throw new RuntimeException(e); - } - }; - } catch (NoSuchMethodException e) { - System.out.println(setter + " is not available on the compiledump version."); - // The option is not available so we just return an empty consumer - return args -> {}; - } - } - - static boolean callReflectiveUtilsMethod( - String methodName, Class<?>[] parameters, Consumer<Function<Object[], Object>> fnConsumer) { - Class<?> utilsClass; - try { - utilsClass = Class.forName("com.android.tools.r8.utils.CompileDumpUtils"); - } catch (ClassNotFoundException e) { - return false; - } - - Method declaredMethod; - try { - declaredMethod = utilsClass.getDeclaredMethod(methodName, parameters); - } catch (NoSuchMethodException e) { - return false; - } - - fnConsumer.accept( - args -> { - try { - return declaredMethod.invoke(null, args); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - return true; - } - @SuppressWarnings({"CatchAndPrintStackTrace", "DefaultCharset"}) // We cannot use StringResource since this class is added to the class path and has access only // to the public APIs. @@ -195,4 +39,28 @@ return content; } + + protected static void runIgnoreMissing(Runnable runnable, String onMissing) { + try { + runnable.run(); + } catch (NoClassDefFoundError | NoSuchMethodError e) { + System.out.println(onMissing); + } + } + + protected static class BooleanBox { + public boolean value = false; + + public BooleanBox(boolean value) { + this.value = value; + } + + public void set(boolean value) { + this.value = value; + } + + public boolean get() { + return value; + } + } }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java index 9112b77..ef8e947 100644 --- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java +++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -10,8 +10,10 @@ import com.android.tools.r8.OutputMode; import com.android.tools.r8.R8; import com.android.tools.r8.R8Command; -import com.android.tools.r8.R8Command.Builder; import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.utils.compiledump.CompilerCommandDumpUtils; +import com.android.tools.r8.utils.compiledump.ResourceShrinkerDumpUtils; +import com.android.tools.r8.utils.compiledump.StartupProfileDumpUtils; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -95,9 +97,9 @@ Path androidResourcesOutput = null; int minApi = 1; int threads = -1; - boolean enableMissingLibraryApiModeling = false; - boolean androidPlatformBuild = false; - boolean isolatedSplits = false; + BooleanBox enableMissingLibraryApiModeling = new BooleanBox(false); + BooleanBox androidPlatformBuild = new BooleanBox(false); + BooleanBox isolatedSplits = new BooleanBox(false); for (int i = 0; i < args.length; i++) { String option = args[i]; if (VALID_OPTIONS.contains(option)) { @@ -123,13 +125,13 @@ break; } case "--enable-missing-library-api-modeling": - enableMissingLibraryApiModeling = true; + enableMissingLibraryApiModeling.set(true); break; case "--android-platform-build": - androidPlatformBuild = true; + androidPlatformBuild.set(true); break; case ISOLATED_SPLITS_FLAG: - isolatedSplits = true; + isolatedSplits.set(true); break; default: throw new IllegalArgumentException("Unimplemented option: " + option); @@ -228,7 +230,7 @@ program.add(Paths.get(option)); } } - Builder commandBuilder = + R8Command.Builder commandBuilder = new CompatProguardCommandBuilder(isCompatMode) .addProgramFiles(program) .addLibraryFiles(library) @@ -238,15 +240,35 @@ .setOutput(outputPath, outputMode) .setMode(compilationMode); addArtProfilesForRewriting(commandBuilder, artProfileFiles); - addStartupProfileProviders(commandBuilder, startupProfileFiles); - setAndroidPlatformBuild(commandBuilder, androidPlatformBuild); - setIsolatedSplits(commandBuilder, isolatedSplits); - setEnableExperimentalMissingLibraryApiModeling(commandBuilder, enableMissingLibraryApiModeling); + if (!startupProfileFiles.isEmpty()) { + runIgnoreMissing( + () -> StartupProfileDumpUtils.addStartupProfiles(startupProfileFiles, commandBuilder), + "Could not add startup profiles."); + } + runIgnoreMissing( + () -> + CompilerCommandDumpUtils.setAndroidPlatformBuild( + commandBuilder, androidPlatformBuild.get()), + "Android platform flag not available."); + runIgnoreMissing( + () -> CompilerCommandDumpUtils.setIsolatedSplits(commandBuilder, isolatedSplits.get()), + "Isolated splits flag not available."); + runIgnoreMissing( + () -> + CompilerCommandDumpUtils.setEnableExperimentalMissingLibraryApiModeling( + commandBuilder, enableMissingLibraryApiModeling.get()), + "Missing library api modeling not available."); if (desugaredLibJson != null) { commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson)); } if (androidResourcesInput != null) { - setupResourceShrinking(androidResourcesInput, androidResourcesOutput, commandBuilder); + Path finalAndroidResourcesInput = androidResourcesInput; + Path finalAndroidResourcesOutput = androidResourcesOutput; + runIgnoreMissing( + () -> + ResourceShrinkerDumpUtils.setupBaseResourceShrinking( + finalAndroidResourcesInput, finalAndroidResourcesOutput, commandBuilder), + "Failed initializing resource shrinker."); } if (desugaredLibKeepRuleConsumer != null) { commandBuilder.setDesugaredLibraryKeepRuleConsumer(desugaredLibKeepRuleConsumer);
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java index 137dfe7..e9ce914 100644 --- a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java +++ b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
@@ -9,6 +9,8 @@ import com.android.tools.r8.D8Command; import com.android.tools.r8.OutputMode; import com.android.tools.r8.StringConsumer; +import com.android.tools.r8.utils.compiledump.CompilerCommandDumpUtils; +import com.android.tools.r8.utils.compiledump.StartupProfileDumpUtils; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -71,8 +73,8 @@ List<Path> startupProfileFiles = new ArrayList<>(); int minApi = 1; int threads = -1; - boolean enableMissingLibraryApiModeling = false; - boolean androidPlatformBuild = false; + BooleanBox enableMissingLibraryApiModeling = new BooleanBox(false); + BooleanBox androidPlatformBuild = new BooleanBox(false); for (int i = 0; i < args.length; i++) { String option = args[i]; if (VALID_OPTIONS.contains(option)) { @@ -93,10 +95,10 @@ break; } case "--enable-missing-library-api-modeling": - enableMissingLibraryApiModeling = true; + enableMissingLibraryApiModeling.set(true); break; case "--android-platform-build": - androidPlatformBuild = true; + androidPlatformBuild.set(true); break; default: throw new IllegalArgumentException("Unimplemented option: " + option); @@ -177,9 +179,21 @@ .setOutput(outputPath, outputMode) .setMode(compilationMode); addArtProfilesForRewriting(commandBuilder, artProfileFiles); - addStartupProfileProviders(commandBuilder, startupProfileFiles); - setAndroidPlatformBuild(commandBuilder, androidPlatformBuild); - setEnableExperimentalMissingLibraryApiModeling(commandBuilder, enableMissingLibraryApiModeling); + if (!startupProfileFiles.isEmpty()) { + runIgnoreMissing( + () -> StartupProfileDumpUtils.addStartupProfiles(startupProfileFiles, commandBuilder), + "Could not add startup profiles."); + } + runIgnoreMissing( + () -> + CompilerCommandDumpUtils.setAndroidPlatformBuild( + commandBuilder, androidPlatformBuild.get()), + "Android platform flag not available."); + runIgnoreMissing( + () -> + CompilerCommandDumpUtils.setEnableExperimentalMissingLibraryApiModeling( + commandBuilder, enableMissingLibraryApiModeling.get()), + "Missing library api modeling not available."); if (desugaredLibJson != null) { commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson)); }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java b/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java deleted file mode 100644 index ef013f6..0000000 --- a/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java +++ /dev/null
@@ -1,84 +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.utils; - -import com.android.tools.r8.AndroidResourceConsumer; -import com.android.tools.r8.AndroidResourceProvider; -import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer; -import com.android.tools.r8.ArchiveProtoAndroidResourceProvider; -import com.android.tools.r8.KeepMethodForCompileDump; -import com.android.tools.r8.origin.Origin; -import com.android.tools.r8.origin.PathOrigin; -import com.android.tools.r8.profile.art.ArtProfileConsumer; -import com.android.tools.r8.profile.art.ArtProfileConsumerUtils; -import com.android.tools.r8.profile.art.ArtProfileProvider; -import com.android.tools.r8.profile.art.ArtProfileProviderUtils; -import com.android.tools.r8.references.MethodReference; -import com.android.tools.r8.references.Reference; -import com.android.tools.r8.startup.StartupProfileBuilder; -import com.android.tools.r8.startup.StartupProfileProvider; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; - -class CompileDumpUtils { - - @KeepMethodForCompileDump - static ArtProfileProvider createArtProfileProviderFromDumpFile(Path artProfile) { - return ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfile); - } - - @KeepMethodForCompileDump - static ArtProfileConsumer createResidualArtProfileConsumerFromDumpFile(Path residualArtProfile) { - return ArtProfileConsumerUtils.create(residualArtProfile); - } - - @KeepMethodForCompileDump - static AndroidResourceProvider createAndroidResourceProviderFromDumpFile(Path resourceInput) { - return new ArchiveProtoAndroidResourceProvider(resourceInput); - } - - @KeepMethodForCompileDump - static AndroidResourceConsumer createAndroidResourceConsumerFromDumpFile(Path resourceOutput) { - return new ArchiveProtoAndroidResourceConsumer(resourceOutput); - } - - @KeepMethodForCompileDump - static StartupProfileProvider createStartupProfileProviderFromDumpFile(Path path) { - return new StartupProfileProvider() { - - @Override - public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) { - try { - try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { - while (bufferedReader.ready()) { - String rule = bufferedReader.readLine(); - MethodReference methodReference = MethodReferenceUtils.parseSmaliString(rule); - if (methodReference != null) { - startupProfileBuilder.addStartupMethod( - startupMethodBuilder -> - startupMethodBuilder.setMethodReference(methodReference)); - } else { - assert DescriptorUtils.isClassDescriptor(rule); - startupProfileBuilder.addStartupClass( - startupClassBuilder -> - startupClassBuilder.setClassReference(Reference.classFromDescriptor(rule))); - } - } - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public Origin getOrigin() { - return new PathOrigin(path); - } - }; - } -}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index f35b8b4..b0757f4 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2481,7 +2481,6 @@ public boolean allowUnusedDontWarnRules = true; public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true; public boolean alwaysUsePessimisticRegisterAllocation = false; - public boolean enableLiveIntervalsSplittingForInvokeRange = false; public boolean enableRegisterHintsForBlockedRegisters = false; // TODO(b/374266460): Investigate why enabling this leads to more moves, for example, in // JetNews. Also investigate the impact on performance and how often the refinement pass is
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java index 5df6add..acd31a4 100644 --- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java +++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -38,6 +38,18 @@ return true; } + public static <T> boolean allWithPrevious( + Iterable<? extends T> iterable, BiPredicate<? super T, ? super T> predicate) { + T previous = null; + for (T element : iterable) { + if (!predicate.test(element, previous)) { + return false; + } + previous = element; + } + return true; + } + public static <S, T> boolean any( Iterable<S> iterable, Function<S, T> transform, Predicate<T> predicate) { for (S element : iterable) {
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java index ea46d64..7ec47cb 100644 --- a/src/main/java/com/android/tools/r8/utils/SetUtils.java +++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -13,6 +13,7 @@ import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -118,6 +119,12 @@ return builder.build(); } + public static <T extends Comparable<T>> TreeSet<T> newTreeSet(T element) { + TreeSet<T> result = new TreeSet<>(); + result.add(element); + return result; + } + public static <T, S> Set<T> mapIdentityHashSet(Collection<S> set, Function<S, T> fn) { Set<T> out = newIdentityHashSet(set.size()); for (S element : set) {
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/ArtProfileDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/ArtProfileDumpUtils.java new file mode 100644 index 0000000..51935bb --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/compiledump/ArtProfileDumpUtils.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2024, 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.utils.compiledump; + +import com.android.tools.r8.BaseCompilerCommand; +import com.android.tools.r8.profile.art.ArtProfileConsumerUtils; +import com.android.tools.r8.profile.art.ArtProfileProviderUtils; +import java.nio.file.Path; + +public class ArtProfileDumpUtils { + public static void addArtProfileForRewriting( + Path input, Path output, BaseCompilerCommand.Builder<?, ?> builder) { + builder.addArtProfileForRewriting( + ArtProfileProviderUtils.createFromHumanReadableArtProfile(input), + ArtProfileConsumerUtils.create(output)); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/CompilerCommandDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/CompilerCommandDumpUtils.java new file mode 100644 index 0000000..3a7f051 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/compiledump/CompilerCommandDumpUtils.java
@@ -0,0 +1,35 @@ +// Copyright (c) 2024, 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.utils.compiledump; + +import com.android.tools.r8.D8Command; +import com.android.tools.r8.R8Command; + +// Simple boolean commands directly on compiler command builders +public class CompilerCommandDumpUtils { + public static void setEnableExperimentalMissingLibraryApiModeling( + R8Command.Builder builder, boolean enable) { + builder.setEnableExperimentalMissingLibraryApiModeling(enable); + } + + public static void setEnableExperimentalMissingLibraryApiModeling( + D8Command.Builder builder, boolean enable) { + builder.setEnableExperimentalMissingLibraryApiModeling(enable); + } + + public static void setAndroidPlatformBuild( + R8Command.Builder builder, boolean androidPlatformBuild) { + builder.setAndroidPlatformBuild(androidPlatformBuild); + } + + public static void setAndroidPlatformBuild( + D8Command.Builder builder, boolean androidPlatformBuild) { + builder.setAndroidPlatformBuild(androidPlatformBuild); + } + + public static void setIsolatedSplits(R8Command.Builder builder, boolean isolatedSplits) { + builder.setEnableIsolatedSplits(isolatedSplits); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/FeatureSplitResourceShrinkerDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/FeatureSplitResourceShrinkerDumpUtils.java new file mode 100644 index 0000000..e58d34e --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/compiledump/FeatureSplitResourceShrinkerDumpUtils.java
@@ -0,0 +1,18 @@ +// Copyright (c) 2024, 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.utils.compiledump; + +import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer; +import com.android.tools.r8.ArchiveProtoAndroidResourceProvider; +import com.android.tools.r8.R8Command; +import java.nio.file.Path; + +public class FeatureSplitResourceShrinkerDumpUtils { + public static void setupBaseResourceShrinking( + Path input, Path output, R8Command.Builder builder) { + builder.setAndroidResourceProvider(new ArchiveProtoAndroidResourceProvider(input)); + builder.setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output)); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java new file mode 100644 index 0000000..7be55c1 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java
@@ -0,0 +1,18 @@ +// Copyright (c) 2024, 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.utils.compiledump; + +import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer; +import com.android.tools.r8.ArchiveProtoAndroidResourceProvider; +import com.android.tools.r8.R8Command; +import java.nio.file.Path; + +public class ResourceShrinkerDumpUtils { + public static void setupBaseResourceShrinking( + Path input, Path output, R8Command.Builder builder) { + builder.setAndroidResourceProvider(new ArchiveProtoAndroidResourceProvider(input)); + builder.setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output)); + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/StartupProfileDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/StartupProfileDumpUtils.java new file mode 100644 index 0000000..c78a082 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/compiledump/StartupProfileDumpUtils.java
@@ -0,0 +1,53 @@ +// Copyright (c) 2024, 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.utils.compiledump; + +import com.android.tools.r8.D8Command; +import com.android.tools.r8.R8Command; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.origin.PathOrigin; +import com.android.tools.r8.startup.StartupProfileBuilder; +import com.android.tools.r8.startup.StartupProfileProvider; +import com.android.tools.r8.utils.UTF8TextInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +public class StartupProfileDumpUtils { + public static void addStartupProfiles(List<Path> inputs, R8Command.Builder builder) { + builder.addStartupProfileProviders(createProviders(inputs)); + } + + public static void addStartupProfiles(List<Path> inputs, D8Command.Builder builder) { + builder.addStartupProfileProviders(createProviders(inputs)); + } + + private static List<StartupProfileProvider> createProviders(List<Path> inputs) { + return inputs.stream() + .map(StartupProfileDumpUtils::createStartupProfileProvider) + .collect(Collectors.toList()); + } + + private static StartupProfileProvider createStartupProfileProvider(Path path) { + return new StartupProfileProvider() { + @Override + public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) { + try { + startupProfileBuilder.addHumanReadableArtProfile( + new UTF8TextInputStream(path), builder -> {}); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public Origin getOrigin() { + return new PathOrigin(path); + } + }; + } +}
diff --git a/src/main/keep.txt b/src/main/keep.txt index ddb667f..6034f07 100644 --- a/src/main/keep.txt +++ b/src/main/keep.txt
@@ -2,8 +2,6 @@ # 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. --keepclasseswithmembers class * { @com.android.tools.r8.KeepMethodForCompileDump <methods>; } - -keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); } -keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); } -keep public class com.android.tools.r8.ExtractMarker { public static void main(java.lang.String[]); }
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt b/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt index 88bb5e4..e81c951 100644 --- a/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt +++ b/src/resourceshrinker/java/com/android/build/shrinker/ResourceTableUtil.kt
@@ -111,7 +111,7 @@ private fun packageIdFromIdentifier( identifier: Int ): Int = - identifier shr 24 + identifier ushr 24 private fun typeIdFromIdentifier( identifier: Int
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java index fccc50d..34e581b 100644 --- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java +++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -58,11 +59,13 @@ private final List<Supplier<InputStream>> manifestProviders = new ArrayList<>(); private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>(); private final Map<FeatureSplit, ResourceTable> resourceTables = new HashMap<>(); + private final ShrinkerDebugReporter shrinkerDebugReporter; private ClassReferenceCallback enqueuerCallback; private Map<Integer, List<String>> resourceIdToXmlFiles; private Set<String> packageNames; private final Set<String> seenNoneClassValues = new HashSet<>(); private final Set<Integer> seenResourceIds = new HashSet<>(); + private final Map<Resource, String> reachabilityMap = new ConcurrentHashMap<>(); private static final Set<String> SPECIAL_MANIFEST_ELEMENTS = ImmutableSet.of( @@ -86,10 +89,11 @@ Function<Exception, RuntimeException> errorHandler, ShrinkerDebugReporter shrinkerDebugReporter) { r8ResourceShrinkerModel = new R8ResourceShrinkerModel(shrinkerDebugReporter, true); + this.shrinkerDebugReporter = shrinkerDebugReporter; this.errorHandler = errorHandler; } - public void trace(int id) { + public void trace(int id, String reachableFrom) { if (!seenResourceIds.add(id)) { return; } @@ -97,12 +101,17 @@ if (resource == null) { return; } + assert reachableFrom != null; + + // For deterministic output, sort the strings lexicographically. + reachabilityMap.compute( + resource, (r, v) -> v == null || v.compareTo(reachableFrom) > 0 ? reachableFrom : v); ResourceUsageModel.markReachable(resource); traceXmlForResourceId(id); if (resource.references != null) { for (Resource reference : resource.references) { if (!reference.isReachable()) { - trace(reference.value); + trace(reference.value, reference.toString()); } } } @@ -122,7 +131,7 @@ r8ResourceShrinkerModel .getResourceStore() .processToolsAttributes() - .forEach(resource -> trace(resource.value)); + .forEach(resource -> trace(resource.value, "keep xml file")); for (Supplier<InputStream> manifestProvider : manifestProviders) { traceXml("AndroidManifest.xml", manifestProvider.get()); } @@ -212,6 +221,13 @@ featureSplit, ResourceTableUtilKt.nullOutEntriesWithIds(resourceTable, resourceIdsToRemove, true)); }); + for (Map.Entry<Resource, String> resourceStringEntry : reachabilityMap.entrySet()) { + shrinkerDebugReporter.debug( + () -> + resourceStringEntry.getKey().toString() + + " reachable from " + + resourceStringEntry.getValue()); + } return new ShrinkerResult(resEntriesToKeep, shrunkenTables); } @@ -243,7 +259,7 @@ // resources for the reachable marker. ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(xmlNode, r8ResourceShrinkerModel) .iterator() - .forEachRemaining(resource -> trace(resource.value)); + .forEachRemaining(resource -> trace(resource.value, xmlFile)); } catch (IOException e) { errorHandler.apply(e); }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java index f29440c..e1c7e00 100644 --- a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java +++ b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
@@ -13,8 +13,8 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper.DexVm.Version; -import com.android.tools.r8.dex.code.DexInvokeDirect; -import com.android.tools.r8.dex.code.DexInvokeVirtual; +import com.android.tools.r8.dex.code.DexInvokeDirectRange; +import com.android.tools.r8.dex.code.DexInvokeVirtualRange; import com.android.tools.r8.graph.DexCode; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.smali.SmaliBuilder; @@ -121,7 +121,7 @@ assertNotNull(method); DexCode code = method.getCode().asDexCode(); // The given invoke line is remained as-is. - assertTrue(code.instructions[2] instanceof DexInvokeDirect); + assertTrue(code.instructions[2] instanceof DexInvokeDirectRange); }); } @@ -152,7 +152,7 @@ assertNotNull(method); DexCode code = method.getCode().asDexCode(); // The given invoke line is changed to invoke-virtual - assertTrue(code.instructions[2] instanceof DexInvokeVirtual); + assertTrue(code.instructions[2] instanceof DexInvokeVirtualRange); }); }
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java index 1028b38..67dbef6 100644 --- a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java +++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
@@ -118,9 +118,22 @@ ensureResourceReachabilityState(strings, "drawable", "foobar", true); ensureRootResourceState(strings, "drawable", "foobar", true); ensureUnusedState(strings, "drawable", "foobar", false); + } else { + assertTrue(finished.get()); + List<String> strings = StringUtils.splitLines(log.toString()); + ensureReachableOptimized(strings, "string", "bar", true); + ensureReachableOptimized(strings, "string", "foo", true); + ensureReachableOptimized(strings, "drawable", "foobar", true); + ensureReachableOptimized(strings, "drawable", "unused_drawable", false); } } + private void ensureReachableOptimized( + List<String> logStrings, String type, String name, boolean reachable) { + assertEquals( + logStrings.stream().anyMatch(s -> s.startsWith(type + ":" + name + ":")), reachable); + } + private void ensureDexReachableResourcesState( List<String> logStrings, String type, String name, boolean reachable) { // Example line:
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingWithFeatures.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingWithFeatures.java index 221f011..e924ca6 100644 --- a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingWithFeatures.java +++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingWithFeatures.java
@@ -35,11 +35,17 @@ @Parameter(1) public boolean optimized; - @Parameters(name = "{0}, optimized: {1}") + @Parameter(2) + public int featurePackageId; + + @Parameters(name = "{0}, optimized: {1}, feature_package_id: {2}") public static List<Object[]> data() { return buildParameters( getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(), - BooleanUtils.values()); + BooleanUtils.values(), + // Ensure that we can handle resource ids both bigger and smaller than 127, see + // b/378470047 + new Integer[] {0x7E, 0x80}); } public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception { @@ -49,11 +55,10 @@ .build(temp); } - public static AndroidTestResource getFeatureSplitTestResources(TemporaryFolder temp) - throws IOException { + public AndroidTestResource getFeatureSplitTestResources(TemporaryFolder temp) throws IOException { return new AndroidTestResourceBuilder() .withSimpleManifestAndAppNameString() - .setPackageId(0x7E) + .setPackageId(featurePackageId) .addRClassInitializeWithDefaultValues(FeatureSplit.R.string.class) .build(temp); } @@ -115,9 +120,9 @@ }) .inspectShrunkenResourcesForFeature( resourceTableInspector -> { - resourceTableInspector.assertContainsResourceWithName("string", "feature_used"); - resourceTableInspector.assertDoesNotContainResourceWithName( - "string", "feature_unused"); + resourceTableInspector.assertContainsResourceWithName("string", "feature_used"); + resourceTableInspector.assertDoesNotContainResourceWithName( + "string", "feature_unused"); }, FeatureSplit.class.getName()) .run(parameters.getRuntime(), Base.class)
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java index 21ee0fc..10abbeb 100644 --- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java +++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -120,9 +120,7 @@ add = it.next(); } it.removeInstructionIgnoreOutValue(); - constNumber.setBlock(tryBlock); - add.setBlock(tryBlock); - tryBlock.getInstructions().add(0, add); - tryBlock.getInstructions().add(0, constNumber); + tryBlock.getInstructions().addFirst(add); + tryBlock.getInstructions().addFirst(constNumber); } }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java index 9c7cf17..d64aa20 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java +++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -8,7 +8,7 @@ import static org.junit.Assert.assertTrue; import com.android.tools.r8.dex.code.DexConstString; -import com.android.tools.r8.dex.code.DexInvokeStatic; +import com.android.tools.r8.dex.code.DexInvokeStaticRange; import com.android.tools.r8.dex.code.DexReturnVoid; import com.android.tools.r8.graph.DexCode; import com.android.tools.r8.smali.SmaliBuilder; @@ -55,7 +55,7 @@ assertTrue(code.instructions[0] instanceof DexConstString); DexConstString constString = (DexConstString) code.instructions[0]; assertNotEquals(BOO, constString.getString().toString()); - assertTrue(code.instructions[1] instanceof DexInvokeStatic); + assertTrue(code.instructions[1] instanceof DexInvokeStaticRange); assertTrue(code.instructions[2] instanceof DexReturnVoid); } @@ -91,7 +91,7 @@ assertTrue(code.instructions[0] instanceof DexConstString); DexConstString constString = (DexConstString) code.instructions[0]; assertEquals(BOO, constString.getString().toString()); - assertTrue(code.instructions[1] instanceof DexInvokeStatic); + assertTrue(code.instructions[1] instanceof DexInvokeStaticRange); assertTrue(code.instructions[2] instanceof DexReturnVoid); }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java index 3449aa1..eb24283 100644 --- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java +++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -15,7 +15,7 @@ import com.android.tools.r8.dex.code.DexConst4; import com.android.tools.r8.dex.code.DexConstClass; import com.android.tools.r8.dex.code.DexConstString; -import com.android.tools.r8.dex.code.DexFilledNewArray; +import com.android.tools.r8.dex.code.DexFilledNewArrayRange; import com.android.tools.r8.dex.code.DexInvokeVirtual; import com.android.tools.r8.dex.code.DexMoveResultObject; import com.android.tools.r8.dex.code.DexNewArray; @@ -75,9 +75,9 @@ private void inspectGetMethodTest(MethodSubject method) { // Accept either array construction style (differs based on minSdkVersion). DexCode code = method.getMethod().getCode().asDexCode(); - if (code.instructions[1] instanceof DexFilledNewArray) { + if (code.instructions[1] instanceof DexFilledNewArrayRange) { assertTrue(code.instructions[0] instanceof DexConstClass); - assertTrue(code.instructions[1] instanceof DexFilledNewArray); + assertTrue(code.instructions[1] instanceof DexFilledNewArrayRange); assertTrue(code.instructions[2] instanceof DexMoveResultObject); assertTrue(code.instructions[3] instanceof DexConstClass); assertTrue(code.instructions[4] instanceof DexConstString);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DayTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DayTest.java index 3b5bfd0..93d7554 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DayTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DayTest.java
@@ -77,12 +77,10 @@ if (parameters.isCfRuntime() && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK9)) { return SIMPLIFIED_CHINESE_EXPECTED_RESULT_JDK8; } - if (parameters.isDexRuntime() && libraryDesugaringSpecification.hasTimeDesugaring(parameters)) { - if (libraryDesugaringSpecification == JDK8) { - return MISSING_STANDALONE; - } else { - return SIMPLIFIED_CHINESE_NARROW_DAY_ISSUE; - } + if (parameters.isDexRuntime() + && libraryDesugaringSpecification.hasTimeDesugaring(parameters) + && libraryDesugaringSpecification == JDK8) { + return MISSING_STANDALONE; } return SIMPLIFIED_CHINESE_EXPECTED_RESULT; }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java index 46bc0d0..01a5e94 100644 --- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -9,7 +9,6 @@ import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.R8TestCompileResult; -import com.android.tools.r8.SingleTestRunResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.codeinspector.CodeInspector; import java.util.EnumSet; @@ -72,11 +71,8 @@ for (Class<?> main : TESTS) { compile .run(parameters.getRuntime(), main) - .applyIf( - main == EnumSetTest.class && enumKeepRules.getKeepRules().isEmpty(), - // EnumSet and EnumMap cannot be used without the enumKeepRules. - SingleTestRunResult::assertFailure, - result -> result.assertSuccess().inspectStdOut(this::assertLines2By2Correct)); + .assertSuccess() + .inspectStdOut(this::assertLines2By2Correct); } }
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java index 484aca5..dbbec8e 100644 --- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java +++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionList; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.NumericType; import com.android.tools.r8.ir.code.Position; @@ -97,7 +98,8 @@ assertEquals(argumentInstructions, test.countArgumentInstructions()); assertEquals(firstBlockInstructions, block.getInstructions().size()); - assertTrue(!block.getInstructions().get(i).isArgument()); + InstructionList instructionList = block.getInstructions(); + assertTrue(!instructionList.getNth(i).isArgument()); InstructionListIterator iterator = test.listIteratorAt(block, i); BasicBlock newBlock = iterator.split(code); @@ -132,7 +134,8 @@ assertEquals(argumentInstructions, test.countArgumentInstructions()); assertEquals(firstBlockInstructions, block.getInstructions().size()); - assertTrue(!block.getInstructions().get(i).isArgument()); + InstructionList instructionList = block.getInstructions(); + assertTrue(!instructionList.getNth(i).isArgument()); InstructionListIterator iterator = test.listIteratorAt(block, i); BasicBlock newBlock = iterator.split(code, 1); @@ -337,7 +340,8 @@ assertEquals(argumentInstructions, test.countArgumentInstructions()); assertEquals(firstBlockInstructions, block.getInstructions().size()); - assertTrue(!block.getInstructions().get(i).isArgument()); + InstructionList instructionList = block.getInstructions(); + assertTrue(!instructionList.getNth(i).isArgument()); InstructionListIterator iterator = test.listIteratorAt(block, i); BasicBlock newBlock = iterator.split(code); @@ -459,7 +463,8 @@ assertEquals(argumentInstructions, test.countArgumentInstructions()); assertEquals(firstBlockInstructions, block.getInstructions().size()); - assertTrue(!block.getInstructions().get(i).isArgument()); + InstructionList instructionList = block.getInstructions(); + assertTrue(!instructionList.getNth(i).isArgument()); InstructionListIterator iterator = test.listIteratorAt(block, i); BasicBlock newBlock = iterator.split(code);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java index eff9d7a..adc7fb7 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -87,11 +87,11 @@ // // Then test that peephole optimization realizes that the last const number // is needed and the value 10 is *not* still in register 0 at that point. + IRMetadata metadata = IRMetadata.unknown(); final NumberGenerator basicBlockNumberGenerator = new NumberGenerator(); - BasicBlock block = new BasicBlock(); + BasicBlock block = new BasicBlock(metadata); block.setNumber(basicBlockNumberGenerator.next()); - IRMetadata metadata = IRMetadata.unknown(); Position position = SyntheticPosition.builder().disableMethodCheck().setLine(0).build(); Value v3 = new Value(3, TypeElement.getLong(), null);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java index 997f0e0..275c0e3 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -69,10 +69,10 @@ // return final NumberGenerator basicBlockNumberGenerator = new NumberGenerator(); Position position = SyntheticPosition.builder().setLine(0).disableMethodCheck().build(); - BasicBlock block2 = new BasicBlock(); + BasicBlock block2 = new BasicBlock(metadata); BasicBlock block0 = BasicBlock.createGotoBlock(basicBlockNumberGenerator.next(), position, metadata, block2); - BasicBlock block1 = new BasicBlock(); + BasicBlock block1 = new BasicBlock(metadata); block1.setNumber(basicBlockNumberGenerator.next()); block2.setNumber(basicBlockNumberGenerator.next()); block0.setFilledForTesting(); @@ -133,9 +133,9 @@ // goto block3 final NumberGenerator basicBlockNumberGenerator = new NumberGenerator(); Position position = SyntheticPosition.builder().setLine(0).disableMethodCheck().build(); - BasicBlock block0 = new BasicBlock(); + BasicBlock block0 = new BasicBlock(metadata); block0.setNumber(basicBlockNumberGenerator.next()); - BasicBlock block2 = new BasicBlock(); + BasicBlock block2 = new BasicBlock(metadata); BasicBlock block1 = BasicBlock.createGotoBlock(basicBlockNumberGenerator.next(), position, metadata); block2.setNumber(basicBlockNumberGenerator.next()); @@ -144,7 +144,7 @@ block2.add(ret, metadata); block2.setFilledForTesting(); - BasicBlock block3 = new BasicBlock(); + BasicBlock block3 = new BasicBlock(metadata); block3.setNumber(basicBlockNumberGenerator.next()); Instruction instruction = new Goto(); instruction.setPosition(position); @@ -196,8 +196,8 @@ IRMetadata.unknown(), MethodConversionOptions.forD8(appView)); new TrivialGotosCollapser(appView).run(code, Timing.empty()); - assertTrue(block0.getInstructions().get(1).isIf()); - assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock()); + assertTrue(block0.getInstructions().getFirst().getNext().isIf()); + assertEquals(block1, block0.getInstructions().getFirst().getNext().asIf().fallthroughBlock()); assertTrue(blocks.containsAll(ImmutableList.of(block0, block1, block2, block3))); } }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java index 269b2f9..9ef01cc 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -169,7 +169,7 @@ // and block 4 to have: // vY : phi(v3, vX) BasicBlock basicBlock = irCode.blocks.get(0); - Argument argument = basicBlock.getInstructions().get(0).asArgument(); + Argument argument = basicBlock.getInstructions().getFirst().asArgument(); assertNotNull(argument); Value argumentValue = argument.outValue(); @@ -203,7 +203,7 @@ secondPhi.addOperands(ImmutableList.of(argumentValue, firstPhi)); // Replace the invoke to use the phi - InstanceGet instanceGet = block1.getInstructions().get(0).asInstanceGet(); + InstanceGet instanceGet = block1.getInstructions().getFirst().asInstanceGet(); assertNotNull(instanceGet); assertEquals(A.class.getTypeName(), instanceGet.getField().holder.toSourceString()); instanceGet.replaceValue(0, firstPhi);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java index b9c24d6..11fb04d 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
@@ -102,8 +102,10 @@ assertTrue(entryBlock.getInstructions().getFirst().isArgument()); assertTrue(entryBlock.getInstructions().getLast().isReturn()); - Instruction nullCheckInstruction = - entryBlock.getInstructions().get(1 + BooleanUtils.intValue(isNPEWithMessage)); + Instruction nullCheckInstruction = entryBlock.getInstructions().getFirst().getNext(); + if (isNPEWithMessage) { + nullCheckInstruction = nullCheckInstruction.getNext(); + } assertFalse(isNPEWithMessage); assertTrue(nullCheckInstruction.isInvokeVirtual()); assertEquals(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java index e3e4fa2..864b64b 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -19,8 +19,11 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.dex.code.DexInstruction; import com.android.tools.r8.dex.code.DexInvokeDirect; +import com.android.tools.r8.dex.code.DexInvokeDirectRange; import com.android.tools.r8.dex.code.DexInvokeStatic; +import com.android.tools.r8.dex.code.DexInvokeStaticRange; import com.android.tools.r8.dex.code.DexInvokeVirtual; +import com.android.tools.r8.dex.code.DexInvokeVirtualRange; import com.android.tools.r8.dex.code.DexSgetObject; import com.android.tools.r8.dex.code.DexSputObject; import com.android.tools.r8.graph.DexCode; @@ -377,19 +380,16 @@ .map(DexInstruction::getField) .filter(fld -> isTypeOfInterest(fld.holder)) .map(DexField::toSourceString), - filterInstructionKind(code, DexInvokeStatic.class) - .map(insn -> (DexInvokeStatic) insn) - .map(DexInvokeStatic::getMethod) + filterInstructionKind(code, DexInvokeStatic.class, DexInvokeStaticRange.class) + .map(DexInstruction::getMethod) .filter(method -> isTypeOfInterest(method.holder)) .map(method -> "STATIC: " + method.toSourceString()), - filterInstructionKind(code, DexInvokeVirtual.class) - .map(insn -> (DexInvokeVirtual) insn) - .map(DexInvokeVirtual::getMethod) + filterInstructionKind(code, DexInvokeVirtual.class, DexInvokeVirtualRange.class) + .map(DexInstruction::getMethod) .filter(method -> isTypeOfInterest(method.holder)) .map(method -> "VIRTUAL: " + method.toSourceString()), - filterInstructionKind(code, DexInvokeDirect.class) - .map(insn -> (DexInvokeDirect) insn) - .map(DexInvokeDirect::getMethod) + filterInstructionKind(code, DexInvokeDirect.class, DexInvokeDirectRange.class) + .map(DexInstruction::getMethod) .filter(method -> isTypeOfInterest(method.holder)) .map(method -> "DIRECT: " + method.toSourceString())) .map(txt -> txt.replace("java.lang.", ""))
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java index 24fbe6d..e5a34c5 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java
@@ -44,7 +44,7 @@ inspector.clazz(Main.class).uniqueInstanceInitializer(); assertThat(testMethodSubject, isPresent()); assertEquals( - 2, + 1, testMethodSubject .streamInstructions() .filter(InstructionSubject::isMove)
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeAll4BitRegistersTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeAll4BitRegistersTest.java new file mode 100644 index 0000000..42b845d --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeAll4BitRegistersTest.java
@@ -0,0 +1,93 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InvokeRangeAll4BitRegistersTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build(); + } + + @Test + public void testD8Debug() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .debug() + .setMinApi(parameters) + .compile() + .runDex2Oat(parameters.getRuntime()) + .assertNoVerificationErrors(); + } + + @Test + public void testD8Release() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .release() + .setMinApi(parameters) + .compile() + .runDex2Oat(parameters.getRuntime()) + .assertNoVerificationErrors(); + } + + static class Main { + + Main f; + + static void test() { + int a0 = 0; + int a1 = 1; + int a2 = 2; + int a3 = 3; + int a4 = 4; + int a5 = 5; + int a6 = 6; + int a7 = 7; + int a8 = 8; + int a9 = 9; + int a10 = 10; + int a11 = 11; + int a12 = 12; + int a13 = 13; + int a14 = 14; + int a15 = 15; + Main main = accept16(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + main.f = main; + } + + static Main accept16( + int a0, + int a1, + int a2, + int a3, + int a4, + int a5, + int a6, + int a7, + int a8, + int a9, + int a10, + int a11, + int a12, + int a13, + int a14, + int a15) { + return null; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeAll4BitRegistersWideTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeAll4BitRegistersWideTest.java new file mode 100644 index 0000000..f5a7060 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeAll4BitRegistersWideTest.java
@@ -0,0 +1,94 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InvokeRangeAll4BitRegistersWideTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build(); + } + + @Test + public void testD8Debug() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .debug() + .setMinApi(parameters) + .compile() + .runDex2Oat(parameters.getRuntime()) + .assertNoVerificationErrors(); + } + + @Test + public void testD8Release() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .release() + .setMinApi(parameters) + .compile() + .runDex2Oat(parameters.getRuntime()) + .assertNoVerificationErrors(); + } + + static class Main { + + long f; + + static void test() { + int a0 = 0; + int a1 = 1; + int a2 = 2; + int a3 = 3; + int a4 = 4; + int a5 = 5; + int a6 = 6; + int a7 = 7; + int a8 = 8; + int a9 = 9; + int a10 = 10; + int a11 = 11; + int a12 = 12; + int a13 = 13; + int a14 = 14; + int a15 = 15; + long wide = accept16(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + Main main = null; + main.f = wide; + } + + static long accept16( + int a0, + int a1, + int a2, + int a3, + int a4, + int a5, + int a6, + int a7, + int a8, + int a9, + int a10, + int a11, + int a12, + int a13, + int a14, + int a15) { + return 0; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java new file mode 100644 index 0000000..95ade31 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java
@@ -0,0 +1,50 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class InvokeRangeWithSameValueRepeatedTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build(); + } + + @Test + public void testD8() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .addOptionsModification( + options -> { + options.getTestingOptions().enableRegisterAllocation8BitRefinement = true; + options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true; + }) + .release() + .setMinApi(parameters) + .compile() + .runDex2Oat(parameters.getRuntime()) + .assertNoVerificationErrors(); + } + + static class Main { + + void test(long a) { + invoke(a, a, a); + } + + static void invoke(long a, long b, long c) {} + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java index 9ead116..1b947ab 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java
@@ -35,7 +35,6 @@ .addInnerClasses(getClass()) .addOptionsModification( options -> { - options.getTestingOptions().enableLiveIntervalsSplittingForInvokeRange = true; options.getTestingOptions().enableRegisterAllocation8BitRefinement = true; options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true; }) @@ -62,7 +61,7 @@ Main(long a, long b, long c, long d, long e, long f, long g, long h) {} - void test(long a, long b, long c, long d, long e, long f, long g, long h) { + Main test(long a, long b, long c, long d, long e, long f, long g, long h) { forceIntoLowRegister(a, a); forceIntoLowRegister(b, b); forceIntoLowRegister(c, c); @@ -71,7 +70,7 @@ forceIntoLowRegister(f, f); forceIntoLowRegister(g, g); forceIntoLowRegister(h, h); - new Main(a, b, c, d, e, f, g, h); + return new Main(a, b, c, d, e, f, g, h); } static void forceIntoLowRegister(long a, long b) {}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java index ed272e7..804b482 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -5,6 +5,9 @@ import static org.junit.Assert.assertEquals; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppView; @@ -39,8 +42,12 @@ import java.util.function.Consumer; import java.util.function.UnaryOperator; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -public class RegisterMoveSchedulerTest { +@RunWith(Parameterized.class) +public class RegisterMoveSchedulerTest extends TestBase { private static class CollectMovesIterator implements InstructionListIterator { @@ -56,7 +63,8 @@ } @Override - public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) { + public void replaceCurrentInstruction( + Instruction newInstruction, AffectedValues affectedValues) { throw new Unimplemented(); } @@ -121,7 +129,7 @@ @Override public void replaceCurrentInstructionWithStaticGet( - AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) { + AppView<?> appView, IRCode code, DexField field, AffectedValues affectedValues) { throw new Unimplemented(); } @@ -243,6 +251,15 @@ } } + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + public RegisterMoveSchedulerTest(TestParameters parameters) { + parameters.assertNoneRuntime(); + } + @Test public void testSingleParallelMove() { CollectMovesIterator moves = new CollectMovesIterator(); @@ -371,18 +388,10 @@ scheduler.addMove(new RegisterMove(0, 3, TypeElement.getLong())); scheduler.schedule(); assertEquals(3, moves.size()); - Move firstMove = moves.get(0).asMove(); - Move secondMove = moves.get(1).asMove(); - Move thirdMove = moves.get(2).asMove(); - assertEquals(ValueType.LONG, firstMove.outType()); - assertEquals(ValueType.LONG, secondMove.outType()); - assertEquals(ValueType.LONG, thirdMove.outType()); - assertEquals(42, firstMove.dest().asFixedRegisterValue().getRegister()); - assertEquals(1, firstMove.src().asFixedRegisterValue().getRegister()); - assertEquals(0, secondMove.dest().asFixedRegisterValue().getRegister()); - assertEquals(3, secondMove.src().asFixedRegisterValue().getRegister()); - assertEquals(2, thirdMove.dest().asFixedRegisterValue().getRegister()); - assertEquals(42, thirdMove.src().asFixedRegisterValue().getRegister()); + assertEquals("42 <- 3", toString(moves.get(0))); + assertEquals("2 <- 1", toString(moves.get(1))); + assertEquals("0 <- 42", toString(moves.get(2))); + assertEquals(2, scheduler.getUsedTempRegisters()); } @Test @@ -443,12 +452,13 @@ scheduler.addMove(new RegisterMove(12, 19, TypeElement.getLong())); scheduler.schedule(); // In order to resolve these moves, we need to use two temporary register pairs. - assertEquals(6, moves.size()); - assertEquals(42, moves.get(0).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(11, moves.get(0).asMove().src().asFixedRegisterValue().getRegister()); - assertEquals(44, moves.get(1).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(13, moves.get(1).asMove().src().asFixedRegisterValue().getRegister()); - assertEquals(12, moves.get(2).asMove().dest().asFixedRegisterValue().getRegister()); + assertEquals(5, moves.size()); + assertEquals("42 <- 13", toString(moves.get(0))); + assertEquals("14 <- 11", toString(moves.get(1))); + assertEquals("10 <- 17", toString(moves.get(2))); + assertEquals("16 <- 42", toString(moves.get(3))); + assertEquals("12 <- 19", toString(moves.get(4))); + assertEquals(2, scheduler.getUsedTempRegisters()); } @Test @@ -470,19 +480,68 @@ scheduler.addMove(new RegisterMove(23, 28, TypeElement.getLong())); scheduler.schedule(); // For this example we need recursive unblocking. - assertEquals(6, moves.size()); - assertEquals(42, moves.get(0).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(26, moves.get(0).asMove().src().asFixedRegisterValue().getRegister()); - assertEquals(26, moves.get(1).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(22, moves.get(1).asMove().src().asFixedRegisterValue().getRegister()); - assertEquals(43, moves.get(2).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(28, moves.get(2).asMove().src().asFixedRegisterValue().getRegister()); - assertEquals(28, moves.get(3).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(42, moves.get(3).asMove().src().asFixedRegisterValue().getRegister()); - assertEquals(29, moves.get(4).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(24, moves.get(4).asMove().src().asFixedRegisterValue().getRegister()); - assertEquals(23, moves.get(5).asMove().dest().asFixedRegisterValue().getRegister()); - assertEquals(43, moves.get(5).asMove().src().asFixedRegisterValue().getRegister()); + assertEquals(5, moves.size()); + assertEquals("42 <- 28", toString(moves.get(0))); + assertEquals("29 <- 24", toString(moves.get(1))); + assertEquals("23 <- 42", toString(moves.get(2))); + assertEquals("28 <- 26", toString(moves.get(3))); + assertEquals("26 <- 22", toString(moves.get(4))); + assertEquals(2, scheduler.getUsedTempRegisters()); + } + + @Test + public void reuseTempRegister() { + CollectMovesIterator moves = new CollectMovesIterator(); + int temp = 42; + RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp); + scheduler.addMove(new RegisterMove(0, 1, TypeElement.getInt())); + scheduler.addMove(new RegisterMove(1, 0, TypeElement.getInt())); + scheduler.addMove(new RegisterMove(2, 3, TypeElement.getInt())); + scheduler.addMove(new RegisterMove(3, 2, TypeElement.getInt())); + scheduler.schedule(); + // Verify that the temp register has been reused. + assertEquals("42 <- 1", toString(moves.get(0))); + assertEquals("1 <- 0", toString(moves.get(1))); + assertEquals("0 <- 42", toString(moves.get(2))); + assertEquals("42 <- 3", toString(moves.get(3))); + assertEquals("3 <- 2", toString(moves.get(4))); + assertEquals("2 <- 42", toString(moves.get(5))); + assertEquals(1, scheduler.getUsedTempRegisters()); + } + + @Test + public void useDestinationRegisterAsTemporary() { + CollectMovesIterator moves = new CollectMovesIterator(); + int temp = 42; + RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp); + scheduler.addMove(new RegisterMove(0, 1, TypeElement.getInt())); + scheduler.addMove(new RegisterMove(1, 0, TypeElement.getInt())); + scheduler.addMove(new RegisterMove(2, 3, TypeElement.getInt())); + scheduler.schedule(); + // Verify that the temp register has been reused. + assertEquals("2 <- 1", toString(moves.get(0))); + assertEquals("1 <- 0", toString(moves.get(1))); + assertEquals("0 <- 2", toString(moves.get(2))); + assertEquals("2 <- 3", toString(moves.get(3))); + assertEquals(0, scheduler.getUsedTempRegisters()); + } + + @Test + public void openMoveCycle() { + CollectMovesIterator moves = new CollectMovesIterator(); + int temp = 42; + RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp); + scheduler.addMove(new RegisterMove(2, 0, TypeElement.getInt())); + scheduler.addMove(new RegisterMove(0, 2, TypeElement.getLong())); + scheduler.addMove(new RegisterMove(4, 1, TypeElement.getInt())); + scheduler.schedule(); + // The cycle is blocked by the move 4 <- 1, so we should emit this move first. + assertEquals(4, moves.size()); + assertEquals("4 <- 1", toString(moves.get(0))); + assertEquals("42 <- 2", toString(moves.get(1))); + assertEquals("2 <- 0", toString(moves.get(2))); + assertEquals("0 <- 42", toString(moves.get(3))); + assertEquals(2, scheduler.getUsedTempRegisters()); } // Debugging aid. @@ -495,4 +554,10 @@ } System.out.println("----------------"); } + + private String toString(Instruction move) { + return move.outValue().asFixedRegisterValue().getRegister() + + " <- " + + move.getFirstOperand().asFixedRegisterValue().getRegister(); + } }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java index f88127a..15ff3af 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java
@@ -5,12 +5,11 @@ 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.assertTrue; 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.DexMoveFrom16; import com.android.tools.r8.dex.code.DexMoveResult; import com.android.tools.r8.graph.DexCode; import com.android.tools.r8.utils.codeinspector.InstructionSubject; @@ -57,17 +56,13 @@ .asDexInstruction() .getInstruction(); - DexMoveFrom16 spillMove = + // TODO(b/375142715): The test no longer spills the `i` value. Look into if the test + // can be tweeked so that `i` is spilled, and validate that it is spilled to the + // unused argument register. + assertTrue( testMethodSubject .streamInstructions() - .filter(i -> i.isMoveFrom(moveResult.AA)) - .collect(MoreCollectors.onlyElement()) - .asDexInstruction() - .getInstruction(); - int firstArgumentRegister = code.registerSize - code.incomingRegisterSize; - // TODO(b/375142715): We could have spilled this value to the unused argument - // register, which would have lead to fewer registers being used. - assertEquals(firstArgumentRegister - 1, spillMove.AA); + .noneMatch(i -> i.isMoveFrom(moveResult.AA))); }); }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java new file mode 100644 index 0000000..152a959 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java
@@ -0,0 +1,51 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.regalloc; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ValueUsedInMultipleInvokeRangeInstructionsTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build(); + } + + @Test + public void testD8() throws Exception { + testForD8() + .addInnerClasses(getClass()) + .addOptionsModification( + options -> { + options.getTestingOptions().enableRegisterAllocation8BitRefinement = true; + options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true; + }) + .release() + .setMinApi(parameters) + .compile() + .runDex2Oat(parameters.getRuntime()) + .assertNoVerificationErrors(); + } + + static class Main { + + void test(long a) { + invoke(a, a, a); + invoke(a, a, a); + } + + static void invoke(long a, long b, long c) {} + } +}
diff --git a/src/test/java/com/android/tools/r8/metadata/D8BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/D8BuildMetadataTest.java index a10e2cc..79519aa 100644 --- a/src/test/java/com/android/tools/r8/metadata/D8BuildMetadataTest.java +++ b/src/test/java/com/android/tools/r8/metadata/D8BuildMetadataTest.java
@@ -73,7 +73,7 @@ if (parameters.isDexRuntime()) { assertNotNull(libraryDesugaringMetadata); assertEquals( - "com.tools.android:desugar_jdk_libs_configuration:2.1.2", + "com.tools.android:desugar_jdk_libs_configuration:2.1.3", libraryDesugaringMetadata.getIdentifier()); } else { assertNull(libraryDesugaringMetadata);
diff --git a/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java index 0216ecc..c1e2c35 100644 --- a/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java +++ b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
@@ -152,7 +152,7 @@ if (parameters.isDexRuntime()) { assertNotNull(libraryDesugaringMetadata); assertEquals( - "com.tools.android:desugar_jdk_libs_configuration:2.1.2", + "com.tools.android:desugar_jdk_libs_configuration:2.1.3", libraryDesugaringMetadata.getIdentifier()); } else { assertNull(libraryDesugaringMetadata);
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java index cc150aa..42a037e 100644 --- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java +++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -18,9 +18,10 @@ import com.android.tools.r8.dex.code.DexConst4; import com.android.tools.r8.dex.code.DexConstClass; import com.android.tools.r8.dex.code.DexConstString; -import com.android.tools.r8.dex.code.DexFilledNewArray; -import com.android.tools.r8.dex.code.DexInvokeDirect; +import com.android.tools.r8.dex.code.DexFilledNewArrayRange; +import com.android.tools.r8.dex.code.DexInvokeDirectRange; import com.android.tools.r8.dex.code.DexInvokeStatic; +import com.android.tools.r8.dex.code.DexInvokeStaticRange; import com.android.tools.r8.dex.code.DexInvokeVirtual; import com.android.tools.r8.dex.code.DexIputObject; import com.android.tools.r8.dex.code.DexMoveResultObject; @@ -85,7 +86,10 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, DexConstString.class, DexIputObject.class, DexReturnVoid.class)); + DexInvokeDirectRange.class, + DexConstString.class, + DexIputObject.class, + DexReturnVoid.class)); DexConstString constString = (DexConstString) code.instructions[1]; assertEquals(BOO, constString.getString().toString()); } @@ -119,7 +123,7 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexSgetObject.class, DexConstString.class, DexInvokeVirtual.class, @@ -160,7 +164,7 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexSgetObject.class, DexConstString.class, DexInvokeVirtual.class, @@ -411,7 +415,7 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexConstString.class, DexConstString.class, DexInvokeStatic.class, @@ -455,11 +459,11 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexSgetObject.class, DexConstString.class, DexInvokeVirtual.class, - DexInvokeStatic.class, + DexInvokeStaticRange.class, DexReturnVoid.class)); DexConstString constString = (DexConstString) code.instructions[2]; assertEquals(BOO, constString.getString().toString()); @@ -500,12 +504,12 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexSgetObject.class, DexConstString.class, DexInvokeVirtual.class, DexConstString.class, - DexInvokeStatic.class, + DexInvokeStaticRange.class, DexReturnVoid.class)); DexConstString constString = (DexConstString) code.instructions[2]; assertEquals(BOO, constString.getString().toString()); @@ -555,7 +559,7 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexConstClass.class, DexConstString.class, DexInvokeStatic.class, @@ -606,7 +610,7 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexConstClass.class, DexConstString.class, DexInvokeStatic.class, @@ -662,13 +666,13 @@ DexCode code = method.getCode().asDexCode(); // Accept either array construction style (differs based on minSdkVersion). - if (code.instructions[2].getClass() == DexFilledNewArray.class) { + if (code.instructions[2].getClass() == DexFilledNewArrayRange.class) { checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexConstClass.class, - DexFilledNewArray.class, + DexFilledNewArrayRange.class, DexMoveResultObject.class, DexConstString.class, DexInvokeStatic.class, @@ -677,7 +681,7 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexConstClass.class, DexConst4.class, DexNewArray.class, @@ -738,13 +742,13 @@ DexCode code = method.getCode().asDexCode(); // Accept either array construction style (differs based on minSdkVersion). - if (code.instructions[2].getClass() == DexFilledNewArray.class) { + if (code.instructions[2].getClass() == DexFilledNewArrayRange.class) { checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexConstClass.class, - DexFilledNewArray.class, + DexFilledNewArrayRange.class, DexMoveResultObject.class, DexConstString.class, DexInvokeStatic.class, @@ -753,7 +757,7 @@ checkInstructions( code, ImmutableList.of( - DexInvokeDirect.class, + DexInvokeDirectRange.class, DexConstClass.class, DexConst4.class, DexNewArray.class,
diff --git a/src/test/java/com/android/tools/r8/optimize/ReproduceKT72888Test.java b/src/test/java/com/android/tools/r8/optimize/ReproduceKT72888Test.java new file mode 100644 index 0000000..a61ef15 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/ReproduceKT72888Test.java
@@ -0,0 +1,111 @@ +// Copyright (c) 2024, 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.optimize; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ReproduceKT72888Test extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private static final String EXPECTED_OUTPUT = StringUtils.lines("3", "4", "null"); + + @Test + public void testJvm() throws Exception { + parameters.assumeJvmTestParameters(); + testForJvm(parameters) + .addInnerClasses(getClass()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws Exception { + parameters.assumeDexRuntime(); + testForD8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .addKeepClassRules(MyAnnotation.class, A.class, B.class) + .addKeepAttributeAnnotationDefault() + .addKeepRuntimeVisibleAnnotations() + .setMinApi(parameters) + .run(parameters.getRuntime(), TestClass.class) + .assertFailureWithErrorThatThrows(ClassCastException.class); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface MyAnnotation { + int x() default 0; + } + + public static class MyAnnotationImpl implements MyAnnotation { + private final int x; + + public MyAnnotationImpl(int x) { + this.x = x + 2; + } + + @Override + public int x() { + return x; + } + + @Override + public Class<? extends java.lang.annotation.Annotation> annotationType() { + return MyAnnotation.class; + } + } + + @MyAnnotation(x = 1) + static class A {} + + @MyAnnotation(x = 2) + static class B {} + + static class TestClass { + public static MyAnnotation copyOfMyAnnotation(Class<?> clazz) { + Object a = clazz.getAnnotation(MyAnnotation.class); + if (a == null) { + return null; + } + MyAnnotation copy = (MyAnnotation) a; + return new MyAnnotationImpl(copy.x()); + } + + public static void main(String[] args) { + MyAnnotation a = copyOfMyAnnotation(A.class); + System.out.println(a == null ? "null" : a.x()); + a = copyOfMyAnnotation(B.class); + System.out.println(a == null ? "null" : a.x()); + a = copyOfMyAnnotation(TestClass.class); + System.out.println(a == null ? "null" : a.x()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress302826300.java b/src/test/java/com/android/tools/r8/regress/Regress302826300.java index d8cda04..baa5061 100644 --- a/src/test/java/com/android/tools/r8/regress/Regress302826300.java +++ b/src/test/java/com/android/tools/r8/regress/Regress302826300.java
@@ -45,7 +45,7 @@ .setMinApi(parameters) .run(parameters.getRuntime(), Foo.class); if (parameters.getRuntime().asDex().getVersion().isDalvik()) { - run.assertFailureWithErrorThatMatches(containsString("rejecting opcode 0x6e")); + run.assertFailureWithErrorThatMatches(containsString("rejecting opcode 0x")); } else { run.assertSuccessWithOutputLines(EXPECTED); }
diff --git a/src/test/java/com/android/tools/r8/regress/b68378480/B68378480.java b/src/test/java/com/android/tools/r8/regress/b68378480/B68378480.java index 2492b31..7b0ca00 100644 --- a/src/test/java/com/android/tools/r8/regress/b68378480/B68378480.java +++ b/src/test/java/com/android/tools/r8/regress/b68378480/B68378480.java
@@ -25,7 +25,6 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.concurrent.ExecutionException; import org.junit.Test; class SuperClass { @@ -68,16 +67,14 @@ } @Test - public void addExtraLocalToConstructor() - throws IOException, CompilationFailedException, ExecutionException { + public void addExtraLocalToConstructor() throws IOException, CompilationFailedException { DexCode code = compileClassesGetSubClassInit(AndroidApiLevel.L_MR1.getLevel()); assertTrue(code.registerSize > code.incomingRegisterSize); assertTrue(Arrays.stream(code.instructions).anyMatch((i) -> i instanceof SingleConstant)); } @Test - public void doNotAddExtraLocalToConstructor() - throws IOException, CompilationFailedException, ExecutionException { + public void doNotAddExtraLocalToConstructor() throws IOException, CompilationFailedException { DexCode code = compileClassesGetSubClassInit(AndroidApiLevel.M.getLevel()); assertEquals(code.registerSize, code.incomingRegisterSize); assertTrue(Arrays.stream(code.instructions).noneMatch((i) -> i instanceof SingleConstant));
diff --git a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java index 375b845..c84f519 100644 --- a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java +++ b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
@@ -8,7 +8,9 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.dex.Marker.Tool; +import com.android.tools.r8.dex.code.DexInstruction; import com.android.tools.r8.dex.code.DexInvokeStatic; +import com.android.tools.r8.dex.code.DexInvokeStaticRange; import com.android.tools.r8.graph.DexCode; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -418,10 +420,9 @@ factory.createString("isNaN"), factory.booleanDescriptor, new DexString[]{factory.doubleDescriptor}); - for (int i = 0; i < code.instructions.length; i++) { - if (code.instructions[i] instanceof DexInvokeStatic) { - DexInvokeStatic invoke = (DexInvokeStatic) code.instructions[i]; - if (invoke.getMethod() == doubleIsNaN) { + for (DexInstruction instruction : code.instructions) { + if (instruction instanceof DexInvokeStatic || instruction instanceof DexInvokeStaticRange) { + if (instruction.getMethod() == doubleIsNaN) { count++; } }
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 80ea5f5..359ccd0 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
@@ -53,7 +53,7 @@ } private static final Class<?>[] DEX_ARRAY_INSTRUCTIONS = { - DexNewArray.class, DexFilledNewArray.class, DexFilledNewArrayRange.class, DexFillArrayData.class + DexNewArray.class, DexFilledNewArray.class, DexFillArrayData.class }; private static final String[] EXPECTED_OUTPUT = { @@ -334,12 +334,15 @@ private static void assertArrayTypes(MethodSubject method, Class<?>... allowedArrayInst) { assertTrue(method.isPresent()); + List<Class<?>> allowedClasses = Lists.newArrayList(allowedArrayInst); + if (allowedClasses.contains(DexFilledNewArray.class)) { + allowedClasses.add(DexFilledNewArrayRange.class); + } List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS); for (Class<?> allowedArr : allowedArrayInst) { disallowedClasses.remove(allowedArr); } - assertTrue( - method.streamInstructions().anyMatch(isInstruction(Arrays.asList(allowedArrayInst)))); + assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedClasses))); assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses))); }
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesFromClasspathOrLibraryTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesFromClasspathOrLibraryTest.java index 552e81d..d8fb33f 100644 --- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesFromClasspathOrLibraryTest.java +++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesFromClasspathOrLibraryTest.java
@@ -6,6 +6,7 @@ import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType; import static com.android.tools.r8.OriginMatcher.hasPart; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; @@ -28,6 +29,7 @@ import com.android.tools.r8.ResourceException; import com.android.tools.r8.TestParameters; import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.errors.DuplicateTypesDiagnostic; import com.android.tools.r8.origin.ArchiveEntryOrigin; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; @@ -50,10 +52,6 @@ public class LibraryProvidedProguardRulesFromClasspathOrLibraryTest extends LibraryProvidedProguardRulesTestBase { - @interface Keep {} - - public interface Interface {} - private enum HowToAdd { API_CLASSPATH, API_LIBRARY, @@ -108,6 +106,33 @@ } @Test + public void testDuplicateClassesInLibraryJar() throws Exception { + assumeTrue(howToAdd == HowToAdd.API_LIBRARY); + assumeTrue(libraryType == LibraryType.JAR_WITH_RULES); + Path library = + buildLibrary( + ImmutableList.of( + "-keep class * implements " + Interface.class.getTypeName() + " { *; }")); + assertThrows( + CompilationFailedException.class, + () -> + testForR8(parameters.getBackend()) + .addProgramClasses(A.class, B.class) + .addKeepRules("-libraryjars " + library.toAbsolutePath()) + .addKeepRules("-libraryjars " + library.toAbsolutePath()) + .setMinApi(parameters) + .allowStdoutMessages() + .apply( + b -> + ToolHelper.setReadEmbeddedRulesFromClasspathAndLibrary( + b.getBuilder(), true)) + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + diagnosticType(DuplicateTypesDiagnostic.class)))); + } + + @Test public void providedKeepRuleImplements() throws Exception { CodeInspector inspector = runTest("-keep class * implements " + Interface.class.getTypeName() + " { *; }"); @@ -263,6 +288,12 @@ diagnosticOrigin(is(Origin.unknown())))))); } + // Classes in the library. + @interface Keep {} + + public interface Interface {} + + // Classes in the program. static class A implements Interface {} @Keep
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumCollectionsTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumCollectionsTest.java new file mode 100644 index 0000000..2a9872c --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumCollectionsTest.java
@@ -0,0 +1,208 @@ +// Copyright (c) 2024, 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.shaking.enums; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class EnumCollectionsTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build(); + } + + private static final List<String> EXPECTED_OUTPUT = + Arrays.asList( + "none: [A, B]", + "all: [B, C]", + "of: [C]", + "of: [D, E]", + "of: [E, F, G]", + "of: [F, G, H, I]", + "of: [G, H, I, J, K]", + "of: [H, I, J, K, L, M]", + "range: [I, J]", + "map: {J=1}", + "valueOf: K", + "phi: [B]"); + + public static class TestMain { + public enum EnumA { + A, + B + } + + public enum EnumB { + B, + C + } + + public enum EnumC { + C, + D + } + + public enum EnumD { + D, + E + } + + public enum EnumE { + E, + F, + G + } + + public enum EnumF { + F, + G, + H, + I + } + + public enum EnumG { + G, + H, + I, + J, + K + } + + public enum EnumH { + H, + I, + J, + K, + L, + M + } + + public enum EnumI { + I, + J + } + + public enum EnumJ { + J, + K + } + + public enum EnumK { + K, + L + } + + @NeverInline + private static void noneOf() { + System.out.println("none: " + EnumSet.complementOf(EnumSet.noneOf(EnumA.class))); + } + + @NeverInline + private static void allOf() { + System.out.println("all: " + EnumSet.allOf(EnumB.class)); + } + + @NeverInline + private static void of1() { + System.out.println("of: " + EnumSet.of(EnumC.C)); + } + + @NeverInline + private static void of2() { + System.out.println("of: " + EnumSet.of(EnumD.D, EnumD.E)); + } + + @NeverInline + private static void of3() { + System.out.println("of: " + EnumSet.of(EnumE.E, EnumE.F, EnumE.G)); + } + + @NeverInline + private static void of4() { + System.out.println("of: " + EnumSet.of(EnumF.F, EnumF.G, EnumF.H, EnumF.I)); + } + + @NeverInline + private static void of5() { + System.out.println("of: " + EnumSet.of(EnumG.G, EnumG.H, EnumG.I, EnumG.J, EnumG.K)); + } + + @NeverInline + private static void ofVarArgs() { + System.out.println("of: " + EnumSet.of(EnumH.H, EnumH.I, EnumH.J, EnumH.K, EnumH.L, EnumH.M)); + } + + @NeverInline + private static void range() { + System.out.println("range: " + EnumSet.range(EnumI.I, EnumI.J)); + } + + @NeverInline + private static void map() { + EnumMap<EnumJ, Integer> map = new EnumMap<>(EnumJ.class); + map.put(EnumJ.J, 1); + System.out.println("map: " + map); + } + + @NeverInline + private static void valueOf() { + System.out.println("valueOf: " + EnumK.valueOf("K")); + } + + public static void main(String[] args) { + // Use different methods to ensure Enqueuer.traceInvokeStatic() triggers for each one. + noneOf(); + allOf(); + of1(); + of2(); + of3(); + of4(); + of5(); + ofVarArgs(); + range(); + map(); + valueOf(); + // Ensure phi as argument does not cause issues. + System.out.println( + "phi: " + EnumSet.of((Enum) (args.length > 10 ? (Object) EnumA.A : (Object) EnumB.B))); + } + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClassesAndInnerClasses(TestMain.class) + .run(parameters.getRuntime(), TestMain.class) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .setMinApi(parameters) + .addProgramClassesAndInnerClasses(TestMain.class) + .enableInliningAnnotations() + .addKeepMainRule(TestMain.class) + .compile() + .run(parameters.getRuntime(), TestMain.class) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } +}
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java index 4ce22fc..55b0158 100644 --- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java +++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -106,8 +106,7 @@ if (defBlock.canThrow()) { // Found the invoke instruction / block. assertEquals(2, defBlock.getSuccessors().size()); - assertTrue( - defBlock.getInstructions().get(defBlock.getInstructions().size() - 2).isInvoke()); + assertTrue(defBlock.getInstructions().getLast().getPrev().isInvoke()); for (BasicBlock returnPredecessor : block.getPredecessors()) { if (defBlock.hasCatchSuccessor(returnPredecessor)) { hasExceptionalPredecessor = true;
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java index 9642e4e..c7adad6 100644 --- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java +++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -14,7 +14,7 @@ import com.android.tools.r8.dex.code.DexIfLez; import com.android.tools.r8.dex.code.DexIfLtz; import com.android.tools.r8.dex.code.DexIfNez; -import com.android.tools.r8.dex.code.DexInvokeVirtual; +import com.android.tools.r8.dex.code.DexInvokeVirtualRange; import com.android.tools.r8.dex.code.DexReturn; import com.android.tools.r8.dex.code.DexReturnObject; import com.android.tools.r8.graph.DexCode; @@ -399,7 +399,7 @@ " goto :label_7"); DexCode code = method.getCode().asDexCode(); assertEquals(3, code.instructions.length); - assertTrue(code.instructions[0] instanceof DexInvokeVirtual); + assertTrue(code.instructions[0] instanceof DexInvokeVirtualRange); assertTrue(code.instructions[1] instanceof DexConst4); assertEquals(0, ((DexConst4) code.instructions[1]).B); assertTrue(code.instructions[2] instanceof DexReturnObject);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java index 9b534e2..b3a09bf 100644 --- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java +++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -575,7 +575,7 @@ DexInvokeStatic invoke = (DexInvokeStatic) mainCode.instructions[4]; assertTrue(isOutlineMethodName(invoke.getMethod())); } else if (i == 3) { - DexInvokeStatic invoke = (DexInvokeStatic) mainCode.instructions[1]; + DexInvokeStaticRange invoke = (DexInvokeStaticRange) mainCode.instructions[1]; assertTrue(isOutlineMethodName(invoke.getMethod())); } else { assert i == 4 || i == 5; @@ -1634,7 +1634,8 @@ } private static boolean isOutlineInvoke(DexInstruction instruction) { - return instruction instanceof DexInvokeStatic && isOutlineMethodName(instruction.getMethod()); + return (instruction instanceof DexInvokeStatic || instruction instanceof DexInvokeStaticRange) + && isOutlineMethodName(instruction.getMethod()); } private void assertHasOutlineInvoke(DexEncodedMethod method) {
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java index 8cdcbc0..58fd949 100644 --- a/src/test/testbase/java/com/android/tools/r8/TestBase.java +++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -1523,10 +1523,11 @@ } protected Stream<DexInstruction> filterInstructionKind( - DexCode dexCode, Class<? extends DexInstruction> kind) { + DexCode dexCode, Class<? extends DexInstruction>... kinds) { return Arrays.stream(dexCode.instructions) - .filter(kind::isInstance) - .map(kind::cast); + .filter( + instruction -> + Arrays.asList(kinds).stream().anyMatch(kind -> kind.isInstance(instruction))); } protected long countCall(MethodSubject method, String className, String methodName) {
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 index 9f5883e..7a144f7 100644 --- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 +++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@ -f3213584e94bf6951c3765c6b8d23405c332ca1b \ No newline at end of file +5a35d5323fe418db348576d23428cb3346d28535 \ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py index c819865..e9a462d 100755 --- a/tools/compiledump.py +++ b/tools/compiledump.py
@@ -502,7 +502,25 @@ return False +def compile_reflective_helper(temp, jdkhome): + gradle.RunGradle([utils.GRADLE_TASK_MAIN_COMPILE]) + base_path = os.path.join( + utils.REPO_ROOT, + 'src/main/java/com/android/tools/r8/utils/compiledump') + + cmd = [ + jdk.GetJavacExecutable(jdkhome), + '-d', + temp, + '-cp', + utils.BUILD_JAVA_MAIN_DIR, + ] + cmd.extend(os.path.join(base_path, f) for f in os.listdir(base_path)) + utils.PrintCmd(cmd) + subprocess.check_output(cmd) + def prepare_r8_wrapper(dist, temp, jdkhome): + compile_reflective_helper(temp, jdkhome) compile_wrapper_with_javac( dist, temp, jdkhome, os.path.join( @@ -530,7 +548,7 @@ '-d', temp, '-cp', - dist, + "%s:%s" % (dist, temp), ] utils.PrintCmd(cmd) subprocess.check_output(cmd)
diff --git a/tools/test.py b/tools/test.py index c071e71..26eda1e 100755 --- a/tools/test.py +++ b/tools/test.py
@@ -409,6 +409,10 @@ if options.low_priority: gradle_args.append('--priority=low') + # Default is 3, but some VMs become unresponsive with this value. + # Increase to reduce concurrency. + if not os.environ.get('R8_GRADLE_CORES_PER_FORK'): + os.environ['R8_GRADLE_CORES_PER_FORK'] = '5' # Set all necessary Gradle properties and options first. if options.shard_count:
diff --git a/tools/toolhelper.py b/tools/toolhelper.py index 5a6b8ee..cfc386c 100644 --- a/tools/toolhelper.py +++ b/tools/toolhelper.py
@@ -69,7 +69,7 @@ 'com.android.tools.r8.tracereferences.TraceReferences' ]) else: - cmd.extend(['-jar', utils.R8_JAR, tool]) + cmd.extend(['-cp', utils.R8_JAR, 'com.android.tools.r8.SwissArmyKnife', tool]) lib, args = extract_lib_from_args(args) if lib: cmd.extend(["--lib", lib])
diff --git a/tools/utils.py b/tools/utils.py index 86d7e8d..66f8e36 100644 --- a/tools/utils.py +++ b/tools/utils.py
@@ -32,7 +32,8 @@ DEPENDENCIES_DIR = os.path.join(THIRD_PARTY, 'dependencies') BUILD = os.path.join(REPO_ROOT, 'build') -BUILD_JAVA_MAIN_DIR = os.path.join(BUILD, 'classes', 'java', 'main') +BUILD_JAVA_MAIN_DIR = os.path.join(REPO_ROOT, 'd8_r8', 'main', 'build', + 'classes', 'java', 'main') LIBS = os.path.join(BUILD, 'libs') CUSTOM_CONVERSION_DIR = os.path.join(THIRD_PARTY, 'openjdk', 'custom_conversion') @@ -44,6 +45,7 @@ GRADLE_TASK_CONSOLIDATED_LICENSE = ':main:consolidatedLicense' GRADLE_TASK_KEEP_ANNO_JAR = ':keepanno:keepAnnoAnnotationsJar' GRADLE_TASK_KEEP_ANNO_DOC = ':keepanno:keepAnnoAnnotationsDoc' +GRADLE_TASK_MAIN_COMPILE = ':main:compileJava' GRADLE_TASK_R8 = ':main:r8WithRelocatedDeps' GRADLE_TASK_R8LIB = ':test:assembleR8LibWithRelocatedDeps' GRADLE_TASK_R8LIB_NO_DEPS = ':test:assembleR8LibNoDeps'