| // 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.AppInfoWithClassHierarchy; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.DexTypeUtils; |
| import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens.Builder; |
| import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo; |
| import com.android.tools.r8.utils.IterableUtils; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.BiConsumer; |
| import java.util.function.Predicate; |
| |
| public interface ClassInstanceFieldsMerger { |
| |
| void setClassIdField(DexEncodedField classIdField); |
| |
| DexEncodedField[] merge(); |
| |
| static ClassInstanceFieldsMerger create( |
| AppView<?> appView, HorizontalClassMergerGraphLens.Builder lensBuilder, MergeGroup group) { |
| if (appView.hasClassHierarchy()) { |
| return new ClassInstanceFieldsMergerImpl(appView.withClassHierarchy(), lensBuilder, group); |
| } else { |
| assert group.getInstanceFieldMap().isEmpty(); |
| assert appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics(); |
| return new ClassInstanceFieldsMerger() { |
| @Override |
| public void setClassIdField(DexEncodedField classIdField) { |
| throw new UnsupportedOperationException("No instance field merging in D8"); |
| } |
| |
| @Override |
| public DexEncodedField[] merge() { |
| return DexEncodedField.EMPTY_ARRAY; |
| } |
| }; |
| } |
| } |
| |
| /** |
| * Adds all fields from {@param clazz} to the class merger. For each field, we must choose which |
| * field on the target class to merge into. |
| * |
| * <p>A field that stores a reference type can be merged into a field that stores a different |
| * reference type. To avoid that we change fields that store a reference type to have type |
| * java.lang.Object when it is not needed (e.g., class Foo has fields 'A a' and 'B b' and class |
| * Bar has fields 'A b' and 'B a'), we make a prepass that matches fields with the same reference |
| * type. |
| */ |
| static void mapFields( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| DexProgramClass source, |
| DexProgramClass target, |
| BiConsumer<DexEncodedField, DexEncodedField> consumer) { |
| Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo = |
| getAvailableFieldsByExactInfo(target); |
| List<DexEncodedField> needsMerge = new ArrayList<>(); |
| |
| // Pass 1: Match fields that have the exact same type. |
| for (DexEncodedField oldField : source.instanceFields()) { |
| InstanceFieldInfo info = InstanceFieldInfo.createExact(oldField); |
| LinkedList<DexEncodedField> availableFieldsWithExactSameInfo = |
| availableFieldsByExactInfo.get(info); |
| if (availableFieldsWithExactSameInfo == null || availableFieldsWithExactSameInfo.isEmpty()) { |
| needsMerge.add(oldField); |
| } else { |
| DexEncodedField newField = availableFieldsWithExactSameInfo.removeFirst(); |
| consumer.accept(oldField, newField); |
| if (availableFieldsWithExactSameInfo.isEmpty()) { |
| availableFieldsByExactInfo.remove(info); |
| } |
| } |
| } |
| |
| // Pass 2: Match fields that do not have the same reference type. |
| Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo = |
| getAvailableFieldsByRelaxedInfo(appView, availableFieldsByExactInfo); |
| for (DexEncodedField oldField : needsMerge) { |
| assert oldField.getType().isReferenceType(); |
| DexEncodedField newField = |
| availableFieldsByRelaxedInfo |
| .get(InstanceFieldInfo.createRelaxed(oldField, appView.dexItemFactory())) |
| .removeFirst(); |
| assert newField != null; |
| assert newField.getType().isReferenceType(); |
| consumer.accept(oldField, newField); |
| } |
| } |
| |
| static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo( |
| DexProgramClass target) { |
| Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByInfo = |
| new LinkedHashMap<>(); |
| for (DexEncodedField field : target.instanceFields()) { |
| availableFieldsByInfo |
| .computeIfAbsent(InstanceFieldInfo.createExact(field), ignore -> new LinkedList<>()) |
| .add(field); |
| } |
| return availableFieldsByInfo; |
| } |
| |
| static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByRelaxedInfo( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) { |
| Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo = |
| new LinkedHashMap<>(); |
| availableFieldsByExactInfo.forEach( |
| (info, fields) -> |
| availableFieldsByRelaxedInfo |
| .computeIfAbsent( |
| info.toInfoWithRelaxedType(appView.dexItemFactory()), |
| ignore -> new LinkedList<>()) |
| .addAll(fields)); |
| return availableFieldsByRelaxedInfo; |
| } |
| |
| class ClassInstanceFieldsMergerImpl implements ClassInstanceFieldsMerger { |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| private final MergeGroup group; |
| private final Builder lensBuilder; |
| |
| private DexEncodedField classIdField; |
| |
| private final Set<DexField> committedFields = Sets.newIdentityHashSet(); |
| |
| private ClassInstanceFieldsMergerImpl( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| HorizontalClassMergerGraphLens.Builder lensBuilder, |
| MergeGroup group) { |
| this.appView = appView; |
| this.group = group; |
| this.lensBuilder = lensBuilder; |
| } |
| |
| @Override |
| public void setClassIdField(DexEncodedField classIdField) { |
| this.classIdField = classIdField; |
| } |
| |
| @Override |
| public DexEncodedField[] merge() { |
| assert group.hasInstanceFieldMap(); |
| List<DexEncodedField> newFields = new ArrayList<>(); |
| if (classIdField != null) { |
| newFields.add(classIdField); |
| committedFields.add(classIdField.getReference()); |
| } |
| group |
| .getInstanceFieldMap() |
| .forEachManyToOneMapping( |
| (sourceFields, targetField) -> { |
| DexEncodedField newField = |
| mergeSourceFieldsToTargetField(targetField, sourceFields); |
| newFields.add(newField); |
| committedFields.add(newField.getReference()); |
| }); |
| return newFields.toArray(DexEncodedField.EMPTY_ARRAY); |
| } |
| |
| private DexEncodedField mergeSourceFieldsToTargetField( |
| DexEncodedField targetField, Set<DexEncodedField> sourceFields) { |
| fixAccessFlags(targetField, sourceFields); |
| |
| DexEncodedField newField; |
| if (needsRelaxedType(targetField, sourceFields)) { |
| DexType newFieldType = |
| DexTypeUtils.computeLeastUpperBound( |
| appView, |
| Iterables.transform( |
| Iterables.concat(IterableUtils.singleton(targetField), sourceFields), |
| DexEncodedField::getType)); |
| newField = |
| targetField.toTypeSubstitutedField( |
| appView, |
| targetField.getReference().withType(newFieldType, appView.dexItemFactory())); |
| } else { |
| newField = targetField; |
| } |
| |
| if (committedFields.contains(newField.getReference())) { |
| newField = |
| targetField.toTypeSubstitutedField( |
| appView, |
| appView |
| .dexItemFactory() |
| .createFreshFieldNameWithoutHolder( |
| newField.getHolderType(), |
| newField.getType(), |
| newField.getName().toString(), |
| Predicate.not(committedFields::contains))); |
| } |
| |
| lensBuilder.recordNewFieldSignature( |
| Iterables.transform( |
| IterableUtils.append(sourceFields, targetField), DexEncodedField::getReference), |
| newField.getReference(), |
| targetField.getReference()); |
| |
| return newField; |
| } |
| |
| private void fixAccessFlags(DexEncodedField newField, Collection<DexEncodedField> oldFields) { |
| if (newField.isSynthetic() && Iterables.any(oldFields, oldField -> !oldField.isSynthetic())) { |
| newField.getAccessFlags().demoteFromSynthetic(); |
| } |
| if (newField.isFinal() && Iterables.any(oldFields, oldField -> !oldField.isFinal())) { |
| newField.getAccessFlags().demoteFromFinal(); |
| } |
| } |
| |
| private boolean needsRelaxedType( |
| DexEncodedField targetField, Iterable<DexEncodedField> sourceFields) { |
| return Iterables.any( |
| sourceFields, sourceField -> sourceField.getType() != targetField.getType()); |
| } |
| } |
| } |