| // Copyright (c) 2021, 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.policies.deadlock; |
| |
| 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.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.UseRegistry; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.android.tools.r8.utils.collections.ProgramMethodMap; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| |
| /** |
| * Stores the single caller (if any) for each non-virtual method. Virtual methods are not considered |
| * since computing single caller information for such methods is expensive (it involves computing |
| * the possible dispatch targets for each virtual invoke). |
| * |
| * <p>Unlike the {@link com.android.tools.r8.ir.conversion.CallGraph} that is used to determine if a |
| * method can be single caller inlined, this considers a method that is called from multiple call |
| * sites in the same method to have a single caller. |
| */ |
| // TODO(b/205611444): account for -keep rules. |
| public class SingleCallerInformation { |
| |
| private final ProgramMethodMap<ProgramMethod> singleCallers; |
| private final Map<DexProgramClass, ProgramMethod> singleClinitCallers; |
| |
| SingleCallerInformation( |
| ProgramMethodMap<ProgramMethod> singleCallers, |
| Map<DexProgramClass, ProgramMethod> singleClinitCallers) { |
| this.singleCallers = singleCallers; |
| this.singleClinitCallers = singleClinitCallers; |
| } |
| |
| public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) { |
| return new Builder(appView); |
| } |
| |
| public ProgramMethod getSingleCaller(ProgramMethod method) { |
| return singleCallers.get(method); |
| } |
| |
| public ProgramMethod getSingleClassInitializerCaller(DexProgramClass clazz) { |
| return singleClinitCallers.get(clazz); |
| } |
| |
| public static class Builder { |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| |
| // The single callers for each method and class initializer. |
| // If a method is not in the map, then a call to that method has never been seen. |
| // If a method is mapped to Optional.empty(), then the method has multiple calling contexts. |
| // If a method is mapped to Optional.of(m), then the method is only called from method m. |
| final ProgramMethodMap<Optional<ProgramMethod>> callers = ProgramMethodMap.createConcurrent(); |
| final Map<DexProgramClass, Optional<ProgramMethod>> clinitCallers = new ConcurrentHashMap<>(); |
| |
| Builder(AppView<? extends AppInfoWithClassHierarchy> appView) { |
| this.appView = appView; |
| } |
| |
| public Builder analyze(ExecutorService executorService) throws ExecutionException { |
| ThreadUtils.processItems( |
| appView.appInfo()::forEachMethod, this::processMethod, executorService); |
| return this; |
| } |
| |
| public SingleCallerInformation build() { |
| ProgramMethodMap<ProgramMethod> singleCallers = ProgramMethodMap.create(); |
| callers.forEach( |
| (method, callers) -> callers.ifPresent(caller -> singleCallers.put(method, caller))); |
| Map<DexProgramClass, ProgramMethod> singleClinitCallers = new IdentityHashMap<>(); |
| clinitCallers.forEach( |
| (clazz, callers) -> callers.ifPresent(caller -> singleClinitCallers.put(clazz, caller))); |
| return new SingleCallerInformation(singleCallers, singleClinitCallers); |
| } |
| |
| private void processMethod(ProgramMethod method) { |
| method.registerCodeReferences(new InvokeExtractor(appView, method)); |
| } |
| |
| private class InvokeExtractor extends UseRegistry<ProgramMethod> { |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| |
| InvokeExtractor(AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) { |
| super(appView, context); |
| this.appView = appView; |
| } |
| |
| private void recordDispatchTarget(ProgramMethod target) { |
| callers.compute( |
| target, |
| (key, value) -> { |
| if (value == null) { |
| // This target is now called from the current context (only). |
| return Optional.of(getContext()); |
| } |
| // If the target is only called from the current context, then that is still the |
| // case. |
| if (value.orElse(null) == getContext()) { |
| return value; |
| } |
| // The target is now called from more than one place. |
| return Optional.empty(); |
| }); |
| } |
| |
| private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexType type) { |
| DexProgramClass clazz = type.asProgramClass(appView); |
| if (clazz != null) { |
| triggerClassInitializerIfNotAlreadyTriggeredInContext(clazz); |
| } |
| } |
| |
| private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexProgramClass clazz) { |
| if (!isClassAlreadyInitializedInCurrentContext(clazz)) { |
| triggerClassInitializer(clazz); |
| } |
| } |
| |
| private boolean isClassAlreadyInitializedInCurrentContext(DexProgramClass clazz) { |
| return appView.appInfo().isSubtype(getContext().getHolder(), clazz); |
| } |
| |
| private void triggerClassInitializer(DexType type) { |
| DexProgramClass clazz = type.asProgramClass(appView); |
| if (clazz != null) { |
| triggerClassInitializer(clazz); |
| } |
| } |
| |
| private void triggerClassInitializer(DexProgramClass clazz) { |
| Optional<ProgramMethod> callers = clinitCallers.get(clazz); |
| if (callers != null) { |
| if (!callers.isPresent()) { |
| // Optional.empty() represents that this class initializer has multiple (unknown) |
| // callers. Since this <clinit> and all of the parent <clinit>s are already triggered |
| // from multiple places, there is no need to record it is also triggered from the |
| // current context. |
| return; |
| } |
| if (callers.get() == getContext()) { |
| // This <clinit> is already triggered from the current context. No need to record this |
| // again. |
| return; |
| } |
| } |
| |
| // Record that the given class is now initialized from the current context. |
| clinitCallers.compute( |
| clazz, |
| (key, value) -> { |
| if (value == null) { |
| // This <clinit> was not triggered before. |
| return Optional.of(getContext()); |
| } |
| // This <clinit> was triggered from another context than the current. |
| assert value.orElse(null) != getContext(); |
| return Optional.empty(); |
| }); |
| |
| // Repeat for the parent classes. |
| triggerClassInitializer(clazz.getSuperType()); |
| } |
| |
| @Override |
| public void registerInitClass(DexType type) { |
| DexType rewrittenType = appView.graphLens().lookupType(type); |
| triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType); |
| } |
| |
| @Override |
| public void registerInstanceFieldRead(DexField field) { |
| // Intentionally empty. |
| } |
| |
| @Override |
| public void registerInstanceFieldWrite(DexField field) { |
| // Intentionally empty. |
| } |
| |
| @Override |
| public void registerInvokeDirect(DexMethod method) { |
| DexMethod rewrittenMethod = |
| appView.graphLens().lookupInvokeDirect(method, getContext()).getReference(); |
| DexProgramClass holder = rewrittenMethod.getHolderType().asProgramClass(appView); |
| ProgramMethod target = rewrittenMethod.lookupOnProgramClass(holder); |
| if (target != null) { |
| recordDispatchTarget(target); |
| } |
| } |
| |
| @Override |
| public void registerInvokeInterface(DexMethod method) { |
| // Intentionally empty, as we don't aim to collect single caller information for virtual |
| // methods. |
| } |
| |
| @Override |
| public void registerInvokeStatic(DexMethod method) { |
| DexMethod rewrittenMethod = |
| appView.graphLens().lookupInvokeDirect(method, getContext()).getReference(); |
| ProgramMethod target = |
| appView |
| .appInfo() |
| .unsafeResolveMethodDueToDexFormat(rewrittenMethod) |
| .getResolvedProgramMethod(); |
| if (target != null) { |
| recordDispatchTarget(target); |
| triggerClassInitializerIfNotAlreadyTriggeredInContext(target.getHolder()); |
| } |
| } |
| |
| @Override |
| public void registerInvokeSuper(DexMethod method) { |
| // Intentionally empty, as we don't aim to collect single caller information for virtual |
| // methods. |
| } |
| |
| @Override |
| public void registerInvokeVirtual(DexMethod method) { |
| // Intentionally empty, as we don't aim to collect single caller information for virtual |
| // methods. |
| } |
| |
| @Override |
| public void registerNewInstance(DexType type) { |
| DexType rewrittenType = appView.graphLens().lookupType(type); |
| triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType); |
| } |
| |
| @Override |
| public void registerStaticFieldRead(DexField field) { |
| DexField rewrittenField = appView.graphLens().lookupField(field); |
| triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType()); |
| } |
| |
| @Override |
| public void registerStaticFieldWrite(DexField field) { |
| DexField rewrittenField = appView.graphLens().lookupField(field); |
| triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType()); |
| } |
| |
| @Override |
| public void registerTypeReference(DexType type) { |
| // Intentionally empty. |
| } |
| } |
| } |
| } |