Add logging for debugging potentially dead fields
Change-Id: I2e2fd8904024d3097d74b86f99aa0fcc45177de0
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index c8baebd..18e9055 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -645,9 +645,9 @@
executorService,
timing));
- if (Log.ENABLED) {
+ if (Log.ENABLED && Log.isLoggingEnabledFor(GeneratedExtensionRegistryShrinker.class)) {
appView.withGeneratedExtensionRegistryShrinker(
- GeneratedExtensionRegistryShrinker::logDeadProtoExtensionFields);
+ GeneratedExtensionRegistryShrinker::logRemainingProtoExtensionFields);
}
if (options.isShrinking()) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 7b4abd6..2af1aa5 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -21,6 +21,8 @@
void forEachIndirectAccessWithContexts(BiConsumer<DexField, Set<DexEncodedMethod>> consumer);
+ void forEachReadContext(Consumer<DexMethod> consumer);
+
boolean isRead();
boolean isReadOnlyIn(DexEncodedMethod method);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 6198d50..b6c54cb 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -109,6 +109,23 @@
}
}
+ @Override
+ public void forEachReadContext(Consumer<DexMethod> 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<DexMethod> visited = Sets.newIdentityHashSet();
+ if (readsWithContexts != null) {
+ for (Set<DexEncodedMethod> encodedReadContexts : readsWithContexts.values()) {
+ for (DexEncodedMethod encodedReadContext : encodedReadContexts) {
+ DexMethod readContext = encodedReadContext.method;
+ if (visited.add(readContext)) {
+ consumer.accept(readContext);
+ }
+ }
+ }
+ }
+ }
+
/** Returns true if this field is read by the program. */
@Override
public boolean isRead() {
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 cb915b2..ef2ee55 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
@@ -5,8 +5,10 @@
package com.android.tools.r8.ir.analysis.proto;
import static com.google.common.base.Predicates.alwaysFalse;
+import static com.google.common.base.Predicates.not;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -26,7 +28,16 @@
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* This optimization is responsible for pruning dead proto extensions.
@@ -196,11 +207,91 @@
}
/** For debugging. */
- public void logDeadProtoExtensionFields() {
- if (Log.isLoggingEnabledFor(GeneratedExtensionRegistryShrinker.class)) {
- forEachDeadProtoExtensionField(
- field ->
- System.out.println("Dead proto extension field: `" + field.toSourceString() + "`"));
+ public void logRemainingProtoExtensionFields() {
+ Predicate<DexField> skip = getSkipPredicate(null);
+
+ Set<DexField> remainingProtoExtensionFieldReads = Sets.newIdentityHashSet();
+ forEachFindLiteExtensionByNumberMethod(
+ method -> {
+ Log.info(
+ GeneratedExtensionRegistryShrinker.class,
+ "Extracting remaining proto extension field reads from method `%s`",
+ method.method.toSourceString());
+
+ assert method.hasCode();
+ method
+ .getCode()
+ .registerCodeReferences(
+ method,
+ new DefaultUseRegistry(appView.dexItemFactory()) {
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ if (!skip.test(field)) {
+ remainingProtoExtensionFieldReads.add(field);
+ }
+ return true;
+ }
+ });
+ });
+
+ Log.info(
+ GeneratedExtensionRegistryShrinker.class,
+ "Number of remaining proto extension fields: %s",
+ remainingProtoExtensionFieldReads.size());
+
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+ appView.appInfo().getFieldAccessInfoCollection();
+ for (DexField field : remainingProtoExtensionFieldReads) {
+ StringBuilder message = new StringBuilder(field.toSourceString());
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field);
+ fieldAccessInfo.forEachReadContext(
+ readContext ->
+ message
+ .append(System.lineSeparator())
+ .append("- ")
+ .append(readContext.toSourceString()));
+ Log.info(GeneratedExtensionRegistryShrinker.class, message.toString());
}
}
+
+ /**
+ * Utility to disable logging for proto extensions fields that are expected to be present in the
+ * output.
+ *
+ * <p>Each proto extension field that is expected to be present in the output can be added to the
+ * given file. Then no logs will be emitted for that field.
+ *
+ * <p>Example: File expected-proto-extensions.txt with lines like this:
+ *
+ * <pre>
+ * foo.bar.SomeClass.someField
+ * foo.bar.SomeOtherClass.someOtherField
+ * </pre>
+ */
+ private Predicate<DexField> getSkipPredicate(Path file) {
+ if (file != null) {
+ try {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ Set<DexField> skipFields =
+ FileUtils.readAllLines(file).stream()
+ .map(String::trim)
+ .filter(not(String::isEmpty))
+ .map(
+ x -> {
+ int separatorIndex = x.lastIndexOf(".");
+ return dexItemFactory.createField(
+ dexItemFactory.createType(
+ DescriptorUtils.javaTypeToDescriptor(x.substring(0, separatorIndex))),
+ references.generatedExtensionType,
+ dexItemFactory.createString(x.substring(separatorIndex + 1)));
+ })
+ .collect(Collectors.toSet());
+ return skipFields::contains;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return Predicates.alwaysFalse();
+ }
}