blob: 9ee8dcff09a54ddefcd8429c23730683cdc29d53 [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 static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
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.CfPosition;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfSafeCheckCast;
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.CfCodeWithLens;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
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.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.SingleConstValue;
import com.android.tools.r8.ir.analysis.value.SingleDexItemBasedStringValue;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.ExtraParameter;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.lightir.LirBuilder;
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.lightir.LirEncodingStrategy;
import com.android.tools.r8.lightir.LirStrategy;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
/**
* Similar to CfCode, but with a marker that makes it possible to recognize this is synthesized by
* the horizontal class merger.
*/
public class IncompleteMergedInstanceInitializerCode extends IncompleteHorizontalClassMergerCode {
private final DexField classIdField;
private final int extraNulls;
private final DexMethod originalMethodReference;
private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre;
private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost;
private final DexMethod parentConstructor;
private final List<InstanceFieldInitializationInfo> parentConstructorArguments;
IncompleteMergedInstanceInitializerCode(
DexField classIdField,
int extraNulls,
DexMethod originalMethodReference,
Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre,
Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost,
DexMethod parentConstructor,
List<InstanceFieldInitializationInfo> parentConstructorArguments) {
this.classIdField = classIdField;
this.extraNulls = extraNulls;
this.originalMethodReference = originalMethodReference;
this.instanceFieldAssignmentsPre = instanceFieldAssignmentsPre;
this.instanceFieldAssignmentsPost = instanceFieldAssignmentsPost;
this.parentConstructor = parentConstructor;
this.parentConstructorArguments = parentConstructorArguments;
}
@Override
public CfCode toCfCode(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod method,
HorizontalClassMergerGraphLens lens) {
int[] argumentToLocalIndex = new int[method.getDefinition().getNumberOfArguments()];
int maxLocals = 0;
for (int argumentIndex = 0; argumentIndex < argumentToLocalIndex.length; argumentIndex++) {
argumentToLocalIndex[argumentIndex] = maxLocals;
maxLocals += method.getArgumentType(argumentIndex).getRequiredRegisters();
}
IntBox maxStack = new IntBox();
ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
Position preamblePosition =
SyntheticPosition.builder()
.setLine(0)
.setMethod(method.getReference())
.setIsD8R8Synthesized(method.getDefinition().isD8R8Synthesized())
.build();
CfPosition position = new CfPosition(new CfLabel(), preamblePosition);
instructionBuilder.add(position);
instructionBuilder.add(position.getLabel());
// Assign class id.
if (classIdField != null) {
int classIdLocalIndex = maxLocals - 1 - extraNulls;
instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
instructionBuilder.add(new CfLoad(ValueType.INT, classIdLocalIndex));
instructionBuilder.add(new CfInstanceFieldWrite(lens.getNextFieldSignature(classIdField)));
maxStack.set(2);
}
// Assign each field.
addCfInstructionsForInstanceFieldAssignments(
appView,
method,
instructionBuilder,
instanceFieldAssignmentsPre,
argumentToLocalIndex,
maxStack,
lens);
// Load receiver for parent constructor call.
int stackHeightForParentConstructorCall = 1;
instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
// Load constructor arguments.
MethodLookupResult parentConstructorLookup =
lens.lookupInvokeDirect(parentConstructor, method, appView.codeLens());
int i = 0;
for (InstanceFieldInitializationInfo initializationInfo : parentConstructorArguments) {
stackHeightForParentConstructorCall +=
addCfInstructionsForInitializationInfo(
instructionBuilder,
initializationInfo,
argumentToLocalIndex,
parentConstructorLookup.getReference().getParameter(i));
i++;
}
for (ExtraParameter extraParameter :
parentConstructorLookup.getPrototypeChanges().getExtraParameters()) {
stackHeightForParentConstructorCall +=
addCfInstructionsForInitializationInfo(
instructionBuilder,
extraParameter.getValue(appView),
argumentToLocalIndex,
parentConstructorLookup.getReference().getParameter(i));
i++;
}
assert i == parentConstructorLookup.getReference().getParameters().size();
// Invoke parent constructor.
instructionBuilder.add(
new CfInvoke(Opcodes.INVOKESPECIAL, parentConstructorLookup.getReference(), false));
maxStack.setMax(stackHeightForParentConstructorCall);
// Assign each field.
addCfInstructionsForInstanceFieldAssignments(
appView,
method,
instructionBuilder,
instanceFieldAssignmentsPost,
argumentToLocalIndex,
maxStack,
lens);
// Return.
instructionBuilder.add(new CfReturnVoid());
return new CfCodeWithLens(
lens,
originalMethodReference.getHolderType(),
maxStack.get(),
maxLocals,
instructionBuilder.build());
}
@Override
public LirCode<Integer> toLirCode(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod method,
HorizontalClassMergerGraphLens lens) {
LirEncodingStrategy<Value, Integer> strategy =
LirStrategy.getDefaultStrategy().getEncodingStrategy();
LirBuilder<Value, Integer> lirBuilder =
LirCode.builder(
method.getReference(),
method.getDefinition().isD8R8Synthesized(),
strategy,
appView.options());
int instructionIndex = 0;
List<Value> argumentValues = new ArrayList<>();
// Add receiver argument.
DexType receiverType = method.getHolderType();
TypeElement receiverTypeElement = receiverType.toTypeElement(appView, definitelyNotNull());
Value receiverValue = Value.createNoDebugLocal(instructionIndex, receiverTypeElement);
argumentValues.add(receiverValue);
strategy.defineValue(receiverValue, receiverValue.getNumber());
lirBuilder.addArgument(receiverValue.getNumber(), false);
instructionIndex++;
// Add non-receiver arguments.
for (; instructionIndex < method.getDefinition().getNumberOfArguments(); instructionIndex++) {
DexType argumentType = method.getArgumentType(instructionIndex);
TypeElement argumentTypeElement = argumentType.toTypeElement(appView);
Value argumentValue = Value.createNoDebugLocal(instructionIndex, argumentTypeElement);
argumentValues.add(argumentValue);
strategy.defineValue(argumentValue, argumentValue.getNumber());
lirBuilder.addArgument(argumentValue.getNumber(), argumentType.isBooleanType());
}
// Assign class id.
if (classIdField != null) {
Value classIdValue = argumentValues.get(argumentValues.size() - 1 - extraNulls);
lirBuilder.addInstancePut(
lens.getNextFieldSignature(classIdField), receiverValue, classIdValue);
instructionIndex++;
}
// Assign each field.
instructionIndex =
addLirInstructionsForInstanceFieldAssignments(
appView,
method,
lirBuilder,
strategy,
argumentValues,
instructionIndex,
instanceFieldAssignmentsPre,
lens);
// Load constructor arguments.
MethodLookupResult parentConstructorLookup =
lens.lookupInvokeDirect(parentConstructor, method, appView.codeLens());
List<Value> parentConstructorArgumentValues = new ArrayList<>();
parentConstructorArgumentValues.add(receiverValue);
int parentConstructorArgumentIndex = 0;
for (InstanceFieldInitializationInfo initializationInfo : parentConstructorArguments) {
instructionIndex =
addLirInstructionsForInitializationInfo(
appView,
lirBuilder,
strategy,
initializationInfo,
argumentValues,
instructionIndex,
parentConstructorLookup.getReference().getParameter(parentConstructorArgumentIndex),
parentConstructorArgumentValues::add);
parentConstructorArgumentIndex++;
}
for (ExtraParameter extraParameter :
parentConstructorLookup.getPrototypeChanges().getExtraParameters()) {
instructionIndex =
addLirInstructionsForInitializationInfo(
appView,
lirBuilder,
strategy,
extraParameter.getValue(appView),
argumentValues,
instructionIndex,
parentConstructorLookup.getReference().getParameter(parentConstructorArgumentIndex),
parentConstructorArgumentValues::add);
parentConstructorArgumentIndex++;
}
// Invoke parent constructor.
lirBuilder.addInvokeDirect(
parentConstructorLookup.getReference(), parentConstructorArgumentValues, false);
instructionIndex++;
// Assign each field.
addLirInstructionsForInstanceFieldAssignments(
appView,
method,
lirBuilder,
strategy,
argumentValues,
instructionIndex,
instanceFieldAssignmentsPost,
lens);
// Return.
lirBuilder.addReturnVoid();
instructionIndex++;
return new LirCode<>(lirBuilder.build()) {
@Override
public boolean hasExplicitCodeLens() {
return true;
}
@Override
public GraphLens getCodeLens(AppView<?> appView) {
return lens;
}
};
}
private static void addCfInstructionsForInstanceFieldAssignments(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod method,
ImmutableList.Builder<CfInstruction> instructionBuilder,
Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
int[] argumentToLocalIndex,
IntBox maxStack,
HorizontalClassMergerGraphLens lens) {
instanceFieldAssignments.forEach(
(field, initializationInfo) -> {
// Load the receiver, the field value, and then set the field.
instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
int stackSizeForInitializationInfo =
addCfInstructionsForInitializationInfo(
instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
DexField rewrittenField = lens.getNextFieldSignature(field);
// Insert a check to ensure the program continues to type check according to Java type
// checking. Otherwise, instance initializer merging may cause open interfaces. If
// <init>(A) and <init>(B) both have the behavior `this.i = arg; this.j = arg` where the
// type of `i` is I and the type of `j` is J, and both A and B implements I and J, then
// the constructors are merged into a single constructor <init>(java.lang.Object), which
// is no longer strictly type checking. Note that no choice of parameter type would solve
// this.
if (initializationInfo.isArgumentInitializationInfo()) {
int argumentIndex =
initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
if (argumentIndex > 0) {
DexType argumentType = method.getArgumentType(argumentIndex);
if (argumentType.isClassType()
&& !appView.appInfo().isSubtype(argumentType, rewrittenField.getType())) {
instructionBuilder.add(new CfSafeCheckCast(rewrittenField.getType()));
}
}
}
instructionBuilder.add(new CfInstanceFieldWrite(rewrittenField));
maxStack.setMax(stackSizeForInitializationInfo + 1);
});
}
private static int addCfInstructionsForInitializationInfo(
ImmutableList.Builder<CfInstruction> instructionBuilder,
InstanceFieldInitializationInfo initializationInfo,
int[] argumentToLocalIndex,
DexType type) {
if (initializationInfo.isArgumentInitializationInfo()) {
int argumentIndex = initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
instructionBuilder.add(
new CfLoad(ValueType.fromDexType(type), argumentToLocalIndex[argumentIndex]));
return type.getRequiredRegisters();
}
assert initializationInfo.isSingleValue();
assert initializationInfo.asSingleValue().isSingleConstValue();
SingleConstValue singleConstValue = initializationInfo.asSingleValue().asSingleConstValue();
if (singleConstValue.isSingleConstClassValue()) {
instructionBuilder.add(
new CfConstClass(singleConstValue.asSingleConstClassValue().getType()));
return 1;
} else if (singleConstValue.isSingleDexItemBasedStringValue()) {
SingleDexItemBasedStringValue dexItemBasedStringValue =
singleConstValue.asSingleDexItemBasedStringValue();
instructionBuilder.add(
new CfDexItemBasedConstString(
dexItemBasedStringValue.getItem(), dexItemBasedStringValue.getNameComputationInfo()));
return 1;
} else if (singleConstValue.isNull()) {
assert type.isReferenceType();
instructionBuilder.add(new CfConstNull());
return 1;
} else if (singleConstValue.isSingleNumberValue()) {
assert type.isPrimitiveType();
instructionBuilder.add(
new CfConstNumber(
singleConstValue.asSingleNumberValue().getValue(), ValueType.fromDexType(type)));
return type.getRequiredRegisters();
} else {
assert singleConstValue.isSingleStringValue();
instructionBuilder.add(
new CfConstString(singleConstValue.asSingleStringValue().getDexString()));
return 1;
}
}
private static int addLirInstructionsForInstanceFieldAssignments(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod method,
LirBuilder<Value, Integer> lirBuilder,
LirEncodingStrategy<Value, Integer> strategy,
List<Value> argumentValues,
int instructionIndex,
Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
HorizontalClassMergerGraphLens lens) {
for (Entry<DexField, InstanceFieldInitializationInfo> entry :
instanceFieldAssignments.entrySet()) {
DexField field = entry.getKey();
InstanceFieldInitializationInfo initializationInfo = entry.getValue();
// Load the field value and then set the field.
Box<Value> fieldValueBox = new Box<>();
instructionIndex =
addLirInstructionsForInitializationInfo(
appView,
lirBuilder,
strategy,
initializationInfo,
argumentValues,
instructionIndex,
field.getType(),
fieldValueBox::set);
Value fieldValue = fieldValueBox.get();
// Insert a check to ensure the program continues to type check according to Java type
// checking. Otherwise, instance initializer merging may cause open interfaces. If
// <init>(A) and <init>(B) both have the behavior `this.i = arg; this.j = arg` where the
// type of `i` is I and the type of `j` is J, and both A and B implements I and J, then
// the constructors are merged into a single constructor <init>(java.lang.Object), which
// is no longer strictly type checking. Note that no choice of parameter type would solve
// this.
DexField rewrittenField = lens.getNextFieldSignature(field);
if (initializationInfo.isArgumentInitializationInfo()) {
int argumentIndex = initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
if (argumentIndex > 0) {
DexType argumentType = method.getArgumentType(argumentIndex);
if (argumentType.isClassType()
&& !appView.appInfo().isSubtype(argumentType, rewrittenField.getType())) {
TypeElement newFieldValueTypeElement =
rewrittenField.getType().toTypeElement(appView, fieldValue.getType().nullability());
Value newFieldValue =
Value.createNoDebugLocal(instructionIndex, newFieldValueTypeElement);
strategy.defineValue(newFieldValue, newFieldValue.getNumber());
lirBuilder.addSafeCheckCast(rewrittenField.getType(), fieldValue);
fieldValue = newFieldValue;
instructionIndex++;
}
}
}
lirBuilder.addInstancePut(rewrittenField, ListUtils.first(argumentValues), fieldValue);
instructionIndex++;
}
return instructionIndex;
}
private static int addLirInstructionsForInitializationInfo(
AppView<? extends AppInfoWithClassHierarchy> appView,
LirBuilder<Value, Integer> lirBuilder,
LirEncodingStrategy<Value, Integer> strategy,
InstanceFieldInitializationInfo initializationInfo,
List<Value> argumentValues,
int instructionIndex,
DexType type,
Consumer<Value> valueConsumer) {
Value value;
if (initializationInfo.isArgumentInitializationInfo()) {
int argumentIndex = initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
value = argumentValues.get(argumentIndex);
} else {
assert initializationInfo.isSingleValue();
assert initializationInfo.asSingleValue().isSingleConstValue();
SingleConstValue singleConstValue = initializationInfo.asSingleValue().asSingleConstValue();
TypeElement valueTypeElement;
if (singleConstValue.isSingleConstClassValue()) {
DexType classType = singleConstValue.asSingleConstClassValue().getType();
lirBuilder.addConstClass(classType, false);
valueTypeElement = TypeElement.classClassType(appView, definitelyNotNull());
} else if (singleConstValue.isSingleDexItemBasedStringValue()) {
SingleDexItemBasedStringValue dexItemBasedStringValue =
singleConstValue.asSingleDexItemBasedStringValue();
lirBuilder.addDexItemBasedConstString(
dexItemBasedStringValue.getItem(), dexItemBasedStringValue.getNameComputationInfo());
valueTypeElement = TypeElement.stringClassType(appView, definitelyNotNull());
} else if (singleConstValue.isNull()) {
assert type.isReferenceType();
lirBuilder.addConstNull();
valueTypeElement = TypeElement.getNull();
} else if (singleConstValue.isSingleNumberValue()) {
assert type.isPrimitiveType();
long numberValue = singleConstValue.asSingleNumberValue().getValue();
lirBuilder.addConstNumber(ValueType.fromDexType(type), numberValue);
valueTypeElement = type.toTypeElement(appView);
} else {
assert singleConstValue.isSingleStringValue();
DexString string = singleConstValue.asSingleStringValue().getDexString();
lirBuilder.addConstString(string);
valueTypeElement = TypeElement.stringClassType(appView, definitelyNotNull());
}
value = Value.createNoDebugLocal(instructionIndex, valueTypeElement);
strategy.defineValue(value, value.getNumber());
instructionIndex++;
}
valueConsumer.accept(value);
return instructionIndex;
}
@Override
public String toString() {
return "IncompleteMergedInstanceInitializerCode";
}
}