Add support for feature splits in R8 partial
Bug: b/388737195
Change-Id: I3f43f1119f4ba510c40b65a4ae9b69a6f4598e64
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 03f99fa..1478bff 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.utils.FeatureSplitConsumers;
import java.util.ArrayList;
import java.util.List;
@@ -40,9 +41,9 @@
};
private ProgramConsumer programConsumer;
- private final List<ProgramResourceProvider> programResourceProviders;
+ private List<ProgramResourceProvider> programResourceProviders;
private final AndroidResourceProvider androidResourceProvider;
- private final AndroidResourceConsumer androidResourceConsumer;
+ private AndroidResourceConsumer androidResourceConsumer;
private FeatureSplit(
ProgramConsumer programConsumer,
@@ -55,14 +56,31 @@
this.androidResourceConsumer = androidResourceConsumer;
}
- public boolean isBase() {
- return false;
- }
-
void internalSetProgramConsumer(ProgramConsumer consumer) {
this.programConsumer = consumer;
}
+ void internalSetProgramResourceProviders(List<ProgramResourceProvider> programResourceProviders) {
+ this.programResourceProviders = programResourceProviders;
+ }
+
+ FeatureSplitConsumers internalClearConsumers() {
+ FeatureSplitConsumers consumers =
+ new FeatureSplitConsumers(programConsumer, androidResourceConsumer);
+ programConsumer = null;
+ androidResourceConsumer = null;
+ return consumers;
+ }
+
+ void internalSetConsumers(FeatureSplitConsumers consumers) {
+ programConsumer = consumers.getProgramConsumer();
+ androidResourceConsumer = consumers.getAndroidResourceConsumer();
+ }
+
+ public boolean isBase() {
+ return false;
+ }
+
public List<ProgramResourceProvider> getProgramResourceProviders() {
return programResourceProviders;
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ed07e79..a6a1b23 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -230,7 +230,7 @@
} else {
if (options.partialSubCompilationConfiguration != null) {
R8PartialR8SubCompilationConfiguration r8SubCompilationConfiguration =
- options.partialSubCompilationConfiguration.asR8SubCompilationConfiguration();
+ options.partialSubCompilationConfiguration.asR8();
r8SubCompilationConfiguration.commitDexingOutputClasses(appView.withClassHierarchy());
}
ApplicationWriter.create(appView, marker).write(executorService, inputApp);
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index e0bf09a..eccfd59 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -5,6 +5,8 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.diagnostic.R8VersionDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.partial.R8PartialD8Result;
import com.android.tools.r8.partial.R8PartialInput;
@@ -13,14 +15,19 @@
import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialR8SubCompilationConfiguration;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.FeatureSplitConsumers;
import com.android.tools.r8.utils.ForwardingDiagnosticsHandler;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalProgramClassProvider;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutorService;
class R8Partial {
@@ -55,14 +62,24 @@
if (!options.getStartupOptions().getStartupProfileProviders().isEmpty()) {
throw options.reporter.fatalError("Partial shrinking does not support startup profiles");
}
+ if (options.getProguardConfiguration().isProtoShrinkingEnabled()) {
+ throw options.reporter.fatalError("Partial shrinking does not support proto shrinking");
+ }
+
+ Map<FeatureSplit, FeatureSplitConsumers> featureSplitConsumers =
+ getAndClearFeatureSplitConsumers();
timing.begin("Process input");
R8PartialInput input = runProcessInputStep(app, executor);
timing.end().begin("Run D8");
R8PartialD8Result d8Result = runD8Step(input, executor);
+ timing.end();
- timing.end().begin("Run R8");
+ setFeatureSplitConsumers(featureSplitConsumers);
+ lockFeatureSplitProgramResourceProviders();
+
+ timing.begin("Run R8");
runR8Step(app, input, d8Result, executor);
timing.end();
@@ -103,9 +120,11 @@
options.partialCompilationConfiguration.d8DexOptionsConsumer.accept(d8Options);
R8PartialD8SubCompilationConfiguration subCompilationConfiguration =
new R8PartialD8SubCompilationConfiguration(input.getD8Types(), input.getR8Types(), timing);
+ d8Options.setFeatureSplitConfiguration(options.getFeatureSplitConfiguration());
d8Options.partialSubCompilationConfiguration = subCompilationConfiguration;
D8.runInternal(d8App, d8Options, executor);
return new R8PartialD8Result(
+ subCompilationConfiguration.getClassToFeatureSplitMap(),
subCompilationConfiguration.getDexedOutputClasses(),
subCompilationConfiguration.getDesugaredOutputClasses());
}
@@ -171,7 +190,9 @@
InternalOptions r8Options = r8Command.getInternalOptions();
options.partialCompilationConfiguration.r8OptionsConsumer.accept(r8Options);
r8Options.partialSubCompilationConfiguration =
- new R8PartialR8SubCompilationConfiguration(d8Result.getDexedClasses(), timing);
+ new R8PartialR8SubCompilationConfiguration(
+ d8Result.getClassToFeatureSplitMap(), d8Result.getDexedClasses(), timing);
+ r8Options.setFeatureSplitConfiguration(options.getFeatureSplitConfiguration());
r8Options.mapConsumer = options.mapConsumer;
if (options.androidResourceProvider != null) {
r8Options.androidResourceProvider = options.androidResourceProvider;
@@ -180,4 +201,61 @@
}
R8.runInternal(r8App, r8Options, executor);
}
+
+ private Map<FeatureSplit, FeatureSplitConsumers> getAndClearFeatureSplitConsumers() {
+ FeatureSplitConfiguration featureSplitConfiguration = options.getFeatureSplitConfiguration();
+ if (featureSplitConfiguration == null) {
+ return null;
+ }
+ Map<FeatureSplit, FeatureSplitConsumers> featureSplitConsumers = new IdentityHashMap<>();
+ for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+ featureSplitConsumers.put(featureSplit, featureSplit.internalClearConsumers());
+ }
+ return featureSplitConsumers;
+ }
+
+ private void setFeatureSplitConsumers(
+ Map<FeatureSplit, FeatureSplitConsumers> featureSplitConsumers) {
+ if (featureSplitConsumers == null) {
+ return;
+ }
+ FeatureSplitConfiguration featureSplitConfiguration = options.getFeatureSplitConfiguration();
+ for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+ featureSplit.internalSetConsumers(featureSplitConsumers.get(featureSplit));
+ }
+ featureSplitConsumers.clear();
+ }
+
+ private void lockFeatureSplitProgramResourceProviders() {
+ FeatureSplitConfiguration featureSplitConfiguration = options.getFeatureSplitConfiguration();
+ if (featureSplitConfiguration == null) {
+ return;
+ }
+ for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+ List<ProgramResourceProvider> programResourceProviders =
+ featureSplit.getProgramResourceProviders();
+ List<ProgramResourceProvider> replacementProgramResourceProviders =
+ ListUtils.map(
+ programResourceProviders,
+ programResourceProvider ->
+ new ProgramResourceProvider() {
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return programResourceProvider.getDataResourceProvider();
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) throws IOException {
+ programResourceProvider.finished(handler);
+ }
+ });
+ featureSplit.internalSetProgramResourceProviders(replacementProgramResourceProviders);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index f4a0bea..e3e251c 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
@@ -17,6 +18,7 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialR8SubCompilationConfiguration;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
@@ -44,16 +46,17 @@
public static ClassToFeatureSplitMap createInitialD8ClassToFeatureSplitMap(
InternalOptions options) {
- // We only support feature splits in D8 when run through R8 partial.
- if (options.partialSubCompilationConfiguration != null) {
- return createInitialClassToFeatureSplitMap(
- options.dexItemFactory(), options.getFeatureSplitConfiguration(), options.reporter);
- }
- return createEmptyClassToFeatureSplitMap();
+ return createInitialClassToFeatureSplitMap(
+ options.dexItemFactory(), options.getFeatureSplitConfiguration(), options.reporter);
}
public static ClassToFeatureSplitMap createInitialR8ClassToFeatureSplitMap(
InternalOptions options) {
+ if (options.partialSubCompilationConfiguration != null) {
+ R8PartialR8SubCompilationConfiguration subCompilationConfiguration =
+ options.partialSubCompilationConfiguration.asR8();
+ return subCompilationConfiguration.getClassToFeatureSplitMap();
+ }
return createInitialClassToFeatureSplitMap(
options.dexItemFactory(), options.getFeatureSplitConfiguration(), options.reporter);
}
@@ -93,6 +96,23 @@
return new ClassToFeatureSplitMap(classToFeatureSplitMap, representativeStringsForFeatureSplit);
}
+ public ClassToFeatureSplitMap commitSyntheticsForR8Partial(AppView<AppInfo> appView) {
+ Map<DexType, FeatureSplit> newClassToFeatureSplitMap =
+ new IdentityHashMap<>(classToFeatureSplitMap);
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ assert !syntheticItems.hasPendingSyntheticClasses();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (syntheticItems.isSynthetic(clazz)) {
+ FeatureSplit featureSplit = getFeatureSplit(clazz, syntheticItems);
+ if (!featureSplit.isBase()) {
+ newClassToFeatureSplitMap.put(clazz.getType(), featureSplit);
+ }
+ }
+ }
+ return new ClassToFeatureSplitMap(
+ newClassToFeatureSplitMap, representativeStringsForFeatureSplit);
+ }
+
public int compareFeatureSplits(FeatureSplit featureSplitA, FeatureSplit featureSplitB) {
assert featureSplitA != null;
assert featureSplitB != null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 901e1fa..b9c1ed7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -636,7 +636,9 @@
// Since we've copied the code object from an existing method from the same class, the
// code is already processed, and thus we don't need to schedule it for processing in D8.
assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
- assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
+ assert !appView.options().isGeneratingDex()
+ || replacement.getCode().isDexCode()
+ || appView.options().partialSubCompilationConfiguration != null;
return new ProgramMethod(implMethodHolder, replacement);
}
// The method might already have been moved by another invoke-dynamic targeting it.
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java b/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
index d698676..54ee91b 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
@@ -3,20 +3,29 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.partial;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.DexProgramClass;
import java.util.Collection;
public class R8PartialD8Result {
+ private final ClassToFeatureSplitMap classToFeatureSplitMap;
private final Collection<DexProgramClass> dexedClasses;
private final Collection<DexProgramClass> desugaredClasses;
public R8PartialD8Result(
- Collection<DexProgramClass> dexedClasses, Collection<DexProgramClass> desugaredClasses) {
+ ClassToFeatureSplitMap classToFeatureSplitMap,
+ Collection<DexProgramClass> dexedClasses,
+ Collection<DexProgramClass> desugaredClasses) {
+ this.classToFeatureSplitMap = classToFeatureSplitMap;
this.dexedClasses = dexedClasses;
this.desugaredClasses = desugaredClasses;
}
+ public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
+ return classToFeatureSplitMap;
+ }
+
public Collection<DexProgramClass> getDexedClasses() {
return dexedClasses;
}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialResourceUseCollector.java b/src/main/java/com/android/tools/r8/partial/R8PartialResourceUseCollector.java
index ad8fcdf..ef26726 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialResourceUseCollector.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialResourceUseCollector.java
@@ -20,7 +20,7 @@
public void run() {
R8PartialR8SubCompilationConfiguration r8SubCompilationConfiguration =
- appView.options().partialSubCompilationConfiguration.asR8SubCompilationConfiguration();
+ appView.options().partialSubCompilationConfiguration.asR8();
ResourceShrinker.runForTesting(r8SubCompilationConfiguration.getDexingOutputClasses(), this);
}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
index ec8b0a9..cce90ee 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.partial;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -40,7 +42,7 @@
return false;
}
- public R8PartialR8SubCompilationConfiguration asR8SubCompilationConfiguration() {
+ public R8PartialR8SubCompilationConfiguration asR8() {
return null;
}
@@ -50,6 +52,7 @@
private final Set<DexType> d8Types;
private final Set<DexType> r8Types;
+ private ClassToFeatureSplitMap classToFeatureSplitMap;
private Collection<DexProgramClass> dexedOutputClasses;
private Collection<DexProgramClass> desugaredOutputClasses;
@@ -60,6 +63,10 @@
this.r8Types = r8Types;
}
+ public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
+ return classToFeatureSplitMap;
+ }
+
public Collection<DexProgramClass> getDexedOutputClasses() {
assert dexedOutputClasses != null;
return dexedOutputClasses;
@@ -103,7 +110,9 @@
return this;
}
- public void writeApplication(AppView<?> appView) {
+ public void writeApplication(AppView<AppInfo> appView) {
+ classToFeatureSplitMap =
+ appView.appInfo().getClassToFeatureSplitMap().commitSyntheticsForR8Partial(appView);
dexedOutputClasses = new ArrayList<>();
desugaredOutputClasses = new ArrayList<>();
for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -119,15 +128,22 @@
public static class R8PartialR8SubCompilationConfiguration
extends R8PartialSubCompilationConfiguration {
+ private ClassToFeatureSplitMap classToFeatureSplitMap;
private Collection<DexProgramClass> dexingOutputClasses;
public R8PartialR8SubCompilationConfiguration(
+ ClassToFeatureSplitMap classToFeatureSplitMap,
Collection<DexProgramClass> dexingOutputClasses,
Timing timing) {
super(timing);
+ this.classToFeatureSplitMap = classToFeatureSplitMap;
this.dexingOutputClasses = dexingOutputClasses;
}
+ public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
+ return classToFeatureSplitMap;
+ }
+
public Collection<DexProgramClass> getDexingOutputClasses() {
assert dexingOutputClasses != null;
return dexingOutputClasses;
@@ -173,7 +189,7 @@
}
@Override
- public R8PartialR8SubCompilationConfiguration asR8SubCompilationConfiguration() {
+ public R8PartialR8SubCompilationConfiguration asR8() {
return this;
}
}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java b/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java
index 0af71c2..5fe7e73 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java
@@ -51,7 +51,7 @@
public void run(ExecutorService executorService) throws ExecutionException {
R8PartialR8SubCompilationConfiguration r8SubCompilationConfiguration =
- appView.options().partialSubCompilationConfiguration.asR8SubCompilationConfiguration();
+ appView.options().partialSubCompilationConfiguration.asR8();
traceClasses(r8SubCompilationConfiguration.getDexingOutputClasses(), executorService);
commitPackagesToKeep();
}
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 242a7eb..3f5389f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1873,7 +1873,17 @@
analyses.traceInstanceFieldRead(
fieldReference, singleResolutionResult, currentMethod, worklist);
- ProgramField field = singleResolutionResult.getProgramField();
+ DexClassAndField classField = singleResolutionResult.getResolutionPair();
+ assert classField != null;
+
+ DexClass initialResolutionHolder = singleResolutionResult.getInitialResolutionHolder();
+ if (initialResolutionHolder != classField.getHolder()) {
+ // Mark the initial resolution holder as live. Note that this should only be done if
+ // the field is not a dead proto field (in which case we bail-out above).
+ markTypeAsLive(initialResolutionHolder, currentMethod);
+ }
+
+ ProgramField field = classField.asProgramField();
if (field == null) {
// No need to trace into the non-program code.
return;
@@ -1891,11 +1901,6 @@
fieldAccessInfoCollection.get(field.getReference()).setReadFromRecordInvokeDynamic();
}
- if (field.getReference() != fieldReference) {
- // Mark the initial resolution holder as live.
- markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
- }
-
worklist.enqueueMarkFieldAsReachableAction(
field, currentMethod, KeepReason.fieldReferencedIn(currentMethod));
},
@@ -1937,7 +1942,17 @@
analyses.traceInstanceFieldWrite(
fieldReference, singleResolutionResult, currentMethod, worklist);
- ProgramField field = singleResolutionResult.getProgramField();
+ DexClassAndField classField = singleResolutionResult.getResolutionPair();
+ assert classField != null;
+
+ DexClass initialResolutionHolder = singleResolutionResult.getInitialResolutionHolder();
+ if (initialResolutionHolder != classField.getHolder()) {
+ // Mark the initial resolution holder as live. Note that this should only be done if
+ // the field is not a dead proto field (in which case we bail-out above).
+ markTypeAsLive(initialResolutionHolder, currentMethod);
+ }
+
+ ProgramField field = classField.asProgramField();
if (field == null) {
// No need to trace into the non-program code.
return;
@@ -1953,11 +1968,6 @@
fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
}
- if (field.getReference() != fieldReference) {
- // Mark the initial resolution holder as live.
- markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
- }
-
KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
worklist.enqueueMarkFieldAsReachableAction(field, currentMethod, reason);
},
@@ -2014,7 +2024,17 @@
analyses.traceStaticFieldRead(
fieldReference, singleResolutionResult, currentMethod, worklist);
- ProgramField field = singleResolutionResult.getProgramField();
+ DexClassAndField classField = singleResolutionResult.getResolutionPair();
+ assert classField != null;
+
+ DexClass initialResolutionHolder = singleResolutionResult.getInitialResolutionHolder();
+ if (initialResolutionHolder != classField.getHolder()) {
+ // Mark the initial resolution holder as live. Note that this should only be done if
+ // the field is not a dead proto field (in which case we bail-out above).
+ markTypeAsLive(initialResolutionHolder, currentMethod);
+ }
+
+ ProgramField field = classField.asProgramField();
if (field == null) {
// No need to trace into the non-program code.
return;
@@ -2039,13 +2059,6 @@
joiner -> joiner.disallowMinification().disallowOptimization().disallowShrinking());
}
- if (field.getReference() != fieldReference) {
- // Mark the initial resolution holder as live. Note that this should only be done if
- // the field
- // is not a dead proto field (in which case we bail-out above).
- markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
- }
-
markFieldAsLive(field, currentMethod);
},
failedResolution -> {
@@ -2102,7 +2115,17 @@
analyses.traceStaticFieldWrite(
fieldReference, singleResolutionResult, currentMethod, worklist);
- ProgramField field = singleResolutionResult.getProgramField();
+ DexClassAndField classField = singleResolutionResult.getResolutionPair();
+ assert classField != null;
+
+ DexClass initialResolutionHolder = singleResolutionResult.getInitialResolutionHolder();
+ if (initialResolutionHolder != classField.getHolder()) {
+ // Mark the initial resolution holder as live. Note that this should only be done if
+ // the field is not a dead proto field (in which case we bail-out above).
+ markTypeAsLive(initialResolutionHolder, currentMethod);
+ }
+
+ ProgramField field = classField.asProgramField();
if (field == null) {
// No need to trace into the non-program code.
return;
@@ -2118,13 +2141,6 @@
fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
}
- if (field.getReference() != fieldReference) {
- // Mark the initial resolution holder as live. Note that this should only be done if
- // the field
- // is not a dead proto field (in which case we bail-out above).
- markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
- }
-
markFieldAsLive(field, currentMethod);
},
failedResolution -> {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 71121cd..b331bcb 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassResolutionResult;
import com.android.tools.r8.graph.ClasspathMethod;
@@ -666,12 +665,7 @@
private SynthesizingContext getSynthesizingContext(
ProgramDefinition context, AppView<?> appView) {
- if (appView.hasClassHierarchy()) {
- AppInfoWithClassHierarchy appInfo = appView.appInfoWithClassHierarchy();
- return getSynthesizingContext(context, appInfo.getClassToFeatureSplitMap());
- }
- return getSynthesizingContext(
- context, ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap());
+ return getSynthesizingContext(context, appView.appInfo().getClassToFeatureSplitMap());
}
/** Used to find the synthesizing context for a new synthetic that is about to be created. */
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureSplitConsumers.java b/src/main/java/com/android/tools/r8/utils/FeatureSplitConsumers.java
new file mode 100644
index 0000000..610544b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FeatureSplitConsumers.java
@@ -0,0 +1,27 @@
+// 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.utils;
+
+import com.android.tools.r8.AndroidResourceConsumer;
+import com.android.tools.r8.ProgramConsumer;
+
+public class FeatureSplitConsumers {
+
+ private final ProgramConsumer programConsumer;
+ private final AndroidResourceConsumer androidResourceConsumer;
+
+ public FeatureSplitConsumers(
+ ProgramConsumer programConsumer, AndroidResourceConsumer androidResourceConsumer) {
+ this.programConsumer = programConsumer;
+ this.androidResourceConsumer = androidResourceConsumer;
+ }
+
+ public ProgramConsumer getProgramConsumer() {
+ return programConsumer;
+ }
+
+ public AndroidResourceConsumer getAndroidResourceConsumer() {
+ return androidResourceConsumer;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index efaeb5f..9153858 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -93,6 +93,10 @@
|| name.endsWith(AAR_EXTENSION);
}
+ public static String readTextFile(Path file) throws IOException {
+ return readTextFile(file, StandardCharsets.UTF_8);
+ }
+
public static String readTextFile(Path file, Charset charset) throws IOException {
return new String(Files.readAllBytes(file), charset);
}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index df805ff..b217907 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -30,6 +30,8 @@
import com.android.tools.r8.keepanno.annotations.KeepEdge;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.lang.annotation.RetentionPolicy;
@@ -401,7 +403,12 @@
TestBase.testForR8Partial(environment.getTemp(), Backend.DEX)
.addProgramFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
- .addKeepRuleFiles(dump.getProguardConfigFile())
+ .addKeepRules(
+ // TODO(b/392529669): Add support for proto shrinking.
+ StringUtils.replaceAll(
+ FileUtils.readTextFile(dump.getProguardConfigFile()),
+ "-shrinkunusedprotofields",
+ ""))
.addR8PartialOptionsModification(
options -> {
options.apiModelingOptions().androidApiExtensionPackages =
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
index b58c940..679c4a3 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
@@ -5,10 +5,12 @@
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8PartialTestBuilder;
+import com.android.tools.r8.R8PartialTestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.benchmarks.BenchmarkBase;
import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.android.tools.r8.utils.LibraryProvidedProguardRulesTestUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -44,7 +46,8 @@
.setName("ChromeAppPartial")
.setDumpDependencyPath(dir)
.setFromRevision(16457)
- .buildR8WithPartialShrinking(ChromeBenchmarks::configurePartial));
+ .buildR8WithPartialShrinking(
+ ChromeBenchmarks::configurePartial, ChromeBenchmarks::inspectPartial));
}
private static void configure(R8FullTestBuilder testBuilder) {
@@ -60,12 +63,21 @@
private static void configurePartial(R8PartialTestBuilder testBuilder) {
testBuilder
- .allowDiagnosticInfoMessages()
+ .addDontWarn("android.adservices.common.AdServicesOutcomeReceiver")
+ .allowDiagnosticMessages()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.allowUnnecessaryDontWarnWildcards();
}
+ private static void inspectPartial(R8PartialTestCompileResult compileResult) {
+ compileResult.inspectDiagnosticMessages(
+ diagnostics ->
+ diagnostics
+ .assertWarningsMatch(LibraryProvidedProguardRulesTestUtils.getDiagnosticMatcher())
+ .assertNoErrors());
+ }
+
@Ignore
@Test
@Override
@@ -78,8 +90,6 @@
testBenchmarkWithName("ChromeApp");
}
- // TODO(b/388421578): Add support for feature splits in R8 partial.
- @Ignore
@Test
public void testChromeAppPartial() throws Exception {
testBenchmarkWithName("ChromeAppPartial");
diff --git a/tools/perf.py b/tools/perf.py
index e891508..6215f3e 100755
--- a/tools/perf.py
+++ b/tools/perf.py
@@ -20,6 +20,9 @@
'ChromeApp': {
'targets': ['r8-full']
},
+ 'ChromeAppPartial': {
+ 'targets': ['r8-full']
+ },
'CraneApp': {
'targets': ['r8-full']
},