blob: 494e383c871038c72fee0a758d7aba03d1daf2e4 [file] [log] [blame]
// Copyright (c) 2020, 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.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
import com.android.tools.r8.utils.IntBox;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.ArrayList;
import java.util.List;
/**
* Assuming a method signature <code>
* void method([args]);
* </code>. This class generates code depending on which of the following cases it matches.
*
* <p>If the method does not override a method and is implemented by many (e.g. 2) classes:
*
* <pre>
* void method([args]) {
* switch (classId) {
* case 0:
* return method$1([args]);
* default:
* return method$2([args]);
* }
* }
* </pre>
*
* <p>If the method overrides a method and is implemented by any number of classes:
*
* <pre>
* void method([args]) {
* switch (classId) {
* case 0:
* return method$1([args]);
* // ... further cases ...
* default:
* return super.method$1([args]);
* }
* }
* </pre>
*/
public class VirtualMethodEntryPoint extends SyntheticSourceCode {
private final Int2ReferenceSortedMap<DexMethod> mappedMethods;
private final DexField classIdField;
private final DexMethod superMethod;
public VirtualMethodEntryPoint(
Int2ReferenceSortedMap<DexMethod> mappedMethods,
DexField classIdField,
DexMethod superMethod,
DexMethod newMethod,
Position callerPosition,
DexMethod originalMethod) {
super(newMethod.holder, newMethod, callerPosition, originalMethod);
assert classIdField != null;
this.mappedMethods = mappedMethods;
this.classIdField = classIdField;
this.superMethod = superMethod;
}
void addInvokeDirect(DexMethod method) {
add(
builder -> {
List<Value> arguments = new ArrayList<>(method.getArity() + 1);
arguments.add(builder.getReceiverValue());
if (builder.getArgumentValues() != null) {
arguments.addAll(builder.getArgumentValues());
}
builder.addInvoke(Type.DIRECT, method, method.proto, arguments, false);
});
}
void addInvokeSuper() {
assert superMethod != null;
add(
builder -> {
List<Value> arguments = new ArrayList<>(method.getArity() + 1);
arguments.add(builder.getReceiverValue());
if (builder.getArgumentValues() != null) {
arguments.addAll(builder.getArgumentValues());
}
builder.addInvoke(Type.SUPER, superMethod, superMethod.proto, arguments, false);
});
}
void handleReturn(int retRegister) {
if (proto.returnType.isVoidType()) {
add(IRBuilder::addReturn, endsBlock);
} else {
add(builder -> builder.addMoveResult(retRegister));
add(builder -> builder.addReturn(retRegister), endsBlock);
}
}
@Override
protected void prepareInstructions() {
int casesCount = mappedMethods.size();
// If there is no super method, use one of the cases as a fallthrough case.
if (superMethod == null) {
casesCount--;
}
assert casesCount > 0;
// Return value register if needed.
int returnRegister =
!proto.returnType.isVoidType() ? nextRegister(ValueType.fromDexType(proto.returnType)) : -1;
int[] keys = new int[casesCount];
int[] offsets = new int[casesCount];
IntBox fallthrough = new IntBox();
// Fetch the class id from the class id field.
int idRegister = nextRegister(ValueType.INT);
add(builder -> builder.addInstanceGet(idRegister, getReceiverRegister(), classIdField));
int switchIndex = lastInstructionIndex();
add(
builder -> builder.addSwitch(idRegister, keys, fallthrough.get(), offsets),
builder -> endsSwitch(builder, switchIndex, fallthrough.get(), offsets));
int index = 0;
for (Entry<DexMethod> entry : mappedMethods.int2ReferenceEntrySet()) {
int classId = entry.getIntKey();
DexMethod mappedMethod = entry.getValue();
// If there is no super method, then use the last case as the default case.
if (index >= casesCount) {
fallthrough.set(nextInstructionIndex());
} else {
keys[index] = classId;
offsets[index] = nextInstructionIndex();
}
addInvokeDirect(mappedMethod);
handleReturn(returnRegister);
index++;
}
// If the super class implements this method, then the fallthrough case should execute it.
if (superMethod != null) {
fallthrough.set(nextInstructionIndex());
addInvokeSuper();
handleReturn(returnRegister);
}
}
}