Canonicalize KeepInfo
Change-Id: Ia3e925dbf2c7cbabf32c59c03d9e7cebed3f7c16
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index bacf716..6e97625 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -414,7 +414,7 @@
reachableVirtualTargets = new IdentityHashMap<>();
/** Collection of keep requirements for the program. */
- private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
+ private final MutableKeepInfoCollection keepInfo;
/**
* Conditional minimum keep info for classes, fields, and methods, which should only be applied if
@@ -508,6 +508,7 @@
this.missingClassesBuilder = appView.appInfo().getMissingClasses().builder();
this.mode = mode;
this.options = options;
+ this.keepInfo = new MutableKeepInfoCollection(options);
this.useRegistryFactory = createUseRegistryFactory();
this.worklist = EnqueuerWorklist.createWorklist(this);
this.proguardCompatibilityActionsBuilder =
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index 3bb0f20..985559d 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -186,6 +186,34 @@
return this.equals(bottom());
}
+ @Override
+ public boolean equalsNoAnnotations(KeepClassInfo other) {
+ return super.equalsNoAnnotations(other)
+ && allowClassInlining == other.internalIsClassInliningAllowed()
+ && allowHorizontalClassMerging == other.internalIsHorizontalClassMergingAllowed()
+ && allowPermittedSubclassesRemoval == other.internalIsPermittedSubclassesRemovalAllowed()
+ && allowRepackaging == other.internalIsRepackagingAllowed()
+ && allowSyntheticSharing == other.internalIsSyntheticSharingAllowed()
+ && allowUnusedInterfaceRemoval == other.internalIsUnusedInterfaceRemovalAllowed()
+ && allowVerticalClassMerging == other.internalIsVerticalClassMergingAllowed()
+ && checkEnumUnboxed == other.internalIsCheckEnumUnboxedEnabled();
+ }
+
+ @Override
+ public int hashCodeNoAnnotations() {
+ int hash = super.hashCodeNoAnnotations();
+ int index = super.numberOfBooleans();
+ hash += bit(allowClassInlining, index++);
+ hash += bit(allowHorizontalClassMerging, index++);
+ hash += bit(allowPermittedSubclassesRemoval, index++);
+ hash += bit(allowRepackaging, index++);
+ hash += bit(allowSyntheticSharing, index++);
+ hash += bit(allowUnusedInterfaceRemoval, index++);
+ hash += bit(allowVerticalClassMerging, index++);
+ hash += bit(checkEnumUnboxed, index);
+ return hash;
+ }
+
public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
private boolean allowClassInlining;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index 2483029..1c6cfa5 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -71,6 +71,23 @@
return this.equals(bottom());
}
+ @Override
+ public boolean equalsNoAnnotations(KeepFieldInfo other) {
+ return super.equalsNoAnnotations(other)
+ && (allowFieldTypeStrengthening == other.internalIsFieldTypeStrengtheningAllowed())
+ && (allowRedundantFieldLoadElimination
+ == other.internalIsRedundantFieldLoadEliminationAllowed());
+ }
+
+ @Override
+ public int hashCodeNoAnnotations() {
+ int hash = super.hashCodeNoAnnotations();
+ int index = super.numberOfBooleans();
+ hash += bit(allowFieldTypeStrengthening, index++);
+ hash += bit(allowRedundantFieldLoadElimination, index);
+ return hash;
+ }
+
public static class Builder extends KeepMemberInfo.Builder<Builder, KeepFieldInfo> {
private boolean allowFieldTypeStrengthening;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index d37185a..a085f21 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -292,6 +292,39 @@
&& typeAnnotationsInfo.isLessThanOrEqualTo(other.internalTypeAnnotationsInfo());
}
+ public boolean equalsNoAnnotations(K other) {
+ return getClass() == other.getClass()
+ && (allowAccessModification == other.internalIsAccessModificationAllowed())
+ && (allowAccessModificationForTesting
+ == other.internalIsAccessModificationAllowedForTesting())
+ && (allowMinification == other.internalIsMinificationAllowed())
+ && (allowOptimization == other.internalIsOptimizationAllowed())
+ && (allowShrinking == other.internalIsShrinkingAllowed())
+ && (allowSignatureRemoval == other.internalIsSignatureRemovalAllowed())
+ && (checkDiscarded == other.internalIsCheckDiscardedEnabled());
+ }
+
+ public int hashCodeNoAnnotations() {
+ int hash = 0;
+ int index = 0;
+ hash += bit(allowAccessModification, index++);
+ hash += bit(allowAccessModificationForTesting, index++);
+ hash += bit(allowMinification, index++);
+ hash += bit(allowOptimization, index++);
+ hash += bit(allowShrinking, index++);
+ hash += bit(allowSignatureRemoval, index++);
+ hash += bit(checkDiscarded, index);
+ return hash;
+ }
+
+ protected int numberOfBooleans() {
+ return 7;
+ }
+
+ protected int bit(boolean bool, int index) {
+ return bool ? 1 << index : 0;
+ }
+
/** Builder to construct an arbitrary keep info object. */
public abstract static class Builder<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCanonicalizer.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCanonicalizer.java
new file mode 100644
index 0000000..ada990b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCanonicalizer.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2024, 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.shaking;
+
+import com.android.tools.r8.shaking.KeepInfoEquivalenceNoAnnotations.ClassEquivalenceNoAnnotations;
+import com.android.tools.r8.shaking.KeepInfoEquivalenceNoAnnotations.FieldEquivalenceNoAnnotations;
+import com.android.tools.r8.shaking.KeepInfoEquivalenceNoAnnotations.MethodEquivalenceNoAnnotations;
+import com.android.tools.r8.utils.LRUCache;
+import com.google.common.base.Equivalence;
+import java.util.Map;
+
+public abstract class KeepInfoCanonicalizer {
+
+ private static final int CACHE_SIZE = 25;
+
+ public static KeepInfoCanonicalizer newCanonicalizer() {
+ return new KeepInfoConcreteCanonicalizer();
+ }
+
+ public static KeepInfoCanonicalizer newNopCanonicalizer() {
+ return new KeepInfoNopCanonicalizer();
+ }
+
+ public abstract KeepClassInfo canonicalizeKeepClassInfo(KeepClassInfo classInfo);
+
+ public abstract KeepMethodInfo canonicalizeKeepMethodInfo(KeepMethodInfo methodInfo);
+
+ public abstract KeepFieldInfo canonicalizeKeepFieldInfo(KeepFieldInfo fieldInfo);
+
+ static class KeepInfoConcreteCanonicalizer extends KeepInfoCanonicalizer {
+
+ private final ClassEquivalenceNoAnnotations classEquivalence =
+ new ClassEquivalenceNoAnnotations();
+ private final Map<Equivalence.Wrapper<KeepClassInfo>, Equivalence.Wrapper<KeepClassInfo>>
+ keepClassInfos = new LRUCache<>(CACHE_SIZE);
+ private final MethodEquivalenceNoAnnotations methodEquivalence =
+ new MethodEquivalenceNoAnnotations();
+ private final Map<Equivalence.Wrapper<KeepMethodInfo>, Equivalence.Wrapper<KeepMethodInfo>>
+ keepMethodInfos = new LRUCache<>(CACHE_SIZE);
+ private final FieldEquivalenceNoAnnotations fieldEquivalence =
+ new FieldEquivalenceNoAnnotations();
+ private final Map<Equivalence.Wrapper<KeepFieldInfo>, Equivalence.Wrapper<KeepFieldInfo>>
+ keepFieldInfos = new LRUCache<>(CACHE_SIZE);
+
+ private boolean hasKeepInfoAnnotationInfo(KeepInfo<?, ?> info) {
+ return !info.internalAnnotationsInfo().isTop() || !info.internalTypeAnnotationsInfo().isTop();
+ }
+
+ @Override
+ public KeepClassInfo canonicalizeKeepClassInfo(KeepClassInfo classInfo) {
+ if (hasKeepInfoAnnotationInfo(classInfo)) {
+ return classInfo;
+ }
+ return keepClassInfos.computeIfAbsent(classEquivalence.wrap(classInfo), w -> w).get();
+ }
+
+ @Override
+ public KeepMethodInfo canonicalizeKeepMethodInfo(KeepMethodInfo methodInfo) {
+ if (hasKeepInfoAnnotationInfo(methodInfo)
+ || !methodInfo.internalParameterAnnotationsInfo().isTop()) {
+ return methodInfo;
+ }
+ return keepMethodInfos.computeIfAbsent(methodEquivalence.wrap(methodInfo), w -> w).get();
+ }
+
+ @Override
+ public KeepFieldInfo canonicalizeKeepFieldInfo(KeepFieldInfo fieldInfo) {
+ if (hasKeepInfoAnnotationInfo(fieldInfo)) {
+ return fieldInfo;
+ }
+ return keepFieldInfos.computeIfAbsent(fieldEquivalence.wrap(fieldInfo), w -> w).get();
+ }
+ }
+
+ static class KeepInfoNopCanonicalizer extends KeepInfoCanonicalizer {
+
+ @Override
+ public KeepClassInfo canonicalizeKeepClassInfo(KeepClassInfo classInfo) {
+ return classInfo;
+ }
+
+ @Override
+ public KeepMethodInfo canonicalizeKeepMethodInfo(KeepMethodInfo methodInfo) {
+ return methodInfo;
+ }
+
+ @Override
+ public KeepFieldInfo canonicalizeKeepFieldInfo(KeepFieldInfo fieldInfo) {
+ return fieldInfo;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index a63cce4..d89da04 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -287,7 +287,9 @@
// Collection of materialized rules.
private MaterializedRules materializedRules;
- MutableKeepInfoCollection() {
+ private final KeepInfoCanonicalizer canonicalizer;
+
+ MutableKeepInfoCollection(InternalOptions options) {
this(
new IdentityHashMap<>(),
new IdentityHashMap<>(),
@@ -295,7 +297,10 @@
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
- MaterializedRules.empty());
+ MaterializedRules.empty(),
+ options.testing.enableKeepInfoCanonicalizer
+ ? KeepInfoCanonicalizer.newCanonicalizer()
+ : KeepInfoCanonicalizer.newNopCanonicalizer());
}
private MutableKeepInfoCollection(
@@ -305,7 +310,8 @@
Map<DexType, KeepClassInfo.Joiner> classRuleInstances,
Map<DexField, KeepFieldInfo.Joiner> fieldRuleInstances,
Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances,
- MaterializedRules materializedRules) {
+ MaterializedRules materializedRules,
+ KeepInfoCanonicalizer keepInfoCanonicalizer) {
this.keepClassInfo = keepClassInfo;
this.keepMethodInfo = keepMethodInfo;
this.keepFieldInfo = keepFieldInfo;
@@ -313,6 +319,7 @@
this.fieldRuleInstances = fieldRuleInstances;
this.methodRuleInstances = methodRuleInstances;
this.materializedRules = materializedRules;
+ this.canonicalizer = keepInfoCanonicalizer;
}
public void setMaterializedRules(MaterializedRules materializedRules) {
@@ -385,7 +392,8 @@
methodRuleInstances,
lens::getRenamedMethodSignature,
KeepMethodInfo::newEmptyJoiner),
- materializedRules.rewriteWithLens(lens));
+ materializedRules.rewriteWithLens(lens),
+ canonicalizer);
timing.end();
return result;
}
@@ -599,7 +607,7 @@
fn.accept(joiner);
KeepClassInfo joined = joiner.join();
if (!info.equals(joined)) {
- keepClassInfo.put(clazz.type, joined);
+ keepClassInfo.put(clazz.type, canonicalizer.canonicalizeKeepClassInfo(joined));
}
}
@@ -617,7 +625,7 @@
fn.accept(joiner);
KeepMethodInfo joined = joiner.join();
if (!info.equals(joined)) {
- keepMethodInfo.put(method.getReference(), joined);
+ keepMethodInfo.put(method.getReference(), canonicalizer.canonicalizeKeepMethodInfo(joined));
}
}
@@ -635,7 +643,7 @@
fn.accept(joiner);
KeepFieldInfo joined = joiner.join();
if (!info.equals(joined)) {
- keepFieldInfo.put(field.getReference(), joined);
+ keepFieldInfo.put(field.getReference(), canonicalizer.canonicalizeKeepFieldInfo(joined));
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoEquivalenceNoAnnotations.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoEquivalenceNoAnnotations.java
new file mode 100644
index 0000000..d1b3e72
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoEquivalenceNoAnnotations.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2024, 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.shaking;
+
+import com.google.common.base.Equivalence;
+
+public class KeepInfoEquivalenceNoAnnotations<T extends KeepInfo<?, T>> extends Equivalence<T> {
+
+ @Override
+ protected boolean doEquivalent(T a, T b) {
+ return a.equalsNoAnnotations(b);
+ }
+
+ @Override
+ protected int doHash(T a) {
+ return a.hashCodeNoAnnotations();
+ }
+
+ static class ClassEquivalenceNoAnnotations
+ extends KeepInfoEquivalenceNoAnnotations<KeepClassInfo> {}
+
+ static class MethodEquivalenceNoAnnotations
+ extends KeepInfoEquivalenceNoAnnotations<KeepMethodInfo> {}
+
+ static class FieldEquivalenceNoAnnotations
+ extends KeepInfoEquivalenceNoAnnotations<KeepFieldInfo> {}
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
index ab20efa..887dff9 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
@@ -90,6 +90,25 @@
}
}
+ @Override
+ public boolean equalsNoAnnotations(K other) {
+ return super.equalsNoAnnotations(other)
+ && (allowValuePropagation == other.internalIsValuePropagationAllowed());
+ }
+
+ @Override
+ public int hashCodeNoAnnotations() {
+ int hash = super.hashCodeNoAnnotations();
+ int index = super.numberOfBooleans();
+ hash += bit(allowValuePropagation, index);
+ return hash;
+ }
+
+ @Override
+ protected int numberOfBooleans() {
+ return super.numberOfBooleans() + 1;
+ }
+
public abstract static class Joiner<
J extends Joiner<J, B, K>, B extends Builder<B, K>, K extends KeepMemberInfo<B, K>>
extends KeepInfo.Joiner<J, B, K> {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index a40c7e6..0711d8a 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -280,6 +280,52 @@
return this.equals(bottom());
}
+ @Override
+ public boolean equalsNoAnnotations(KeepMethodInfo other) {
+ return super.equalsNoAnnotations(other)
+ && allowThrowsRemoval == other.internalIsThrowsRemovalAllowed()
+ && allowClassInlining == other.internalIsClassInliningAllowed()
+ && allowClosedWorldReasoning == other.internalIsClosedWorldReasoningAllowed()
+ && allowCodeReplacement == other.internalIsCodeReplacementAllowed()
+ && allowConstantArgumentOptimization
+ == other.internalIsConstantArgumentOptimizationAllowed()
+ && allowInlining == other.internalIsInliningAllowed()
+ && allowMethodStaticizing == other.internalIsMethodStaticizingAllowed()
+ && allowParameterRemoval == other.internalIsParameterRemovalAllowed()
+ && allowParameterReordering == other.internalIsParameterReorderingAllowed()
+ && allowParameterTypeStrengthening == other.internalIsParameterTypeStrengtheningAllowed()
+ && allowReprocessing == other.internalIsReprocessingAllowed()
+ && allowReturnTypeStrengthening == other.internalIsReturnTypeStrengtheningAllowed()
+ && allowSingleCallerInlining == other.internalIsSingleCallerInliningAllowed()
+ && allowUnusedArgumentOptimization == other.internalIsUnusedArgumentOptimizationAllowed()
+ && allowUnusedReturnValueOptimization
+ == other.internalIsUnusedReturnValueOptimizationAllowed()
+ && allowParameterNamesRemoval == other.internalIsParameterNamesRemovalAllowed();
+ }
+
+ @Override
+ public int hashCodeNoAnnotations() {
+ int hash = super.hashCodeNoAnnotations();
+ int index = super.numberOfBooleans();
+ hash += bit(allowThrowsRemoval, index++);
+ hash += bit(allowClassInlining, index++);
+ hash += bit(allowClosedWorldReasoning, index++);
+ hash += bit(allowCodeReplacement, index++);
+ hash += bit(allowConstantArgumentOptimization, index++);
+ hash += bit(allowInlining, index++);
+ hash += bit(allowMethodStaticizing, index++);
+ hash += bit(allowParameterRemoval, index++);
+ hash += bit(allowParameterReordering, index++);
+ hash += bit(allowParameterTypeStrengthening, index++);
+ hash += bit(allowReprocessing, index++);
+ hash += bit(allowReturnTypeStrengthening, index++);
+ hash += bit(allowSingleCallerInlining, index++);
+ hash += bit(allowUnusedArgumentOptimization, index++);
+ hash += bit(allowUnusedReturnValueOptimization, index++);
+ hash += bit(allowParameterNamesRemoval, index);
+ return hash;
+ }
+
public static class Builder extends KeepMemberInfo.Builder<Builder, KeepMethodInfo> {
private boolean allowThrowsRemoval;
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 02881e6..16b3ca9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2472,6 +2472,7 @@
public boolean allowUnusedDontWarnRules = true;
public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
public boolean alwaysUsePessimisticRegisterAllocation = false;
+ public boolean enableKeepInfoCanonicalizer = true;
public boolean enableBridgeHoistingToSharedSyntheticSuperclass = false;
public boolean enableCheckCastAndInstanceOfRemoval = true;
public boolean enableDeadSwitchCaseElimination = true;
diff --git a/src/main/java/com/android/tools/r8/utils/LRUCache.java b/src/main/java/com/android/tools/r8/utils/LRUCache.java
new file mode 100644
index 0000000..acf4c00
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/LRUCache.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2024, 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.utils;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class LRUCache<K, V> extends LinkedHashMap<K, V> {
+
+ private final int maxSize;
+
+ public LRUCache(int maxSize) {
+ super(maxSize + 1, 0.75F, true);
+ this.maxSize = maxSize;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+ return this.size() > this.maxSize;
+ }
+}