| // 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.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); |
| 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); |
| 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; |
| // 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 (!invoke.getBlock().hasCatchHandlers()) { |
| value = |
| iterator.insertConstStringInstruction( |
| appView, code, appView.dexItemFactory().createString(replacement)); |
| } 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(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 getConstructorWithStringParameter( |
| DexMethod invokedMethod, DexItemFactory factory) { |
| if (invokedMethod.getHolderType() == factory.stringBufferType) { |
| return factory.stringBufferMethods.stringConstructor; |
| } else { |
| assert invokedMethod.getHolderType() == factory.stringBuilderType; |
| return factory.stringBuilderMethods.stringConstructor; |
| } |
| } |
| |
| 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; |
| } |
| } |
| } |
| } |