blob: 4383519c717c5ea6b809b036caa16f2e6e583dca [file] [log] [blame]
// 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.ir.optimize.string;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.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.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
import com.google.common.collect.ImmutableList;
/** StringBuilderAction defines an interface for updating the IR code based on optimizations. */
public interface StringBuilderAction {
void perform(
AppView<?> appView,
IRCode code,
InstructionListIterator iterator,
Instruction instruction,
StringBuilderOracle oracle);
/** The RemoveStringBuilderAction will simply remove the instruction completely. */
class RemoveStringBuilderAction implements StringBuilderAction {
private static final RemoveStringBuilderAction INSTANCE = new RemoveStringBuilderAction();
@Override
public void perform(
AppView<?> appView,
IRCode code,
InstructionListIterator iterator,
Instruction instruction,
StringBuilderOracle oracle) {
assert oracle.isModeledStringBuilderInstruction(
instruction,
value ->
value.getType().isClassType()
&& oracle.isStringBuilderType(value.getType().asClassType().getClassType()));
if (oracle.isAppend(instruction) && instruction.outValue() != null) {
// Append will return the string builder instance. Before removing, ensure that
// all users of the output values uses the receiver.
instruction.outValue().replaceUsers(instruction.getFirstOperand());
}
iterator.removeOrReplaceByDebugLocalRead();
}
static RemoveStringBuilderAction getInstance() {
return INSTANCE;
}
}
/**
* ReplaceByConstantString will replace a toString() call on StringBuilder with a constant string.
*/
class ReplaceByConstantString implements StringBuilderAction {
private final String replacement;
ReplaceByConstantString(String replacement) {
this.replacement = replacement;
}
@Override
public void perform(
AppView<?> appView,
IRCode code,
InstructionListIterator iterator,
Instruction instruction,
StringBuilderOracle oracle) {
assert oracle.isToString(instruction, instruction.getFirstOperand());
iterator.replaceCurrentInstructionWithConstString(appView, code, replacement);
}
}
/**
* AppendWithNewConstantString will change the current instruction to be an append with a constant
* string. If the current instruction is init the instruction will be changed to an init taking a
* string as argument.
*/
class AppendWithNewConstantString implements StringBuilderAction {
private final String replacement;
AppendWithNewConstantString(String replacement) {
this.replacement = replacement;
}
@Override
public void perform(
AppView<?> appView,
IRCode code,
InstructionListIterator iterator,
Instruction instruction,
StringBuilderOracle oracle) {
Instruction previous = iterator.previous();
InvokeMethodWithReceiver invoke = previous.asInvokeMethodWithReceiver();
assert invoke != null;
Value value = insertStringConstantInstruction(appView, code, iterator, invoke, replacement);
iterator.next();
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invoke.isInvokeConstructor(appView.dexItemFactory())) {
iterator.replaceCurrentInstruction(
InvokeDirect.builder()
.setArguments(ImmutableList.of(invoke.getReceiver(), value))
.setMethod(
getConstructorWithStringParameter(invokedMethod, appView.dexItemFactory()))
.setOutValue(invoke.outValue())
.build());
} else if (!isAppendWithString(invokedMethod, appView.dexItemFactory())) {
iterator.replaceCurrentInstruction(
InvokeVirtual.builder()
.setArguments(ImmutableList.of(invoke.getReceiver(), value))
.setMethod(getAppendWithStringParameter(invokedMethod, appView.dexItemFactory()))
.setOutValue(invoke.outValue())
.build());
} else {
invoke.replaceValue(1, value);
}
}
private boolean isAppendWithString(DexMethod method, DexItemFactory factory) {
return factory.stringBufferMethods.isAppendStringMethod(method)
|| factory.stringBuilderMethods.isAppendStringMethod(method);
}
private DexMethod getAppendWithStringParameter(
DexMethod invokedMethod, DexItemFactory factory) {
if (invokedMethod.getHolderType() == factory.stringBufferType) {
return factory.stringBufferMethods.appendString;
} else {
assert invokedMethod.getHolderType() == factory.stringBuilderType;
return factory.stringBuilderMethods.appendString;
}
}
}
class ReplaceByExistingString implements StringBuilderAction {
private final Value existingString;
public ReplaceByExistingString(Value existingString) {
assert existingString.isNeverNull();
this.existingString = existingString;
}
@Override
public void perform(
AppView<?> appView,
IRCode code,
InstructionListIterator iterator,
Instruction instruction,
StringBuilderOracle oracle) {
instruction.outValue().replaceUsers(existingString);
iterator.removeOrReplaceByDebugLocalRead();
}
}
class ReplaceByStringConcat implements StringBuilderAction {
private final Value first;
private final Value second;
private final String newConstant;
private ReplaceByStringConcat(Value first, Value second, String newConstant) {
assert first != null || newConstant != null;
assert second != null || newConstant != null;
this.first = first;
this.second = second;
this.newConstant = newConstant;
}
public static ReplaceByStringConcat replaceByValues(Value first, Value second) {
return new ReplaceByStringConcat(first, second, null);
}
public static ReplaceByStringConcat replaceByNewConstantConcatValue(
String newConstant, Value second) {
return new ReplaceByStringConcat(null, second, newConstant);
}
public static ReplaceByStringConcat replaceByValueConcatNewConstant(
Value first, String newConstant) {
return new ReplaceByStringConcat(first, null, newConstant);
}
@Override
public void perform(
AppView<?> appView,
IRCode code,
InstructionListIterator iterator,
Instruction instruction,
StringBuilderOracle oracle) {
Value constString = null;
if (newConstant != null) {
Instruction previous = iterator.previous();
constString =
insertStringConstantInstruction(appView, code, iterator, previous, newConstant);
iterator.next();
}
assert first != null || constString != null;
assert second != null || constString != null;
// To ensure that we do not fail narrowing when evaluating String.concat, we mark the type
// as maybe null.
Value newOutValue =
code.createValue(
TypeElement.stringClassType(appView, Nullability.maybeNull()),
instruction.getLocalInfo());
iterator.replaceCurrentInstruction(
InvokeVirtual.builder()
.setOutValue(newOutValue)
.setMethod(appView.dexItemFactory().stringMembers.concat)
.setArguments(
ImmutableList.of(
first != null ? first : constString, second != null ? second : constString))
.build());
}
}
static DexMethod getConstructorWithStringParameter(
DexMethod invokedMethod, DexItemFactory factory) {
if (invokedMethod.getHolderType() == factory.stringBufferType) {
return factory.stringBufferMethods.stringConstructor;
} else {
assert invokedMethod.getHolderType() == factory.stringBuilderType;
return factory.stringBuilderMethods.stringConstructor;
}
}
static Value insertStringConstantInstruction(
AppView<?> appView,
IRCode code,
InstructionListIterator iterator,
Instruction instruction,
String newString) {
// If the block has catch handlers, inserting a constant string in the same block as the
// append violates our block representation in DEX since constant string is throwing. If the
// append is in a block with catch handlers, we simply insert a new constant string in the
// entry block after all arguments.
Value value;
if (!instruction.getBlock().hasCatchHandlers()) {
value =
iterator.insertConstStringInstruction(
appView, code, appView.dexItemFactory().createString(newString));
} else {
InstructionListIterator stringInsertIterator = code.entryBlock().listIterator(code);
while (stringInsertIterator.hasNext()) {
Instruction next = stringInsertIterator.next();
if (!next.isArgument()) {
stringInsertIterator.previous();
break;
}
}
value =
stringInsertIterator.insertConstStringInstruction(
appView, code, appView.dexItemFactory().createString(newString));
}
return value;
}
}