blob: d0938235b320bf506f947f9b3e2fceab863f1b80 [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.lightir;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.code.Opcodes;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.utils.ArrayUtils;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
public class SimpleLensLirRewriter<EV> extends LirParsedInstructionCallback<EV> {
private final ProgramMethod context;
private final DexMethod contextReference;
private final GraphLens graphLens;
private final GraphLens codeLens;
private final LensCodeRewriterUtils helper;
private int numberOfInvokeTypeChanges = 0;
private Map<LirConstant, LirConstant> constantPoolMapping = null;
public SimpleLensLirRewriter(
LirCode<EV> code,
ProgramMethod context,
GraphLens graphLens,
GraphLens codeLens,
LensCodeRewriterUtils helper) {
super(code);
this.context = context;
this.contextReference = context.getReference();
this.graphLens = graphLens;
this.codeLens = codeLens;
this.helper = helper;
}
@Override
public int getCurrentValueIndex() {
// We do not need to interpret values.
return -1;
}
public void onTypeReference(DexType type) {
addRewrittenMapping(type, graphLens.lookupType(type, codeLens));
}
public void onFieldReference(DexField field) {
addRewrittenMapping(field, graphLens.lookupField(field, codeLens));
}
public void onCallSiteReference(DexCallSite callSite) {
addRewrittenMapping(callSite, helper.rewriteCallSite(callSite, context));
}
public void onProtoReference(DexProto proto) {
addRewrittenMapping(proto, helper.rewriteProto(proto));
}
private void onInvoke(DexMethod method, InvokeType type) {
MethodLookupResult result = graphLens.lookupMethod(method, contextReference, type, codeLens);
if (result.getType() != type) {
assert (type == InvokeType.VIRTUAL && result.getType() == InvokeType.INTERFACE)
|| (type == InvokeType.INTERFACE && result.getType() == InvokeType.VIRTUAL);
numberOfInvokeTypeChanges++;
} else {
// All non-type dependent mappings are just rewritten in the content pool.
addRewrittenMapping(method, result.getReference());
}
}
private void addRewrittenMapping(LirConstant item, LirConstant rewrittenItem) {
if (item == rewrittenItem) {
return;
}
if (constantPoolMapping == null) {
constantPoolMapping =
new IdentityHashMap<>(
// Avoid using initial capacity larger than the number of actual constants.
Math.min(getCode().getConstantPool().length, 32));
}
LirConstant old = constantPoolMapping.put(item, rewrittenItem);
if (old != null && old != rewrittenItem) {
throw new Unreachable(
"Unexpected rewriting of item: "
+ item
+ " to two distinct items: "
+ rewrittenItem
+ " and "
+ old);
}
}
@Override
public void onInvokeDirect(DexMethod method, List<EV> arguments, boolean isInterface) {
onInvoke(method, InvokeType.DIRECT);
}
@Override
public void onInvokeSuper(DexMethod method, List<EV> arguments, boolean isInterface) {
onInvoke(method, InvokeType.SUPER);
}
@Override
public void onInvokeVirtual(DexMethod method, List<EV> arguments) {
onInvoke(method, InvokeType.VIRTUAL);
}
@Override
public void onInvokeStatic(DexMethod method, List<EV> arguments, boolean isInterface) {
onInvoke(method, InvokeType.STATIC);
}
@Override
public void onInvokeInterface(DexMethod method, List<EV> arguments) {
onInvoke(method, InvokeType.INTERFACE);
}
private InvokeType getInvokeTypeThatMayChange(int opcode) {
if (opcode == LirOpcodes.INVOKEVIRTUAL) {
return InvokeType.VIRTUAL;
}
if (opcode == LirOpcodes.INVOKEINTERFACE) {
return InvokeType.INTERFACE;
}
return null;
}
public LirCode<EV> rewrite() {
LirCode<EV> rewritten = rewriteConstantPoolAndScanForTypeChanges(getCode());
return rewriteInstructionsWithInvokeTypeChanges(rewritten);
}
private LirCode<EV> rewriteConstantPoolAndScanForTypeChanges(LirCode<EV> code) {
// The code may need to be rewritten by the lens.
// First pass scans just the constant pool to see if any types change or if there are any
// fields/methods that need to be examined.
boolean hasPotentialRewrittenMethod = false;
for (LirConstant constant : code.getConstantPool()) {
if (constant instanceof DexType) {
onTypeReference((DexType) constant);
} else if (constant instanceof DexField) {
onFieldReference((DexField) constant);
} else if (constant instanceof DexCallSite) {
onCallSiteReference((DexCallSite) constant);
} else if (constant instanceof DexProto) {
onProtoReference((DexProto) constant);
} else if (!hasPotentialRewrittenMethod && constant instanceof DexMethod) {
// We might be able to still fast-case this if we can guarantee the method is never
// rewritten. Say it is an java.lang.Object reference or if the lens can fast-check it.
hasPotentialRewrittenMethod = true;
}
}
// If there are potential method rewritings then we need to iterate the instructions as the
// rewriting is instruction-sensitive (i.e., may be dependent on the invoke type).
if (hasPotentialRewrittenMethod) {
for (LirInstructionView view : code) {
view.accept(this);
}
}
if (constantPoolMapping == null) {
return code;
}
return code.newCodeWithRewrittenConstantPool(
item -> constantPoolMapping.getOrDefault(item, item));
}
private LirCode<EV> rewriteInstructionsWithInvokeTypeChanges(LirCode<EV> code) {
if (numberOfInvokeTypeChanges == 0) {
return code;
}
// Build a small map from method refs to index in case the type-dependent methods are already
// in the constant pool.
Reference2IntMap<DexMethod> methodIndices = new Reference2IntOpenHashMap<>();
LirConstant[] rewrittenConstants = code.getConstantPool();
for (int i = 0, length = rewrittenConstants.length; i < length; i++) {
LirConstant constant = rewrittenConstants[i];
if (constant instanceof DexMethod) {
methodIndices.put((DexMethod) constant, i);
}
}
IRMetadata irMetadata = code.getMetadataForIR();
ByteArrayWriter byteWriter = new ByteArrayWriter();
LirWriter lirWriter = new LirWriter(byteWriter);
List<LirConstant> methodsToAppend = new ArrayList<>(numberOfInvokeTypeChanges);
for (LirInstructionView view : code) {
int opcode = view.getOpcode();
// Instructions that do not have an invoke-type change are just mapped via identity.
if (LirOpcodes.isOneByteInstruction(opcode)) {
lirWriter.writeOneByteInstruction(opcode);
continue;
}
InvokeType type = getInvokeTypeThatMayChange(opcode);
if (type == null) {
int size = view.getRemainingOperandSizeInBytes();
lirWriter.writeInstruction(opcode, size);
while (size-- > 0) {
lirWriter.writeOperand(view.getNextU1());
}
continue;
}
// This is potentially an invoke with a type change, in such cases the method is mapped with
// the instruction updated to the new type. The constant pool is amended with the mapped
// method if needed.
int constantIndex = view.getNextConstantOperand();
DexMethod method = (DexMethod) code.getConstantItem(constantIndex);
MethodLookupResult result =
graphLens.lookupMethod(method, context.getReference(), type, codeLens);
if (result.getType() != type) {
--numberOfInvokeTypeChanges;
if (result.getType().isVirtual()) {
opcode = LirOpcodes.INVOKEVIRTUAL;
irMetadata.record(Opcodes.INVOKE_VIRTUAL);
} else if (result.getType().isInterface()) {
opcode = LirOpcodes.INVOKEINTERFACE;
irMetadata.record(Opcodes.INVOKE_INTERFACE);
} else {
throw new Unreachable(
"Unexpected change of invoke that may need an interface bit set: "
+ result.getType());
}
constantIndex =
methodIndices.computeIfAbsent(
result.getReference(),
ref -> {
methodsToAppend.add(ref);
return rewrittenConstants.length + methodsToAppend.size() - 1;
});
}
int constantIndexSize = ByteUtils.intEncodingSize(constantIndex);
int remainingSize = view.getRemainingOperandSizeInBytes();
lirWriter.writeInstruction(opcode, constantIndexSize + remainingSize);
ByteUtils.writeEncodedInt(constantIndex, lirWriter::writeOperand);
while (remainingSize-- > 0) {
lirWriter.writeOperand(view.getNextU1());
}
}
assert numberOfInvokeTypeChanges == 0;
// Note that since we assume 'null' in the mapping is identity this may end up with a stale
// reference to a no longer used method. That is not an issue as it will be pruned when
// building IR again, it is just a small and size overhead.
LirCode<EV> newCode =
code.copyWithNewConstantsAndInstructions(
irMetadata,
ArrayUtils.appendElements(code.getConstantPool(), methodsToAppend),
byteWriter.toByteArray());
return newCode;
}
}