blob: 06e410bc682b4a61399613b99789bafcd9197022 [file] [log] [blame]
// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion.passes;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.IteratorUtils;
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;
import java.util.List;
/**
* Pass that attempts to hoist the parent constructor call inside constructors to avoid
* instance-puts to the unininitialized this, as this enables more aggressive inlining of
* constructors.
*/
// TODO(b/278975138): Do not remove verification errors from hoisting.
public class ParentConstructorHoistingCodeRewriter
extends CodeRewriterPass<AppInfoWithClassHierarchy> {
private List<InvokeDirect> sideEffectFreeConstructorCalls;
public ParentConstructorHoistingCodeRewriter(AppView<?> appView) {
super(appView);
}
@Override
String getTimingId() {
return "Parent constructor hoisting pass";
}
@Override
void rewriteCode(ProgramMethod method, IRCode code) {
for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) {
hoistSideEffectFreeConstructorCall(code, invoke);
}
}
private void hoistSideEffectFreeConstructorCall(IRCode code, InvokeDirect invoke) {
Deque<Instruction> constants = new ArrayDeque<>();
// TODO(b/281975599): This loop would not be needed if we did not have any trivial gotos.
while (true) {
hoistSideEffectFreeConstructorCallInCurrentBlock(code, invoke, constants);
Instruction firstHoistedInstruction = CollectionUtils.getFirstOrDefault(constants, invoke);
if (invoke.getBlock().entry() != firstHoistedInstruction
|| !hoistSideEffectFreeConstructorCallIntoPredecessorBlock(code, invoke, constants)) {
break;
}
}
}
// TODO(b/278975138): Instead of hoisting constructor call one instruction up at a time as a
// peephole optimization, consider finding the insertion position and then modifying the IR once.
private void hoistSideEffectFreeConstructorCallInCurrentBlock(
IRCode code, InvokeDirect invoke, Deque<Instruction> constants) {
InstructionListIterator instructionIterator = invoke.getBlock().listIterator(code);
instructionIterator.positionBeforeNextInstruction(
CollectionUtils.getFirstOrDefault(constants, invoke));
while (instructionIterator.hasPrevious()) {
Instruction previousInstruction = instructionIterator.previous();
if (previousInstruction.isArgument()) {
// Cannot hoist the constructor call above the Argument instruction.
return;
}
if (previousInstruction.hasOutValue()
&& invoke.inValues().contains(previousInstruction.outValue())) {
if (previousInstruction.isConstNumber() || previousInstruction.isConstString()) {
// Record that the constant instruction should be hoisted along with the constructor call.
constants.addFirst(previousInstruction);
continue;
}
// Cannot hoist the constructor call above the definition of a value that is used as an
// argument to the constructor call.
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);
}
}
private boolean hoistSideEffectFreeConstructorCallIntoPredecessorBlock(
IRCode code, InvokeDirect invoke, Deque<Instruction> constants) {
BasicBlock block = invoke.getBlock();
if (!block.hasUniquePredecessor()) {
return false;
}
BasicBlock predecessorBlock = block.getUniquePredecessor();
if (!predecessorBlock.hasUniqueSuccessor() || predecessorBlock.hasCatchHandlers()) {
return false;
}
// Remove the constants and the invoke from the block.
constants.forEach(constant -> block.getInstructions().removeFirst());
block.getInstructions().removeFirst();
// Add the constants and the invoke before the exit instruction in the predecessor block.
InstructionListIterator predecessorInstructionIterator =
predecessorBlock.listIterator(code, predecessorBlock.getInstructions().size() - 1);
constants.forEach(predecessorInstructionIterator::add);
predecessorInstructionIterator.add(invoke);
// Position the predecessor instruction iterator right before the first instruction that is
// subject to hoisting.
IteratorUtils.skip(predecessorInstructionIterator, -constants.size() - 1);
assert predecessorInstructionIterator.peekNext()
== CollectionUtils.getFirstOrDefault(constants, invoke);
return true;
}
/** Only run this when the rewriting may actually enable more constructor inlining. */
@Override
boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
if (!appView.hasClassHierarchy()) {
assert !appView.enableWholeProgramOptimizations();
return false;
}
if (!method.getDefinition().isInstanceInitializer()
|| !options.canInitNewInstanceUsingSuperclassConstructor()) {
return false;
}
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
return keepInfo.isOptimizationAllowed(options)
&& keepInfo.isShrinkingAllowed(options)
&& hoistingMayRemoveInstancePutToUninitializedThis(code);
}
private boolean hoistingMayRemoveInstancePutToUninitializedThis(IRCode code) {
if (!code.metadata().mayHaveInstancePut()) {
return false;
}
Value thisValue = code.getThis();
WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList();
for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) {
// Check if any of the previous instructions in the current block has an instance-put that
// assigns a field on `this`.
if (IterableUtils.anyBefore(
invoke.getBlock().getInstructions(),
instruction -> isInstancePutToUninitializedThis(instruction, thisValue),
instruction -> instruction == invoke)) {
return true;
}
// Otherwise check if any of the (transitive) predecessor blocks has an instance-put that
// assigns a field on `this`.
worklist.addIfNotSeen(invoke.getBlock().getPredecessors());
}
return worklist
.run(
block -> {
if (Iterables.any(
block.getInstructions(),
instruction -> isInstancePutToUninitializedThis(instruction, thisValue))) {
return TraversalContinuation.doBreak();
}
worklist.addIfNotSeen(block.getPredecessors());
return TraversalContinuation.doContinue();
})
.shouldBreak();
}
private List<InvokeDirect> getOrComputeSideEffectFreeConstructorCalls(IRCode code) {
if (sideEffectFreeConstructorCalls == null) {
sideEffectFreeConstructorCalls = computeSideEffectFreeConstructorCalls(code);
}
return sideEffectFreeConstructorCalls;
}
private List<InvokeDirect> computeSideEffectFreeConstructorCalls(IRCode code) {
Value thisValue = code.getThis();
return ListUtils.filter(
thisValue.uniqueUsers(),
instruction -> {
if (!instruction.isInvokeConstructor(dexItemFactory)) {
return false;
}
InvokeDirect invoke = instruction.asInvokeDirect();
if (invoke.getReceiver() != thisValue) {
return false;
}
DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context());
return target != null
&& !target.getOptimizationInfo().mayHaveSideEffects(invoke, options);
});
}
private static boolean isInstancePutToUninitializedThis(
Instruction instruction, Value thisValue) {
return instruction.isInstancePut() && instruction.asInstancePut().object() == thisValue;
}
}