Reland "Introduce a structure of preservation requirements for program items."
The reverts and merges the previous CL. To fix the issue for the revert,
keep-info bottom is used for non-program classes.
Bug: 156715504
Bug: 157538235
Change-Id: I6717e897bc8512ad3d2e5a4b3368a13de677ce5f
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 95d4085..22aa578 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.android.tools.r8.utils.SetUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -14,9 +15,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.Supplier;
@@ -256,6 +259,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 dcbf623..2daa142 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
@@ -54,7 +54,6 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -373,13 +372,18 @@
}
private void updatePinnedItems(Set<DexType> enumsToUnbox) {
- AppInfoWithLivenessModifier modifier = AppInfoWithLiveness.modifier();
- for (DexType type : enumsToUnbox) {
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
- assert !appView.appInfo().isPinned(clazz.type);
- modifier.removePinnedClassMembers(clazz);
- }
- modifier.modify(appView.appInfo());
+ appView
+ .appInfo()
+ .getKeepInfo()
+ .mutate(
+ keepInfo -> {
+ for (DexType type : enumsToUnbox) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ assert !keepInfo.getClassInfo(clazz).isPinned();
+ clazz.forEachProgramMethod(keepInfo::unpinMethod);
+ clazz.forEachField(field -> keepInfo.unpinField(clazz, field));
+ }
+ });
}
public void finishAnalysis() {
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..ed16561 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
@@ -7,17 +7,15 @@
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.DexMember;
-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 +144,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(field -> removePinnedIfNotHolder(field, field.type));
+ keepInfo.forEachPinnedMethod(
+ 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..8b503e6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -34,9 +34,9 @@
// 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());
+ kotlinMetadataClass == null
+ || kotlinMetadataClass.isNotProgramClass()
+ || 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 34fdfe7..4d3b6aa 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -47,11 +47,9 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
-import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.Visibility;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
@@ -133,10 +131,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. */
@@ -214,7 +216,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,
@@ -250,7 +252,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;
@@ -301,7 +303,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,
@@ -337,7 +339,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;
@@ -389,7 +391,7 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- previous.pinnedItems,
+ previous.keepInfo,
previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
@@ -440,9 +442,7 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- additionalPinnedItems == null
- ? previous.pinnedItems
- : SetUtils.newIdentityHashSet(previous.pinnedItems, additionalPinnedItems),
+ extendPinnedItems(previous, additionalPinnedItems),
previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
@@ -468,7 +468,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(
@@ -488,7 +525,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;
@@ -749,27 +786,6 @@
singleTargetLookupCache.removeInstantiatedType(clazz.type, this);
}
- void removePinnedItem(DexReference item) {
- pinnedItems.remove(item);
- }
-
- private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) {
- Set<DexType> typeSet = ImmutableSet.copyOf(types);
- for (DexReference item : items) {
- DexType typeToCheck;
- if (item.isDexType()) {
- typeToCheck = item.asDexType();
- } else if (item.isDexMethod()) {
- typeToCheck = item.asDexMethod().holder;
- } else {
- assert item.isDexField();
- typeToCheck = item.asDexField().holder;
- }
- assert !typeSet.contains(typeToCheck);
- }
- return true;
- }
-
private boolean isInstantiatedDirectly(DexProgramClass clazz) {
assert checkIfObsolete();
DexType type = clazz.type;
@@ -796,7 +812,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.
@@ -868,7 +884,7 @@
return method.getDefinition().hasCode()
&& !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
&& !neverReprocess.contains(reference)
- && !pinnedItems.contains(reference);
+ && !keepInfo.getMethodInfo(method).isPinned();
}
public boolean mayPropagateValueFor(DexReference reference) {
@@ -919,7 +935,7 @@
public boolean isPinned(DexReference reference) {
assert checkIfObsolete();
- return pinnedItems.contains(reference);
+ return keepInfo.isPinned(reference, this);
}
public boolean hasPinnedInstanceInitializer(DexType type) {
@@ -935,9 +951,8 @@
return false;
}
- public Set<DexReference> getPinnedItems() {
- assert checkIfObsolete();
- return pinnedItems;
+ public KeepInfoCollection getKeepInfo() {
+ return keepInfo;
}
/**
@@ -987,8 +1002,6 @@
.filter(Objects::nonNull)
.collect(Collectors.toList()));
- assert lens.assertReferencesNotModified(pinnedItems);
-
return new AppInfoWithLiveness(
application,
deadProtoTypes,
@@ -1011,7 +1024,7 @@
// TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
// after second tree shaking.
callSites,
- pinnedItems,
+ keepInfo.rewrite(lens),
lens.rewriteReferences(allowAccessModification),
lens.rewriteReferenceKeys(mayHaveSideEffects),
lens.rewriteReferenceKeys(noSideEffects),
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
index ae59f8b..32a8501 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.google.common.collect.Sets;
@@ -16,7 +15,6 @@
public class AppInfoWithLivenessModifier {
private final Set<DexProgramClass> noLongerInstantiatedClasses = Sets.newConcurrentHashSet();
- private final Set<DexReference> noLongerPinnedItems = Sets.newConcurrentHashSet();
private final Set<DexField> noLongerWrittenFields = Sets.newConcurrentHashSet();
AppInfoWithLivenessModifier() {}
@@ -29,10 +27,6 @@
noLongerInstantiatedClasses.add(clazz);
}
- public void removePinnedClassMembers(DexProgramClass clazz) {
- clazz.members().forEach(member -> noLongerPinnedItems.add(member.toReference()));
- }
-
public void removeWrittenField(DexField field) {
noLongerWrittenFields.add(field);
}
@@ -42,8 +36,6 @@
noLongerInstantiatedClasses.forEach(appInfo::removeFromSingleTargetLookupCache);
appInfo.mutateObjectAllocationInfoCollection(
mutator -> noLongerInstantiatedClasses.forEach(mutator::markNoLongerInstantiated));
- // Pinned items.
- noLongerPinnedItems.forEach(appInfo::removePinnedItem);
// Written fields.
FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
appInfo.getMutableFieldAccessInfoCollection();
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..57c1f32
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -0,0 +1,316 @@
+// 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.function.Consumer;
+
+// Non-mutable collection of keep information pertaining to a program.
+public abstract class KeepInfoCollection {
+
+ // TODO(b/157538235): This should not be bottom.
+ private static KeepClassInfo keepInfoForNonProgramClass() {
+ return KeepClassInfo.bottom();
+ }
+
+ // TODO(b/157538235): This should not be bottom.
+ private static KeepMethodInfo keepInfoForNonProgramMethod() {
+ return KeepMethodInfo.bottom();
+ }
+
+ // TODO(b/157538235): This should not be bottom.
+ private static KeepFieldInfo keepInfoForNonProgramField() {
+ return KeepFieldInfo.bottom();
+ }
+
+ /**
+ * 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 ? keepInfoForNonProgramClass() : 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 keepInfoForNonProgramMethod();
+ }
+ 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 keepInfoForNonProgramField();
+ }
+ 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) -> {
+ DexMethod newMethod = lens.getRenamedMethodSignature(method);
+ assert !info.isPinned() || method == newMethod;
+ newMethodInfo.put(newMethod, info);
+ });
+ Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size());
+ keepFieldInfo.forEach(
+ (field, info) -> {
+ DexField newField = lens.getRenamedFieldSignature(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());
+ }
+ }
+
+ public void unpinField(DexProgramClass holder, DexEncodedField field) {
+ assert holder.type == field.holder();
+ assert !getClassInfo(holder).isPinned();
+ KeepFieldInfo info = this.keepFieldInfo.get(field.toReference());
+ if (info != null && info.isPinned()) {
+ keepFieldInfo.put(field.toReference(), info.builder().unpin().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(