Introduce a structure of preservation requirements for program items.
Bug: 156715504
Change-Id: I5a0b3deb917e001bb632e93f5e37a1e30b66694a
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index ea55ec4..4dce9d4 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.shaking.KeepInfoCollection;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
@@ -13,9 +14,11 @@
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@@ -263,6 +266,14 @@
return true;
}
+ public <T extends DexReference> boolean assertPinnedNotModified(KeepInfoCollection keepInfo) {
+ List<DexReference> pinnedItems = new ArrayList<>();
+ keepInfo.forEachPinnedType(pinnedItems::add);
+ keepInfo.forEachPinnedMethod(pinnedItems::add);
+ keepInfo.forEachPinnedField(pinnedItems::add);
+ return assertReferencesNotModified(pinnedItems);
+ }
+
public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) {
for (DexReference reference : references) {
if (reference.isDexField()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index c64dff4..667c1ff 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -9,7 +9,6 @@
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.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -26,6 +25,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -190,7 +190,7 @@
resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
return field != null
&& isDeadProtoExtensionField(
- field, appInfo.getFieldAccessInfoCollection(), appInfo.getPinnedItems());
+ field, appInfo.getFieldAccessInfoCollection(), appInfo.getKeepInfo());
}
return false;
}
@@ -198,8 +198,8 @@
public boolean isDeadProtoExtensionField(
ProgramField field,
FieldAccessInfoCollection<?> fieldAccessInfoCollection,
- Set<DexReference> pinnedItems) {
- if (pinnedItems.contains(field.getReference())) {
+ KeepInfoCollection keepInfo) {
+ if (keepInfo.getFieldInfo(field).isPinned()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 3239c8c..483d2c1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.enums;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import com.android.tools.r8.graph.AppView;
@@ -325,6 +326,17 @@
return;
}
ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
+ // Update keep info on any of the enum methods of the removed classes.
+ appView
+ .appInfo()
+ .getKeepInfo()
+ .mutate(
+ keepInfo -> {
+ for (DexType type : enumsToUnbox) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ clazz.forEachProgramMethod(keepInfo::unpinMethod);
+ }
+ });
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index b5530c2..8c23763 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -13,11 +13,11 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -146,22 +146,17 @@
// A holder type, for field or method, should block enum unboxing only if the enum type is
// also kept. This is to allow the keep rule -keepclassmembers to be used on enums while
// enum unboxing can still be performed.
- for (DexReference item : appView.appInfo().getPinnedItems()) {
- if (item.isDexType()) {
- removePinnedCandidate(item.asDexType());
- } else if (item.isDexField()) {
- DexField field = item.asDexField();
- removePinnedIfNotHolder(field, field.type);
- } else {
- assert item.isDexMethod();
- DexMethod method = item.asDexMethod();
- DexProto proto = method.proto;
- removePinnedIfNotHolder(method, proto.returnType);
- for (DexType parameterType : proto.parameters.values) {
- removePinnedIfNotHolder(method, parameterType);
- }
- }
- }
+ KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
+ keepInfo.forEachPinnedType(this::removePinnedCandidate);
+ keepInfo.forEachPinnedField((DexField field) -> removePinnedIfNotHolder(field, field.type));
+ keepInfo.forEachPinnedMethod(
+ (DexMethod method) -> {
+ DexProto proto = method.proto;
+ removePinnedIfNotHolder(method, proto.returnType);
+ for (DexType parameterType : proto.parameters.values) {
+ removePinnedIfNotHolder(method, parameterType);
+ }
+ });
}
private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 28928ef..c65cefa 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -29,14 +28,11 @@
@Override
public void done(Enqueuer enqueuer) {
DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType;
- DexClass kotlinMetadataClass =
- appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType);
// We will process kotlin.Metadata even if the type is not present in the program, as long as
// the annotation will be in the output
boolean keepMetadata =
- enqueuer.isPinned(kotlinMetadataType)
- || enqueuer.isMissing(kotlinMetadataType)
- || (kotlinMetadataClass != null && kotlinMetadataClass.isNotProgramClass());
+ // This is true for any non-program type (also missing) and for kept program types.
+ enqueuer.isPinned(kotlinMetadataType);
enqueuer.forAllLiveClasses(
clazz -> {
clazz.setKotlinInfo(
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 31e239b..54decce 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -254,8 +254,9 @@
// Remove all of the bridges in the eligible subclasses.
for (DexProgramClass subclass : eligibleSubclasses) {
+ assert !appView.appInfo().isPinned(method);
DexEncodedMethod removed = subclass.removeMethod(method);
- assert removed != null && !appView.appInfo().isPinned(removed.method);
+ assert removed != null;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 584b84b..02aca6b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -305,14 +305,15 @@
if (appView.appInfo().isPinned(ica.getInner())) {
return false;
}
- if (appView.appInfo().isPinned(ica.getOuter())) {
+ DexType outer = ica.getOuter();
+ if (outer != null && appView.appInfo().isPinned(outer)) {
return false;
}
if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) {
return false;
}
if (finalKeepForThisEnclosingClass
- && ica.getOuter() == clazz.type
+ && outer == clazz.type
&& classesToRetainInnerClassAttributeFor.contains(ica.getInner())) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 989ae9a..6a2c794 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -136,10 +136,14 @@
* will have been removed from the code.
*/
public final Set<DexCallSite> callSites;
- /** Set of all items that have to be kept independent of whether they are used. */
- final Set<DexReference> pinnedItems;
- /** Set of kept items that are allowed to be publicized. */
- final Set<DexReference> allowAccessModification;
+ /** Collection of keep requirements for the program. */
+ private final KeepInfoCollection keepInfo;
+ /**
+ * Set of kept items that are allowed to be publicized.
+ *
+ * <p>TODO(b/156715504): merge into keep info.
+ */
+ private final Set<DexReference> allowAccessModification;
/** All items with assumemayhavesideeffects rule. */
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
/** All items with assumenosideeffects rule. */
@@ -217,7 +221,7 @@
SortedMap<DexMethod, ProgramMethodSet> directInvokes,
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
- Set<DexReference> pinnedItems,
+ KeepInfoCollection keepInfo,
Set<DexReference> allowAccessModification,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -253,7 +257,7 @@
this.liveMethods = liveMethods;
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
- this.pinnedItems = pinnedItems;
+ this.keepInfo = keepInfo;
this.allowAccessModification = allowAccessModification;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
@@ -304,7 +308,7 @@
SortedMap<DexMethod, ProgramMethodSet> directInvokes,
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
- Set<DexReference> pinnedItems,
+ KeepInfoCollection keepInfo,
Set<DexReference> allowAccessModification,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -340,7 +344,7 @@
this.liveMethods = liveMethods;
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
- this.pinnedItems = pinnedItems;
+ this.keepInfo = keepInfo;
this.allowAccessModification = allowAccessModification;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
@@ -392,7 +396,7 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- previous.pinnedItems,
+ previous.keepInfo,
previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
@@ -443,9 +447,7 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- additionalPinnedItems == null
- ? previous.pinnedItems
- : CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
+ extendPinnedItems(previous, additionalPinnedItems),
previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
@@ -471,7 +473,44 @@
previous.constClassReferences,
previous.initClassReferences);
copyMetadataFromPrevious(previous);
- assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
+ assert keepInfo.verifyNoneArePinned(removedClasses, previous);
+ }
+
+ private static KeepInfoCollection extendPinnedItems(
+ AppInfoWithLiveness previous, Collection<DexReference> additionalPinnedItems) {
+ if (additionalPinnedItems == null || additionalPinnedItems.isEmpty()) {
+ return previous.keepInfo;
+ }
+ return previous.keepInfo.mutate(
+ collection -> {
+ for (DexReference reference : additionalPinnedItems) {
+ if (reference.isDexType()) {
+ DexProgramClass clazz =
+ asProgramClassOrNull(previous.definitionFor(reference.asDexType()));
+ if (clazz != null) {
+ collection.pinClass(clazz);
+ }
+ } else if (reference.isDexMethod()) {
+ DexMethod method = reference.asDexMethod();
+ DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(method.holder));
+ if (clazz != null) {
+ DexEncodedMethod definition = clazz.lookupMethod(method);
+ if (definition != null) {
+ collection.pinMethod(clazz, definition);
+ }
+ }
+ } else {
+ DexField field = reference.asDexField();
+ DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(field.holder));
+ if (clazz != null) {
+ DexEncodedField definition = clazz.lookupField(field);
+ if (definition != null) {
+ collection.pinField(clazz, definition);
+ }
+ }
+ }
+ }
+ });
}
public AppInfoWithLiveness(
@@ -491,7 +530,7 @@
this.liveMethods = previous.liveMethods;
this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
- this.pinnedItems = previous.pinnedItems;
+ this.keepInfo = previous.keepInfo;
this.allowAccessModification = previous.allowAccessModification;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
this.noSideEffects = previous.noSideEffects;
@@ -795,7 +834,7 @@
if (info != null && info.isRead()) {
return true;
}
- return isPinned(field)
+ return keepInfo.isPinned(field, this)
// Fields in the class that is synthesized by D8/R8 would be used soon.
|| field.holder.isD8R8SynthesizedClassType()
// For library classes we don't know whether a field is read.
@@ -867,7 +906,7 @@
return method.getDefinition().hasCode()
&& !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
&& !neverReprocess.contains(reference)
- && !pinnedItems.contains(reference);
+ && !keepInfo.getMethodInfo(method).isPinned();
}
public boolean mayPropagateValueFor(DexReference reference) {
@@ -930,7 +969,7 @@
public boolean isPinned(DexReference reference) {
assert checkIfObsolete();
- return pinnedItems.contains(reference);
+ return keepInfo.isPinned(reference, this);
}
public boolean hasPinnedInstanceInitializer(DexType type) {
@@ -946,9 +985,8 @@
return false;
}
- public Set<DexReference> getPinnedItems() {
- assert checkIfObsolete();
- return pinnedItems;
+ public KeepInfoCollection getKeepInfo() {
+ return keepInfo;
}
/**
@@ -1022,7 +1060,7 @@
// TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
// after second tree shaking.
callSites,
- lens.rewriteReferencesConservatively(pinnedItems),
+ keepInfo.rewrite(lens),
lens.rewriteReferencesConservatively(allowAccessModification),
rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference),
rewriteReferenceKeys(noSideEffects, lens::lookupReference),
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 8d6df10..01032ab 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -92,6 +92,7 @@
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
+import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
@@ -305,11 +306,8 @@
*/
private final Set<DexReference> reportedMissing = Sets.newIdentityHashSet();
- /**
- * A set of references that we are keeping due to keep rules. This may differ from the root set
- * due to dependent keep rules.
- */
- private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
+ /** Collection of keep requirements for the program. */
+ private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
/**
* A set of references that we are keeping due to keep rules, which we are allowed to publicize.
@@ -546,6 +544,10 @@
return clazz;
}
+ public boolean isPinned(DexType type) {
+ return keepInfo.isPinned(type, appInfo);
+ }
+
private void addLiveNonProgramType(DexClass clazz) {
assert clazz.isNotProgramClass();
// Fast path to avoid the worklist when the class is already seen.
@@ -641,6 +643,7 @@
if (item.isDexClass()) {
DexProgramClass clazz = item.asDexClass().asProgramClass();
KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
+ keepInfo.pinClass(clazz);
if (clazz.isAnnotation()) {
workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness);
} else if (clazz.isInterface()) {
@@ -663,6 +666,7 @@
DexEncodedField field = item.asDexEncodedField();
DexProgramClass holder = getProgramClassOrNull(field.holder());
if (holder != null) {
+ keepInfo.pinField(holder, field);
workList.enqueueMarkFieldKeptAction(
new ProgramField(holder, field),
graphReporter.reportKeepField(precondition, rules, field));
@@ -671,6 +675,7 @@
DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
DexProgramClass holder = getProgramClassOrNull(encodedMethod.holder());
if (holder != null) {
+ keepInfo.pinMethod(holder, encodedMethod);
workList.enqueueMarkMethodKeptAction(
new ProgramMethod(holder, encodedMethod),
graphReporter.reportKeepMethod(precondition, rules, encodedMethod));
@@ -678,7 +683,7 @@
} else {
throw new IllegalArgumentException(item.toString());
}
- addPinnedItem(item.toReference(), rules);
+ setAllowAccessModification(item.toReference(), rules);
}
private void enqueueFirstNonSerializableClassInitializer(
@@ -1314,7 +1319,7 @@
boolean skipTracing =
appView.withGeneratedExtensionRegistryShrinker(
shrinker ->
- shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems),
+ shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo),
false);
if (skipTracing) {
addDeadProtoTypeCandidate(field.getHolder());
@@ -1373,7 +1378,7 @@
boolean skipTracing =
appView.withGeneratedExtensionRegistryShrinker(
shrinker ->
- shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems),
+ shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo),
false);
if (skipTracing) {
addDeadProtoTypeCandidate(field.getHolder());
@@ -1808,12 +1813,14 @@
holder,
(dexType, ignored) -> {
if (holder.isProgramClass()) {
- DexReference holderReference = holder.toReference();
- addPinnedItem(holderReference);
- rootSet.shouldNotBeMinified(holderReference);
+ DexProgramClass holderClass = holder.asProgramClass();
+ keepInfo.pinClass(holderClass);
+ setAllowAccessModification(holderClass.type);
+ rootSet.shouldNotBeMinified(holder.toReference());
for (DexEncodedMember<?, ?> member : holder.members()) {
+ keepInfo.pinMember(holderClass, member);
DexMember<?, ?> memberReference = member.toReference();
- addPinnedItem(memberReference);
+ setAllowAccessModification(memberReference);
rootSet.shouldNotBeMinified(memberReference);
}
}
@@ -2461,7 +2468,7 @@
(type, subTypeConsumer, lambdaConsumer) ->
objectAllocationInfoCollection.forEachInstantiatedSubType(
type, subTypeConsumer, lambdaConsumer, appInfo),
- pinnedItems::contains)
+ reference -> keepInfo.isPinned(reference, appInfo))
.forEach(
target ->
markVirtualDispatchTargetAsLive(
@@ -2528,7 +2535,8 @@
// TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
// marking for not renaming it is in the root set.
workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
- addPinnedItem(valuesMethod.toReference());
+ keepInfo.pinMethod(clazz, valuesMethod);
+ setAllowAccessModification(valuesMethod.toReference());
rootSet.shouldNotBeMinified(valuesMethod.toReference());
}
}
@@ -2632,16 +2640,11 @@
return appInfoWithLiveness;
}
- public boolean isPinned(DexReference reference) {
- return pinnedItems.contains(reference);
- }
-
- private boolean addPinnedItem(DexReference reference) {
+ private void setAllowAccessModification(DexReference reference) {
allowAccessModification.put(reference, OptionalBool.unknown());
- return pinnedItems.add(reference);
}
- private boolean addPinnedItem(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+ private void setAllowAccessModification(DexReference reference, Set<ProguardKeepRuleBase> rules) {
assert rules != null;
assert !rules.isEmpty();
OptionalBool allowAccessModificationOfReference =
@@ -2658,11 +2661,6 @@
}
allowAccessModification.put(reference, allowAccessModificationOfReference);
}
- return pinnedItems.add(reference);
- }
-
- public boolean isMissing(DexType type) {
- return missingTypes.contains(type);
}
private static class SyntheticAdditions {
@@ -2675,7 +2673,7 @@
Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
// Subset of live methods that need to be pinned.
- Set<DexMethod> pinnedMethods = Sets.newIdentityHashSet();
+ Set<ProgramMethod> pinnedMethods = Sets.newIdentityHashSet();
// Subset of synthesized classes that need to be added to the main-dex file.
Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
@@ -2708,7 +2706,7 @@
void addLiveAndPinnedMethod(ProgramMethod method) {
addLiveMethod(method);
- pinnedMethods.add(method.getDefinition().method);
+ pinnedMethods.add(method);
}
void amendApplication(Builder appBuilder) {
@@ -2727,7 +2725,11 @@
// All synthetic additions are initial tree shaking only. No need to track keep reasons.
KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
- pinnedMethods.forEach(enqueuer::addPinnedItem);
+ pinnedMethods.forEach(
+ method -> {
+ enqueuer.keepInfo.pinMethod(method);
+ enqueuer.setAllowAccessModification(method.getReference());
+ });
for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
syntheticInstantiations.values()) {
enqueuer.workList.enqueueMarkInstantiatedAction(
@@ -2918,7 +2920,7 @@
toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
callSites,
- pinnedItems,
+ keepInfo,
allowAccessModification.keySet(),
rootSet.mayHaveSideEffects,
rootSet.noSideEffects,
@@ -3327,7 +3329,7 @@
assert desugaredLambdaImplementationMethods.isEmpty()
|| options.desugarState == DesugarState.ON;
for (DexMethod method : desugaredLambdaImplementationMethods) {
- pinnedItems.remove(method);
+ keepInfo.unpinMethod(method);
rootSet.prune(method);
}
desugaredLambdaImplementationMethods.clear();
@@ -3614,7 +3616,9 @@
workList.enqueueMarkInstantiatedAction(
clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
}
- if (addPinnedItem(encodedField.field)) {
+ if (!keepInfo.getFieldInfo(encodedField, clazz).isPinned()) {
+ keepInfo.pinField(clazz, encodedField);
+ setAllowAccessModification(encodedField.field);
markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method));
}
} else {
@@ -3801,14 +3805,16 @@
// Add this interface to the set of pinned items to ensure that we do not merge the
// interface into its unique subtype, if any.
// TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
- addPinnedItem(clazz.type);
+ keepInfo.pinClass(clazz);
+ setAllowAccessModification(clazz.type);
KeepReason reason = KeepReason.reflectiveUseIn(method);
markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
// Also pin all of its virtual methods to ensure that the devirtualizer does not perform
// illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
- addPinnedItem(virtualMethod.method);
+ keepInfo.pinMethod(clazz, virtualMethod);
+ setAllowAccessModification(virtualMethod.method);
markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
new file mode 100644
index 0000000..a8cf2df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+/** Immutable keep requirements for a class. */
+public final class KeepClassInfo extends KeepInfo {
+
+ // Requires all aspects of a class to be kept.
+ private static final KeepClassInfo TOP = new KeepClassInfo(true);
+
+ // Requires no aspects of a class to be kept.
+ private static final KeepClassInfo BOTTOM = new KeepClassInfo(false);
+
+ public static KeepClassInfo top() {
+ return TOP;
+ }
+
+ public static KeepClassInfo bottom() {
+ return BOTTOM;
+ }
+
+ private KeepClassInfo(boolean pinned) {
+ super(pinned);
+ }
+
+ public Builder builder() {
+ return new Builder(this);
+ }
+
+ public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
+
+ private Builder(KeepClassInfo original) {
+ super(original);
+ }
+
+ @Override
+ public KeepClassInfo top() {
+ return TOP;
+ }
+
+ @Override
+ public KeepClassInfo bottom() {
+ return BOTTOM;
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public boolean isEqualTo(KeepClassInfo other) {
+ return true;
+ }
+
+ @Override
+ public KeepClassInfo doBuild() {
+ return new KeepClassInfo(isPinned());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
new file mode 100644
index 0000000..7416d8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+/** Immutable keep requirements for a field. */
+public final class KeepFieldInfo extends KeepInfo {
+
+ // Requires all aspects of a field to be kept.
+ private static final KeepFieldInfo TOP = new KeepFieldInfo(true);
+
+ // Requires no aspects of a field to be kept.
+ private static final KeepFieldInfo BOTTOM = new KeepFieldInfo(false);
+
+ public static KeepFieldInfo top() {
+ return TOP;
+ }
+
+ public static KeepFieldInfo bottom() {
+ return BOTTOM;
+ }
+
+ private KeepFieldInfo(boolean pinned) {
+ super(pinned);
+ }
+
+ public Builder builder() {
+ return new Builder(this);
+ }
+
+ public static class Builder extends KeepInfo.Builder<Builder, KeepFieldInfo> {
+
+ private Builder(KeepFieldInfo original) {
+ super(original);
+ }
+
+ @Override
+ public KeepFieldInfo top() {
+ return TOP;
+ }
+
+ @Override
+ public KeepFieldInfo bottom() {
+ return BOTTOM;
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public boolean isEqualTo(KeepFieldInfo other) {
+ return true;
+ }
+
+ @Override
+ public KeepFieldInfo doBuild() {
+ return new KeepFieldInfo(isPinned());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
new file mode 100644
index 0000000..246eeb7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+/** Keep information that can be associated with any item, i.e., class, method or field. */
+public abstract class KeepInfo {
+
+ private final boolean pinned;
+
+ public KeepInfo(boolean pinned) {
+ this.pinned = pinned;
+ }
+
+ public boolean isPinned() {
+ return pinned;
+ }
+
+ public abstract static class Builder<B extends Builder, K extends KeepInfo> {
+
+ public abstract B self();
+
+ public abstract K doBuild();
+
+ public abstract K top();
+
+ public abstract K bottom();
+
+ public abstract boolean isEqualTo(K other);
+
+ private K original;
+ private boolean pinned;
+
+ public Builder(K original) {
+ this.original = original;
+ pinned = original.isPinned();
+ }
+
+ public K build() {
+ if (internalIsEqualTo(original)) {
+ return original;
+ }
+ if (internalIsEqualTo(top())) {
+ return top();
+ }
+ if (internalIsEqualTo(bottom())) {
+ return bottom();
+ }
+ return doBuild();
+ }
+
+ private boolean internalIsEqualTo(K other) {
+ return isPinned() == other.isPinned() && isEqualTo(other);
+ }
+
+ public boolean isPinned() {
+ return pinned;
+ }
+
+ public B setPinned(boolean pinned) {
+ this.pinned = pinned;
+ return self();
+ }
+
+ public B pin() {
+ return setPinned(true);
+ }
+
+ public B unpin() {
+ return setPinned(false);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
new file mode 100644
index 0000000..1665e21
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -0,0 +1,298 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+// Non-mutable collection of keep information pertaining to a program.
+public abstract class KeepInfoCollection {
+
+ /**
+ * Base accessor for keep info on a class.
+ *
+ * <p>Access may never be granted directly on DexType as the "keep info" for any non-program type
+ * is not the same as the default keep info for a program type. By typing the interface at program
+ * item we can eliminate errors where a reference to a non-program item results in optimizations
+ * assuming aspects of it can be changed when in fact they can not.
+ */
+ public abstract KeepClassInfo getClassInfo(DexProgramClass clazz);
+
+ /**
+ * Base accessor for keep info on a method.
+ *
+ * <p>See comment on class access for why this is typed at program method.
+ */
+ public abstract KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder);
+
+ /**
+ * Base accessor for keep info on a field.
+ *
+ * <p>See comment on class access for why this is typed at program field.
+ */
+ public abstract KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder);
+
+ public final KeepClassInfo getClassInfo(DexType type, DexDefinitionSupplier definitions) {
+ DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
+ return clazz == null ? KeepClassInfo.top() : getClassInfo(clazz);
+ }
+
+ public final KeepMethodInfo getMethodInfo(ProgramMethod method) {
+ return getMethodInfo(method.getDefinition(), method.getHolder());
+ }
+
+ public final KeepMethodInfo getMethodInfo(DexMethod method, DexDefinitionSupplier definitions) {
+ DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(method.holder));
+ if (holder == null) {
+ return KeepMethodInfo.top();
+ }
+ DexEncodedMethod definition = holder.lookupMethod(method);
+ return definition == null ? KeepMethodInfo.bottom() : getMethodInfo(definition, holder);
+ }
+
+ public final KeepFieldInfo getFieldInfo(ProgramField field) {
+ return getFieldInfo(field.getDefinition(), field.getHolder());
+ }
+
+ public final KeepFieldInfo getFieldInfo(DexField field, DexDefinitionSupplier definitions) {
+ DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(field.holder));
+ if (holder == null) {
+ return KeepFieldInfo.top();
+ }
+ DexEncodedField definition = holder.lookupField(field);
+ return definition == null ? KeepFieldInfo.bottom() : getFieldInfo(definition, holder);
+ }
+
+ public final KeepInfo getInfo(DexReference reference, DexDefinitionSupplier definitions) {
+ if (reference.isDexType()) {
+ return getClassInfo(reference.asDexType(), definitions);
+ }
+ if (reference.isDexMethod()) {
+ return getMethodInfo(reference.asDexMethod(), definitions);
+ }
+ if (reference.isDexField()) {
+ return getFieldInfo(reference.asDexField(), definitions);
+ }
+ throw new Unreachable();
+ }
+
+ public final boolean isPinned(DexReference reference, DexDefinitionSupplier definitions) {
+ return getInfo(reference, definitions).isPinned();
+ }
+
+ public final boolean isPinned(DexType type, DexDefinitionSupplier definitions) {
+ return getClassInfo(type, definitions).isPinned();
+ }
+
+ public final boolean isPinned(DexMethod method, DexDefinitionSupplier definitions) {
+ return getMethodInfo(method, definitions).isPinned();
+ }
+
+ public final boolean isPinned(DexField field, DexDefinitionSupplier definitions) {
+ return getFieldInfo(field, definitions).isPinned();
+ }
+
+ public final boolean verifyNoneArePinned(Collection<DexType> types, AppInfo appInfo) {
+ for (DexType type : types) {
+ DexProgramClass clazz =
+ asProgramClassOrNull(appInfo.definitionForWithoutExistenceAssert(type));
+ assert clazz == null || !getClassInfo(clazz).isPinned();
+ }
+ return true;
+ }
+
+ // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+ @Deprecated
+ public abstract void forEachPinnedType(Consumer<DexType> consumer);
+
+ // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+ @Deprecated
+ public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer);
+
+ // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+ @Deprecated
+ public abstract void forEachPinnedField(Consumer<DexField> consumer);
+
+ public abstract KeepInfoCollection rewrite(NestedGraphLense lens);
+
+ public abstract KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator);
+
+ // Mutation interface for building up the keep info.
+ public static class MutableKeepInfoCollection extends KeepInfoCollection {
+
+ // These are typed at signatures but the interface should make sure never to allow access
+ // directly with a signature. See the comment in KeepInfoCollection.
+ private final Map<DexType, KeepClassInfo> keepClassInfo;
+ private final Map<DexMethod, KeepMethodInfo> keepMethodInfo;
+ private final Map<DexField, KeepFieldInfo> keepFieldInfo;
+
+ MutableKeepInfoCollection() {
+ this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+ }
+
+ private MutableKeepInfoCollection(
+ Map<DexType, KeepClassInfo> keepClassInfo,
+ Map<DexMethod, KeepMethodInfo> keepMethodInfo,
+ Map<DexField, KeepFieldInfo> keepFieldInfo) {
+ this.keepClassInfo = keepClassInfo;
+ this.keepMethodInfo = keepMethodInfo;
+ this.keepFieldInfo = keepFieldInfo;
+ }
+
+ @Override
+ public KeepInfoCollection rewrite(NestedGraphLense lens) {
+ Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
+ keepClassInfo.forEach(
+ (type, info) -> {
+ DexType newType = lens.lookupType(type);
+ assert !info.isPinned() || type == newType;
+ newClassInfo.put(newType, info);
+ });
+ Map<DexMethod, KeepMethodInfo> newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size());
+ keepMethodInfo.forEach(
+ (method, info) -> {
+ Set<DexMethod> methods = lens.lookupMethodInAllContexts(method);
+ assert !info.isPinned() || methods.contains(method);
+ for (DexMethod newMethod : methods) {
+ // TODO(b/157012327): Should this distribute keep-info from pinned methods too?
+ if (!info.isPinned() || method == newMethod) {
+ newMethodInfo.put(newMethod, info);
+ }
+ }
+ });
+ Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size());
+ keepFieldInfo.forEach(
+ (field, info) -> {
+ DexField newField = lens.lookupField(field);
+ assert !info.isPinned() || field == newField;
+ newFieldInfo.put(newField, info);
+ });
+ return new MutableKeepInfoCollection(newClassInfo, newMethodInfo, newFieldInfo);
+ }
+
+ @Override
+ public KeepClassInfo getClassInfo(DexProgramClass clazz) {
+ return keepClassInfo.getOrDefault(clazz.type, KeepClassInfo.bottom());
+ }
+
+ @Override
+ public KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder) {
+ assert method.holder() == holder.type;
+ return keepMethodInfo.getOrDefault(method.method, KeepMethodInfo.bottom());
+ }
+
+ @Override
+ public KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder) {
+ assert field.holder() == holder.type;
+ return keepFieldInfo.getOrDefault(field.field, KeepFieldInfo.bottom());
+ }
+
+ public void pinClass(DexProgramClass clazz) {
+ KeepClassInfo info = getClassInfo(clazz);
+ if (!info.isPinned()) {
+ keepClassInfo.put(clazz.type, info.builder().pin().build());
+ }
+ }
+
+ public void pinMember(DexProgramClass holder, DexEncodedMember<?, ?> member) {
+ if (member.isDexEncodedMethod()) {
+ pinMethod(holder, member.asDexEncodedMethod());
+ } else {
+ assert member.isDexEncodedField();
+ pinField(holder, member.asDexEncodedField());
+ }
+ }
+
+ public void pinMethod(ProgramMethod programMethod) {
+ pinMethod(programMethod.getHolder(), programMethod.getDefinition());
+ }
+
+ public void pinMethod(DexProgramClass holder, DexEncodedMethod method) {
+ KeepMethodInfo info = getMethodInfo(method, holder);
+ if (!info.isPinned()) {
+ keepMethodInfo.put(method.method, info.builder().pin().build());
+ }
+ }
+
+ public void unpinMethod(ProgramMethod method) {
+ assert !getClassInfo(method.getHolder()).isPinned();
+ unpinMethod(method.getReference());
+ }
+
+ // TODO(b/156715504): We should never need to unpin items. Rather avoid pinning to begin with.
+ @Deprecated
+ public void unpinMethod(DexMethod method) {
+ KeepMethodInfo info = keepMethodInfo.get(method);
+ if (info != null && info.isPinned()) {
+ keepMethodInfo.put(method, info.builder().unpin().build());
+ }
+ }
+
+ public void pinField(ProgramField programField) {
+ pinField(programField.getHolder(), programField.getDefinition());
+ }
+
+ public void pinField(DexProgramClass holder, DexEncodedField field) {
+ KeepFieldInfo info = getFieldInfo(field, holder);
+ if (!info.isPinned()) {
+ keepFieldInfo.put(field.field, info.builder().pin().build());
+ }
+ }
+
+ @Override
+ public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
+ mutator.accept(this);
+ return this;
+ }
+
+ @Override
+ public void forEachPinnedType(Consumer<DexType> consumer) {
+ keepClassInfo.forEach(
+ (type, info) -> {
+ if (info.isPinned()) {
+ consumer.accept(type);
+ }
+ });
+ }
+
+ @Override
+ public void forEachPinnedMethod(Consumer<DexMethod> consumer) {
+ keepMethodInfo.forEach(
+ (method, info) -> {
+ if (info.isPinned()) {
+ consumer.accept(method);
+ }
+ });
+ }
+
+ @Override
+ public void forEachPinnedField(Consumer<DexField> consumer) {
+ keepFieldInfo.forEach(
+ (field, info) -> {
+ if (info.isPinned()) {
+ consumer.accept(field);
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
new file mode 100644
index 0000000..f3bb073
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+/** Immutable keep requirements for a method. */
+public final class KeepMethodInfo extends KeepInfo {
+
+ // Requires all aspects of a method to be kept.
+ private static final KeepMethodInfo TOP = new KeepMethodInfo(true);
+
+ // Requires no aspects of a method to be kept.
+ private static final KeepMethodInfo BOTTOM = new KeepMethodInfo(false);
+
+ public static KeepMethodInfo top() {
+ return TOP;
+ }
+
+ public static KeepMethodInfo bottom() {
+ return BOTTOM;
+ }
+
+ private KeepMethodInfo(boolean pinned) {
+ super(pinned);
+ }
+
+ public Builder builder() {
+ return new Builder(this);
+ }
+
+ public static class Builder extends KeepInfo.Builder<Builder, KeepMethodInfo> {
+
+ private Builder(KeepMethodInfo original) {
+ super(original);
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public KeepMethodInfo top() {
+ return TOP;
+ }
+
+ @Override
+ public KeepMethodInfo bottom() {
+ return BOTTOM;
+ }
+
+ @Override
+ public boolean isEqualTo(KeepMethodInfo other) {
+ return true;
+ }
+
+ @Override
+ public KeepMethodInfo doBuild() {
+ return new KeepMethodInfo(isPinned());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
index 0979ef9..c6a1725 100644
--- a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
+++ b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
@@ -50,8 +49,10 @@
getClassesWithLibraryMethodOverrides(appView);
// Remove all types that are pinned from the initial set of non-escaping classes.
- DexReference.filterDexType(appView.appInfo().pinnedItems.stream())
- .forEach(initialNonEscapingClassesWithLibraryMethodOverrides::remove);
+ appView
+ .appInfo()
+ .getKeepInfo()
+ .forEachPinnedType(initialNonEscapingClassesWithLibraryMethodOverrides::remove);
return initialNonEscapingClassesWithLibraryMethodOverrides;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index ffb940a..859e313 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -689,53 +689,59 @@
// TODO(b/67934426): Test this code.
public static void writeSeeds(
AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
- for (DexReference seed : appInfo.getPinnedItems()) {
- if (seed.isDexType()) {
- if (include.test(seed.asDexType())) {
- out.println(seed.toSourceString());
- }
- } else if (seed.isDexField()) {
- DexField field = seed.asDexField();
- if (include.test(field.holder)) {
- out.println(
- field.holder.toSourceString()
- + ": "
- + field.type.toSourceString()
- + " "
- + field.name.toSourceString());
- }
- } else {
- assert seed.isDexMethod();
- DexMethod method = seed.asDexMethod();
- if (!include.test(method.holder)) {
- continue;
- }
- out.print(method.holder.toSourceString() + ": ");
- DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
- if (encodedMethod.accessFlags.isConstructor()) {
- if (encodedMethod.accessFlags.isStatic()) {
- out.print(Constants.CLASS_INITIALIZER_NAME);
- } else {
- String holderName = method.holder.toSourceString();
- String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
- out.print(constrName);
- }
- } else {
- out.print(
- method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
- }
- boolean first = true;
- out.print("(");
- for (DexType param : method.proto.parameters.values) {
- if (!first) {
- out.print(",");
- }
- first = false;
- out.print(param.toSourceString());
- }
- out.println(")");
- }
- }
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedType(
+ type -> {
+ if (include.test(type)) {
+ out.println(type.toSourceString());
+ }
+ });
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedField(
+ field -> {
+ if (include.test(field.holder)) {
+ out.println(
+ field.holder.toSourceString()
+ + ": "
+ + field.type.toSourceString()
+ + " "
+ + field.name.toSourceString());
+ }
+ });
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedMethod(
+ method -> {
+ if (!include.test(method.holder)) {
+ return;
+ }
+ out.print(method.holder.toSourceString() + ": ");
+ DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
+ if (encodedMethod.accessFlags.isConstructor()) {
+ if (encodedMethod.accessFlags.isStatic()) {
+ out.print(Constants.CLASS_INITIALIZER_NAME);
+ } else {
+ String holderName = method.holder.toSourceString();
+ String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
+ out.print(constrName);
+ }
+ } else {
+ out.print(
+ method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
+ }
+ boolean first = true;
+ out.print("(");
+ for (DexType param : method.proto.parameters.values) {
+ if (!first) {
+ out.print(",");
+ }
+ first = false;
+ out.print(param.toSourceString());
+ }
+ out.println(")");
+ });
out.close();
}
@@ -1643,14 +1649,11 @@
}
public boolean verifyKeptItemsAreKept(DexApplication application, AppInfo appInfo) {
- Set<DexReference> pinnedItems =
- appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
-
// Create a mapping from each required type to the set of required members on that type.
Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>();
for (DexReference reference : noShrinking.keySet()) {
// Check that `pinnedItems` is a super set of the root set.
- assert pinnedItems == null || pinnedItems.contains(reference)
+ assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(reference)
: "Expected reference `" + reference.toSourceString() + "` to be pinned";
if (reference.isDexType()) {
DexType type = reference.asDexType();
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 da9038d..3dd0f4f 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -273,7 +273,12 @@
// For all pinned fields, also pin the type of the field (because changing the type of the field
// implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
// the return type and the parameter types of the method.
- extractPinnedItems(appInfo.pinnedItems, AbortReason.PINNED_SOURCE);
+ // TODO(b/156715504): Compute referenced-by-pinned in the keep info objects.
+ List<DexReference> pinnedItems = new ArrayList<>();
+ appInfo.getKeepInfo().forEachPinnedType(pinnedItems::add);
+ appInfo.getKeepInfo().forEachPinnedMethod(pinnedItems::add);
+ appInfo.getKeepInfo().forEachPinnedField(pinnedItems::add);
+ extractPinnedItems(pinnedItems, AbortReason.PINNED_SOURCE);
// TODO(christofferqa): Remove the invariant that the graph lense should not modify any
// methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
@@ -680,7 +685,7 @@
// that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
// be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
// pinned, because this rewriting does not affect A.method() in any way.
- assert graphLense.assertReferencesNotModified(appInfo.pinnedItems);
+ assert graphLense.assertPinnedNotModified(appInfo.getKeepInfo());
for (DexProgramClass clazz : appInfo.classes()) {
for (DexEncodedMethod encodedMethod : clazz.methods()) {
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
index 105bf6b..512a9ff 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -103,8 +103,7 @@
*/
"getstatic " + client.name + "/" + obj2.name + " " + itf2.getDescriptor(),
"invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
- "return"
- );
+ "return");
final String mainClassName = mainClass.name;
String proguardConfig = StringUtils.lines(