Reserve library method signatures during parameter removal
Change-Id: I738cfcbda4016fe3ff0f4b0cf8c34cb131aa74cb
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
index fa003ea..07102c1 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -46,12 +46,12 @@
}
public void forEachImmediateSuperClass(
- DexProgramClass clazz, BiConsumer<? super DexType, ? super DexClass> consumer) {
+ DexClass clazz, BiConsumer<? super DexType, ? super DexClass> consumer) {
forEachImmediateSuperClassMatching(clazz, (supertype, superclass) -> true, consumer);
}
public void forEachImmediateSuperClassMatching(
- DexProgramClass clazz,
+ DexClass clazz,
BiPredicate<? super DexType, ? super DexClass> predicate,
BiConsumer<? super DexType, ? super DexClass> consumer) {
clazz.forEachImmediateSupertype(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 45420b6..227851f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -22,12 +22,10 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
-import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
/** Optimization that propagates information about arguments from call sites to method entries. */
public class ArgumentPropagator {
@@ -143,8 +141,8 @@
// methods with constant parameters to methods with the constant parameters removed.
Set<DexProgramClass> affectedClasses = Sets.newConcurrentHashSet();
ArgumentPropagatorGraphLens graphLens =
- optimizeMethodParameters(
- stronglyConnectedProgramComponents, affectedClasses::add, executorService);
+ new ArgumentPropagatorProgramOptimizer(appView, immediateSubtypingInfo)
+ .run(stronglyConnectedProgramComponents, affectedClasses::add, executorService);
// Find all the code objects that need reprocessing.
new ArgumentPropagatorMethodReprocessingEnqueuer(appView)
@@ -185,25 +183,4 @@
reprocessingCriteriaCollection = null;
timing.end();
}
-
- /** Called by {@link IRConverter} to optimize method definitions. */
- private ArgumentPropagatorGraphLens optimizeMethodParameters(
- List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
- Consumer<DexProgramClass> affectedClassConsumer,
- ExecutorService executorService)
- throws ExecutionException {
- Collection<ArgumentPropagatorGraphLens.Builder> partialGraphLensBuilders =
- ThreadUtils.processItemsWithResults(
- stronglyConnectedProgramComponents,
- classes ->
- new ArgumentPropagatorProgramOptimizer(appView)
- .optimize(classes, affectedClassConsumer),
- executorService);
-
- // Merge all the partial, disjoint graph lens builders into a single graph lens.
- ArgumentPropagatorGraphLens.Builder graphLensBuilder =
- ArgumentPropagatorGraphLens.builder(appView);
- partialGraphLensBuilders.forEach(graphLensBuilder::mergeDisjoint);
- return graphLensBuilder.build();
- }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index f469ddc..fa8114f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -7,10 +7,13 @@
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
@@ -18,341 +21,428 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
public class ArgumentPropagatorProgramOptimizer {
private final AppView<AppInfoWithLiveness> appView;
+ private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
- private final DexItemFactory dexItemFactory;
+ private final Map<DexClass, DexMethodSignatureSet> libraryMethods = new ConcurrentHashMap<>();
- private final InternalOptions options;
-
- private final Map<DexMethodSignature, IntSet> removableVirtualMethodParameters = new HashMap<>();
-
- // Reserved names, i.e., mappings from pairs (old method signature, number of removed arguments)
- // to the new method signature for that method.
- private final Map<DexMethodSignature, Map<IntSet, DexMethodSignature>> newMethodSignatures =
- new HashMap<>();
-
- // Occupied method signatures (inverse of reserved names). Used to effectively check if a given
- // method signature is already reserved.
- private final Map<DexMethodSignature, Pair<IntSet, DexMethodSignature>> occupiedMethodSignatures =
- new HashMap<>();
-
- public ArgumentPropagatorProgramOptimizer(AppView<AppInfoWithLiveness> appView) {
+ public ArgumentPropagatorProgramOptimizer(
+ AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- this.options = appView.options();
+ this.immediateSubtypingInfo = immediateSubtypingInfo;
}
- // TODO(b/190154391): Remove unused parameters by simulating they are constant.
- // TODO(b/190154391): Strengthen the static type of parameters.
- // TODO(b/190154391): If we learn that a method returns a constant, then consider changing its
- // return type to void.
- // TODO(b/69963623): If we optimize a method to be unconditionally throwing (because it has a
- // bottom parameter), then for each caller that becomes unconditionally throwing, we could
- // also enqueue the caller's callers for reprocessing. This would propagate the throwing
- // information to all call sites.
- public ArgumentPropagatorGraphLens.Builder optimize(
- Set<DexProgramClass> stronglyConnectedProgramClasses,
- Consumer<DexProgramClass> affectedClassConsumer) {
- // First reserve pinned method signatures.
- reservePinnedMethodSignatures(stronglyConnectedProgramClasses);
+ public ArgumentPropagatorGraphLens run(
+ List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
+ Consumer<DexProgramClass> affectedClassConsumer,
+ ExecutorService executorService)
+ throws ExecutionException {
+ Collection<Builder> partialGraphLensBuilders =
+ ThreadUtils.processItemsWithResults(
+ stronglyConnectedProgramComponents,
+ classes ->
+ new StronglyConnectedComponentOptimizer().optimize(classes, affectedClassConsumer),
+ executorService);
- // To ensure that we preserve the overriding relationships between methods, we only remove a
- // constant or unused parameter from a virtual method when it can be removed from all other
- // virtual methods in the component with the same method signature.
- computeRemovableVirtualMethodParameters(stronglyConnectedProgramClasses);
-
- // Build a graph lens while visiting the classes in the component.
- // TODO(b/190154391): Consider visiting the interfaces first, and then processing the
- // (non-interface) classes in top-down order to reduce the amount of reserved names.
- ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder =
+ // Merge all the partial, disjoint graph lens builders into a single graph lens.
+ ArgumentPropagatorGraphLens.Builder graphLensBuilder =
ArgumentPropagatorGraphLens.builder(appView);
- for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
- if (visitClass(clazz, partialGraphLensBuilder)) {
- affectedClassConsumer.accept(clazz);
- }
- }
- return partialGraphLensBuilder;
+ partialGraphLensBuilders.forEach(graphLensBuilder::mergeDisjoint);
+ return graphLensBuilder.build();
}
- private void reservePinnedMethodSignatures(Set<DexProgramClass> stronglyConnectedProgramClasses) {
- DexMethodSignatureSet pinnedMethodSignatures = DexMethodSignatureSet.create();
- for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
- clazz.forEachProgramMethod(
- method -> {
- if (!appView.getKeepInfo(method).isShrinkingAllowed(options)) {
- pinnedMethodSignatures.add(method.getMethodSignature());
+ private DexMethodSignatureSet getOrComputeLibraryMethods(DexClass clazz) {
+ DexMethodSignatureSet libraryMethodsOnClass = libraryMethods.get(clazz);
+ if (libraryMethodsOnClass != null) {
+ return libraryMethodsOnClass;
+ }
+ return computeLibraryMethods(clazz);
+ }
+
+ private DexMethodSignatureSet computeLibraryMethods(DexClass clazz) {
+ DexMethodSignatureSet libraryMethodsOnClass = DexMethodSignatureSet.create();
+ immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+ clazz,
+ (supertype, superclass) -> superclass != null,
+ (supertype, superclass) ->
+ libraryMethodsOnClass.addAll(getOrComputeLibraryMethods(superclass)));
+ clazz.forEachClassMethodMatching(
+ DexEncodedMethod::belongsToVirtualPool,
+ method -> libraryMethodsOnClass.add(method.getMethodSignature()));
+ libraryMethods.put(clazz, libraryMethodsOnClass);
+ return libraryMethodsOnClass;
+ }
+
+ public class StronglyConnectedComponentOptimizer {
+
+ private final DexItemFactory dexItemFactory;
+ private final InternalOptions options;
+
+ private final Map<DexMethodSignature, IntSet> removableVirtualMethodParameters =
+ new HashMap<>();
+
+ // Reserved names, i.e., mappings from pairs (old method signature, number of removed arguments)
+ // to the new method signature for that method.
+ private final Map<DexMethodSignature, Map<IntSet, DexMethodSignature>> newMethodSignatures =
+ new HashMap<>();
+
+ // Occupied method signatures (inverse of reserved names). Used to effectively check if a given
+ // method signature is already reserved.
+ private final Map<DexMethodSignature, Pair<IntSet, DexMethodSignature>>
+ occupiedMethodSignatures = new HashMap<>();
+
+ public StronglyConnectedComponentOptimizer() {
+ this.dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ }
+
+ // TODO(b/190154391): Remove unused parameters by simulating they are constant.
+ // TODO(b/190154391): Strengthen the static type of parameters.
+ // TODO(b/190154391): If we learn that a method returns a constant, then consider changing its
+ // return type to void.
+ // TODO(b/69963623): If we optimize a method to be unconditionally throwing (because it has a
+ // bottom parameter), then for each caller that becomes unconditionally throwing, we could
+ // also enqueue the caller's callers for reprocessing. This would propagate the throwing
+ // information to all call sites.
+ // TODO(b/190154391): If we learn that a parameter of an instance initializer is constant, and
+ // this parameter is assigned to a field on the class, and this field does not have any other
+ // writes in the program, then we should replace all field reads by the constant value and
+ // prune
+ // the field. Alternatively, we could consider building flow constraints for field assignments,
+ // similarly to the way we deal with call chains in argument propagation. If a field is only
+ // assigned the parameter of a given method, we would add the flow constraint "parameter p ->
+ // field f".
+ public ArgumentPropagatorGraphLens.Builder optimize(
+ Set<DexProgramClass> stronglyConnectedProgramClasses,
+ Consumer<DexProgramClass> affectedClassConsumer) {
+ // First reserve pinned method signatures.
+ reservePinnedMethodSignatures(stronglyConnectedProgramClasses);
+
+ // To ensure that we preserve the overriding relationships between methods, we only remove a
+ // constant or unused parameter from a virtual method when it can be removed from all other
+ // virtual methods in the component with the same method signature.
+ computeRemovableVirtualMethodParameters(stronglyConnectedProgramClasses);
+
+ // Build a graph lens while visiting the classes in the component.
+ // TODO(b/190154391): Consider visiting the interfaces first, and then processing the
+ // (non-interface) classes in top-down order to reduce the amount of reserved names.
+ ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder =
+ ArgumentPropagatorGraphLens.builder(appView);
+ for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
+ if (visitClass(clazz, partialGraphLensBuilder)) {
+ affectedClassConsumer.accept(clazz);
+ }
+ }
+ return partialGraphLensBuilder;
+ }
+
+ private void reservePinnedMethodSignatures(
+ Set<DexProgramClass> stronglyConnectedProgramClasses) {
+ DexMethodSignatureSet pinnedMethodSignatures = DexMethodSignatureSet.create();
+ Set<DexClass> seenLibraryClasses = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
+ clazz.forEachProgramMethod(
+ method -> {
+ if (!appView.getKeepInfo(method).isShrinkingAllowed(options)) {
+ pinnedMethodSignatures.add(method.getMethodSignature());
+ }
+ });
+ immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+ clazz,
+ (supertype, superclass) ->
+ superclass != null
+ && !superclass.isProgramClass()
+ && seenLibraryClasses.add(superclass),
+ (supertype, superclass) ->
+ pinnedMethodSignatures.addAll(getOrComputeLibraryMethods(superclass)));
+ }
+ pinnedMethodSignatures.forEach(
+ signature -> reserveMethodSignature(signature, signature, IntSets.EMPTY_SET));
+ }
+
+ private void reserveMethodSignature(
+ DexMethodSignature newMethodSignature,
+ DexMethodSignature originalMethodSignature,
+ IntSet removedParameterIndices) {
+ // Record that methods with the given signature and removed parameters should be mapped to the
+ // new signature.
+ newMethodSignatures
+ .computeIfAbsent(originalMethodSignature, ignoreKey(HashMap::new))
+ .put(removedParameterIndices, newMethodSignature);
+
+ // Record that the new method signature is used, by a method with the old signature that had
+ // the
+ // given removed parameters.
+ occupiedMethodSignatures.put(
+ newMethodSignature, new Pair<>(removedParameterIndices, originalMethodSignature));
+ }
+
+ private void computeRemovableVirtualMethodParameters(
+ Set<DexProgramClass> stronglyConnectedProgramClasses) {
+ // Group the virtual methods in the component by their signatures.
+ Map<DexMethodSignature, ProgramMethodSet> virtualMethodsBySignature =
+ computeVirtualMethodsBySignature(stronglyConnectedProgramClasses);
+ virtualMethodsBySignature.forEach(
+ (signature, methods) -> {
+ // Check that there are no keep rules that prohibit parameter removal from any of the
+ // methods.
+ if (Iterables.any(methods, method -> !isParameterRemovalAllowed(method))) {
+ return;
+ }
+
+ // Find the parameters that are constant or unused in all methods.
+ IntSet removableVirtualMethodParametersInAllMethods = new IntArraySet();
+ for (int parameterIndex = 1;
+ parameterIndex < signature.getProto().getArity() + 1;
+ parameterIndex++) {
+ if (canRemoveParameterFromVirtualMethods(parameterIndex, methods)) {
+ removableVirtualMethodParametersInAllMethods.add(parameterIndex);
+ }
+ }
+
+ // If any parameters could be removed, record it.
+ if (!removableVirtualMethodParametersInAllMethods.isEmpty()) {
+ removableVirtualMethodParameters.put(
+ signature, removableVirtualMethodParametersInAllMethods);
}
});
}
- pinnedMethodSignatures.forEach(
- signature -> reserveMethodSignature(signature, signature, IntSets.EMPTY_SET));
- }
- private void reserveMethodSignature(
- DexMethodSignature newMethodSignature,
- DexMethodSignature originalMethodSignature,
- IntSet removedParameterIndices) {
- // Record that methods with the given signature and removed parameters should be mapped to the
- // new signature.
- newMethodSignatures
- .computeIfAbsent(originalMethodSignature, ignoreKey(HashMap::new))
- .put(removedParameterIndices, newMethodSignature);
+ private Map<DexMethodSignature, ProgramMethodSet> computeVirtualMethodsBySignature(
+ Set<DexProgramClass> stronglyConnectedProgramClasses) {
+ Map<DexMethodSignature, ProgramMethodSet> virtualMethodsBySignature = new HashMap<>();
+ for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
+ clazz.forEachProgramVirtualMethod(
+ method ->
+ virtualMethodsBySignature
+ .computeIfAbsent(
+ method.getMethodSignature(), ignoreKey(ProgramMethodSet::create))
+ .add(method));
+ }
+ return virtualMethodsBySignature;
+ }
- // Record that the new method signature is used, by a method with the old signature that had the
- // given removed parameters.
- occupiedMethodSignatures.put(
- newMethodSignature, new Pair<>(removedParameterIndices, originalMethodSignature));
- }
+ private boolean isParameterRemovalAllowed(ProgramMethod method) {
+ return appView.getKeepInfo(method).isParameterRemovalAllowed(options)
+ && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue();
+ }
- private void computeRemovableVirtualMethodParameters(
- Set<DexProgramClass> stronglyConnectedProgramClasses) {
- // Group the virtual methods in the component by their signatures.
- Map<DexMethodSignature, ProgramMethodSet> virtualMethodsBySignature =
- computeVirtualMethodsBySignature(stronglyConnectedProgramClasses);
- virtualMethodsBySignature.forEach(
- (signature, methods) -> {
- // Check that there are no keep rules that prohibit parameter removal from any of the
- // methods.
- if (Iterables.any(methods, method -> !isParameterRemovalAllowed(method))) {
- return;
- }
-
- // Find the parameters that are constant or unused in all methods.
- IntSet removableVirtualMethodParametersInAllMethods = new IntArraySet();
- for (int parameterIndex = 1;
- parameterIndex < signature.getProto().getArity() + 1;
- parameterIndex++) {
- if (canRemoveParameterFromVirtualMethods(parameterIndex, methods)) {
- removableVirtualMethodParametersInAllMethods.add(parameterIndex);
+ private boolean canRemoveParameterFromVirtualMethods(
+ int parameterIndex, ProgramMethodSet methods) {
+ for (ProgramMethod method : methods) {
+ if (method.getDefinition().isAbstract()) {
+ DexProgramClass holder = method.getHolder();
+ if (holder.isInterface()) {
+ ObjectAllocationInfoCollection objectAllocationInfoCollection =
+ appView.appInfo().getObjectAllocationInfoCollection();
+ if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(holder)) {
+ return false;
}
}
-
- // If any parameters could be removed, record it.
- if (!removableVirtualMethodParametersInAllMethods.isEmpty()) {
- removableVirtualMethodParameters.put(
- signature, removableVirtualMethodParametersInAllMethods);
- }
- });
- }
-
- private Map<DexMethodSignature, ProgramMethodSet> computeVirtualMethodsBySignature(
- Set<DexProgramClass> stronglyConnectedProgramClasses) {
- Map<DexMethodSignature, ProgramMethodSet> virtualMethodsBySignature = new HashMap<>();
- for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
- clazz.forEachProgramVirtualMethod(
- method ->
- virtualMethodsBySignature
- .computeIfAbsent(method.getMethodSignature(), ignoreKey(ProgramMethodSet::create))
- .add(method));
- }
- return virtualMethodsBySignature;
- }
-
- private boolean isParameterRemovalAllowed(ProgramMethod method) {
- return appView.getKeepInfo(method).isParameterRemovalAllowed(options)
- && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue();
- }
-
- private boolean canRemoveParameterFromVirtualMethods(
- int parameterIndex, ProgramMethodSet methods) {
- for (ProgramMethod method : methods) {
- if (method.getDefinition().isAbstract()) {
- DexProgramClass holder = method.getHolder();
- if (holder.isInterface()) {
- ObjectAllocationInfoCollection objectAllocationInfoCollection =
- appView.appInfo().getObjectAllocationInfoCollection();
- if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(holder)) {
- return false;
- }
- }
- // OK, this parameter can be removed.
- continue;
- }
- CallSiteOptimizationInfo optimizationInfo =
- method.getDefinition().getCallSiteOptimizationInfo();
- if (optimizationInfo.isConcreteCallSiteOptimizationInfo()) {
- ConcreteCallSiteOptimizationInfo concreteOptimizationInfo =
- optimizationInfo.asConcreteCallSiteOptimizationInfo();
- AbstractValue abstractValue =
- concreteOptimizationInfo.getAbstractArgumentValue(parameterIndex);
- if (abstractValue.isSingleValue()
- && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
- // OK, this parameter has a constant value and can be removed.
+ // OK, this parameter can be removed.
continue;
}
- }
- return false;
- }
- return true;
- }
-
- // Returns true if the class was changed as a result of argument propagation.
- private boolean visitClass(
- DexProgramClass clazz, ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder) {
- BooleanBox affected = new BooleanBox();
- clazz.forEachProgramMethod(
- method -> {
- ArgumentInfoCollection removableParameters =
- method.getDefinition().belongsToDirectPool()
- ? computeRemovableParametersFromDirectMethod(method)
- : computeRemovableParametersFromVirtualMethod(method);
- DexMethod newMethodSignature = getNewMethodSignature(method, removableParameters);
- if (newMethodSignature != method.getReference()) {
- partialGraphLensBuilder.recordMove(
- method.getReference(), newMethodSignature, removableParameters);
- affected.set();
+ CallSiteOptimizationInfo optimizationInfo =
+ method.getDefinition().getCallSiteOptimizationInfo();
+ if (optimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+ ConcreteCallSiteOptimizationInfo concreteOptimizationInfo =
+ optimizationInfo.asConcreteCallSiteOptimizationInfo();
+ AbstractValue abstractValue =
+ concreteOptimizationInfo.getAbstractArgumentValue(parameterIndex);
+ if (abstractValue.isSingleValue()
+ && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
+ // OK, this parameter has a constant value and can be removed.
+ continue;
}
- });
- return affected.get();
- }
-
- private DexMethod getNewMethodSignature(
- ProgramMethod method, ArgumentInfoCollection removableParameters) {
- DexMethodSignature methodSignatureWithoutParametersRemoved = method.getMethodSignature();
- IntSet removableParameterIndices = removableParameters.getKeys();
-
- // Check if there is a reserved signature for this already.
- DexMethodSignature reservedSignature =
- newMethodSignatures
- .getOrDefault(methodSignatureWithoutParametersRemoved, Collections.emptyMap())
- .get(removableParameterIndices);
- if (reservedSignature != null) {
- return reservedSignature.withHolder(method.getHolderType(), dexItemFactory);
+ }
+ return false;
+ }
+ return true;
}
- DexMethod methodReferenceWithParametersRemoved =
- removableParameters.rewriteMethod(method, dexItemFactory);
- DexMethodSignature methodSignatureWithParametersRemoved =
- methodReferenceWithParametersRemoved.getSignature();
+ // Returns true if the class was changed as a result of argument propagation.
+ private boolean visitClass(
+ DexProgramClass clazz, ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder) {
+ BooleanBox affected = new BooleanBox();
+ clazz.forEachProgramMethod(
+ method -> {
+ ArgumentInfoCollection removableParameters =
+ method.getDefinition().belongsToDirectPool()
+ ? computeRemovableParametersFromDirectMethod(method)
+ : computeRemovableParametersFromVirtualMethod(method);
+ DexMethod newMethodSignature = getNewMethodSignature(method, removableParameters);
+ if (newMethodSignature != method.getReference()) {
+ partialGraphLensBuilder.recordMove(
+ method.getReference(), newMethodSignature, removableParameters);
+ affected.set();
+ }
+ });
+ return affected.get();
+ }
- // Find a method signature. First check if the current signature is available.
- if (!occupiedMethodSignatures.containsKey(methodSignatureWithParametersRemoved)) {
+ private DexMethod getNewMethodSignature(
+ ProgramMethod method, ArgumentInfoCollection removableParameters) {
+ DexMethodSignature methodSignatureWithoutParametersRemoved = method.getMethodSignature();
+ IntSet removableParameterIndices = removableParameters.getKeys();
+
+ // Check if there is a reserved signature for this already.
+ DexMethodSignature reservedSignature =
+ newMethodSignatures
+ .getOrDefault(methodSignatureWithoutParametersRemoved, Collections.emptyMap())
+ .get(removableParameterIndices);
+ if (reservedSignature != null) {
+ return reservedSignature.withHolder(method.getHolderType(), dexItemFactory);
+ }
+
+ DexMethod methodReferenceWithParametersRemoved =
+ removableParameters.rewriteMethod(method, dexItemFactory);
+ DexMethodSignature methodSignatureWithParametersRemoved =
+ methodReferenceWithParametersRemoved.getSignature();
+
+ // Find a method signature. First check if the current signature is available.
+ if (!occupiedMethodSignatures.containsKey(methodSignatureWithParametersRemoved)) {
+ reserveMethodSignature(
+ methodSignatureWithParametersRemoved,
+ methodSignatureWithoutParametersRemoved,
+ removableParameterIndices);
+ return methodReferenceWithParametersRemoved;
+ }
+
+ Pair<IntSet, DexMethodSignature> occupant =
+ occupiedMethodSignatures.get(methodSignatureWithParametersRemoved);
+ // In this case we should have found a reserved method signature above.
+ assert !(occupant.getFirst().equals(removableParameterIndices)
+ && occupant.getSecond().equals(methodSignatureWithoutParametersRemoved));
+
+ // We need to find a new name for this method, since the signature is already occupied.
+ // TODO(b/190154391): Instead of generating a new name, we could also try permuting the order
+ // of
+ // parameters.
+ DexMethod newMethod =
+ dexItemFactory.createFreshMethodNameWithoutHolder(
+ method.getName().toString(),
+ methodReferenceWithParametersRemoved.getProto(),
+ method.getHolderType(),
+ candidate -> {
+ Pair<IntSet, DexMethodSignature> candidateOccupant =
+ occupiedMethodSignatures.get(candidate.getSignature());
+ if (candidateOccupant == null) {
+ return true;
+ }
+ return candidateOccupant.getFirst().equals(removableParameterIndices)
+ && candidateOccupant
+ .getSecond()
+ .equals(methodSignatureWithoutParametersRemoved);
+ });
+
+ // Reserve the newly generated method signature.
reserveMethodSignature(
- methodSignatureWithParametersRemoved,
+ newMethod.getSignature(),
methodSignatureWithoutParametersRemoved,
removableParameterIndices);
- return methodReferenceWithParametersRemoved;
+
+ return newMethod;
}
- Pair<IntSet, DexMethodSignature> occupant =
- occupiedMethodSignatures.get(methodSignatureWithParametersRemoved);
- // In this case we should have found a reserved method signature above.
- assert !(occupant.getFirst().equals(removableParameterIndices)
- && occupant.getSecond().equals(methodSignatureWithoutParametersRemoved));
-
- // We need to find a new name for this method, since the signature is already occupied.
- // TODO(b/190154391): Instead of generating a new name, we could also try permuting the order of
- // parameters.
- DexMethod newMethod =
- dexItemFactory.createFreshMethodNameWithoutHolder(
- method.getName().toString(),
- methodReferenceWithParametersRemoved.getProto(),
- method.getHolderType(),
- candidate -> {
- Pair<IntSet, DexMethodSignature> candidateOccupant =
- occupiedMethodSignatures.get(candidate.getSignature());
- if (candidateOccupant == null) {
- return true;
- }
- return candidateOccupant.getFirst().equals(removableParameterIndices)
- && candidateOccupant.getSecond().equals(methodSignatureWithoutParametersRemoved);
- });
-
- // Reserve the newly generated method signature.
- reserveMethodSignature(
- newMethod.getSignature(),
- methodSignatureWithoutParametersRemoved,
- removableParameterIndices);
-
- return newMethod;
- }
-
- private ArgumentInfoCollection computeRemovableParametersFromDirectMethod(ProgramMethod method) {
- assert method.getDefinition().belongsToDirectPool();
- if (method.getDefinition().isInstanceInitializer()) {
+ private ArgumentInfoCollection computeRemovableParametersFromDirectMethod(
+ ProgramMethod method) {
+ assert method.getDefinition().belongsToDirectPool();
// TODO(b/190154391): Allow parameter removal from initializers. We need to guarantee absence
// of collisions since initializers can't be renamed.
- return ArgumentInfoCollection.empty();
- }
- return computeRemovableParametersFromMethod(method);
- }
-
- private ArgumentInfoCollection computeRemovableParametersFromVirtualMethod(ProgramMethod method) {
- IntSet removableParameterIndices =
- removableVirtualMethodParameters.getOrDefault(
- method.getMethodSignature(), IntSets.EMPTY_SET);
- if (removableParameterIndices.isEmpty()) {
- return ArgumentInfoCollection.empty();
+ if (!appView.getKeepInfo(method).isParameterRemovalAllowed(options)
+ || method.getDefinition().isInstanceInitializer()) {
+ return ArgumentInfoCollection.empty();
+ }
+ return computeRemovableParametersFromMethod(method);
}
- if (method.getAccessFlags().isAbstract()) {
- // Treat the parameters as unused.
+ private ArgumentInfoCollection computeRemovableParametersFromVirtualMethod(
+ ProgramMethod method) {
+ IntSet removableParameterIndices =
+ removableVirtualMethodParameters.getOrDefault(
+ method.getMethodSignature(), IntSets.EMPTY_SET);
+ if (removableParameterIndices.isEmpty()) {
+ return ArgumentInfoCollection.empty();
+ }
+
+ if (method.getAccessFlags().isAbstract()) {
+ // Treat the parameters as unused.
+ ArgumentInfoCollection.Builder removableParametersBuilder =
+ ArgumentInfoCollection.builder();
+ for (int removableParameterIndex : removableParameterIndices) {
+ removableParametersBuilder.addArgumentInfo(
+ removableParameterIndex,
+ RemovedArgumentInfo.builder()
+ .setType(method.getArgumentType(removableParameterIndex))
+ .build());
+ }
+ return removableParametersBuilder.build();
+ }
+
+ ArgumentInfoCollection removableParameters =
+ computeRemovableParametersFromMethod(method, removableParameterIndices::contains);
+ assert removableParameters.size() == removableParameterIndices.size();
+ return removableParameters;
+ }
+
+ private ArgumentInfoCollection computeRemovableParametersFromMethod(ProgramMethod method) {
+ return computeRemovableParametersFromMethod(method, parameterIndex -> true);
+ }
+
+ private ArgumentInfoCollection computeRemovableParametersFromMethod(
+ ProgramMethod method, IntPredicate removableParameterIndices) {
+ ConcreteCallSiteOptimizationInfo optimizationInfo =
+ method.getDefinition().getCallSiteOptimizationInfo().asConcreteCallSiteOptimizationInfo();
+ if (optimizationInfo == null) {
+ return ArgumentInfoCollection.empty();
+ }
+
ArgumentInfoCollection.Builder removableParametersBuilder = ArgumentInfoCollection.builder();
- for (int removableParameterIndex : removableParameterIndices) {
- removableParametersBuilder.addArgumentInfo(
- removableParameterIndex,
- RemovedArgumentInfo.builder()
- .setType(method.getArgumentType(removableParameterIndex))
- .build());
+ for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
+ argumentIndex < method.getDefinition().getNumberOfArguments();
+ argumentIndex++) {
+ if (!removableParameterIndices.test(argumentIndex)) {
+ continue;
+ }
+ AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
+ if (abstractValue.isSingleValue()
+ && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
+ removableParametersBuilder.addArgumentInfo(
+ argumentIndex,
+ RemovedArgumentInfo.builder()
+ .setSingleValue(abstractValue.asSingleValue())
+ .setType(method.getArgumentType(argumentIndex))
+ .build());
+ }
}
return removableParametersBuilder.build();
}
-
- ArgumentInfoCollection removableParameters =
- computeRemovableParametersFromMethod(method, removableParameterIndices::contains);
- assert removableParameters.size() == removableParameterIndices.size();
- return removableParameters;
- }
-
- private ArgumentInfoCollection computeRemovableParametersFromMethod(ProgramMethod method) {
- return computeRemovableParametersFromMethod(method, parameterIndex -> true);
- }
-
- private ArgumentInfoCollection computeRemovableParametersFromMethod(
- ProgramMethod method, IntPredicate removableParameterIndices) {
- ConcreteCallSiteOptimizationInfo optimizationInfo =
- method.getDefinition().getCallSiteOptimizationInfo().asConcreteCallSiteOptimizationInfo();
- if (optimizationInfo == null) {
- return ArgumentInfoCollection.empty();
- }
-
- ArgumentInfoCollection.Builder removableParametersBuilder = ArgumentInfoCollection.builder();
- for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
- argumentIndex < method.getDefinition().getNumberOfArguments();
- argumentIndex++) {
- if (!removableParameterIndices.test(argumentIndex)) {
- continue;
- }
- AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
- if (abstractValue.isSingleValue()
- && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
- removableParametersBuilder.addArgumentInfo(
- argumentIndex,
- RemovedArgumentInfo.builder()
- .setSingleValue(abstractValue.asSingleValue())
- .setType(method.getArgumentType(argumentIndex))
- .build());
- }
- }
- return removableParametersBuilder.build();
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
index 301738e..68191f9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
@@ -82,10 +82,13 @@
}
ParameterReprocessingCriteria.Builder builder = ParameterReprocessingCriteria.builder();
- for (Instruction instruction : argument.outValue().aliasedUsers()) {
- // TODO(b/190154391): Introduce analysis for usefulness of abstract value and nullability.
- builder.setReprocessDueToAbstractValue().setReprocessDueToNullability();
+ // TODO(b/190154391): Introduce analysis for usefulness of abstract value and nullability.
+ if (argument.outValue().hasAnyUsers()) {
+ builder.setReprocessDueToAbstractValue().setReprocessDueToNullability();
+ }
+
+ for (Instruction instruction : argument.outValue().aliasedUsers()) {
switch (instruction.opcode()) {
case ASSUME:
case IF:
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java
new file mode 100644
index 0000000..d12d4ea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java
@@ -0,0 +1,89 @@
+// 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CollisionWithLibraryMethodAfterConstantParameterRemovalTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options ->
+ options
+ .callSiteOptimizationOptions()
+ .setEnableExperimentalArgumentPropagation(true))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ MethodSubject toStringMethodSubject =
+ aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+ assertThat(toStringMethodSubject, isPresent());
+ assertEquals(0, toStringMethodSubject.getProgramMethod().getReference().getArity());
+ assertTrue(
+ toStringMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("Hello world!")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello world!", "false");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // If we change A.toString(String) to A.toString(), then we change the semantics of calling
+ // A.toString().
+ String libString = new A().toString();
+ String greetingString = new A().toString("Hello world!");
+ System.out.println(greetingString);
+ System.out.println(libString.equals(greetingString));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @NeverInline
+ public String toString(String whichOne) {
+ return System.currentTimeMillis() > 0 ? whichOne : null;
+ }
+ }
+}