Rewrite field usage information from Enqueuer
This CL aims to clean up the whole program field usage information that is being computed by the Enqueuer.
More specifically, this CL rewrites the following fields of the Enqueuer:
SortedSet<DexField> fieldsRead;
SortedSet<DexField> fieldsWritten;
SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads;
SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads;
SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites;
into a single field of type `FieldAccessInfoCollection`.
Change-Id: I4004d42cff9a90cc1e130305548b8e0e7a30089f
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
new file mode 100644
index 0000000..975776a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/** Provides immutable access to {@link FieldAccessInfoImpl}. */
+public interface FieldAccessInfo {
+
+ DexField getField();
+
+ void forEachIndirectAccess(Consumer<DexField> consumer);
+
+ void forEachIndirectAccessWithContexts(BiConsumer<DexField, Set<DexEncodedMethod>> consumer);
+
+ boolean isRead();
+
+ boolean isWritten();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
new file mode 100644
index 0000000..340d606
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.function.Consumer;
+
+/** Provides immutable access to {@link FieldAccessInfoCollectionImpl}. */
+public interface FieldAccessInfoCollection<T extends FieldAccessInfo> {
+
+ void forEach(Consumer<T> consumer);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
new file mode 100644
index 0000000..8427d53
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.utils.SetUtils;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+
+public class FieldAccessInfoCollectionImpl
+ implements FieldAccessInfoCollection<FieldAccessInfoImpl> {
+
+ private Map<DexField, FieldAccessInfoImpl> infos = new IdentityHashMap<>();
+
+ public FieldAccessInfoImpl get(DexField field) {
+ return infos.get(field);
+ }
+
+ public void extend(DexField field, FieldAccessInfoImpl info) {
+ assert !infos.containsKey(field);
+ infos.put(field, info);
+ }
+
+ @Override
+ public void forEach(Consumer<FieldAccessInfoImpl> consumer) {
+ // Verify that the mapping os one-to-one, otherwise the caller could receive duplicates.
+ assert verifyMappingIsOneToOne();
+ infos.values().forEach(consumer);
+ }
+
+ public void removeIf(BiPredicate<DexField, FieldAccessInfoImpl> predicate) {
+ infos.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
+ }
+
+ public FieldAccessInfoCollectionImpl rewrittenWithLens(GraphLense lens) {
+ FieldAccessInfoCollectionImpl collection = new FieldAccessInfoCollectionImpl();
+ infos.forEach(
+ (field, info) ->
+ collection.infos.put(lens.lookupField(field), info.rewrittenWithLens(lens)));
+ return collection;
+ }
+
+ // This is used to verify that the temporary mappings inserted into `infos` by the Enqueuer are
+ // removed.
+ public boolean verifyMappingIsOneToOne() {
+ assert infos.values().size() == SetUtils.newIdentityHashSet(infos.values()).size();
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
new file mode 100644
index 0000000..23cb198
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Holds whole program information about the usage of a given field.
+ *
+ * <p>The information is generated by the {@link com.android.tools.r8.shaking.Enqueuer}.
+ */
+public class FieldAccessInfoImpl implements FieldAccessInfo {
+
+ public static final FieldAccessInfoImpl MISSING_FIELD_ACCESS_INFO = new FieldAccessInfoImpl(null);
+
+ // A direct reference to the definition of the field.
+ private DexField field;
+
+ // Maps every direct and indirect reference in a read-context to the set of methods in which that
+ // reference appears.
+ private Map<DexField, Set<DexEncodedMethod>> readsWithContexts;
+
+ // Maps every direct and indirect reference in a write-context to the set of methods in which that
+ // reference appears.
+ private Map<DexField, Set<DexEncodedMethod>> writesWithContexts;
+
+ public FieldAccessInfoImpl(DexField field) {
+ this.field = field;
+ }
+
+ @Override
+ public DexField getField() {
+ return field;
+ }
+
+ @Override
+ public void forEachIndirectAccess(Consumer<DexField> consumer) {
+ // There can be indirect reads and writes of the same field reference, so we need to keep track
+ // of the previously-seen indirect accesses to avoid reporting duplicates.
+ Set<DexField> visited = Sets.newIdentityHashSet();
+ forEachAccessInMap(
+ readsWithContexts, access -> access != field && visited.add(access), consumer);
+ forEachAccessInMap(
+ writesWithContexts, access -> access != field && visited.add(access), consumer);
+ }
+
+ private static void forEachAccessInMap(
+ Map<DexField, Set<DexEncodedMethod>> accessesWithContexts,
+ Predicate<DexField> predicate,
+ Consumer<DexField> consumer) {
+ if (accessesWithContexts != null) {
+ accessesWithContexts.forEach(
+ (access, contexts) -> {
+ if (predicate.test(access)) {
+ consumer.accept(access);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void forEachIndirectAccessWithContexts(
+ BiConsumer<DexField, Set<DexEncodedMethod>> consumer) {
+ Map<DexField, Set<DexEncodedMethod>> indirectAccessesWithContexts = new IdentityHashMap<>();
+ extendAccessesWithContexts(
+ indirectAccessesWithContexts, access -> access != field, readsWithContexts);
+ extendAccessesWithContexts(
+ indirectAccessesWithContexts, access -> access != field, writesWithContexts);
+ indirectAccessesWithContexts.forEach(consumer);
+ }
+
+ private void extendAccessesWithContexts(
+ Map<DexField, Set<DexEncodedMethod>> accessesWithContexts,
+ Predicate<DexField> predicate,
+ Map<DexField, Set<DexEncodedMethod>> extension) {
+ if (extension != null) {
+ extension.forEach(
+ (access, contexts) -> {
+ if (predicate.test(access)) {
+ accessesWithContexts
+ .computeIfAbsent(access, ignore -> Sets.newIdentityHashSet())
+ .addAll(contexts);
+ }
+ });
+ }
+ }
+
+ /** Returns true if this field is read by the program. */
+ @Override
+ public boolean isRead() {
+ return readsWithContexts != null && !readsWithContexts.isEmpty();
+ }
+
+ /** Returns true if this field is written by the program. */
+ @Override
+ public boolean isWritten() {
+ return writesWithContexts != null && !writesWithContexts.isEmpty();
+ }
+
+ public boolean recordRead(DexField access, DexEncodedMethod context) {
+ if (readsWithContexts == null) {
+ readsWithContexts = new IdentityHashMap<>();
+ }
+ return readsWithContexts
+ .computeIfAbsent(access, ignore -> Sets.newIdentityHashSet())
+ .add(context);
+ }
+
+ public boolean recordWrite(DexField access, DexEncodedMethod context) {
+ if (writesWithContexts == null) {
+ writesWithContexts = new IdentityHashMap<>();
+ }
+ return writesWithContexts
+ .computeIfAbsent(access, ignore -> Sets.newIdentityHashSet())
+ .add(context);
+ }
+
+ public void clearWrites() {
+ writesWithContexts = null;
+ }
+
+ public FieldAccessInfoImpl rewrittenWithLens(GraphLense lens) {
+ FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field));
+ if (readsWithContexts != null) {
+ rewritten.readsWithContexts = new IdentityHashMap<>();
+ readsWithContexts.forEach(
+ (access, contexts) ->
+ rewritten
+ .readsWithContexts
+ .computeIfAbsent(lens.lookupField(access), ignore -> Sets.newIdentityHashSet())
+ .addAll(contexts));
+ }
+ if (writesWithContexts != null) {
+ rewritten.writesWithContexts = new IdentityHashMap<>();
+ writesWithContexts.forEach(
+ (access, contexts) ->
+ rewritten
+ .writesWithContexts
+ .computeIfAbsent(lens.lookupField(access), ignore -> Sets.newIdentityHashSet())
+ .addAll(contexts));
+ }
+ return rewritten;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 437d78b..e4a2785 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -10,6 +10,8 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
@@ -218,15 +220,16 @@
}
private void renameNonReboundReferences() {
- // TODO(b/123068484): Collect non-rebound references instead of visiting all references.
- AppInfoWithLiveness appInfo = appView.appInfo();
- Sets.union(
- Sets.union(appInfo.staticFieldReads.keySet(), appInfo.staticFieldWrites.keySet()),
- Sets.union(appInfo.instanceFieldReads.keySet(), appInfo.instanceFieldWrites.keySet()))
- .forEach(this::renameNonReboundReference);
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+ appView.appInfo().getFieldAccessInfoCollection();
+ fieldAccessInfoCollection.forEach(this::renameNonReboundAccessesToField);
}
- private void renameNonReboundReference(DexField field) {
+ private void renameNonReboundAccessesToField(FieldAccessInfo fieldAccessInfo) {
+ fieldAccessInfo.forEachIndirectAccess(this::renameNonReboundAccessToField);
+ }
+
+ private void renameNonReboundAccessToField(DexField field) {
// Already renamed
if (renaming.containsKey(field)) {
return;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index de51ab3..64773de 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -12,15 +12,13 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
@@ -280,28 +278,39 @@
return null;
}
- private void computeFieldRebinding(
- Map<DexField, Set<DexEncodedMethod>> fieldsWithContexts,
- BiFunction<DexType, DexField, DexEncodedField> lookup,
- BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) {
- for (DexField field : fieldsWithContexts.keySet()) {
- DexEncodedField target = lookup.apply(field.holder, field);
- // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
- // are not visible from the access context.
- Set<DexEncodedMethod> contexts = fieldsWithContexts.get(field);
- if (target != null
- && target.field != field
- && contexts.stream()
- .allMatch(
- context ->
- isMemberVisibleFromOriginalContext(
- appView,
- context.method.holder,
- target.field.holder,
- target.accessFlags))) {
- builder.map(field,
- lense.lookupField(validTargetFor(target.field, field, lookupTargetOnClass)));
- }
+ private void computeFieldRebinding() {
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+ appView.appInfo().getFieldAccessInfoCollection();
+ fieldAccessInfoCollection.forEach(this::computeFieldRebindingForIndirectAccesses);
+ }
+
+ private void computeFieldRebindingForIndirectAccesses(FieldAccessInfo fieldAccessInfo) {
+ fieldAccessInfo.forEachIndirectAccessWithContexts(
+ this::computeFieldRebindingForIndirectAccessWithContexts);
+ }
+
+ private void computeFieldRebindingForIndirectAccessWithContexts(
+ DexField field, Set<DexEncodedMethod> contexts) {
+ DexEncodedField target = appView.appInfo().resolveField(field);
+ if (target == null) {
+ assert false;
+ return;
+ }
+
+ if (target.field == field) {
+ assert false;
+ return;
+ }
+
+ // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
+ // are not visible from the access context.
+ if (contexts.stream()
+ .allMatch(
+ context ->
+ isMemberVisibleFromOriginalContext(
+ appView, context.method.holder, target.field.holder, target.accessFlags))) {
+ builder.map(
+ field, lense.lookupField(validTargetFor(target.field, field, DexClass::lookupField)));
}
}
@@ -321,20 +330,6 @@
return memberVisibility != ConstraintWithTarget.NEVER;
}
- private Map<DexField, Set<DexEncodedMethod>> mergeFieldAccessContexts(
- Map<DexField, Set<DexEncodedMethod>> reads,
- Map<DexField, Set<DexEncodedMethod>> writes) {
- Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>();
- Set<DexField> fields = Sets.union(reads.keySet(), writes.keySet());
- for (DexField field : fields) {
- Set<DexEncodedMethod> contexts = Sets.newIdentityHashSet();
- contexts.addAll(reads.getOrDefault(field, ImmutableSet.of()));
- contexts.addAll(writes.getOrDefault(field, ImmutableSet.of()));
- result.put(field, contexts);
- }
- return Collections.unmodifiableMap(result);
- }
-
public GraphLense run() {
AppInfoWithLiveness appInfo = appView.appInfo();
// Virtual invokes are on classes, so use class resolution.
@@ -348,12 +343,7 @@
// Likewise static invokes.
computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
- computeFieldRebinding(
- mergeFieldAccessContexts(appInfo.staticFieldReads, appInfo.staticFieldWrites),
- appInfo::resolveFieldOn, DexClass::lookupField);
- computeFieldRebinding(
- mergeFieldAccessContexts(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
- appInfo::resolveFieldOn, DexClass::lookupField);
+ computeFieldRebinding();
return builder.build(lense);
}
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 ab30104..5fdc8f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
+import static com.google.common.base.Predicates.not;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
@@ -17,6 +18,10 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -53,7 +58,7 @@
*/
public final SortedSet<DexType> liveTypes;
/** Set of annotation types that are instantiated. */
- final SortedSet<DexType> instantiatedAnnotationTypes;
+ private final SortedSet<DexType> instantiatedAnnotationTypes;
/**
* Set of service types (from META-INF/services/) that may have been instantiated reflectively via
* ServiceLoader.load() or ServiceLoader.loadInstalled().
@@ -85,28 +90,16 @@
*/
public final SortedSet<DexMethod> liveMethods;
/**
- * Set of all fields which may be touched by a get operation. This is actual field definitions.
- * The set does not include kept fields nor library fields, since these are read by definition.
+ * Information about all fields that are accessed by the program. The information includes whether
+ * a given field is read/written by the program, and it also includes all indirect accesses to
+ * each field. The latter is used, for example, during member rebinding.
*/
- private final SortedSet<DexField> fieldsRead;
- /**
- * Set of all fields which may be touched by a put operation. This is actual field definitions.
- * The set does not include kept fields nor library fields, since these are written by definition.
- */
- private SortedSet<DexField> fieldsWritten;
+ private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
/**
* Set of all static fields that are only written inside the <clinit>() method of their enclosing
* class.
*/
private SortedSet<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer;
- /** Set of all field ids used in instance field reads, along with access context. */
- public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads;
- /** Set of all field ids used in instance field writes, along with access context. */
- public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
- /** Set of all field ids used in static field reads, along with access context. */
- public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads;
- /** Set of all field ids used in static field writes, along with access context. */
- public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites;
/** Set of all methods referenced in virtual invokes, along with calling context. */
public final SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes;
/** Set of all methods referenced in interface invokes, along with calling context. */
@@ -181,13 +174,8 @@
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
SortedSet<DexMethod> liveMethods,
- SortedSet<DexField> fieldsRead,
- SortedSet<DexField> fieldsWritten,
+ FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
SortedSet<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer,
- SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads,
- SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites,
- SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads,
- SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites,
SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> interfaceInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> superInvokes,
@@ -222,12 +210,7 @@
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
this.liveMethods = liveMethods;
- this.instanceFieldReads = instanceFieldReads;
- this.instanceFieldWrites = instanceFieldWrites;
- this.staticFieldReads = staticFieldReads;
- this.staticFieldWrites = staticFieldWrites;
- this.fieldsRead = fieldsRead;
- this.fieldsWritten = fieldsWritten;
+ this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
staticFieldsWrittenOnlyInEnclosingStaticInitializer;
this.pinnedItems = pinnedItems;
@@ -254,8 +237,6 @@
this.switchMaps = switchMaps;
this.ordinalsMaps = ordinalsMaps;
this.instantiatedLambdas = instantiatedLambdas;
- assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
- assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
}
public AppInfoWithLiveness(
@@ -269,13 +250,8 @@
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
SortedSet<DexMethod> liveMethods,
- SortedSet<DexField> fieldsRead,
- SortedSet<DexField> fieldsWritten,
+ FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
SortedSet<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer,
- SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads,
- SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites,
- SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads,
- SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites,
SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> interfaceInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> superInvokes,
@@ -310,12 +286,7 @@
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
this.liveMethods = liveMethods;
- this.instanceFieldReads = instanceFieldReads;
- this.instanceFieldWrites = instanceFieldWrites;
- this.staticFieldReads = staticFieldReads;
- this.staticFieldWrites = staticFieldWrites;
- this.fieldsRead = fieldsRead;
- this.fieldsWritten = fieldsWritten;
+ this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
staticFieldsWrittenOnlyInEnclosingStaticInitializer;
this.pinnedItems = pinnedItems;
@@ -342,8 +313,6 @@
this.switchMaps = switchMaps;
this.ordinalsMaps = ordinalsMaps;
this.instantiatedLambdas = instantiatedLambdas;
- assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
- assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
}
private AppInfoWithLiveness(AppInfoWithLiveness previous) {
@@ -365,13 +334,8 @@
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
previous.liveMethods,
- previous.fieldsRead,
- previous.fieldsWritten,
+ previous.fieldAccessInfoCollection,
previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer,
- previous.instanceFieldReads,
- previous.instanceFieldWrites,
- previous.staticFieldReads,
- previous.staticFieldWrites,
previous.virtualInvokes,
previous.interfaceInvokes,
previous.superInvokes,
@@ -400,8 +364,6 @@
previous.instantiatedLambdas);
copyMetadataFromPrevious(previous);
assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
- assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
- assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
}
private AppInfoWithLiveness(
@@ -421,16 +383,7 @@
this.virtualMethodsTargetedByInvokeDirect =
lense.rewriteMethodsConservatively(previous.virtualMethodsTargetedByInvokeDirect);
this.liveMethods = lense.rewriteMethodsConservatively(previous.liveMethods);
- this.instanceFieldReads =
- rewriteKeysWhileMergingValues(previous.instanceFieldReads, lense::lookupField);
- this.instanceFieldWrites =
- rewriteKeysWhileMergingValues(previous.instanceFieldWrites, lense::lookupField);
- this.staticFieldReads =
- rewriteKeysWhileMergingValues(previous.staticFieldReads, lense::lookupField);
- this.staticFieldWrites =
- rewriteKeysWhileMergingValues(previous.staticFieldWrites, lense::lookupField);
- this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
- this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
+ this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection.rewrittenWithLens(lense);
this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
rewriteItems(
previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer, lense::lookupField);
@@ -490,9 +443,6 @@
.collect(Collectors.toList()));
this.switchMaps = rewriteReferenceKeys(previous.switchMaps, lense::lookupField);
this.ordinalsMaps = rewriteReferenceKeys(previous.ordinalsMaps, lense::lookupType);
- // Sanity check sets after rewriting.
- assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
- assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
}
public AppInfoWithLiveness(
@@ -510,12 +460,7 @@
this.methodsTargetedByInvokeDynamic = previous.methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
this.liveMethods = previous.liveMethods;
- this.instanceFieldReads = previous.instanceFieldReads;
- this.instanceFieldWrites = previous.instanceFieldWrites;
- this.staticFieldReads = previous.staticFieldReads;
- this.staticFieldWrites = previous.staticFieldWrites;
- this.fieldsRead = previous.fieldsRead;
- this.fieldsWritten = previous.fieldsWritten;
+ this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer;
this.pinnedItems = previous.pinnedItems;
@@ -588,10 +533,20 @@
return this;
}
AppInfoWithLiveness result = new AppInfoWithLiveness(this);
- Predicate<DexField> isFieldWritten = field -> !noLongerWrittenFields.contains(field);
- result.fieldsWritten = filter(fieldsWritten, isFieldWritten);
+ result.fieldAccessInfoCollection.forEach(
+ info -> {
+ if (noLongerWrittenFields.contains(info.getField())) {
+ // Note that this implicitly mutates the current AppInfoWithLiveness, since the `info`
+ // instance is shared between the old and the new AppInfoWithLiveness. This should not
+ // lead to any problems, though, since the new AppInfo replaces the old AppInfo (we
+ // never use an obsolete AppInfo).
+ info.clearWrites();
+ }
+ });
result.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
- filter(staticFieldsWrittenOnlyInEnclosingStaticInitializer, isFieldWritten);
+ filter(
+ staticFieldsWrittenOnlyInEnclosingStaticInitializer,
+ not(noLongerWrittenFields::contains));
return result;
}
@@ -612,6 +567,11 @@
return switchMaps.get(field);
}
+ /** This method provides immutable access to `fieldAccessInfoCollection`. */
+ public FieldAccessInfoCollection<? extends FieldAccessInfo> getFieldAccessInfoCollection() {
+ return fieldAccessInfoCollection;
+ }
+
private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) {
Set<DexType> typeSet = ImmutableSet.copyOf(types);
for (DexReference item : items) {
@@ -664,8 +624,11 @@
public boolean isFieldRead(DexField field) {
assert checkIfObsolete();
- return fieldsRead.contains(field)
- || isPinned(field)
+ FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
+ if (info != null && info.isRead()) {
+ return true;
+ }
+ return isPinned(field)
// 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.
@@ -674,8 +637,11 @@
public boolean isFieldWritten(DexField field) {
assert checkIfObsolete();
- return fieldsWritten.contains(field)
- || isPinned(field)
+ FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
+ if (info != null && info.isWritten()) {
+ return true;
+ }
+ return isPinned(field)
// 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 rewritten.
@@ -684,7 +650,7 @@
public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexField field) {
assert checkIfObsolete();
- assert isFieldWritten(field);
+ assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
return staticFieldsWrittenOnlyInEnclosingStaticInitializer.contains(field);
}
@@ -709,19 +675,6 @@
}
private static <T extends PresortedComparable<T>, S>
- SortedMap<T, Set<S>> rewriteKeysWhileMergingValues(
- Map<T, Set<S>> original, Function<T, T> rewrite) {
- SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
- for (T item : original.keySet()) {
- T rewrittenKey = rewrite.apply(item);
- result
- .computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
- .addAll(original.get(item));
- }
- return Collections.unmodifiableSortedMap(result);
- }
-
- private static <T extends PresortedComparable<T>, S>
SortedMap<T, Set<S>> rewriteKeysConservativelyWhileMergingValues(
Map<T, Set<S>> original, Function<T, Set<T>> rewrite) {
SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
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 58caca2..66bfe83 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3,10 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
-import static com.android.tools.r8.shaking.EnqueuerUtils.extractProgramFieldDefinitions;
import static com.android.tools.r8.shaking.EnqueuerUtils.toImmutableSortedMap;
import com.android.tools.r8.Diagnostic;
@@ -43,6 +43,8 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
@@ -86,9 +88,7 @@
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
-import java.util.SortedMap;
import java.util.SortedSet;
-import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
@@ -122,14 +122,8 @@
private final Map<DexMethod, Set<DexEncodedMethod>> superInvokes = new IdentityHashMap<>();
private final Map<DexMethod, Set<DexEncodedMethod>> directInvokes = new IdentityHashMap<>();
private final Map<DexMethod, Set<DexEncodedMethod>> staticInvokes = new IdentityHashMap<>();
- private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsWritten =
- Maps.newIdentityHashMap();
- private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsRead =
- Maps.newIdentityHashMap();
- private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsRead =
- Maps.newIdentityHashMap();
- private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsWritten =
- Maps.newIdentityHashMap();
+ private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
+ new FieldAccessInfoCollectionImpl();
private final Set<DexField> staticFieldsWrittenOutsideEnclosingStaticInitializer =
Sets.newIdentityHashSet();
private final Set<DexCallSite> callSites = Sets.newIdentityHashSet();
@@ -295,20 +289,22 @@
private Set<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer() {
Set<DexField> result = Sets.newIdentityHashSet();
- Set<DexField> visited = Sets.newIdentityHashSet();
- for (Set<TargetWithContext<DexField>> targetsWithContext : staticFieldsWritten.values()) {
- for (TargetWithContext<DexField> targetWithContext : targetsWithContext) {
- DexField staticFieldWritten = targetWithContext.target;
- if (!visited.add(staticFieldWritten)) {
- continue;
- }
- DexEncodedField encodedStaticFieldWritten = appInfo.resolveField(staticFieldWritten);
- if (encodedStaticFieldWritten != null
- && encodedStaticFieldWritten.isProgramField(appInfo)) {
- result.add(encodedStaticFieldWritten.field);
- }
- }
- }
+ fieldAccessInfoCollection.forEach(
+ info -> {
+ if (info == MISSING_FIELD_ACCESS_INFO) {
+ return;
+ }
+ // Note that it is safe to use definitionFor() here, and not lookupField(), since the
+ // field held by `info` is a direct reference to the definition of the field.
+ DexEncodedField encodedField = appView.definitionFor(info.getField());
+ if (encodedField == null) {
+ assert false;
+ return;
+ }
+ if (encodedField.isProgramField(appInfo) && encodedField.isStatic() && info.isWritten()) {
+ result.add(encodedField.field);
+ }
+ });
result.removeAll(staticFieldsWrittenOutsideEnclosingStaticInitializer);
result.removeAll(
pinnedItems.stream()
@@ -407,19 +403,6 @@
// traversals.
//
- private boolean registerFieldWithTargetAndContext(
- Map<DexType, Set<TargetWithContext<DexField>>> seen,
- DexField field,
- DexEncodedMethod context) {
- DexType baseHolder = field.holder.toBaseType(appView.dexItemFactory());
- if (baseHolder.isClassType()) {
- markTypeAsLive(baseHolder);
- return seen.computeIfAbsent(field.holder, ignore -> new HashSet<>())
- .add(new TargetWithContext<>(field, context));
- }
- return false;
- }
-
private boolean registerMethodWithTargetAndContext(
Map<DexMethod, Set<DexEncodedMethod>> seen, DexMethod method, DexEncodedMethod context) {
DexType baseHolder = method.holder.toBaseType(appView.dexItemFactory());
@@ -430,6 +413,46 @@
return false;
}
+ private boolean registerFieldRead(DexField field, DexEncodedMethod context) {
+ return registerFieldAccess(field, context, true);
+ }
+
+ private boolean registerFieldWrite(DexField field, DexEncodedMethod context) {
+ return registerFieldAccess(field, context, false);
+ }
+
+ private boolean registerFieldAccess(DexField field, DexEncodedMethod context, boolean isRead) {
+ FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
+ if (info == null) {
+ DexEncodedField encodedField = appInfo.resolveField(field);
+
+ // If the field does not exist, then record this in the mapping, such that we don't have to
+ // resolve the field the next time.
+ if (encodedField == null) {
+ fieldAccessInfoCollection.extend(field, MISSING_FIELD_ACCESS_INFO);
+ return true;
+ }
+
+ // Check if we have previously created a FieldAccessInfo object for the field definition.
+ info = fieldAccessInfoCollection.get(encodedField.field);
+
+ // If not, we must create one.
+ if (info == null) {
+ info = new FieldAccessInfoImpl(encodedField.field);
+ fieldAccessInfoCollection.extend(encodedField.field, info);
+ }
+
+ // If `field` is an indirect reference, then create a mapping for it, such that we don't have
+ // to resolve the field the next time we see the reference.
+ if (field != encodedField.field) {
+ fieldAccessInfoCollection.extend(field, info);
+ }
+ } else if (info == MISSING_FIELD_ACCESS_INFO) {
+ return false;
+ }
+ return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
+ }
+
private class UseRegistry extends com.android.tools.r8.graph.UseRegistry {
private final DexEncodedMethod currentMethod;
@@ -548,7 +571,7 @@
@Override
public boolean registerInstanceFieldWrite(DexField field) {
- if (!registerFieldWithTargetAndContext(instanceFieldsWritten, field, currentMethod)) {
+ if (!registerFieldWrite(field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -561,7 +584,7 @@
@Override
public boolean registerInstanceFieldRead(DexField field) {
- if (!registerFieldWithTargetAndContext(instanceFieldsRead, field, currentMethod)) {
+ if (!registerFieldRead(field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -583,7 +606,7 @@
@Override
public boolean registerStaticFieldRead(DexField field) {
- if (!registerFieldWithTargetAndContext(staticFieldsRead, field, currentMethod)) {
+ if (!registerFieldRead(field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -595,7 +618,7 @@
@Override
public boolean registerStaticFieldWrite(DexField field) {
- if (!registerFieldWithTargetAndContext(staticFieldsWritten, field, currentMethod)) {
+ if (!registerFieldWrite(field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -1292,6 +1315,10 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking instance field `%s` as reachable.", field);
}
+
+ markTypeAsLive(field.holder);
+ markTypeAsLive(field.type);
+
DexEncodedField encodedField = appInfo.resolveField(field);
if (encodedField == null) {
reportMissingField(field);
@@ -1531,14 +1558,13 @@
ImmutableSortedSet.Builder<DexType> builder =
ImmutableSortedSet.orderedBy(PresortedComparable::slowCompareTo);
enqueuer.liveAnnotations.items.forEach(annotation -> builder.add(annotation.annotation.type));
- SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads =
- enqueuer.collectDescriptors(enqueuer.instanceFieldsRead);
- SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites =
- enqueuer.collectDescriptors(enqueuer.instanceFieldsWritten);
- SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads =
- enqueuer.collectDescriptors(enqueuer.staticFieldsRead);
- SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites =
- enqueuer.collectDescriptors(enqueuer.staticFieldsWritten);
+
+ // Remove the temporary mappings that have been inserted into the field access info collection
+ // and verify that the mapping is then one-to-one.
+ enqueuer.fieldAccessInfoCollection.removeIf(
+ (field, info) -> field != info.getField() || info == MISSING_FIELD_ACCESS_INFO);
+ assert enqueuer.fieldAccessInfoCollection.verifyMappingIsOneToOne();
+
AppInfoWithLiveness appInfoWithLiveness =
new AppInfoWithLiveness(
appInfo,
@@ -1555,24 +1581,10 @@
ImmutableSortedSet.copyOf(
DexMethod::slowCompareTo, enqueuer.virtualMethodsTargetedByInvokeDirect),
toSortedDescriptorSet(enqueuer.liveMethods.getItems()),
- // Filter out library fields and pinned fields, because these are read by default.
- extractProgramFieldDefinitions(
- instanceFieldReads.keySet(),
- staticFieldReads.keySet(),
- appInfo,
- field -> !enqueuer.pinnedItems.contains(field.field)),
- extractProgramFieldDefinitions(
- instanceFieldWrites.keySet(),
- staticFieldWrites.keySet(),
- appInfo,
- field -> !enqueuer.pinnedItems.contains(field.field)),
+ enqueuer.fieldAccessInfoCollection,
ImmutableSortedSet.copyOf(
DexField::slowCompareTo,
enqueuer.staticFieldsWrittenOnlyInEnclosingStaticInitializer()),
- instanceFieldReads,
- instanceFieldWrites,
- staticFieldReads,
- staticFieldWrites,
// TODO(b/132593519): Do we require these sets to be sorted for determinism?
toImmutableSortedMap(enqueuer.virtualInvokes, PresortedComparable::slowCompare),
toImmutableSortedMap(enqueuer.interfaceInvokes, PresortedComparable::slowCompare),
@@ -1892,20 +1904,6 @@
}
}
- private <T extends Descriptor<?, T>> SortedMap<T, Set<DexEncodedMethod>> collectDescriptors(
- Map<DexType, Set<TargetWithContext<T>>> map) {
- SortedMap<T, Set<DexEncodedMethod>> result = new TreeMap<>(PresortedComparable::slowCompare);
- for (Entry<DexType, Set<TargetWithContext<T>>> entry : map.entrySet()) {
- for (TargetWithContext<T> descriptorWithContext : entry.getValue()) {
- T descriptor = descriptorWithContext.getTarget();
- DexEncodedMethod context = descriptorWithContext.getContext();
- result.computeIfAbsent(descriptor, k -> Sets.newIdentityHashSet())
- .add(context);
- }
- }
- return Collections.unmodifiableSortedMap(result);
- }
-
private void markClassAsInstantiatedWithReason(DexClass clazz, KeepReason reason) {
assert clazz.isProgramClass();
workList.add(Action.markInstantiated(clazz, reason));
@@ -2004,13 +2002,8 @@
}
markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
// Fields accessed by reflection is marked as both read and written.
- if (encodedField.isStatic()) {
- registerFieldWithTargetAndContext(staticFieldsRead, encodedField.field, method);
- registerFieldWithTargetAndContext(staticFieldsWritten, encodedField.field, method);
- } else {
- registerFieldWithTargetAndContext(instanceFieldsRead, encodedField.field, method);
- registerFieldWithTargetAndContext(instanceFieldsWritten, encodedField.field, method);
- }
+ registerFieldRead(encodedField.field, method);
+ registerFieldWrite(encodedField.field, method);
}
} else {
assert identifierItem.isDexMethod();
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerUtils.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerUtils.java
index 6cff285..f5b90b2 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerUtils.java
@@ -4,54 +4,12 @@
package com.android.tools.r8.shaking;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.PresortedComparable;
import com.google.common.collect.ImmutableSortedMap;
-import com.google.common.collect.ImmutableSortedSet;
import java.util.Comparator;
import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.function.Predicate;
class EnqueuerUtils {
- static SortedSet<DexField> extractProgramFieldDefinitions(
- Set<DexField> instanceFields,
- Set<DexField> staticFields,
- AppInfo appInfo,
- Predicate<DexEncodedField> predicate) {
- return extractFieldDefinitions(
- instanceFields,
- staticFields,
- appInfo,
- field -> field.isProgramField(appInfo) && predicate.test(field));
- }
-
- static SortedSet<DexField> extractFieldDefinitions(
- Set<DexField> instanceFields,
- Set<DexField> staticFields,
- AppInfo appInfo,
- Predicate<DexEncodedField> predicate) {
- ImmutableSortedSet.Builder<DexField> builder =
- ImmutableSortedSet.orderedBy(PresortedComparable::slowCompareTo);
- for (DexField field : instanceFields) {
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null && predicate.test(encodedField)) {
- builder.add(encodedField.field);
- }
- }
- for (DexField field : staticFields) {
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null && predicate.test(encodedField)) {
- builder.add(encodedField.field);
- }
- }
- return builder.build();
- }
-
static <T, U> ImmutableSortedMap<T, U> toImmutableSortedMap(
Map<T, U> map, Comparator<T> comparator) {
ImmutableSortedMap.Builder<T, U> builder = new ImmutableSortedMap.Builder<>(comparator);
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 3e667a2..7b6eb10 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -119,7 +119,8 @@
public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
for (List<DexType> sourcesForTarget : sources.values()) {
for (DexType source : sourcesForTarget) {
- assert appView.appInfo().wasPruned(source);
+ assert appView.appInfo().wasPruned(source)
+ : "Expected vertically merged class `" + source.toSourceString() + "` to be absent";
}
}
return true;
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
new file mode 100644
index 0000000..f910740
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Set;
+
+public class SetUtils {
+
+ public static <T> Set<T> newIdentityHashSet(Collection<T> c) {
+ Set<T> result = Sets.newIdentityHashSet();
+ result.addAll(c);
+ return result;
+ }
+}