Support resource shrinking in partial with D8 R classes
This adds resource id tracing of the R classes in the D8 part.
Bug: b/388746233
Change-Id: I2f855fea039dc65cff3f7b96a295b0b580361157
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index 3d52ede..ea253eb 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import com.android.build.shrinker.usages.R8ResourceShrinker;
import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
import com.android.tools.r8.StringConsumer.FileConsumer;
@@ -21,6 +22,8 @@
import com.android.tools.r8.partial.R8PartialInputToDumpFlags;
import com.android.tools.r8.partial.R8PartialR8Result;
import com.android.tools.r8.partial.R8PartialTraceReferencesResult;
+import com.android.tools.r8.partial.R8PartialTraceResourcesResult;
+import com.android.tools.r8.partial.ResourceTracingCallback;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.tracereferences.TraceReferencesBridge;
import com.android.tools.r8.tracereferences.TraceReferencesCommand;
@@ -29,6 +32,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ForwardingDiagnosticsHandler;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
@@ -38,6 +42,7 @@
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -83,8 +88,10 @@
R8PartialInput input = runProcessInputStep(app, timing);
R8PartialD8DexResult d8DexResult = runD8DexStep(input, executor);
+ R8PartialTraceResourcesResult resourcesTraceResult = runTraceResourcesStep(d8DexResult);
R8PartialTraceReferencesResult traceReferencesResult = runTraceReferencesStep(input);
- R8PartialR8Result r8Result = runR8PartialStep(input, traceReferencesResult, executor);
+ R8PartialR8Result r8Result =
+ runR8PartialStep(input, traceReferencesResult, resourcesTraceResult, executor);
runD8MergeStep(input, d8DexResult, r8Result, executor);
// Feed the data resource output by R8 to the output consumer. Keeping this at the end after the
@@ -93,6 +100,21 @@
timing.end();
}
+ private R8PartialTraceResourcesResult runTraceResourcesStep(R8PartialD8DexResult d8DexResult)
+ throws IOException {
+ // TODO(b/390135529): Consider tracing these in the enqueuer of R8.
+ ResourceTracingCallback resourceTracingCallback = new ResourceTracingCallback();
+ ZipUtils.iter(
+ d8DexResult.getOutputPath(),
+ (entry, input) -> {
+ if (FileUtils.isDexFile(Paths.get(entry.getName()))) {
+ R8ResourceShrinker.runResourceShrinkerAnalysis(
+ input.readAllBytes(), d8DexResult.getOutputPath(), resourceTracingCallback);
+ }
+ });
+ return new R8PartialTraceResourcesResult(resourceTracingCallback.getPotentialIds());
+ }
+
private R8PartialInput runProcessInputStep(AndroidApp androidApp, Timing timing)
throws IOException {
// Create a dump of the compiler input.
@@ -201,6 +223,7 @@
private R8PartialR8Result runR8PartialStep(
R8PartialInput input,
R8PartialTraceReferencesResult traceReferencesResult,
+ R8PartialTraceResourcesResult resourcesTraceResult,
ExecutorService executor)
throws IOException {
// Compile R8 input with R8 using the keep rules from trace references.
@@ -247,6 +270,7 @@
r8Options.androidResourceProvider = options.androidResourceProvider;
r8Options.androidResourceConsumer = options.androidResourceConsumer;
r8Options.resourceShrinkerConfiguration = options.resourceShrinkerConfiguration;
+ r8Options.d8TracedResourceIDs = resourcesTraceResult.getResourceIdsToTrace();
}
R8.runInternal(r8App, r8Options, executor);
if (r8OutputAppConsumer != null) {
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialTraceResourcesResult.java b/src/main/java/com/android/tools/r8/partial/R8PartialTraceResourcesResult.java
new file mode 100644
index 0000000..7c9a854
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialTraceResourcesResult.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2025, 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.partial;
+
+import it.unimi.dsi.fastutil.ints.IntSet;
+
+public class R8PartialTraceResourcesResult {
+
+ private final IntSet resourceIdsToTrace;
+
+ public R8PartialTraceResourcesResult(IntSet resourceIdsToTrace) {
+ this.resourceIdsToTrace = resourceIdsToTrace;
+ }
+
+ public IntSet getResourceIdsToTrace() {
+ return resourceIdsToTrace;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/ResourceTracingCallback.java b/src/main/java/com/android/tools/r8/partial/ResourceTracingCallback.java
new file mode 100644
index 0000000..d5db2c7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/ResourceTracingCallback.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2025, 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.partial;
+
+import com.android.build.shrinker.usages.AnalysisCallback;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.DescriptorUtils;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+
+public class ResourceTracingCallback implements AnalysisCallback {
+
+ private final IntSet potentialIds = new IntOpenHashSet();
+
+ public IntSet getPotentialIds() {
+ return potentialIds;
+ }
+
+ @Override
+ public boolean shouldProcess(String internalName) {
+ // Only process R classes.
+ return DescriptorUtils.isRClassDescriptor(
+ DescriptorUtils.internalNameToDescriptor(internalName));
+ }
+
+ @Override
+ public void referencedInt(int value) {
+ potentialIds.add(value);
+ }
+
+ @Override
+ public void referencedString(String value) {}
+
+ @Override
+ public void referencedStaticField(String internalName, String fieldName) {}
+
+ @Override
+ public void referencedMethod(String internalName, String methodName, String methodDescriptor) {}
+
+ @Override
+ public void startMethodVisit(MethodReference methodReference) {}
+
+ @Override
+ public void endMethodVisit(MethodReference methodReference) {}
+}
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 a2b0b80..6348333 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3936,7 +3936,7 @@
enqueueAllIfNotShrinking();
timing.end();
timing.begin("Trace");
- traceManifests(timing);
+ traceManifestsAndRoots(timing);
trace(executorService, timing);
timing.end();
options.reporter.failIfPendingErrors();
@@ -3969,10 +3969,13 @@
return result;
}
- private void traceManifests(Timing timing) {
+ private void traceManifestsAndRoots(Timing timing) {
if (options.isOptimizedResourceShrinking()) {
timing.begin("Trace AndroidManifest.xml files");
appView.getResourceShrinkerState().traceKeepXmlAndManifest();
+ for (int d8TracedResourceID : options.d8TracedResourceIDs) {
+ appView.getResourceShrinkerState().trace(d8TracedResourceID, "Non shrunken dex code");
+ }
timing.end();
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 7d8e995..d6ae1ac 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -224,6 +224,21 @@
}
/**
+ * Convert an ASM internal name to a class type descriptor
+ *
+ * @param internalName The internal name string
+ * @return type descriptor
+ */
+ public static String internalNameToDescriptor(String internalName) {
+ switch (internalName.charAt(0)) {
+ case '[':
+ return internalName;
+ default:
+ return new StringBuilder("L").append(internalName).append(";").toString();
+ }
+ }
+
+ /**
* Convert a descriptor to a classifier in Kotlin metadata
* @param descriptor like "Lorg/foo/bar/Baz$Nested;"
* @return className "org/foo/bar/Baz.Nested"
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index fca7ee0..b2044f1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -116,6 +116,8 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -197,6 +199,7 @@
public AndroidResourceProvider androidResourceProvider = null;
public AndroidResourceConsumer androidResourceConsumer = null;
public List<String> androidResourceProguardMapStrings = null;
+ public IntSet d8TracedResourceIDs = new IntOpenHashSet();
public ResourceShrinkerConfiguration resourceShrinkerConfiguration =
ResourceShrinkerConfiguration.DEFAULT_CONFIGURATION;
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingInPartialR8Test.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingInPartialR8Test.java
index 9784553..210b12f 100644
--- a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingInPartialR8Test.java
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingInPartialR8Test.java
@@ -63,14 +63,13 @@
.compile()
.inspectShrunkenResources(
resourceTableInspector -> {
- // TODO(b/388746233): We should still trace the resources when we have the R class
- // in D8 code.
- resourceTableInspector.assertDoesNotContainResourceWithName(
+ resourceTableInspector.assertContainsResourceWithName(
"string", "referencedFromD8Code");
- resourceTableInspector.assertDoesNotContainResourceWithName(
+ resourceTableInspector.assertContainsResourceWithName(
"string", "referencedFromR8Code");
- resourceTableInspector.assertDoesNotContainResourceWithName(
- "string", "unused_string");
+ // The R class is in the D8 part of the code, so we keep all entries, even
+ // unreferenced fields (since the field is still there)
+ resourceTableInspector.assertContainsResourceWithName("string", "unused_string");
})
.run(parameters.getRuntime(), InR8.class)
.assertSuccess();
@@ -79,11 +78,16 @@
private R8PartialTestBuilder getR8PartialTestBuilder(boolean rClassInD8) throws Exception {
return testForR8Partial(parameters.getBackend())
.setMinApi(parameters)
- .addProgramClasses(InR8.class, InD8.class)
- .addAndroidResources(getTestResources(temp))
- .setDefaultIncludeAll()
+ .addR8IncludedClasses(InR8.class)
.addR8ExcludedClasses(InD8.class)
- .applyIf(rClassInD8, b -> b.addR8ExcludedClasses(R.string.class))
+ .addAndroidResources(getTestResources(temp))
+ .enableOptimizedShrinking()
+ .addR8ExcludedClasses(InD8.class)
+ .applyIf(
+ rClassInD8,
+ // These classes are already added as program classes by the resource setup.
+ b -> b.addR8ExcludedClasses(false, R.string.class),
+ b -> b.addR8IncludedClasses(false, R.string.class))
.addKeepMainRule(InR8.class);
}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
index ee97ab3..9d07e01 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
@@ -30,7 +30,6 @@
// TODO(b/388763735): Enable for all API levels.
assumeTrue(parameters.canUseDefaultAndStaticInterfaceMethods());
testForR8Partial(parameters.getBackend())
- .addInnerClasses(getClass())
.addR8IncludedClasses(I.class, J.class)
.addR8ExcludedClasses(Main.class, A.class)
.setMinApi(parameters)
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
index a6bc7fa..8f45b65 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
@@ -24,7 +24,6 @@
private final ArrayList<Class<?>> includedClasses = new ArrayList<>();
private final ArrayList<Class<?>> excludedClasses = new ArrayList<>();
- private boolean defaultIncludeAll = false;
private R8PartialCompilationConfiguration r8PartialConfiguration =
R8PartialCompilationConfiguration.disabledConfiguration();
@@ -86,22 +85,33 @@
return self();
}
- public R8PartialTestBuilder setDefaultIncludeAll() {
- this.defaultIncludeAll = true;
- return self();
+ public R8PartialTestBuilder addR8IncludedClasses(Class<?>... classes) {
+ return addR8IncludedClasses(true, classes);
}
- public R8PartialTestBuilder addR8IncludedClasses(Class<?>... classes) {
+ public R8PartialTestBuilder addR8IncludedClasses(
+ boolean addAsProgramClasses, Class<?>... classes) {
assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
: "Overwriting configuration...?";
Collections.addAll(includedClasses, classes);
+ if (addAsProgramClasses) {
+ addProgramClasses(classes);
+ }
return self();
}
public R8PartialTestBuilder addR8ExcludedClasses(Class<?>... classes) {
+ return addR8ExcludedClasses(true, classes);
+ }
+
+ public R8PartialTestBuilder addR8ExcludedClasses(
+ boolean addAsProgramClasses, Class<?>... classes) {
assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
: "Overwriting configuration...?";
Collections.addAll(excludedClasses, classes);
+ if (addAsProgramClasses) {
+ addProgramClasses(classes);
+ }
return self();
}
@@ -112,12 +122,7 @@
}
R8PartialCompilationConfiguration.Builder partialBuilder =
R8PartialCompilationConfiguration.builder();
- if (defaultIncludeAll) {
- assert includedClasses.isEmpty();
- partialBuilder.includeAll();
- } else {
- partialBuilder.includeClasses(includedClasses);
- }
+ partialBuilder.includeClasses(includedClasses);
partialBuilder.excludeClasses(excludedClasses);
return partialBuilder.build();
}