blob: 443ac30f267813a1bcc2ca3f147638cc0f320ca5 [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.ir.analysis.fieldvalueanalysis;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRCodeUtils;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
import com.android.tools.r8.ir.optimize.info.field.UnknownInstanceFieldInitializationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
public class InstanceFieldValueAnalysis extends FieldValueAnalysis {
// Information about how this instance constructor initializes the fields on the newly created
// instance.
private final InstanceFieldInitializationInfoCollection.Builder builder =
InstanceFieldInitializationInfoCollection.builder();
private final InstanceFieldInitializationInfoFactory factory;
private final DexClassAndMethod parentConstructor;
private final InvokeDirect parentConstructorCall;
private InstanceFieldValueAnalysis(
AppView<AppInfoWithLiveness> appView,
IRCode code,
OptimizationFeedback feedback,
DexClassAndMethod parentConstructor,
InvokeDirect parentConstructorCall) {
super(appView, code, feedback);
this.factory = appView.instanceFieldInitializationInfoFactory();
this.parentConstructor = parentConstructor;
this.parentConstructorCall = parentConstructorCall;
}
/**
* Returns information about how this instance constructor initializes the fields on the newly
* created instance.
*/
public static InstanceFieldInitializationInfoCollection run(
AppView<?> appView,
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
OptimizationFeedback feedback,
Timing timing) {
timing.begin("Analyze instance initializer");
InstanceFieldInitializationInfoCollection result =
run(appView, code, classInitializerDefaultsResult, feedback);
timing.end();
return result;
}
private static InstanceFieldInitializationInfoCollection run(
AppView<?> appView,
IRCode code,
ClassInitializerDefaultsResult classInitializerDefaultsResult,
OptimizationFeedback feedback) {
assert appView.appInfo().hasLiveness();
assert appView.enableWholeProgramOptimizations();
assert code.context().getDefinition().isInstanceInitializer();
InvokeDirect parentConstructorCall =
IRCodeUtils.getUniqueConstructorInvoke(code.getThis(), appView.dexItemFactory());
if (parentConstructorCall == null) {
return EmptyInstanceFieldInitializationInfoCollection.getInstance();
}
DexClassAndMethod parentConstructor =
parentConstructorCall.lookupSingleTarget(appView, code.context());
if (parentConstructor == null) {
return EmptyInstanceFieldInitializationInfoCollection.getInstance();
}
InstanceFieldValueAnalysis analysis =
new InstanceFieldValueAnalysis(
appView.withLiveness(),
code,
feedback,
parentConstructor,
parentConstructorCall);
analysis.computeFieldOptimizationInfo(classInitializerDefaultsResult);
analysis.analyzeParentConstructorCall();
return analysis.builder.build();
}
@Override
boolean isInstanceFieldValueAnalysis() {
return true;
}
@Override
InstanceFieldValueAnalysis asInstanceFieldValueAnalysis() {
return this;
}
@Override
boolean isSubjectToOptimization(DexEncodedField field) {
return !field.isStatic() && field.getHolderType() == context.getHolderType();
}
@Override
void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
if (fieldNeverWrittenBetweenInstancePutAndMethodExit(field, fieldPut.asInstancePut())) {
recordInstanceFieldIsInitializedWithValue(field, value);
}
}
void analyzeForwardingConstructorCall(InvokeDirect invoke, Value thisValue) {
if (invoke.getReceiver() != thisValue
|| invoke.getInvokedMethod().getHolderType() != context.getHolderType()) {
// Not a forwarding constructor call.
return;
}
ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
if (singleTarget == null) {
// Failure, should generally not happen.
return;
}
InstanceFieldInitializationInfoCollection infos =
singleTarget
.getDefinition()
.getOptimizationInfo()
.getInstanceInitializerInfo(invoke)
.fieldInitializationInfos();
for (DexEncodedField field : singleTarget.getHolder().instanceFields()) {
assert isSubjectToOptimization(field);
InstanceFieldInitializationInfo info = infos.get(field);
if (info.isArgumentInitializationInfo()) {
int argumentIndex = info.asArgumentInitializationInfo().getArgumentIndex();
info = getInstanceFieldInitializationInfo(field, invoke.getArgument(argumentIndex));
}
recordFieldPut(field, invoke, info);
}
}
private void analyzeParentConstructorCall() {
if (parentConstructor.getHolderType() == context.getHolderType()) {
// Forwarding constructor calls are handled similar to instance-put instructions.
return;
}
InstanceFieldInitializationInfoCollection infos =
parentConstructor
.getDefinition()
.getOptimizationInfo()
.getInstanceInitializerInfo(parentConstructorCall)
.fieldInitializationInfos();
infos.forEach(
appView,
(field, info) -> {
if (fieldNeverWrittenBetweenParentConstructorCallAndMethodExit(field)) {
if (info.isArgumentInitializationInfo()) {
int argumentIndex = info.asArgumentInitializationInfo().getArgumentIndex();
recordInstanceFieldIsInitializedWithValue(
field, parentConstructorCall.getArgument(argumentIndex));
} else {
assert info.isSingleValue() || info.isTypeInitializationInfo();
builder.recordInitializationInfo(field, info);
}
}
});
}
private InstanceFieldInitializationInfo getInstanceFieldInitializationInfo(
DexEncodedField field, Value value) {
Value root = value.getAliasedValue();
if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
Argument argument = root.definition.asArgument();
return factory.createArgumentInitializationInfo(argument.getIndex());
}
AbstractValue abstractValue = value.getAbstractValue(appView, context);
if (abstractValue.isSingleValue()) {
return abstractValue.asSingleValue();
}
DexType fieldType = field.type();
if (fieldType.isClassType()) {
ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
TypeElement staticFieldType = TypeElement.fromDexType(fieldType, maybeNull(), appView);
if (dynamicLowerBoundType != null || !dynamicUpperBoundType.equals(staticFieldType)) {
return factory.createTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType);
}
}
return UnknownInstanceFieldInitializationInfo.getInstance();
}
void recordInstanceFieldIsInitializedWithInfo(
DexEncodedField field, InstanceFieldInitializationInfo info) {
if (!info.isUnknown()) {
builder.recordInitializationInfo(field, info);
}
}
void recordInstanceFieldIsInitializedWithValue(DexEncodedField field, Value value) {
recordInstanceFieldIsInitializedWithInfo(
field, getInstanceFieldInitializationInfo(field, value));
}
private boolean fieldNeverWrittenBetweenInstancePutAndMethodExit(
DexEncodedField field, InstancePut instancePut) {
if (field.isFinal()) {
return true;
}
if (appView.appInfo().isFieldOnlyWrittenInMethod(field, context.getDefinition())) {
return true;
}
if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
if (parentConstructorCall.getInvokedMethod().holder != context.getHolderType()) {
// The field is only written in instance initializers of the enclosing class, and the
// constructor call targets a constructor in the super class.
return true;
}
// The parent constructor call in this initializer targets another initializer on the same
// class (constructor forwarding), which could potentially assign this field. Therefore, we
// need to check that the instance-put instruction comes after the parent constructor call.
BasicBlock instancePutBlock = instancePut.getBlock();
BasicBlock parentConstructorCallBlock = parentConstructorCall.getBlock();
if (instancePutBlock != parentConstructorCallBlock) {
// Check that the parent constructor call dominates the instance-put instruction.
return getOrCreateDominatorTree().dominatedBy(instancePutBlock, parentConstructorCallBlock);
}
// Check that the parent constructor call comes before the instance-put instruction in the
// block.
for (Instruction instruction : instancePutBlock.getInstructions()) {
if (instruction == instancePut) {
return false;
}
if (instruction == parentConstructorCall) {
return true;
}
}
throw new Unreachable();
}
// Otherwise, conservatively return false.
return false;
}
private boolean fieldNeverWrittenBetweenParentConstructorCallAndMethodExit(
DexEncodedField field) {
if (field.isFinal()) {
return true;
}
if (appView.appInfo().isFieldOnlyWrittenInMethod(field, parentConstructor.getDefinition())) {
return true;
}
// Otherwise, conservatively return false.
return false;
}
}