| // 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.optimize.enums; |
| |
| import static com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl.unboxedIntToOrdinal; |
| |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| 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.graph.lens.NestedGraphLens; |
| import com.android.tools.r8.graph.proto.ArgumentInfoCollection; |
| import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; |
| import com.android.tools.r8.graph.proto.RewrittenTypeInfo; |
| import com.android.tools.r8.ir.analysis.value.AbstractValue; |
| import com.android.tools.r8.ir.analysis.value.AbstractValueFactory; |
| import com.android.tools.r8.ir.analysis.value.SingleFieldValue; |
| import com.android.tools.r8.ir.analysis.value.SingleNumberValue; |
| import com.android.tools.r8.ir.analysis.value.SingleValue; |
| import com.android.tools.r8.ir.code.InvokeType; |
| import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap; |
| import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeMap; |
| import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; |
| import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; |
| import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap; |
| import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; |
| import com.google.common.collect.ImmutableMap; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| public class EnumUnboxingLens extends NestedGraphLens { |
| |
| private final AbstractValueFactory abstractValueFactory; |
| private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod; |
| private final EnumDataMap unboxedEnums; |
| private final Set<DexMethod> dispatchMethods; |
| |
| EnumUnboxingLens( |
| AppView<?> appView, |
| BidirectionalOneToOneMap<DexField, DexField> fieldMap, |
| BidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> renamedSignatures, |
| Map<DexType, DexType> typeMap, |
| Map<DexMethod, DexMethod> methodMap, |
| Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod, |
| Set<DexMethod> dispatchMethods) { |
| super(appView, fieldMap, methodMap, typeMap, renamedSignatures); |
| assert !appView.unboxedEnums().isEmpty(); |
| this.abstractValueFactory = appView.abstractValueFactory(); |
| this.prototypeChangesPerMethod = prototypeChangesPerMethod; |
| this.unboxedEnums = appView.unboxedEnums(); |
| this.dispatchMethods = dispatchMethods; |
| } |
| |
| @Override |
| public boolean hasCustomCodeRewritings() { |
| return true; |
| } |
| |
| @Override |
| public boolean isEnumUnboxerLens() { |
| return true; |
| } |
| |
| @Override |
| public EnumUnboxingLens asEnumUnboxerLens() { |
| return this; |
| } |
| |
| @Override |
| public boolean isContextFreeForMethods(GraphLens codeLens) { |
| if (codeLens == this) { |
| return true; |
| } |
| return !unboxedEnums.hasAnyEnumsWithSubtypes() |
| && getPrevious().isContextFreeForMethods(codeLens); |
| } |
| |
| @Override |
| public boolean verifyIsContextFreeForMethod(DexMethod method, GraphLens codeLens) { |
| if (codeLens == this) { |
| return true; |
| } |
| assert getPrevious().verifyIsContextFreeForMethod(getPreviousMethodSignature(method), codeLens); |
| DexMethod previous = |
| getPrevious() |
| .lookupMethod(getPreviousMethodSignature(method), null, null, codeLens) |
| .getReference(); |
| assert unboxedEnums.representativeType(previous.getHolderType()) == previous.getHolderType(); |
| return true; |
| } |
| |
| public DexMethod lookupRefinedDispatchMethod( |
| DexMethod method, |
| DexMethod context, |
| InvokeType type, |
| GraphLens codeLens, |
| AbstractValue unboxedEnumValue, |
| DexType enumType) { |
| assert codeLens == getPrevious(); |
| DexMethod reference = lookupMethod(method, context, type, codeLens).getReference(); |
| if (!dispatchMethods.contains(reference) || !unboxedEnumValue.isSingleNumberValue()) { |
| return null; |
| } |
| // We know the exact type of enum, so there is no need to go for the dispatch method. Instead, |
| // we compute the exact target from the enum instance. |
| int unboxedEnum = unboxedEnumValue.asSingleNumberValue().getIntValue(); |
| DexType instanceType = |
| unboxedEnums |
| .get(enumType) |
| .valuesTypes |
| .getOrDefault(unboxedIntToOrdinal(unboxedEnum), enumType); |
| DexMethod specializedMethod = method.withHolder(instanceType, dexItemFactory()); |
| DexMethod superEnumMethod = method.withHolder(enumType, dexItemFactory()); |
| DexMethod refined = |
| newMethodSignatures.getRepresentativeValueOrDefault( |
| specializedMethod, newMethodSignatures.getRepresentativeValue(superEnumMethod)); |
| assert refined != null; |
| return refined; |
| } |
| |
| @Override |
| public MethodLookupResult internalDescribeLookupMethod( |
| MethodLookupResult previous, DexMethod context, GraphLens codeLens) { |
| assert context != null || verifyIsContextFreeForMethod(previous.getReference(), codeLens); |
| assert context == null || previous.getType() != null; |
| DexMethod result; |
| if (previous.getType() == InvokeType.SUPER) { |
| assert context != null; |
| DexMethod previousContext = getPreviousMethodSignature(context); |
| DexType superEnum = unboxedEnums.representativeType(previousContext.getHolderType()); |
| if (unboxedEnums.isUnboxedEnum(superEnum)) { |
| if (superEnum != previousContext.getHolderType()) { |
| DexMethod reference = previous.getReference(); |
| if (reference.getHolderType() != superEnum) { |
| // We are in an enum subtype where super-invokes are rebound differently. |
| reference = reference.withHolder(superEnum, dexItemFactory()); |
| } |
| result = newMethodSignatures.getRepresentativeValue(reference); |
| } else { |
| // This is a super-invoke to a library method, not rewritten by the lens. |
| // This is rewritten by the EnumUnboxerRewriter. |
| return previous; |
| } |
| } else { |
| result = methodMap.apply(previous.getReference()); |
| } |
| } else { |
| result = methodMap.apply(previous.getReference()); |
| } |
| if (result == null) { |
| return previous; |
| } |
| return MethodLookupResult.builder(this) |
| .setReference(result) |
| .setPrototypeChanges( |
| internalDescribePrototypeChanges(previous.getPrototypeChanges(), result)) |
| .setType(mapInvocationType(result, previous.getReference(), previous.getType())) |
| .build(); |
| } |
| |
| @Override |
| protected RewrittenPrototypeDescription internalDescribePrototypeChanges( |
| RewrittenPrototypeDescription prototypeChanges, DexMethod method) { |
| // Rewrite the single value of the given RewrittenPrototypeDescription if it is referring to an |
| // unboxed enum field. |
| if (prototypeChanges.hasRewrittenReturnInfo()) { |
| RewrittenTypeInfo rewrittenReturnInfo = prototypeChanges.getRewrittenReturnInfo(); |
| if (rewrittenReturnInfo.hasSingleValue()) { |
| SingleValue singleValue = rewrittenReturnInfo.getSingleValue(); |
| SingleValue rewrittenSingleValue = rewriteSingleValue(singleValue); |
| if (rewrittenSingleValue != singleValue) { |
| prototypeChanges = |
| prototypeChanges.withRewrittenReturnInfo( |
| RewrittenTypeInfo.builder() |
| .setCastType(rewrittenReturnInfo.getCastType()) |
| .setOldType(rewrittenReturnInfo.getOldType()) |
| .setNewType(rewrittenReturnInfo.getNewType()) |
| .setSingleValue(rewrittenSingleValue) |
| .build()); |
| } |
| } |
| } |
| |
| // During the second IR processing enum unboxing is the only optimization rewriting |
| // prototype description, if this does not hold, remove the assertion and merge |
| // the two prototype changes. |
| RewrittenPrototypeDescription enumUnboxingPrototypeChanges = |
| prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none()); |
| return prototypeChanges.combine(enumUnboxingPrototypeChanges); |
| } |
| |
| private SingleValue rewriteSingleValue(SingleValue singleValue) { |
| if (singleValue.isSingleFieldValue()) { |
| SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue(); |
| if (unboxedEnums.hasUnboxedValueFor(singleFieldValue.getField())) { |
| return abstractValueFactory.createSingleNumberValue( |
| unboxedEnums.getUnboxedValue(singleFieldValue.getField())); |
| } |
| } |
| return singleValue; |
| } |
| |
| @Override |
| protected InvokeType mapInvocationType( |
| DexMethod newMethod, DexMethod originalMethod, InvokeType type) { |
| if (typeMap.containsKey(originalMethod.getHolderType())) { |
| // Methods moved from unboxed enums to the utility class are either static or statified. |
| assert newMethod != originalMethod; |
| return InvokeType.STATIC; |
| } |
| return type; |
| } |
| |
| public static Builder enumUnboxingLensBuilder( |
| AppView<AppInfoWithLiveness> appView, EnumDataMap enumDataMap) { |
| return new Builder(appView, enumDataMap); |
| } |
| |
| static class Builder { |
| |
| private final DexItemFactory dexItemFactory; |
| private final AbstractValueFactory abstractValueFactory; |
| private final Map<DexType, DexType> typeMap = new IdentityHashMap<>(); |
| private final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures = |
| new BidirectionalOneToOneHashMap<>(); |
| private final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> |
| newMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>(); |
| private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>(); |
| |
| private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod = |
| new IdentityHashMap<>(); |
| |
| private final EnumDataMap enumDataMap; |
| |
| Builder(AppView<AppInfoWithLiveness> appView, EnumDataMap enumDataMap) { |
| this.dexItemFactory = appView.dexItemFactory(); |
| this.abstractValueFactory = appView.abstractValueFactory(); |
| this.enumDataMap = enumDataMap; |
| } |
| |
| public Builder mapUnboxedEnums(Set<DexType> enumsToUnbox) { |
| for (DexType enumToUnbox : enumsToUnbox) { |
| typeMap.put(enumToUnbox, dexItemFactory.intType); |
| } |
| return this; |
| } |
| |
| public void move(DexField from, DexField to) { |
| if (from == to) { |
| return; |
| } |
| synchronized (this) { |
| newFieldSignatures.put(from, to); |
| } |
| } |
| |
| public void moveAndMap(DexMethod from, DexMethod to, boolean fromStatic) { |
| moveAndMap(from, to, fromStatic, true, Collections.emptyList()); |
| } |
| |
| public void moveVirtual(DexMethod from, DexMethod to) { |
| RewrittenPrototypeDescription prototypeChanges = |
| computePrototypeChanges(from, to, false, true, false, Collections.emptyList()); |
| synchronized (this) { |
| newMethodSignatures.put(from, to); |
| prototypeChangesPerMethod.put(to, prototypeChanges); |
| } |
| } |
| |
| public void mapToDispatch(DexMethod from, DexMethod to) { |
| RewrittenPrototypeDescription prototypeChanges = |
| computePrototypeChanges(from, to, false, true, true, Collections.emptyList()); |
| synchronized (this) { |
| methodMap.put(from, to); |
| prototypeChangesPerMethod.put(to, prototypeChanges); |
| } |
| } |
| |
| public RewrittenPrototypeDescription moveAndMap( |
| DexMethod from, |
| DexMethod to, |
| boolean fromStatic, |
| boolean toStatic, |
| List<ExtraUnusedNullParameter> extraUnusedNullParameters) { |
| RewrittenPrototypeDescription prototypeChanges = |
| computePrototypeChanges(from, to, fromStatic, toStatic, false, extraUnusedNullParameters); |
| synchronized (this) { |
| newMethodSignatures.put(from, to); |
| methodMap.put(from, to); |
| prototypeChangesPerMethod.put(to, prototypeChanges); |
| } |
| return prototypeChanges; |
| } |
| |
| private RewrittenPrototypeDescription computePrototypeChanges( |
| DexMethod from, |
| DexMethod to, |
| boolean fromStatic, |
| boolean toStatic, |
| boolean virtualReceiverAlreadyRemapped, |
| List<ExtraUnusedNullParameter> extraUnusedNullParameters) { |
| assert from != to; |
| int offsetDiff = 0; |
| int toOffset = BooleanUtils.intValue(!toStatic); |
| ArgumentInfoCollection.Builder builder = |
| ArgumentInfoCollection.builder() |
| .setArgumentInfosSize(from.getNumberOfArguments(fromStatic)); |
| if (fromStatic != toStatic) { |
| assert toStatic; |
| offsetDiff = 1; |
| if (!virtualReceiverAlreadyRemapped) { |
| RewrittenTypeInfo.Builder typeInfoBuilder = |
| RewrittenTypeInfo.builder() |
| .setOldType(from.getHolderType()) |
| .setNewType(to.getParameter(0)); |
| SingleNumberValue singleValue = |
| enumDataMap.getSingleNumberValueFromEnumType( |
| abstractValueFactory, from.getHolderType()); |
| if (singleValue != null) { |
| typeInfoBuilder.setSingleValue(singleValue); |
| } |
| builder.addArgumentInfo(0, typeInfoBuilder.build()).setIsConvertedToStaticMethod(); |
| } else { |
| assert to.getParameter(0).isIntType(); |
| assert !fromStatic; |
| assert toStatic; |
| assert from.getArity() == to.getArity() - 1; |
| } |
| } |
| for (int i = 0; i < from.getParameters().size(); i++) { |
| DexType fromType = from.getParameter(i); |
| DexType toType = to.getParameter(i + offsetDiff); |
| if (fromType != toType) { |
| builder.addArgumentInfo( |
| i + offsetDiff + toOffset, |
| RewrittenTypeInfo.builder().setOldType(fromType).setNewType(toType).build()); |
| } |
| } |
| RewrittenTypeInfo returnInfo = |
| from.getReturnType() == to.getReturnType() |
| ? null |
| : RewrittenTypeInfo.builder() |
| .setOldType(from.getReturnType()) |
| .setNewType(to.getReturnType()) |
| .build(); |
| return RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build()) |
| .withExtraParameters(extraUnusedNullParameters); |
| } |
| |
| void recordCheckNotZeroMethod( |
| ProgramMethod checkNotNullMethod, ProgramMethod checkNotZeroMethod) { |
| DexMethod originalCheckNotNullMethodSignature = |
| newMethodSignatures.getKeyOrDefault( |
| checkNotNullMethod.getReference(), checkNotNullMethod.getReference()); |
| newMethodSignatures.put( |
| originalCheckNotNullMethodSignature, checkNotNullMethod.getReference()); |
| newMethodSignatures.put( |
| originalCheckNotNullMethodSignature, checkNotZeroMethod.getReference()); |
| newMethodSignatures.setRepresentative( |
| originalCheckNotNullMethodSignature, checkNotNullMethod.getReference()); |
| } |
| |
| public EnumUnboxingLens build(AppView<?> appView, Set<DexMethod> dispatchMethods) { |
| assert !typeMap.isEmpty(); |
| return new EnumUnboxingLens( |
| appView, |
| newFieldSignatures, |
| newMethodSignatures, |
| typeMap, |
| methodMap, |
| ImmutableMap.copyOf(prototypeChangesPerMethod), |
| dispatchMethods); |
| } |
| } |
| } |