blob: 6c85ffaa94834e37258c6fab33526fa374227532 [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.graph.ProgramMethod;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.InvokeType;
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.BooleanUtils;
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;
/**
* Generate code of the form: <code>
* MyClass(int constructorId, [args]) {
* switch (constructorId) {
* case 1:
* this.Constructor$B([args]);
* return;
* ...
* default:
* this.Constructor$A([args]);
* return;
* }
* }
* </code>
*/
public class ConstructorEntryPoint extends SyntheticSourceCode {
private final DexField classIdField;
private final int numberOfUnusedArguments;
private final ProgramMethod method;
private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
public ConstructorEntryPoint(
Int2ReferenceSortedMap<DexMethod> typeConstructors,
ProgramMethod method,
DexField classIdField,
int numberOfUnusedArguments,
Position position) {
super(method, position);
this.classIdField = classIdField;
this.numberOfUnusedArguments = numberOfUnusedArguments;
this.method = method;
this.typeConstructors = typeConstructors;
}
private boolean hasClassIdField() {
return classIdField != null;
}
void addConstructorInvoke(DexMethod typeConstructor) {
add(
builder -> {
int originalNumberOfNonReceiverArguments =
builder.hasArgumentValues()
? (builder.getArgumentValues().size()
- BooleanUtils.intValue(typeConstructors.size() > 1)
- numberOfUnusedArguments)
: 0;
int newNumberOfNonReceiverArguments = typeConstructor.getArity();
List<Value> arguments = new ArrayList<>(newNumberOfNonReceiverArguments + 1);
arguments.add(builder.getReceiverValue());
if (originalNumberOfNonReceiverArguments >= newNumberOfNonReceiverArguments) {
for (int i = 0; i < newNumberOfNonReceiverArguments; i++) {
arguments.add(builder.getArgumentValues().get(i));
}
} else {
// Exclude the last argument if it is the synthetic class id parameter, since the
// original constructor we are calling does not have it.
for (int i = 0; i < originalNumberOfNonReceiverArguments; i++) {
arguments.add(builder.getArgumentValues().get(i));
}
int extraRegister = nextRegister(ValueType.INT);
ConstNumber constNumber = builder.addIntConst(extraRegister, 0);
while (arguments.size() <= newNumberOfNonReceiverArguments) {
assert ValueType.fromDexType(
typeConstructor.getArgumentTypeForNonStaticMethod(arguments.size()))
== ValueType.INT;
arguments.add(constNumber.outValue());
}
}
assert arguments.size() == typeConstructor.getNumberOfArgumentsForNonStaticMethod();
builder.addInvoke(
InvokeType.DIRECT, typeConstructor, typeConstructor.getProto(), arguments, false);
});
}
/** Assign the given register to the class id field. */
void addRegisterClassIdAssignment(int classIdRegister) {
assert hasClassIdField();
add(builder -> builder.addInstancePut(classIdRegister, getReceiverRegister(), classIdField));
}
protected void prepareMultiConstructorInstructions() {
int typeConstructorCount = typeConstructors.size();
// The class id register is always the first synthetic argument.
int classIdRegister = getParamRegister(method.getArity() - 1 - numberOfUnusedArguments);
if (hasClassIdField()) {
addRegisterClassIdAssignment(classIdRegister);
}
int[] keys = new int[typeConstructorCount - 1];
int[] offsets = new int[typeConstructorCount - 1];
IntBox fallthrough = new IntBox();
int switchIndex = lastInstructionIndex();
add(
builder -> builder.addSwitch(classIdRegister, keys, fallthrough.get(), offsets),
builder -> endsSwitch(builder, switchIndex, fallthrough.get(), offsets));
int index = 0;
for (Entry<DexMethod> entry : typeConstructors.int2ReferenceEntrySet()) {
int classId = entry.getIntKey();
DexMethod typeConstructor = entry.getValue();
if (index == 0) {
// The first constructor is the fallthrough case.
fallthrough.set(nextInstructionIndex());
} else {
// All subsequent constructors are matched on a specific case.
keys[index - 1] = classId;
offsets[index - 1] = nextInstructionIndex();
}
addConstructorInvoke(typeConstructor);
add(IRBuilder::addReturn, endsBlock);
index++;
}
}
protected void prepareSingleConstructorInstructions() {
Entry<DexMethod> entry = typeConstructors.int2ReferenceEntrySet().first();
if (hasClassIdField()) {
int classIdRegister = nextRegister(ValueType.INT);
int classIdValue = entry.getIntKey();
add(builder -> builder.addIntConst(classIdRegister, classIdValue));
addRegisterClassIdAssignment(classIdRegister);
}
addConstructorInvoke(entry.getValue());
add(IRBuilder::addReturn, endsBlock);
}
@Override
protected void prepareInstructions() {
if (typeConstructors.size() > 1) {
prepareMultiConstructorInstructions();
} else {
prepareSingleConstructorInstructions();
}
}
}