blob: 04e85674d2ec4f1d48f19b92744557c2a4dda88a [file] [log] [blame]
// Copyright (c) 2025, 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.outliner.exceptions;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockInstructionListIterator;
import com.android.tools.r8.ir.code.DebugLocalRead;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.ThrowBlockOutlineMarker;
import com.android.tools.r8.ir.code.UnusedArgument;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRFinalizer;
import com.android.tools.r8.ir.conversion.LensCodeArgumentRewriter;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.lightir.Lir2IRConverter;
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.lightir.LirStrategy;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.timing.Timing;
import java.util.Collections;
import java.util.Set;
/** Rewriter that processes {@link ThrowBlockOutlineMarker} instructions. */
public class ThrowBlockOutlineMarkerRewriter {
private final AppView<?> appView;
private final DeadCodeRemover deadCodeRemover;
private final DexItemFactory factory;
ThrowBlockOutlineMarkerRewriter(AppView<?> appView) {
this.appView = appView;
this.deadCodeRemover = new DeadCodeRemover(appView);
this.factory = appView.dexItemFactory();
}
public void processOutlineMethod(
ProgramMethod method, LirCode<Integer> lirCode, ThrowBlockOutline outline) {
IRCode code = buildIRForOutlineMethod(lirCode, method, outline);
removeConstantArgumentsFromOutline(method, code, outline);
finalizeCode(method, code);
}
public void processMethodWithOutlineMarkers(ProgramMethod method, LirCode<Integer> lirCode) {
IRCode code = lirCode.buildIR(method, appView);
processOutlineMarkers(code);
finalizeCode(method, code);
}
private IRCode buildIRForOutlineMethod(
LirCode<Integer> lirCode, ProgramMethod method, ThrowBlockOutline outline) {
if (!outline.hasConstantArgument()) {
return lirCode.buildIR(method, appView);
}
// We need to inform IR construction to insert the arguments that have been removed from the
// proto.
Position callerPosition = null;
return Lir2IRConverter.translate(
method,
lirCode,
LirStrategy.getDefaultStrategy().getDecodingStrategy(lirCode, new NumberGenerator()),
appView,
callerPosition,
outline.getProtoChanges(),
appView.enableWholeProgramOptimizations()
? MethodConversionOptions.forLirPhase(appView)
: MethodConversionOptions.forD8(appView, method));
}
private void finalizeCode(ProgramMethod method, IRCode code) {
// Convert to DEX.
assert appView.enableWholeProgramOptimizations()
? code.getConversionOptions().isGeneratingLir()
: code.getConversionOptions().isGeneratingDex();
IRFinalizer<?> finalizer = code.getConversionOptions().getFinalizer(deadCodeRemover, appView);
Code dexCode = finalizer.finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
method.setCode(dexCode, appView);
}
private void removeConstantArgumentsFromOutline(
ProgramMethod method, IRCode code, ThrowBlockOutline outline) {
if (!outline.hasConstantArgument()) {
return;
}
Set<Phi> affectedPhis = Collections.emptySet();
Set<UnusedArgument> unusedArguments = Collections.emptySet();
new LensCodeArgumentRewriter(appView)
.rewriteArguments(
code, method.getReference(), outline.getProtoChanges(), affectedPhis, unusedArguments);
// Run shorten live ranges to push materialized constants to their uses.
ConstantCanonicalizer constantCanonicalizer = new ConstantCanonicalizer(appView, method, code);
new DexConstantOptimizer(appView, constantCanonicalizer).run(code, Timing.empty());
}
private void processOutlineMarkers(IRCode code) {
for (BasicBlock block : code.getBlocks()) {
ThrowBlockOutlineMarker outlineMarker =
block.entry().nextUntilInclusive(Instruction::isThrowBlockOutlineMarker);
while (outlineMarker != null) {
ThrowBlockOutline outline = outlineMarker.getOutline().getParentOrSelf();
Instruction outlineEnd = getOutlineEnd(block, outline, outlineMarker);
outlineMarker.detachConstantOutlineArguments(outline);
if (outline.isMaterialized()) {
// Insert a call to the materialized outline method and load the return value.
BasicBlockInstructionListIterator instructionIterator = block.listIterator(outlineEnd);
InvokeStatic.Builder invokeBuilder =
InvokeStatic.builder()
.setArguments(outlineMarker.inValues())
.setIsInterface(false)
.setMethod(outline.getMaterializedOutlineMethod())
.setPosition(outlineEnd);
if (outline.isStringBuilderToStringOutline()) {
invokeBuilder.setFreshOutValue(
code, factory.stringType.toTypeElement(appView), outlineEnd.getLocalInfo());
}
InvokeStatic invoke = invokeBuilder.build();
if (outline.isStringBuilderToStringOutline()) {
outlineEnd.replace(invoke);
outlineEnd = invoke;
} else {
assert outline.isThrowOutline();
instructionIterator.add(invoke);
Value returnOrThrowValue = addReturnOrThrowValue(code, instructionIterator);
// Replace the throw instruction by a normal return, but throw null in initializers.
if (code.context().getDefinition().isInstanceInitializer()) {
outlineEnd.replaceValue(0, returnOrThrowValue);
} else {
Return returnInstruction =
Return.builder()
.setPositionForNonThrowingInstruction(
outlineEnd.getPosition(), appView.options())
.setReturnValue(returnOrThrowValue)
.build();
block.replaceLastInstruction(returnInstruction);
outlineEnd = returnInstruction;
}
}
// Remove all outlined instructions bottom up.
instructionIterator = block.listIterator(invoke);
for (Instruction outlinedInstruction = instructionIterator.previous();
outlinedInstruction != outlineMarker;
outlinedInstruction = instructionIterator.previous()) {
assert !outlinedInstruction.isThrowBlockOutlineMarker();
Value outValue = outlinedInstruction.outValue();
if (outValue == null || !outValue.hasNonDebugUsers()) {
// Remove all debug users of the out-value.
if (outValue != null && outValue.hasDebugUsers()) {
for (Instruction debugUser : outValue.debugUsers()) {
debugUser.getDebugValues().remove(outValue);
if (debugUser.isDebugLocalRead() && debugUser.getDebugValues().isEmpty()) {
debugUser.remove();
}
}
outValue.clearDebugUsers();
}
// We are not using `removeOrReplaceByDebugLocalRead` here due to the backwards
// iteration.
if (outlinedInstruction.getDebugValues().isEmpty()) {
outlinedInstruction.remove();
} else {
DebugLocalRead replacement = new DebugLocalRead();
outlinedInstruction.replace(replacement);
Instruction previous = instructionIterator.previous();
assert previous == replacement;
}
}
}
}
// Finally delete the outline marker.
outlineMarker.removeOrReplaceByDebugLocalRead();
// Blocks cannot start with DebugLocalRead.
while (block.entry().isDebugLocalRead()) {
block.entry().moveDebugValues(block.entry().getNext());
block.entry().remove();
}
// Continue searching for outline markers from the end of the current outline.
outlineMarker = outlineEnd.nextUntilExclusive(Instruction::isThrowBlockOutlineMarker);
}
assert block.streamInstructions().noneMatch(Instruction::isThrowBlockOutlineMarker);
}
// TODO(b/443663978): Workaround the fact that we do not correctly patch up the debug info for
// LIR code in interface method desugaring. Remove when resolved.
for (DebugLocalRead dlr : code.<DebugLocalRead>instructions(Instruction::isDebugLocalRead)) {
if (dlr.getDebugValues().isEmpty()) {
dlr.remove();
}
}
// Run the dead code remover to ensure code that has been moved into the outline is removed
// (e.g., constants, the allocation of the exception).
deadCodeRemover.run(code, Timing.empty());
}
private Value addReturnOrThrowValue(
IRCode code, BasicBlockInstructionListIterator instructionIterator) {
InternalOptions options = appView.options();
DexType returnType = code.context().getReturnType();
// We cannot replace a throw in an instance initializer by a return, since it is a verification
// error for instance initializers not to call a parent constructor.
if (returnType.isReferenceType() || code.context().getDefinition().isInstanceInitializer()) {
return instructionIterator.insertConstNullInstruction(code, options);
} else if (returnType.isPrimitiveType()) {
return instructionIterator.insertConstNumberInstruction(
code, options, 0, returnType.toTypeElement(appView));
} else {
assert returnType.isVoidType();
return null;
}
}
private Instruction getOutlineEnd(
BasicBlock block, ThrowBlockOutline outline, ThrowBlockOutlineMarker outlineMarker) {
if (outline.isThrowOutline()) {
return block.exit();
} else {
// The end of a StringBuilder#toString outline is the call to StringBuilder#toString.
return outlineMarker.nextUntilExclusive(
i -> ThrowBlockOutlinerScanner.isStringBuilderToString(i, factory));
}
}
}