Correctly deal with method overrides in the Enum unboxer
Fixes: b/171784168
Fixes: b/191617665
Change-Id: Id1b775be6cb5281dec9ae9cd52002ec49c910a64
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index f49fcbd..200b351 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -32,6 +32,10 @@
this.removedMethods = removedMethods;
}
+ public static Builder concurrentBuilder() {
+ return new ConcurrentBuilder();
+ }
+
public static Builder builder() {
return new Builder();
}
@@ -111,15 +115,22 @@
private DexApplication prunedApp;
- private final Set<DexReference> additionalPinnedItems = Sets.newIdentityHashSet();
- private final Set<DexType> noLongerSyntheticItems = Sets.newIdentityHashSet();
- private Set<DexType> removedClasses = Sets.newIdentityHashSet();
- private final Set<DexField> removedFields = Sets.newIdentityHashSet();
- private Set<DexMethod> removedMethods = Sets.newIdentityHashSet();
+ private final Set<DexReference> additionalPinnedItems;
+ private final Set<DexType> noLongerSyntheticItems;
+ private Set<DexType> removedClasses;
+ private final Set<DexField> removedFields;
+ private Set<DexMethod> removedMethods;
- Builder() {}
+ Builder() {
+ additionalPinnedItems = newEmptySet();
+ noLongerSyntheticItems = newEmptySet();
+ removedClasses = newEmptySet();
+ removedFields = newEmptySet();
+ removedMethods = newEmptySet();
+ }
Builder(PrunedItems prunedItems) {
+ this();
additionalPinnedItems.addAll(prunedItems.getAdditionalPinnedItems());
noLongerSyntheticItems.addAll(prunedItems.getNoLongerSyntheticItems());
prunedApp = prunedItems.getPrunedApp();
@@ -128,6 +139,10 @@
removedMethods.addAll(prunedItems.getRemovedMethods());
}
+ <T> Set<T> newEmptySet() {
+ return Sets.newIdentityHashSet();
+ }
+
public Builder setPrunedApp(DexApplication prunedApp) {
this.prunedApp = prunedApp;
return this;
@@ -196,4 +211,22 @@
removedMethods);
}
}
+
+ public static class ConcurrentBuilder extends Builder {
+
+ @Override
+ <T> Set<T> newEmptySet() {
+ return Sets.newConcurrentHashSet();
+ }
+
+ @Override
+ public Builder setRemovedClasses(Set<DexType> removedClasses) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Builder setRemovedMethods(Set<DexMethod> removedMethods) {
+ throw new UnsupportedOperationException();
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java
new file mode 100644
index 0000000..6afd404
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java
@@ -0,0 +1,224 @@
+// Copyright (c) 2023, 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.graph.fixup;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+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;
+
+public class ConcurrentMethodFixup {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final Map<ClasspathOrLibraryClass, DexMethodSignatureSet> nonProgramVirtualMethods =
+ new ConcurrentHashMap<>();
+ private final ProgramClassFixer programClassFixer;
+
+ public ConcurrentMethodFixup(
+ AppView<AppInfoWithLiveness> appView, ProgramClassFixer programClassFixer) {
+ this.appView = appView;
+ this.programClassFixer = programClassFixer;
+ }
+
+ public void fixupClassesConcurrentlyByConnectedProgramComponents(
+ Timing timing, ExecutorService executorService) throws ExecutionException {
+ timing.begin("Concurrent method fixup");
+ timing.begin("Compute strongly connected components");
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+ ImmediateProgramSubtypingInfo.create(appView);
+ List<Set<DexProgramClass>> connectedComponents =
+ new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
+ .computeStronglyConnectedComponents();
+ timing.end();
+
+ timing.begin("Process strongly connected components");
+ ThreadUtils.processItems(
+ connectedComponents, this::processConnectedProgramComponents, executorService);
+ timing.end();
+ timing.end();
+ }
+
+ public interface ProgramClassFixer {
+ // When a class is fixed-up, it is guaranteed that its supertype and interfaces were processed
+ // before. In addition, all interfaces are processed before any class is processed.
+ void fixupProgramClass(DexProgramClass clazz, MethodNamingUtility namingUtility);
+ }
+
+ private void processConnectedProgramComponents(Set<DexProgramClass> classes) {
+ List<DexProgramClass> sorted = new ArrayList<>(classes);
+ sorted.sort(Comparator.comparing(DexClass::getType));
+ BiMap<DexMethodSignature, DexMethodSignature> componentSignatures = HashBiMap.create();
+
+ // 1) Reserve all library overrides and pinned virtual methods.
+ reserveComponentPinnedAndInterfaceMethodSignatures(sorted, componentSignatures);
+
+ // 2) Map all interfaces top-down updating the componentSignatures.
+ Set<DexProgramClass> processedInterfaces = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : sorted) {
+ if (clazz.isInterface()) {
+ processInterface(clazz, processedInterfaces, componentSignatures);
+ }
+ }
+
+ // 3) Map all classes top-down propagating the inherited signatures.
+ // The componentSignatures are already fully computed and should not be updated anymore.
+ BiMap<DexMethodSignature, DexMethodSignature> immutableComponentSignaturesForTesting =
+ InternalOptions.assertionsEnabled()
+ ? ImmutableBiMap.copyOf(componentSignatures)
+ : componentSignatures;
+ Map<DexProgramClass, BiMap<DexMethodSignature, DexMethodSignature>> processedClasses =
+ new IdentityHashMap<>();
+ for (DexProgramClass clazz : sorted) {
+ if (!clazz.isInterface()) {
+ processClass(clazz, processedClasses, immutableComponentSignaturesForTesting);
+ }
+ }
+ }
+
+ private void processClass(
+ DexProgramClass clazz,
+ Map<DexProgramClass, BiMap<DexMethodSignature, DexMethodSignature>> processedClasses,
+ BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+ assert !clazz.isInterface();
+ if (processedClasses.containsKey(clazz)) {
+ return;
+ }
+ // We need to process first the super-type for the top-down propagation of inherited signatures.
+ DexClass superClass = appView.definitionFor(clazz.superType);
+ BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures;
+ if (superClass == null || !superClass.isProgramClass()) {
+ inheritedSignatures = HashBiMap.create(componentSignatures);
+ } else {
+ DexProgramClass superProgramClass = superClass.asProgramClass();
+ processClass(superProgramClass, processedClasses, componentSignatures);
+ inheritedSignatures = HashBiMap.create(processedClasses.get(superProgramClass));
+ }
+ processedClasses.put(clazz, inheritedSignatures);
+ MethodNamingUtility utility = createMethodNamingUtility(inheritedSignatures, clazz);
+ programClassFixer.fixupProgramClass(clazz, utility);
+ }
+
+ private void processInterface(
+ DexProgramClass clazz,
+ Set<DexProgramClass> processedInterfaces,
+ BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+ assert clazz.isInterface();
+ if (!processedInterfaces.add(clazz)) {
+ return;
+ }
+ // We need to process first all super-interfaces to avoid generating collisions by renaming
+ // private or static methods into inherited virtual method signatures.
+ for (DexType superInterface : clazz.getInterfaces()) {
+ DexProgramClass superInterfaceClass =
+ asProgramClassOrNull(appView.definitionFor(superInterface));
+ if (superInterfaceClass != null) {
+ processInterface(superInterfaceClass, processedInterfaces, componentSignatures);
+ }
+ }
+ MethodNamingUtility utility = createMethodNamingUtility(componentSignatures, clazz);
+ programClassFixer.fixupProgramClass(clazz, utility);
+ }
+
+ private MethodNamingUtility createMethodNamingUtility(
+ BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures, DexProgramClass clazz) {
+ BiMap<DexMethod, DexMethod> localSignatures = HashBiMap.create();
+ clazz.forEachProgramInstanceInitializer(
+ method -> {
+ KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+ if (!keepInfo.isOptimizationAllowed(appView.options())
+ || !keepInfo.isShrinkingAllowed(appView.options())) {
+ localSignatures.put(method.getReference(), method.getReference());
+ }
+ });
+ return new MethodNamingUtility(appView.dexItemFactory(), inheritedSignatures, localSignatures);
+ }
+
+ private void reserveComponentPinnedAndInterfaceMethodSignatures(
+ List<DexProgramClass> stronglyConnectedProgramClasses,
+ BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+ Set<ClasspathOrLibraryClass> seenNonProgramClasses = Sets.newIdentityHashSet();
+ for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
+ // If a private or static method is pinned, we need to reserve the mapping to avoid creating
+ // a collision with a changed virtual method.
+ clazz.forEachProgramMethodMatching(
+ m -> !m.isInstanceInitializer(),
+ method -> {
+ KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+ if (!keepInfo.isOptimizationAllowed(appView.options())
+ || !keepInfo.isShrinkingAllowed(appView.options())) {
+ componentSignatures.put(method.getMethodSignature(), method.getMethodSignature());
+ }
+ });
+ clazz.forEachImmediateSupertype(
+ supertype -> {
+ DexClass superclass = appView.definitionFor(supertype);
+ if (superclass != null
+ && !superclass.isProgramClass()
+ && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass())) {
+ for (DexMethodSignature vMethod :
+ getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass())) {
+ componentSignatures.put(vMethod, vMethod);
+ }
+ }
+ });
+ }
+ }
+
+ private DexMethodSignatureSet getOrComputeNonProgramVirtualMethods(
+ ClasspathOrLibraryClass clazz) {
+ DexMethodSignatureSet libraryMethodsOnClass = nonProgramVirtualMethods.get(clazz);
+ if (libraryMethodsOnClass != null) {
+ return libraryMethodsOnClass;
+ }
+ return computeNonProgramVirtualMethods(clazz);
+ }
+
+ private DexMethodSignatureSet computeNonProgramVirtualMethods(
+ ClasspathOrLibraryClass classpathOrLibraryClass) {
+ DexClass clazz = classpathOrLibraryClass.asDexClass();
+ DexMethodSignatureSet libraryMethodsOnClass = DexMethodSignatureSet.create();
+ clazz.forEachImmediateSupertype(
+ supertype -> {
+ DexClass superclass = appView.definitionFor(supertype);
+ if (superclass != null) {
+ assert !superclass.isProgramClass();
+ libraryMethodsOnClass.addAll(
+ getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass()));
+ }
+ });
+ clazz.forEachClassMethodMatching(
+ DexEncodedMethod::belongsToVirtualPool,
+ method -> libraryMethodsOnClass.add(method.getMethodSignature()));
+ nonProgramVirtualMethods.put(classpathOrLibraryClass, libraryMethodsOnClass);
+ return libraryMethodsOnClass;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java
new file mode 100644
index 0000000..977e01c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2023, 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.graph.fixup;
+
+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.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.BiMap;
+import java.util.function.BiConsumer;
+
+public class MethodNamingUtility {
+
+ private final DexItemFactory factory;
+ private final BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures;
+ private final BiMap<DexMethod, DexMethod> localSignatures;
+
+ public MethodNamingUtility(
+ DexItemFactory factory,
+ BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures,
+ BiMap<DexMethod, DexMethod> localSignatures) {
+ this.factory = factory;
+ this.inheritedSignatures = inheritedSignatures;
+ this.localSignatures = localSignatures;
+ }
+
+ public DexMethod nextUniqueMethod(
+ DexEncodedMethod method, DexProto newProto, DexType initExtraType) {
+ DexMethod reference = method.getReference();
+
+ if (method.isClassInitializer()) {
+ assert reference.getProto() == newProto;
+ return reference;
+ }
+
+ if (method.isInstanceInitializer()) {
+ assert initExtraType != null;
+ return nextUniqueInitializer(reference, newProto, initExtraType);
+ }
+
+ if (method.isNonPrivateVirtualMethod()) {
+ return nextUniqueVirtualMethod(reference, newProto);
+ }
+
+ return nextUniquePrivateOrStaticMethod(reference, newProto);
+ }
+
+ private DexMethod nextUniqueInitializer(
+ DexMethod reference, DexProto newProto, DexType initExtraType) {
+ assert !inheritedSignatures.containsKey(reference.getSignature());
+
+ // 1) We check if the reference has already been reserved (pinning).
+ DexMethod remapped = localSignatures.get(reference);
+ if (remapped != null) {
+ assert remapped.getProto() == newProto;
+ return remapped.withHolder(reference.getHolderType(), factory);
+ }
+
+ // 2) We check for collision with already mapped methods.
+ DexMethod newMethod = reference.withProto(newProto, factory);
+ if (localSignatures.containsValue(newMethod)) {
+ // This collides with something that has been renamed into this.
+ newMethod =
+ factory.createInstanceInitializerWithFreshProto(
+ newMethod, initExtraType, tryMethod -> !localSignatures.containsValue(tryMethod));
+ }
+
+ // 3) Finally register the new method and return it.
+ assert !localSignatures.containsValue(newMethod);
+ localSignatures.put(reference, newMethod);
+ return newMethod;
+ }
+
+ private DexMethod nextUniquePrivateOrStaticMethod(DexMethod reference, DexProto newProto) {
+ return nextUniqueMethod(reference, newProto, localSignatures::put);
+ }
+
+ private DexMethod nextUniqueVirtualMethod(DexMethod reference, DexProto newProto) {
+ return nextUniqueMethod(
+ reference,
+ newProto,
+ (from, to) -> inheritedSignatures.put(from.getSignature(), to.getSignature()));
+ }
+
+ private boolean anyCollision(DexMethod method) {
+ return localSignatures.containsValue(method)
+ || inheritedSignatures.containsValue(method.getSignature());
+ }
+
+ private DexMethod nextUniqueMethod(
+ DexMethod reference, DexProto newProto, BiConsumer<DexMethod, DexMethod> registration) {
+ // 1) We check if the reference has already been reserved (pinning or override).
+ DexMethodSignature remappedSignature = inheritedSignatures.get(reference.getSignature());
+ if (remappedSignature != null) {
+ assert remappedSignature.getProto() == newProto;
+ return remappedSignature.withHolder(reference.getHolderType(), factory);
+ }
+
+ // 2) We check for collision with already mapped methods.
+ DexMethod newMethod = reference.withProto(newProto, factory);
+ if (anyCollision(newMethod)) {
+ newMethod =
+ factory.createFreshMethodNameWithoutHolder(
+ newMethod.getName().toString(),
+ newMethod.getProto(),
+ newMethod.getHolderType(),
+ tryMethod -> !anyCollision(tryMethod));
+ }
+
+ // 3) Finally register the new method and return it.
+ assert !anyCollision(newMethod);
+ registration.accept(reference, newMethod);
+ return newMethod;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
similarity index 93%
rename from src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
rename to src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
index f3cc3e9..bf5e3de 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
@@ -2,8 +2,26 @@
// 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.graph;
+package com.android.tools.r8.graph.fixup;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
+import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.DescriptorUtils;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index a12baa2..482b43f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -18,7 +18,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index da8ac44..3aaff49 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -201,7 +201,7 @@
newMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
- private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
+ private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
new IdentityHashMap<>();
Builder(AppView<AppInfoWithLiveness> appView) {
@@ -219,41 +219,31 @@
if (from == to) {
return;
}
- newFieldSignatures.put(from, to);
- }
-
- private RewrittenPrototypeDescription recordPrototypeChanges(
- DexMethod from,
- DexMethod to,
- boolean fromStatic,
- boolean toStatic,
- boolean virtualReceiverAlreadyRemapped,
- List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
- assert from != to;
- RewrittenPrototypeDescription prototypeChanges =
- computePrototypeChanges(
- from,
- to,
- fromStatic,
- toStatic,
- virtualReceiverAlreadyRemapped,
- extraUnusedNullParameters);
- prototypeChangesPerMethod.put(to, prototypeChanges);
- return prototypeChanges;
+ synchronized (this) {
+ newFieldSignatures.put(from, to);
+ }
}
public void moveAndMap(DexMethod from, DexMethod to, boolean fromStatic) {
moveAndMap(from, to, fromStatic, true, Collections.emptyList());
}
- public RewrittenPrototypeDescription moveVirtual(DexMethod from, DexMethod to) {
- newMethodSignatures.put(from, to);
- return recordPrototypeChanges(from, to, false, true, false, 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 RewrittenPrototypeDescription mapToDispatch(DexMethod from, DexMethod to) {
- methodMap.put(from, to);
- return recordPrototypeChanges(from, to, false, true, true, Collections.emptyList());
+ 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(
@@ -262,10 +252,14 @@
boolean fromStatic,
boolean toStatic,
List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
- newMethodSignatures.put(from, to);
- methodMap.put(from, to);
- return recordPrototypeChanges(
- from, to, fromStatic, toStatic, false, 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(
@@ -275,6 +269,7 @@
boolean toStatic,
boolean virtualReceiverAlreadyRemapped,
List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
+ assert from != to;
int offsetDiff = 0;
int toOffset = BooleanUtils.intValue(!toStatic);
ArgumentInfoCollection.Builder builder =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 8d04a32..e0d0b36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedField.Builder;
@@ -26,6 +25,9 @@
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup.ProgramClassFixer;
+import com.android.tools.r8.graph.fixup.MethodNamingUtility;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -81,7 +83,7 @@
import java.util.concurrent.ExecutorService;
import java.util.function.Predicate;
-class EnumUnboxingTreeFixer {
+class EnumUnboxingTreeFixer implements ProgramClassFixer {
private final EnumUnboxingLens.Builder lensBuilder;
private final AppView<AppInfoWithLiveness> appView;
@@ -94,7 +96,9 @@
// we duplicate that here as DexProgramClasses.
private final Map<DexProgramClass, Set<DexProgramClass>> unboxedEnumHierarchy;
private final EnumUnboxingUtilityClasses utilityClasses;
- private final ProgramMethodMap<CfCodeWithLens> dispatchMethods = ProgramMethodMap.create();
+ private final ProgramMethodMap<CfCodeWithLens> dispatchMethods =
+ ProgramMethodMap.createConcurrent();
+ private final PrunedItems.Builder prunedItemsBuilder;
EnumUnboxingTreeFixer(
AppView<AppInfoWithLiveness> appView,
@@ -110,6 +114,7 @@
this.lensBuilder =
EnumUnboxingLens.enumUnboxingLensBuilder(appView).mapUnboxedEnums(getUnboxedEnums());
this.utilityClasses = utilityClasses;
+ this.prunedItemsBuilder = PrunedItems.concurrentBuilder();
}
private Set<DexProgramClass> computeUnboxedEnumClasses() {
@@ -128,39 +133,13 @@
Result fixupTypeReferences(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
- PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder();
// We do this before so that we can still perform lookup of definitions.
fixupSuperEnumClassInitializers(converter, executorService);
// Fix all methods and fields using enums to unbox.
- // TODO(b/191617665): Parallelize this fixup.
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (enumDataMap.isSuperUnboxedEnum(clazz.getType())) {
-
- // Clear the initializers and move the other methods to the new location.
- LocalEnumUnboxingUtilityClass localUtilityClass =
- utilityClasses.getLocalUtilityClass(clazz);
- Collection<DexEncodedField> localUtilityFields =
- createLocalUtilityFields(clazz, localUtilityClass, prunedItemsBuilder);
- Collection<DexEncodedMethod> localUtilityMethods =
- createLocalUtilityMethods(
- clazz, unboxedEnumHierarchy.get(clazz), localUtilityClass, prunedItemsBuilder);
-
- // Cleanup old classes.
- cleanUpOldClass(clazz);
- for (DexProgramClass subEnum : unboxedEnumHierarchy.get(clazz)) {
- cleanUpOldClass(subEnum);
- }
-
- // Update members on the local utility class.
- localUtilityClass.getDefinition().setDirectMethods(localUtilityMethods);
- localUtilityClass.getDefinition().setStaticFields(localUtilityFields);
- } else if (!enumDataMap.isUnboxedEnum(clazz.getType())) {
- clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
- clazz.getFieldCollection().replaceFields(this::fixupEncodedField);
- }
- }
+ new ConcurrentMethodFixup(appView, this)
+ .fixupClassesConcurrentlyByConnectedProgramComponents(Timing.empty(), executorService);
// Install the new graph lens before processing any checkNotZero() methods.
EnumUnboxingLens lens = lensBuilder.build(appView);
@@ -191,6 +170,29 @@
clazz.getMethodCollection().clearVirtualMethods();
}
+ @Override
+ public void fixupProgramClass(DexProgramClass clazz, MethodNamingUtility utility) {
+ if (enumDataMap.isSuperUnboxedEnum(clazz.getType())) {
+ // Clear the initializers and move the other methods to the new location.
+ LocalEnumUnboxingUtilityClass localUtilityClass = utilityClasses.getLocalUtilityClass(clazz);
+ Collection<DexEncodedField> localUtilityFields =
+ createLocalUtilityFields(clazz, localUtilityClass);
+ Collection<DexEncodedMethod> localUtilityMethods =
+ createLocalUtilityMethods(clazz, unboxedEnumHierarchy.get(clazz), localUtilityClass);
+ // Cleanup old classes.
+ cleanUpOldClass(clazz);
+ for (DexProgramClass subEnum : unboxedEnumHierarchy.get(clazz)) {
+ cleanUpOldClass(subEnum);
+ }
+ // Update members on the local utility class.
+ localUtilityClass.getDefinition().setDirectMethods(localUtilityMethods);
+ localUtilityClass.getDefinition().setStaticFields(localUtilityFields);
+ } else if (!enumDataMap.isUnboxedEnum(clazz.getType())) {
+ clazz.getMethodCollection().replaceMethods(m -> fixupEncodedMethod(m, utility));
+ clazz.getFieldCollection().replaceFields(this::fixupEncodedField);
+ }
+ }
+
private BiMap<DexMethod, DexMethod> duplicateCheckNotNullMethods(
IRConverter converter, ExecutorService executorService) throws ExecutionException {
BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = HashBiMap.create();
@@ -473,9 +475,7 @@
}
private Collection<DexEncodedField> createLocalUtilityFields(
- DexProgramClass unboxedEnum,
- LocalEnumUnboxingUtilityClass localUtilityClass,
- PrunedItems.Builder prunedItemsBuilder) {
+ DexProgramClass unboxedEnum, LocalEnumUnboxingUtilityClass localUtilityClass) {
EnumData enumData = enumDataMap.get(unboxedEnum);
Map<DexField, DexEncodedField> localUtilityFields =
new LinkedHashMap<>(unboxedEnum.staticFields().size());
@@ -532,7 +532,6 @@
private void processMethod(
ProgramMethod method,
- PrunedItems.Builder prunedItemsBuilder,
DexMethodSignatureSet nonPrivateVirtualMethods,
LocalEnumUnboxingUtilityClass localUtilityClass,
Map<DexMethod, DexEncodedMethod> localUtilityMethods) {
@@ -552,8 +551,7 @@
private Collection<DexEncodedMethod> createLocalUtilityMethods(
DexProgramClass unboxedEnum,
Set<DexProgramClass> subEnums,
- LocalEnumUnboxingUtilityClass localUtilityClass,
- PrunedItems.Builder prunedItemsBuilder) {
+ LocalEnumUnboxingUtilityClass localUtilityClass) {
Map<DexMethod, DexEncodedMethod> localUtilityMethods =
new LinkedHashMap<>(
localUtilityClass.getDefinition().getMethodCollection().size()
@@ -568,7 +566,6 @@
method ->
processMethod(
method,
- prunedItemsBuilder,
nonPrivateVirtualMethods,
localUtilityClass,
localUtilityMethods));
@@ -578,7 +575,6 @@
method ->
processMethod(
method,
- prunedItemsBuilder,
nonPrivateVirtualMethods,
localUtilityClass,
localUtilityMethods));
@@ -804,20 +800,22 @@
&& !field.getDefinition().getOptimizationInfo().isDead());
}
- private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
+ private DexEncodedMethod fixupEncodedMethod(
+ DexEncodedMethod method, MethodNamingUtility utility) {
DexProto oldProto = method.getProto();
DexProto newProto = fixupProto(oldProto);
- if (newProto == method.getProto()) {
+ // Even if the protos are identical, we may generate collisions and decide to rename the
+ // unchanged method. In most cases this is a no-op if the proto are identical.
+ DexMethod newMethod =
+ utility.nextUniqueMethod(
+ method, newProto, utilityClasses.getSharedUtilityClass().getType());
+ if (newMethod == method.getReference()) {
return method;
}
assert !method.isClassInitializer();
assert !method.isLibraryMethodOverride().isTrue()
: "Enum unboxing is changing the signature of a library override in a non unboxed class.";
- // We add the $enumunboxing$ suffix to make sure we do not create a library override.
- String newMethodName =
- method.getName().toString() + (method.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
- DexMethod newMethod = factory.createMethod(method.getHolderType(), newProto, newMethodName);
- newMethod = ensureUniqueMethod(method, newMethod);
+
List<ExtraUnusedNullParameter> extraUnusedNullParameters =
ExtraUnusedNullParameter.computeExtraUnusedNullParameters(method.getReference(), newMethod);
boolean isStatic = method.isStatic();
@@ -835,27 +833,6 @@
method.isNonPrivateVirtualMethod(), OptionalBool.FALSE));
}
- private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
- DexClass holder = appView.definitionFor(encodedMethod.getHolderType());
- assert holder != null;
- if (newMethod.isInstanceInitializer(appView.dexItemFactory())) {
- newMethod =
- factory.createInstanceInitializerWithFreshProto(
- newMethod,
- utilityClasses.getSharedUtilityClass().getType(),
- tryMethod -> holder.lookupMethod(tryMethod) == null);
- } else {
- int index = 0;
- while (holder.lookupMethod(newMethod) != null) {
- newMethod =
- newMethod.withName(
- encodedMethod.getName().toString() + "$enumunboxing$" + index++,
- appView.dexItemFactory());
- }
- }
- return newMethod;
- }
-
private DexEncodedField fixupEncodedField(DexEncodedField encodedField) {
DexField field = encodedField.getReference();
DexType newType = fixupType(field.type);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index eff4f92..1f5b5ff 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -18,7 +18,7 @@
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodCollection;
import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index cffb1cf..ff4fc92 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -20,7 +20,7 @@
import com.android.tools.r8.graph.ProgramPackage;
import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.graph.SortedProgramPackageCollection;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
import com.android.tools.r8.graph.lens.NestedGraphLens;
import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
import com.android.tools.r8.repackaging.RepackagingLens.Builder;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 9b437c1..150e7c8 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -56,10 +56,10 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.graph.TreeFixerBase;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.UseRegistryWithResult;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
import com.android.tools.r8.graph.lens.FieldLookupResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 68c2918..c955fad 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -20,7 +20,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.NestedGraphLens;
import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 81578f8..604695c 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -168,7 +168,7 @@
appView.graphLens().getRenamedMethodSignature(method, appliedGraphLens);
DexProgramClass holder = appView.definitionForHolder(rewrittenMethod).asProgramClass();
DexEncodedMethod definition = holder.lookupMethod(rewrittenMethod);
- assert definition != null;
+ assert definition != null : "Missing method: " + rewrittenMethod;
result.createAndAdd(holder, definition);
}
return result;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
index d14fe97..8308e0b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
@@ -6,7 +6,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.NeverClassInline;
@@ -67,8 +67,7 @@
MethodSubject methodOnB =
inspector.clazz(B.class).uniqueMethodWithFinalName(methodOnA.getFinalName());
assertThat(methodOnB, isPresent());
- // TODO(b/171784168): Should be true.
- assertFalse(methodOnB.streamInstructions().anyMatch(x -> x.asDexInstruction().isInvokeSuper()));
+ assertTrue(methodOnB.streamInstructions().anyMatch(x -> x.asDexInstruction().isInvokeSuper()));
}
static class TestClass {