blob: d1a7149df6d498ccd0810325a8c3036803c19b1f [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.lightir;
import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ArgumentUse;
import com.android.tools.r8.graph.ClasspathMethod;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SourcePosition;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.RetracerForCodePrinting;
import com.google.common.collect.ImmutableMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class LirCode<EV> extends Code implements Iterable<LirInstructionView> {
public abstract static class PositionEntry {
private final int fromInstructionIndex;
PositionEntry(int fromInstructionIndex) {
this.fromInstructionIndex = fromInstructionIndex;
}
public int getFromInstructionIndex() {
return fromInstructionIndex;
}
public abstract Position getPosition(DexMethod method);
}
public static class LinePositionEntry extends PositionEntry {
private final int line;
public LinePositionEntry(int fromInstructionIndex, int line) {
super(fromInstructionIndex);
this.line = line;
}
public int getLine() {
return line;
}
@Override
public Position getPosition(DexMethod method) {
return SourcePosition.builder().setMethod(method).setLine(line).build();
}
}
public static class StructuredPositionEntry extends PositionEntry {
private final Position position;
public StructuredPositionEntry(int fromInstructionIndex, Position position) {
super(fromInstructionIndex);
this.position = position;
}
@Override
public Position getPosition(DexMethod method) {
return position;
}
}
public static class TryCatchTable {
final Int2ReferenceMap<CatchHandlers<Integer>> tryCatchHandlers;
public TryCatchTable(Int2ReferenceMap<CatchHandlers<Integer>> tryCatchHandlers) {
assert !tryCatchHandlers.isEmpty();
// Copy the map to ensure it has not over-allocated the backing store.
this.tryCatchHandlers = new Int2ReferenceOpenHashMap<>(tryCatchHandlers);
}
public CatchHandlers<Integer> getHandlersForBlock(int blockIndex) {
return tryCatchHandlers.get(blockIndex);
}
}
public static class DebugLocalInfoTable<EV> {
private final Map<EV, DebugLocalInfo> valueToLocalMap;
private final Int2ReferenceMap<int[]> instructionToEndUseMap;
public DebugLocalInfoTable(
Map<EV, DebugLocalInfo> valueToLocalMap, Int2ReferenceMap<int[]> instructionToEndUseMap) {
assert !valueToLocalMap.isEmpty();
// TODO(b/283049198): Debug ends may not be maintained so we can't assume they are non-empty.
// Copy the maps to ensure they have not over-allocated the backing store.
this.valueToLocalMap = ImmutableMap.copyOf(valueToLocalMap);
this.instructionToEndUseMap =
instructionToEndUseMap.isEmpty()
? null
: new Int2ReferenceOpenHashMap<>(instructionToEndUseMap);
}
public int[] getEnds(int index) {
if (instructionToEndUseMap == null) {
return null;
}
return instructionToEndUseMap.get(index);
}
public void forEachLocalDefinition(BiConsumer<EV, DebugLocalInfo> fn) {
valueToLocalMap.forEach(fn);
}
}
private final LirStrategyInfo<EV> strategyInfo;
private final boolean useDexEstimationStrategy;
private final IRMetadata irMetadata;
/** Constant pool of items. */
private final DexItem[] constants;
private final PositionEntry[] positionTable;
/** Full number of arguments (including receiver for non-static methods). */
private final int argumentCount;
/** Byte encoding of the instructions (excludes arguments, includes phis). */
private final byte[] instructions;
/** Cached value for the number of logical instructions (excludes arguments, includes phis). */
private final int instructionCount;
/** Table of try-catch handlers for each basic block (if present). */
private final TryCatchTable tryCatchTable;
/** Table of debug local information for each SSA value (if present). */
private final DebugLocalInfoTable<EV> debugLocalInfoTable;
/** Table of metadata for each instruction (if present). */
private final Int2ReferenceMap<BytecodeInstructionMetadata> metadataMap;
public static <V, EV> LirBuilder<V, EV> builder(
DexMethod method, LirEncodingStrategy<V, EV> strategy, InternalOptions options) {
return new LirBuilder<>(method, strategy, options);
}
/** Should be constructed using {@link LirBuilder}. */
LirCode(
IRMetadata irMetadata,
DexItem[] constants,
PositionEntry[] positions,
int argumentCount,
byte[] instructions,
int instructionCount,
TryCatchTable tryCatchTable,
DebugLocalInfoTable<EV> debugLocalInfoTable,
LirStrategyInfo<EV> strategyInfo,
boolean useDexEstimationStrategy,
Int2ReferenceMap<BytecodeInstructionMetadata> metadataMap) {
this.irMetadata = irMetadata;
this.constants = constants;
this.positionTable = positions;
this.argumentCount = argumentCount;
this.instructions = instructions;
this.instructionCount = instructionCount;
this.tryCatchTable = tryCatchTable;
this.debugLocalInfoTable = debugLocalInfoTable;
this.strategyInfo = strategyInfo;
this.useDexEstimationStrategy = useDexEstimationStrategy;
this.metadataMap = metadataMap;
}
@SuppressWarnings("unchecked")
@Override
public LirCode<Integer> asLirCode() {
// TODO(b/225838009): Unchecked cast will be removed once the encoding strategy is definitive.
return (LirCode<Integer>) this;
}
@Override
protected int computeHashCode() {
throw new Unreachable("LIR code should not be subject to hashing.");
}
@Override
protected boolean computeEquals(Object other) {
throw new Unreachable("LIR code should not be subject to equality checks.");
}
public EV decodeValueIndex(int encodedValueIndex, int currentValueIndex) {
return strategyInfo
.getReferenceStrategy()
.decodeValueIndex(encodedValueIndex, currentValueIndex);
}
public LirStrategyInfo<EV> getStrategyInfo() {
return strategyInfo;
}
public int getArgumentCount() {
return argumentCount;
}
public byte[] getInstructionBytes() {
return instructions;
}
public int getInstructionCount() {
return instructionCount;
}
public IRMetadata getMetadataForIR() {
return irMetadata;
}
public DexItem getConstantItem(int index) {
return constants[index];
}
public PositionEntry[] getPositionTable() {
return positionTable;
}
public TryCatchTable getTryCatchTable() {
return tryCatchTable;
}
public DebugLocalInfoTable<EV> getDebugLocalInfoTable() {
return debugLocalInfoTable;
}
public DebugLocalInfo getDebugLocalInfo(EV valueIndex) {
return debugLocalInfoTable == null ? null : debugLocalInfoTable.valueToLocalMap.get(valueIndex);
}
public int[] getDebugLocalEnds(int instructionValueIndex) {
return debugLocalInfoTable == null ? null : debugLocalInfoTable.getEnds(instructionValueIndex);
}
@Override
public BytecodeMetadata<? extends CfOrDexInstruction> getMetadata() {
// Bytecode metadata is recomputed when finalizing via IR.
throw new Unreachable();
}
@Override
public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) {
// Bytecode metadata is recomputed when finalizing via IR.
throw new Unreachable();
}
@Override
public LirIterator iterator() {
return new LirIterator(new ByteArrayIterator(instructions));
}
@Override
public IRCode buildIR(
ProgramMethod method,
AppView<?> appView,
Origin origin,
MutableMethodConversionOptions conversionOptions) {
RewrittenPrototypeDescription protoChanges =
appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
return internalBuildIR(method, appView, new NumberGenerator(), null, protoChanges);
}
@Override
public IRCode buildInliningIR(
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
GraphLens codeLens,
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
RewrittenPrototypeDescription protoChanges) {
assert valueNumberGenerator != null;
assert callerPosition != null;
assert protoChanges != null;
return internalBuildIR(method, appView, valueNumberGenerator, callerPosition, protoChanges);
}
private IRCode internalBuildIR(
ProgramMethod method,
AppView<?> appView,
NumberGenerator valueNumberGenerator,
Position callerPosition,
RewrittenPrototypeDescription protoChanges) {
LirCode<Integer> typedLir = asLirCode();
return Lir2IRConverter.translate(
method,
typedLir,
LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, valueNumberGenerator),
appView,
callerPosition,
protoChanges,
appView.graphLens().getOriginalMethodSignature(method.getReference()));
}
@Override
public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
assert registry.getTraversalContinuation().shouldContinue();
LirUseRegistryCallback<EV> registryCallbacks = new LirUseRegistryCallback<>(this, registry);
for (LirInstructionView view : this) {
if (metadataMap != null) {
registryCallbacks.setCurrentMetadata(metadataMap.get(view.getInstructionIndex()));
}
registryCallbacks.onInstructionView(view);
if (registry.getTraversalContinuation().shouldBreak()) {
return;
}
}
if (tryCatchTable != null) {
for (CatchHandlers<Integer> handler : tryCatchTable.tryCatchHandlers.values()) {
for (DexType guard : handler.getGuards()) {
registry.registerExceptionGuard(guard);
if (registry.getTraversalContinuation().shouldBreak()) {
return;
}
}
}
}
}
@Override
public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
throw new Unimplemented();
}
@Override
public Int2ReferenceMap<DebugLocalInfo> collectParameterInfo(
DexEncodedMethod encodedMethod, AppView<?> appView) {
throw new Unimplemented();
}
@Override
public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) {
throw new Unimplemented();
}
@Override
public String toString() {
return new LirPrinter<>(this).prettyPrint();
}
@Override
public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
// TODO(b/225838009): Add retracing to printer.
return toString();
}
@Override
public int estimatedDexCodeSizeUpperBoundInBytes() {
throw new Unimplemented();
}
@Override
public int estimatedSizeForInlining() {
if (useDexEstimationStrategy) {
LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this);
for (LirInstructionView view : this) {
estimation.onInstructionView(view);
}
return estimation.getSizeEstimate();
} else {
// TODO(b/225838009): Currently the size estimation for CF has size one for each instruction
// (even switches!) and ignores stack instructions, thus loads to arguments are not included.
// The result is a much smaller estimate than for DEX. Once LIR is in place we should use the
// same estimate for both.
return instructionCount;
}
}
@Override
public boolean estimatedSizeForInliningAtMost(int threshold) {
if (useDexEstimationStrategy) {
LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this);
for (LirInstructionView view : this) {
estimation.onInstructionView(view);
if (estimation.getSizeEstimate() > threshold) {
return false;
}
}
return true;
} else {
return estimatedSizeForInlining() <= threshold;
}
}
@Override
public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) {
throw new Unimplemented();
}
@Override
public boolean isEmptyVoidMethod() {
for (LirInstructionView view : this) {
int opcode = view.getOpcode();
if (opcode != LirOpcodes.RETURN && opcode != LirOpcodes.DEBUGPOS) {
return false;
}
}
return true;
}
@Override
public boolean hasMonitorInstructions() {
for (LirInstructionView view : this) {
int opcode = view.getOpcode();
if (opcode == LirOpcodes.MONITORENTER || opcode == LirOpcodes.MONITOREXIT) {
return true;
}
}
return false;
}
@Override
public void forEachPosition(DexMethod method, Consumer<Position> positionConsumer) {
for (PositionEntry entry : positionTable) {
positionConsumer.accept(entry.getPosition(method));
}
}
}