blob: 486fe2f0862e0e6b7d8dc6897fdba19bd03da767 [file] [log] [blame]
// 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.tools.r8.PartialOptimizationConfigurationBuilder;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.partial.predicate.AllClassesMatcher;
import com.android.tools.r8.partial.predicate.ClassNameMatcher;
import com.android.tools.r8.partial.predicate.ClassPrefixMatcher;
import com.android.tools.r8.partial.predicate.PackageAndSubpackagePrefixMatcher;
import com.android.tools.r8.partial.predicate.PackagePrefixMatcher;
import com.android.tools.r8.partial.predicate.R8PartialPredicate;
import com.android.tools.r8.partial.predicate.R8PartialPredicateCollection;
import com.android.tools.r8.partial.predicate.UnnamedPackageMatcher;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.PackageReference;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.Splitter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
import java.util.function.Consumer;
public class R8PartialCompilationConfiguration {
public static final String INCLUDE_PROPERTY_NAME =
"com.android.tools.r8.experimentalPartialShrinkingIncludePatterns";
public static final String EXCLUDE_PROPERTY_NAME =
"com.android.tools.r8.experimentalPartialShrinkingExcludePatterns";
public static final String RANDOMIZE_PROPERTY_NAME =
"com.android.tools.r8.experimentalPartialShrinkingRandomize";
public static final String RANDOMIZE_SEED_PROPERTY_NAME =
"com.android.tools.r8.experimentalPartialShrinkingRandomizeSeed";
private final boolean enabled;
private final R8PartialPredicateCollection includePredicates;
private final R8PartialPredicateCollection excludePredicates;
private final Random randomizeForTesting;
public Consumer<InternalOptions> d8DexOptionsConsumer = ConsumerUtils.emptyConsumer();
public Consumer<InternalOptions> r8OptionsConsumer = ConsumerUtils.emptyConsumer();
public boolean printPartitioningForTesting = false;
private static final R8PartialCompilationConfiguration disabledConfiguration =
new R8PartialCompilationConfiguration(false, null, null, null);
private R8PartialCompilationConfiguration(
boolean enabled,
R8PartialPredicateCollection includePredicates,
R8PartialPredicateCollection excludePredicates,
Random randomizeForTesting) {
assert !enabled || !includePredicates.isEmpty() || randomizeForTesting != null;
assert !enabled || excludePredicates != null;
this.enabled = enabled;
this.includePredicates = includePredicates;
this.excludePredicates = excludePredicates;
this.randomizeForTesting = randomizeForTesting;
}
public R8PartialPredicateCollection getIncludePredicates() {
return includePredicates;
}
public R8PartialPredicateCollection getExcludePredicates() {
return excludePredicates;
}
public void partition(
DirectMappedDexApplication app,
Consumer<DexProgramClass> d8ClassConsumer,
Consumer<DexProgramClass> r8ClassConsumer) {
Collection<DexProgramClass> classes =
randomizeForTesting != null ? app.classesWithDeterministicOrder() : app.classes();
for (DexProgramClass clazz : classes) {
if (test(clazz)) {
r8ClassConsumer.accept(clazz);
} else {
d8ClassConsumer.accept(clazz);
}
}
}
private boolean test(DexProgramClass clazz) {
if (randomizeForTesting != null) {
return randomizeForTesting.nextBoolean();
}
return includePredicates.test(clazz) && !excludePredicates.test(clazz);
}
public static R8PartialCompilationConfiguration disabledConfiguration() {
return disabledConfiguration;
}
public static R8PartialCompilationConfiguration fromIncludeExcludePatterns(
String includePatterns, String excludePatterns) {
boolean enabled = includePatterns != null || excludePatterns != null;
if (!enabled) {
return disabledConfiguration();
}
Builder builder = builder();
if (includePatterns != null) {
Splitter.on(",").splitToList(includePatterns).forEach(builder::addJavaTypeIncludePattern);
}
if (excludePatterns != null) {
Splitter.on(",").splitToList(excludePatterns).forEach(builder::addJavaTypeExcludePattern);
}
return builder.build();
}
private static String systemPropertyValue(String propertyName) {
String value = System.getProperty(propertyName);
return value != null ? value : "";
}
public static R8PartialCompilationConfiguration fromSystemProperties() {
R8PartialCompilationConfiguration configuration = fromSystemProperties(true);
if (configuration.isEnabled()) {
System.out.println("R8 partial compilation configered through system properties:");
System.out.println(
RANDOMIZE_PROPERTY_NAME + "=" + systemPropertyValue(RANDOMIZE_PROPERTY_NAME));
System.out.println(
RANDOMIZE_SEED_PROPERTY_NAME + "=" + systemPropertyValue(RANDOMIZE_SEED_PROPERTY_NAME));
System.out.println(INCLUDE_PROPERTY_NAME + "=" + systemPropertyValue(INCLUDE_PROPERTY_NAME));
System.out.println(EXCLUDE_PROPERTY_NAME + "=" + systemPropertyValue(EXCLUDE_PROPERTY_NAME));
}
return configuration;
}
public static R8PartialCompilationConfiguration fromSystemProperties(boolean printSeed) {
if (System.getProperty(RANDOMIZE_PROPERTY_NAME) != null
|| System.getProperty(RANDOMIZE_SEED_PROPERTY_NAME) != null) {
if (System.getProperty(RANDOMIZE_SEED_PROPERTY_NAME) != null) {
long seed = Long.parseLong(System.getProperty(RANDOMIZE_SEED_PROPERTY_NAME));
return builder().randomizeForTesting(printSeed, seed).build();
} else {
return builder().randomizeForTesting(printSeed).build();
}
}
return fromIncludeExcludePatterns(
System.getProperty(INCLUDE_PROPERTY_NAME), System.getProperty(EXCLUDE_PROPERTY_NAME));
}
public static Builder builder() {
return new Builder();
}
public boolean isEnabled() {
return enabled;
}
public boolean isRandomizeForTestingEnabled() {
return randomizeForTesting != null;
}
public static class Builder implements PartialOptimizationConfigurationBuilder {
private final R8PartialPredicateCollection includePredicates =
new R8PartialPredicateCollection();
private final R8PartialPredicateCollection excludePredicates =
new R8PartialPredicateCollection();
private Random randomizeForTesting;
public R8PartialCompilationConfiguration build() {
return new R8PartialCompilationConfiguration(
!includePredicates.isEmpty() || randomizeForTesting != null,
includePredicates,
excludePredicates,
randomizeForTesting);
}
public Builder includeAll() {
includePredicates.add(new AllClassesMatcher());
return this;
}
public Builder excludeAll() {
excludePredicates.add(new AllClassesMatcher());
return this;
}
public Builder randomizeForTesting(boolean printSeed) {
return randomizeForTesting(printSeed, System.currentTimeMillis());
}
public Builder randomizeForTesting(boolean printSeed, long seed) {
randomizeForTesting = new Random();
randomizeForTesting.setSeed(seed);
if (printSeed) {
System.out.println(
"Partial compilation seed: "
+ seed
+ ". Use .setPartialCompilationSeed(parameters, "
+ seed
+ "L) to reproduce.");
}
return this;
}
public Builder addJavaTypeIncludePattern(String pattern) {
includePredicates.add(
createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
return this;
}
public Builder addJavaTypeExcludePattern(String pattern) {
excludePredicates.add(
createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
return this;
}
public Builder includeClasses(Class<?>... classes) {
return includeClasses(Arrays.asList(classes));
}
public Builder includeClasses(Collection<Class<?>> classes) {
for (Class<?> clazz : classes) {
includePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz)));
}
return this;
}
public Builder excludeClasses(Class<?>... classes) {
return excludeClasses(Arrays.asList(classes));
}
public Builder excludeClasses(Collection<Class<?>> classes) {
for (Class<?> clazz : classes) {
excludePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz)));
}
return this;
}
private R8PartialPredicate createPredicate(String descriptorPrefix) {
assert descriptorPrefix.startsWith("L");
assert descriptorPrefix.indexOf('.') == -1;
if (descriptorPrefix.equals(AllClassesMatcher.PATTERN)) {
return new AllClassesMatcher();
} else if (descriptorPrefix.equals(UnnamedPackageMatcher.PATTERN)) {
return new UnnamedPackageMatcher();
} else if (descriptorPrefix.endsWith("/**")) {
return new PackageAndSubpackagePrefixMatcher(
extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 2));
} else if (descriptorPrefix.endsWith("/*")) {
return new PackagePrefixMatcher(
extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 1));
} else if (descriptorPrefix.endsWith("*")) {
return new ClassPrefixMatcher(extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 1));
} else {
return new ClassNameMatcher(
extractDescriptorPrefixWithoutWildcards(descriptorPrefix, 0) + ';');
}
}
private static String extractDescriptorPrefixWithoutWildcards(
String descriptorPattern, int numberOfWildcardsToRemove) {
String descriptorPrefixWithoutWildcards =
descriptorPattern.substring(0, descriptorPattern.length() - numberOfWildcardsToRemove);
if (descriptorPrefixWithoutWildcards.indexOf('*') >= 0) {
throw new IllegalArgumentException(
"Expected descriptor prefix without wildcards, got: "
+ descriptorPrefixWithoutWildcards);
}
return descriptorPrefixWithoutWildcards;
}
@Override
public PartialOptimizationConfigurationBuilder addClass(ClassReference classReference) {
includePredicates.add(createPredicate("L" + classReference.getBinaryName()));
return this;
}
@Override
public PartialOptimizationConfigurationBuilder addPackage(PackageReference packageReference) {
includePredicates.add(createPredicate("L" + packageReference.getPackageBinaryName() + "/*"));
return this;
}
}
}