| // 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.optimize; |
| |
| import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts; |
| import com.android.tools.r8.graph.AppInfoWithClassHierarchy; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.FieldAccessInfoCollection; |
| import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl; |
| import com.android.tools.r8.graph.FieldAccessInfoImpl; |
| import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult; |
| import com.android.tools.r8.graph.MethodAccessInfoCollection; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; |
| import com.android.tools.r8.graph.UseRegistry; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.android.tools.r8.utils.collections.ProgramMethodSet; |
| import com.google.common.collect.Sets; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| |
| public class MemberRebindingIdentityLensFactory { |
| |
| /** |
| * In order to construct an instance of {@link MemberRebindingIdentityLens} we need a mapping from |
| * non-rebound field and method references to their definitions. |
| * |
| * <p>If shrinking or minification is enabled, we retrieve these from {@link AppInfoWithLiveness}. |
| * Otherwise we apply the {@link NonReboundMemberReferencesRegistry} below to all code objects to |
| * compute the mapping. |
| */ |
| public static MemberRebindingIdentityLens create( |
| AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService) |
| throws ExecutionException { |
| FieldAccessInfoCollection<?> fieldAccessInfoCollection; |
| MethodAccessInfoCollection methodAccessInfoCollection; |
| if (appView.appInfo().hasLiveness() |
| && appView.options().testing.alwaysUseExistingAccessInfoCollectionsInMemberRebinding) { |
| AppInfoWithLiveness appInfo = appView.appInfo().withLiveness(); |
| fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection(); |
| methodAccessInfoCollection = appInfo.getMethodAccessInfoCollection(); |
| } else { |
| FieldAccessInfoCollectionImpl mutableFieldAccessInfoCollection = |
| new FieldAccessInfoCollectionImpl(new ConcurrentHashMap<>()); |
| MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder = |
| MethodAccessInfoCollection.concurrentBuilder(); |
| initializeMemberAccessInfoCollectionsForMemberRebinding( |
| appView, |
| mutableFieldAccessInfoCollection, |
| methodAccessInfoCollectionBuilder, |
| executorService); |
| fieldAccessInfoCollection = mutableFieldAccessInfoCollection; |
| methodAccessInfoCollection = methodAccessInfoCollectionBuilder.build(); |
| } |
| return create(appView, fieldAccessInfoCollection, methodAccessInfoCollection); |
| } |
| |
| public static MemberRebindingIdentityLens create( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| FieldAccessInfoCollection<?> fieldAccessInfoCollection, |
| MethodAccessInfoCollection methodAccessInfoCollection) { |
| MemberRebindingIdentityLens.Builder builder = MemberRebindingIdentityLens.builder(appView); |
| fieldAccessInfoCollection.forEach(builder::recordNonReboundFieldAccesses); |
| methodAccessInfoCollection.forEachMethodReference(builder::recordMethodAccess); |
| return builder.build(); |
| } |
| |
| /** |
| * Applies {@link NonReboundMemberReferencesRegistry} to all code objects to construct a mapping |
| * from non-rebound field references to their definition. |
| */ |
| private static void initializeMemberAccessInfoCollectionsForMemberRebinding( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| FieldAccessInfoCollectionImpl fieldAccessInfoCollection, |
| MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder, |
| ExecutorService executorService) |
| throws ExecutionException { |
| Set<DexField> seenFieldReferences = Sets.newConcurrentHashSet(); |
| Set<DexMethod> seenMethodReferences = Sets.newConcurrentHashSet(); |
| ThreadUtils.processItems( |
| appView.appInfo()::forEachMethod, |
| method -> |
| new NonReboundMemberReferencesRegistry( |
| appView, |
| method, |
| fieldAccessInfoCollection, |
| methodAccessInfoCollectionBuilder, |
| seenFieldReferences, |
| seenMethodReferences) |
| .accept(method), |
| executorService); |
| } |
| |
| private static class NonReboundMemberReferencesRegistry extends UseRegistry { |
| |
| private final AppInfoWithClassHierarchy appInfo; |
| private final ProgramMethod context; |
| private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection; |
| private final MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder; |
| private final Set<DexField> seenFieldReferences; |
| private final Set<DexMethod> seenMethodReferences; |
| |
| public NonReboundMemberReferencesRegistry( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| ProgramMethod context, |
| FieldAccessInfoCollectionImpl fieldAccessInfoCollection, |
| MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder, |
| Set<DexField> seenFieldReferences, |
| Set<DexMethod> seenMethodReferences) { |
| super(appView.dexItemFactory()); |
| this.appInfo = appView.appInfo(); |
| this.context = context; |
| this.fieldAccessInfoCollection = fieldAccessInfoCollection; |
| this.methodAccessInfoCollectionBuilder = methodAccessInfoCollectionBuilder; |
| this.seenFieldReferences = seenFieldReferences; |
| this.seenMethodReferences = seenMethodReferences; |
| } |
| |
| @Override |
| public void registerInstanceFieldRead(DexField field) { |
| registerFieldAccess(field); |
| } |
| |
| @Override |
| public void registerInstanceFieldWrite(DexField field) { |
| registerFieldAccess(field); |
| } |
| |
| @Override |
| public void registerStaticFieldRead(DexField field) { |
| registerFieldAccess(field); |
| } |
| |
| @Override |
| public void registerStaticFieldWrite(DexField field) { |
| registerFieldAccess(field); |
| } |
| |
| private void registerFieldAccess(DexField field) { |
| if (!seenFieldReferences.add(field)) { |
| return; |
| } |
| SuccessfulFieldResolutionResult resolutionResult = |
| appInfo.resolveField(field).asSuccessfulResolution(); |
| if (resolutionResult == null) { |
| return; |
| } |
| DexField reboundReference = resolutionResult.getResolvedField().getReference(); |
| if (field == reboundReference) { |
| // For the purpose of member rebinding, we don't care about already rebound references. |
| return; |
| } |
| FieldAccessInfoImpl fieldAccessInfo = |
| fieldAccessInfoCollection.computeIfAbsent(reboundReference, FieldAccessInfoImpl::new); |
| synchronized (fieldAccessInfo) { |
| // Record the fact that there is a non-rebound access to the given field. We don't |
| // distinguish between non-rebound reads and writes, so we just record it as a read. |
| if (fieldAccessInfo.getReadsWithContexts().isBottom()) { |
| fieldAccessInfo.setReadsWithContexts(new ConcreteAccessContexts()); |
| } else { |
| assert fieldAccessInfo.getReadsWithContexts().isConcrete(); |
| } |
| // For the purpose of member rebinding, we don't care about the access contexts, so we |
| // simply use the empty set. |
| ConcreteAccessContexts accessContexts = fieldAccessInfo.getReadsWithContexts().asConcrete(); |
| accessContexts.getAccessesWithContexts().put(field, ProgramMethodSet.empty()); |
| } |
| } |
| |
| @Override |
| public void registerInvokeDirect(DexMethod method) { |
| registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getDirectInvokes()); |
| } |
| |
| @Override |
| public void registerInvokeInterface(DexMethod method) { |
| registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getInterfaceInvokes()); |
| } |
| |
| @Override |
| public void registerInvokeStatic(DexMethod method) { |
| registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getStaticInvokes()); |
| } |
| |
| @Override |
| public void registerInvokeSuper(DexMethod method) { |
| registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getSuperInvokes()); |
| } |
| |
| @Override |
| public void registerInvokeVirtual(DexMethod method) { |
| registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getVirtualInvokes()); |
| } |
| |
| private void registerInvokeMethod(DexMethod method, Map<DexMethod, ProgramMethodSet> invokes) { |
| if (!seenMethodReferences.add(method)) { |
| return; |
| } |
| if (method.getHolderType().isArrayType()) { |
| return; |
| } |
| DexClass holder = appInfo.definitionFor(method.getHolderType(), context); |
| if (holder == null) { |
| return; |
| } |
| SingleResolutionResult resolutionResult = |
| appInfo.resolveMethodOn(holder, method).asSingleResolution(); |
| if (resolutionResult == null) { |
| return; |
| } |
| DexMethod reboundReference = resolutionResult.getResolvedMethod().getReference(); |
| if (method == reboundReference) { |
| // For the purpose of member rebinding, we don't care about already rebound references. |
| return; |
| } |
| // For the purpose of member rebinding, we don't care about the access contexts, so we |
| // simply use the empty set. |
| invokes.put(method, ProgramMethodSet.empty()); |
| } |
| |
| @Override |
| public void registerInitClass(DexType type) { |
| // Intentionally empty. |
| } |
| |
| @Override |
| public void registerNewInstance(DexType type) { |
| // Intentionally empty. |
| } |
| |
| @Override |
| public void registerTypeReference(DexType type) { |
| // Intentionally empty. |
| } |
| |
| @Override |
| public void registerInstanceOf(DexType type) { |
| // Intentionally empty. |
| } |
| } |
| } |