| // 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.graph.AppInfoWithClassHierarchy; |
| import com.android.tools.r8.graph.AppView; |
| 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.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.ListUtils; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.function.Consumer; |
| |
| /** |
| * 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 int numberOfUnusedArguments; |
| |
| 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 numberOfUnusedArguments, |
| Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre, |
| Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost, |
| DexMethod parentConstructor, |
| List<InstanceFieldInitializationInfo> parentConstructorArguments) { |
| this.classIdField = classIdField; |
| this.numberOfUnusedArguments = numberOfUnusedArguments; |
| this.instanceFieldAssignmentsPre = instanceFieldAssignmentsPre; |
| this.instanceFieldAssignmentsPost = instanceFieldAssignmentsPost; |
| this.parentConstructor = parentConstructor; |
| this.parentConstructorArguments = parentConstructorArguments; |
| } |
| |
| @Override |
| public void addExtraUnusedArguments(int numberOfUnusedArguments) { |
| this.numberOfUnusedArguments += numberOfUnusedArguments; |
| } |
| |
| @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 - numberOfUnusedArguments); |
| 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 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"; |
| } |
| } |