blob: 835daa4dfaa7988db83ca8a3479d841dc7f6a931 [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.horizontalclassmerging;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.cf.code.CfInstanceFieldRead;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfSwitch;
import com.android.tools.r8.cf.code.CfSwitch.Kind;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.IterableUtils;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import org.objectweb.asm.Opcodes;
/**
* A short-lived piece of code that will be converted into {@link CfCode} using the method {@link
* IncompleteHorizontalClassMergerCode#toCfCode(AppView, ProgramMethod,
* HorizontalClassMergerGraphLens)}.
*/
public class IncompleteVirtuallyMergedMethodCode extends IncompleteHorizontalClassMergerCode {
private final DexField classIdField;
private final Int2ReferenceSortedMap<DexMethod> mappedMethods;
private final DexMethod originalMethod;
private final DexMethod superMethod;
public IncompleteVirtuallyMergedMethodCode(
DexField classIdField,
Int2ReferenceSortedMap<DexMethod> mappedMethods,
DexMethod originalMethod,
DexMethod superMethod) {
this.mappedMethods = mappedMethods;
this.classIdField = classIdField;
this.superMethod = superMethod;
this.originalMethod = originalMethod;
}
/**
* Given a mapping from class ids to methods to invoke, this creates a piece of {@link CfCode} on
* the following form.
*
* <pre>
* public Bar m(Foo foo) {
* switch (this.classId) {
* case 0:
* return this.m1(foo);
* case 1:
* return this.m2(foo);
* ...
* default:
* return this.mN(foo); // or super.m(foo);
* }
* }
* </pre>
*
* <p>Note that the methods to invoke must be rewritten using {@param lens}, since the invoked
* methods may be changed as a result of the horizontal class merger's fixup (e.g., if the method
* signature refers to a horizontally merged type).
*/
@Override
public CfCode toCfCode(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod method,
HorizontalClassMergerGraphLens lens) {
// We store each argument in a local.
int maxLocals = 1 + IterableUtils.sumInt(method.getParameters(), DexType::getRequiredRegisters);
// We load all arguments on the stack and then the receiver to fetch the class id.
int maxStack = maxLocals + 1;
// Create instructions.
List<CfInstruction> instructions = new ArrayList<>();
// Setup keys and labels for switch.
IntBidirectionalIterator classIdIterator = mappedMethods.keySet().iterator();
int[] keys = new int[mappedMethods.size() - BooleanUtils.intValue(superMethod == null)];
List<CfLabel> labels = new ArrayList<>();
for (int key = 0; key < keys.length; key++) {
keys[key] = classIdIterator.nextInt();
labels.add(new CfLabel());
}
CfLabel fallthroughLabel = new CfLabel();
// Add instructions.
instructions.add(new CfLoad(ValueType.OBJECT, 0));
int localIndex = 1;
for (DexType parameter : method.getParameters()) {
instructions.add(new CfLoad(ValueType.fromDexType(parameter), localIndex));
localIndex += parameter.getRequiredRegisters();
}
instructions.add(new CfLoad(ValueType.OBJECT, 0));
instructions.add(new CfInstanceFieldRead(classIdField));
// Emit switch.
instructions.add(new CfSwitch(Kind.LOOKUP, fallthroughLabel, keys, labels));
for (int key = 0; key < keys.length; key++) {
int classId = keys[key];
DexMethod target = lens.getNextMethodSignature(mappedMethods.get(classId));
instructions.add(labels.get(key));
instructions.add(createCfFrameForSwitchCase(method, maxLocals));
instructions.add(
new CfInvoke(Opcodes.INVOKESPECIAL, target, method.getHolder().isInterface()));
if (method.getReturnType().isVoidType()) {
instructions.add(new CfReturnVoid());
} else {
instructions.add(new CfReturn(ValueType.fromDexType(method.getReturnType())));
}
}
// Emit fallthrough.
instructions.add(fallthroughLabel);
instructions.add(createCfFrameForSwitchCase(method, maxLocals));
DexMethod fallthroughTarget =
lens.getNextMethodSignature(
superMethod != null ? superMethod : mappedMethods.get(mappedMethods.lastIntKey()));
instructions.add(
new CfInvoke(Opcodes.INVOKESPECIAL, fallthroughTarget, method.getHolder().isInterface()));
// Emit return.
if (method.getReturnType().isVoidType()) {
instructions.add(new CfReturnVoid());
} else {
instructions.add(new CfReturn(ValueType.fromDexType(method.getReturnType())));
}
return new CfCode(originalMethod.getHolderType(), maxStack, maxLocals, instructions) {
@Override
public GraphLens getCodeLens(AppView<?> appView) {
return lens;
}
};
}
private static CfFrame createCfFrameForSwitchCase(ProgramMethod representative, int localsSize) {
Deque<FrameType> stack =
new ArrayDeque<>(representative.getDefinition().getNumberOfArguments());
for (int argumentIndex = 0;
argumentIndex < representative.getDefinition().getNumberOfArguments();
argumentIndex++) {
stack.add(FrameType.initialized(representative.getArgumentType(argumentIndex)));
}
return new CfFrame(createLocalFrames(representative, localsSize), stack);
}
private static Int2ReferenceAVLTreeMap<FrameType> createLocalFrames(
ProgramMethod representative, int localsSize) {
Int2ReferenceAVLTreeMap<FrameType> locals = new Int2ReferenceAVLTreeMap<>();
for (int argumentIndex = 0, localIndex = 0;
argumentIndex < representative.getDefinition().getNumberOfArguments();
argumentIndex++) {
FrameType frameType = FrameType.initialized(representative.getArgumentType(argumentIndex));
locals.put(localIndex++, frameType);
if (frameType.isWide()) {
locals.put(localIndex++, frameType);
}
}
assert locals.size() == localsSize;
return locals;
}
@Override
public String toString() {
return "IncompleteVirtuallyMergedMethodCode";
}
}