blob: 6e1cc11602966fcd9ac450720de0120df783a4d7 [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.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.
}
}
}