Merge commit '8743dd9bd14d0194ee5ebf5ec505234c2d76365b' into dev-release
Change-Id: Iab5d12a85885a7b21c81e456b6beab489486ec18
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
index f8a35af..65cf737 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
@@ -120,18 +120,38 @@
|| project.hasProperty("one_line_per_test")
|| project.hasProperty("update_test_timestamp")) {
test.addTestListener(object : TestListener {
+ val testTimes = mutableMapOf<TestDescriptor?,Long>()
+ val maxPrintTimesCount = 1000
override fun beforeSuite(desc: TestDescriptor?) {}
- override fun afterSuite(desc: TestDescriptor?, result: TestResult?) {}
+ override fun afterSuite(desc: TestDescriptor?, result: TestResult?) {
+ if (project.hasProperty("print_times")) {
+ // desc.parent == null when we are all done
+ if (desc?.parent == null) {
+ testTimes.toList()
+ .sortedByDescending { it.second }
+ .take(maxPrintTimesCount)
+ .forEach {
+ println("${it.first} took: ${it.second}")
+ }
+ }
+ }
+ }
override fun beforeTest(desc: TestDescriptor?) {
if (project.hasProperty("one_line_per_test")) {
println("Start executing ${desc}")
}
+ if (project.hasProperty("print_times")) {
+ testTimes[desc] = Date().getTime()
+ }
}
override fun afterTest(desc: TestDescriptor?, result: TestResult?) {
if (project.hasProperty("one_line_per_test")) {
println("Done executing ${desc} with result: ${result?.resultType}")
}
+ if (project.hasProperty("print_times")) {
+ testTimes[desc] = Date().getTime() - testTimes[desc]!!
+ }
if (project.hasProperty("update_test_timestamp")) {
File(project.property("update_test_timestamp")!!.toString())
.writeText(Date().getTime().toString())
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 30432af..1094432 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -263,6 +263,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -300,6 +301,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -337,6 +339,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -374,6 +377,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -411,6 +415,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -448,6 +453,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -485,6 +491,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -522,6 +529,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -559,6 +567,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -596,6 +605,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -633,6 +643,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -670,6 +681,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -707,6 +719,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -744,6 +757,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -781,6 +795,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -818,6 +833,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -855,6 +871,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -892,6 +909,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -929,6 +947,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -966,6 +985,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1003,6 +1023,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1040,6 +1061,7 @@
' "--all_tests",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1076,6 +1098,7 @@
' "--runtimes=dex-default",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1112,6 +1135,7 @@
' "--runtimes=dex-default",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1212,6 +1236,7 @@
' "--runtimes=jdk11",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1248,6 +1273,7 @@
' "--runtimes=jdk11",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1284,6 +1310,7 @@
' "--runtimes=jdk17",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1320,6 +1347,7 @@
' "--runtimes=jdk17",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1356,6 +1384,7 @@
' "--runtimes=jdk21",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1392,6 +1421,7 @@
' "--runtimes=jdk21",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1428,6 +1458,7 @@
' "--runtimes=jdk8",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1464,6 +1495,7 @@
' "--runtimes=jdk8",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1500,6 +1532,7 @@
' "--runtimes=jdk9",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1536,6 +1569,7 @@
' "--runtimes=jdk9",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1646,6 +1680,7 @@
' "--runtimes=none",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1682,6 +1717,7 @@
' "--runtimes=none",'
' "--command_cache_dir=/tmp/ccache",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
@@ -1814,13 +1850,14 @@
' "test_options": ['
' "--all_tests",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
' ]'
'}'
priority: 26
- execution_timeout_secs: 21600
+ execution_timeout_secs: 28800
expiration_secs: 126000
build_numbers: YES
service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -1849,13 +1886,14 @@
' "test_options": ['
' "--all_tests",'
' "--tool=r8",'
+ ' "--print-times",'
' "--no_internal",'
' "--one_line_per_test",'
' "--archive_failures"'
' ]'
'}'
priority: 26
- execution_timeout_secs: 21600
+ execution_timeout_secs: 28800
expiration_secs: 126000
build_numbers: YES
service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index 613f25e..57e6c02 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@
name: "r8"
access: "group:all"
lucicfg {
- version: "1.43.4"
+ version: "1.43.5"
package_dir: ".."
config_dir: "generated"
entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 4a1764f..d6fb5d7 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -155,11 +155,14 @@
common_test_options = [
"--tool=r8",
+ "--print-times",
"--no_internal",
"--one_line_per_test",
"--archive_failures"
]
+default_timeout = time.hour * 6
+
def get_dimensions(windows=False, internal=False, archive=False):
# We use the following setup:
# windows -> always windows machine
@@ -218,7 +221,7 @@
def r8_tester(name,
test_options,
dimensions = None,
- execution_timeout = time.hour * 6,
+ execution_timeout = default_timeout,
expiration_timeout = time.hour * 35,
max_concurrent_invocations = 1,
category=None,
@@ -244,10 +247,12 @@
dimensions=None,
category=None,
release_trigger=None,
- max_concurrent_invocations = 1):
+ max_concurrent_invocations = 1,
+ execution_timeout = default_timeout):
r8_tester(name, test_options + common_test_options,
dimensions = dimensions, category = category, release_trigger=release_trigger,
- max_concurrent_invocations = max_concurrent_invocations)
+ max_concurrent_invocations = max_concurrent_invocations,
+ execution_timeout = execution_timeout)
def archivers():
for name in [
@@ -350,6 +355,7 @@
r8_tester_with_default("windows", ["--all_tests"],
dimensions=get_dimensions(windows=True),
+ execution_timeout = time.hour * 8,
max_concurrent_invocations = 2)
def internal():
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 766371d..46f1ce4 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -460,7 +460,9 @@
reporter.error(
"D8 does not support main-dex inputs and outputs when compiling to API level "
+ AndroidApiLevel.L_MR1.getLevel()
- + " and above");
+ + " and above (min API level "
+ + getMinApiLevel()
+ + " was provided)");
}
}
if (hasDesugaredLibraryConfiguration() && getDisableDesugaring()) {
@@ -479,7 +481,9 @@
reporter.error(
"D8 startup layout requires native multi dex support (API level "
+ AndroidApiLevel.L.getLevel()
- + " and above)");
+ + " and above, min API level "
+ + getMinApiLevel()
+ + " was provided)");
}
}
super.validate();
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 2ef79c8..fa0a094 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
import com.android.tools.r8.profile.startup.StartupProfileProviderUtils;
@@ -19,12 +20,15 @@
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
public class R8CommandParser extends BaseCompilerCommandParser<R8Command, R8Command.Builder> {
@@ -41,6 +45,7 @@
"--main-dex-rules",
"--main-dex-list",
"--feature",
+ "--android-resources",
"--main-dex-list-output",
"--pg-conf",
"--pg-conf-output",
@@ -57,7 +62,7 @@
// Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS =
- ImmutableSet.of(ART_PROFILE_FLAG, "--feature");
+ ImmutableSet.of(ART_PROFILE_FLAG, "--feature", "--android-resources");
// Due to the family of flags (for assertions and diagnostics) we can't base the one/two args
// on this setup of flags. Thus, the flag collection just encodes the descriptive content.
@@ -96,11 +101,21 @@
.add(ParseFlagInfoImpl.getMainDexList())
.add(
flag2(
- "--feature",
+ "--android-resources",
"<input>",
"<output>",
+ "Add android resource input and output to be used in resource shrinking. Both ",
+ "input and output must be specified."))
+ .add(
+ flag2(
+ "--feature",
+ "<input>[:|;<res-input>]",
+ "<output>[:|;<res-output>]",
"Add feature <input> file to <output> file. Several ",
- "occurrences can map to the same output."))
+ "occurrences can map to the same output. If <res-input> and <res-output> are ",
+ "specified use these as resource shrinker input and output. Separator is : on ",
+ "linux/mac, ; on windows. It is possible to supply resource only features by ",
+ " using an empty string for <input> and <output>, e.g. --feature :in.ap_ :out.ap_"))
.add(ParseFlagInfoImpl.getIsolatedSplits())
.add(flag1("--main-dex-list-output", "<file>", "Output the full main-dex list in <file>."))
.addAll(ParseFlagInfoImpl.getAssertionsFlags())
@@ -193,7 +208,7 @@
private void parse(
String[] args, Origin argsOrigin, R8Command.Builder builder, ParseState state) {
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
- Map<Path, List<Path>> featureSplitJars = new HashMap<>();
+ FeatureSplitConfigCollector featureSplitConfigCollector = new FeatureSplitConfigCollector();
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
@@ -290,10 +305,15 @@
builder.setDisableDesugaring(true);
} else if (arg.equals("--main-dex-rules")) {
builder.addMainDexRulesFiles(Paths.get(nextArg));
+ } else if (arg.equals("--android-resources")) {
+ Path inputPath = Paths.get(nextArg);
+ Path outputPath = Paths.get(nextNextArg);
+ builder.setAndroidResourceProvider(
+ new ArchiveProtoAndroidResourceProvider(inputPath, new PathOrigin(inputPath)));
+ builder.setAndroidResourceConsumer(
+ new ArchiveProtoAndroidResourceConsumer(outputPath, inputPath));
} else if (arg.equals("--feature")) {
- featureSplitJars
- .computeIfAbsent(Paths.get(nextNextArg), k -> new ArrayList<>())
- .add(Paths.get(nextArg));
+ featureSplitConfigCollector.addInputOutput(nextArg, nextNextArg);
} else if (arg.equals(ISOLATED_SPLITS_FLAG)) {
builder.setEnableIsolatedSplits(true);
} else if (arg.equals("--main-dex-list")) {
@@ -358,20 +378,117 @@
builder.addProgramFiles(Paths.get(arg));
}
}
- featureSplitJars.forEach(
- (outputPath, inputJars) -> addFeatureJar(builder, outputPath, inputJars));
+ addFeatureSplitConfigs(builder, featureSplitConfigCollector.getConfigs());
}
- public void addFeatureJar(R8Command.Builder builder, Path outputPath, List<Path> inputJarPaths) {
- builder.addFeatureSplit(
- featureSplitGenerator -> {
- featureSplitGenerator.setProgramConsumer(
- builder.createProgramOutputConsumer(outputPath, OutputMode.DexIndexed, true));
- for (Path inputPath : inputJarPaths) {
- featureSplitGenerator.addProgramResourceProvider(
- ArchiveProgramResourceProvider.fromArchive(inputPath));
- }
- return featureSplitGenerator.build();
- });
+ private void addFeatureSplitConfigs(
+ R8Command.Builder builder, Collection<FeatureSplitConfig> featureSplitConfigs) {
+ for (FeatureSplitConfig featureSplitConfig : featureSplitConfigs) {
+ builder.addFeatureSplit(
+ featureSplitGenerator -> {
+ if (featureSplitConfig.outputJar != null) {
+ featureSplitGenerator.setProgramConsumer(
+ builder.createProgramOutputConsumer(
+ featureSplitConfig.outputJar, OutputMode.DexIndexed, true));
+ }
+ for (Path inputPath : featureSplitConfig.inputJars) {
+ featureSplitGenerator.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(inputPath));
+ }
+ if (featureSplitConfig.inputResources != null) {
+ featureSplitGenerator.setAndroidResourceProvider(
+ new ArchiveProtoAndroidResourceProvider(
+ featureSplitConfig.inputResources,
+ new PathOrigin(featureSplitConfig.inputResources)));
+ }
+ if (featureSplitConfig.outputResources != null) {
+ featureSplitGenerator.setAndroidResourceConsumer(
+ new ArchiveProtoAndroidResourceConsumer(
+ featureSplitConfig.outputResources, featureSplitConfig.inputResources));
+ }
+ return featureSplitGenerator.build();
+ });
+ }
+ }
+
+ // Represents a set of paths parsed from a string that may contain a ":" (";" on windows).
+ // Supported examples are:
+ // pathA -> first = pathA, second = null
+ // pathA:pathB -> first = pathA, second = pathB
+ // :pathB -> first = null, second = pathB
+ // pathA: -> first = pathA, second = null
+ private static class PossibleDoublePath {
+
+ public final Path first;
+ public final Path second;
+
+ private PossibleDoublePath(Path first, Path second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ public static PossibleDoublePath parse(String input) {
+ Path first = null, second = null;
+ List<String> inputSplit = StringUtils.split(input, File.pathSeparatorChar);
+ if (inputSplit.size() == 0 || inputSplit.size() > 2) {
+ throw new IllegalArgumentException("Feature input/output takes one or two paths.");
+ }
+ String firstString = inputSplit.get(0);
+ if (!firstString.isEmpty()) {
+ first = Paths.get(firstString);
+ }
+ if (inputSplit.size() == 2) {
+ // "a:".split() gives just ["a"], so we should never get here if we don't have
+ // a second string. ":b".split gives ["", "b"] which is handled for first above.
+ assert inputSplit.get(1).length() > 0;
+ second = Paths.get(inputSplit.get(1));
+ }
+ return new PossibleDoublePath(first, second);
+ }
+ }
+
+ private static class FeatureSplitConfig {
+ private List<Path> inputJars = new ArrayList<>();
+ private Path inputResources;
+ private Path outputResources;
+ private Path outputJar;
+ }
+
+ private static class FeatureSplitConfigCollector {
+
+ private List<FeatureSplitConfig> resourceOnlySplits = new ArrayList<>();
+ private Map<Path, FeatureSplitConfig> withCodeSplits = new HashMap<>();
+
+ public void addInputOutput(String input, String output) {
+ PossibleDoublePath inputPaths = PossibleDoublePath.parse(input);
+ PossibleDoublePath outputPaths = PossibleDoublePath.parse(output);
+ FeatureSplitConfig featureSplitConfig;
+ if (outputPaths.first != null) {
+ featureSplitConfig =
+ withCodeSplits.computeIfAbsent(outputPaths.first, k -> new FeatureSplitConfig());
+ featureSplitConfig.outputJar = outputPaths.first;
+ // We support adding resources independently of the input jars, which later --feature
+ // can add, so we might have no input jars here, example:
+ // ... --feature :input_feature.ap_ out.jar:out_feature.ap_ --feature in.jar out.jar
+ if (inputPaths.first != null) {
+ featureSplitConfig.inputJars.add(inputPaths.first);
+ }
+ } else {
+ featureSplitConfig = new FeatureSplitConfig();
+ resourceOnlySplits.add(featureSplitConfig);
+ }
+ if (Objects.isNull(inputPaths.second) != Objects.isNull(outputPaths.second)) {
+ throw new IllegalArgumentException(
+ "Both input and output for feature resources must be provided");
+ }
+ featureSplitConfig.inputResources = inputPaths.second;
+ featureSplitConfig.outputResources = outputPaths.second;
+ }
+
+ public Collection<FeatureSplitConfig> getConfigs() {
+ ArrayList<FeatureSplitConfig> featureSplitConfigs = new ArrayList<>(resourceOnlySplits);
+ featureSplitConfigs.addAll(withCodeSplits.values());
+ return featureSplitConfigs;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 1290cc8..e096d1e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -196,7 +196,7 @@
timing.time(
"Compilation context", () -> CompilationContext.createInitialContext(options()));
this.wholeProgramOptimizations = wholeProgramOptimizations;
- abstractValueFactory = new AbstractValueFactory(options());
+ abstractValueFactory = new AbstractValueFactory();
abstractValueConstantPropagationJoiner = new AbstractValueConstantPropagationJoiner(this);
if (enableWholeProgramOptimizations()) {
abstractValueFieldJoiner = new AbstractValueFieldJoiner(withClassHierarchy());
@@ -542,7 +542,7 @@
}
public ComposeReferences getComposeReferences() {
- assert testing().modelUnknownChangedAndDefaultArgumentsToComposableFunctions;
+ assert options().getJetpackComposeOptions().isAnyOptimizationsEnabled();
if (composeReferences == null) {
composeReferences = new ComposeReferences(dexItemFactory());
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index e6ac132..b9f074c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -35,7 +35,8 @@
this.appView = appView;
this.fieldBitAccessAnalysis =
options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null;
- this.fieldAssignmentTracker = new FieldAssignmentTracker(appView);
+ this.fieldAssignmentTracker =
+ options.enableFieldAssignmentTracker ? new FieldAssignmentTracker(appView) : null;
this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView);
this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index c1a2b62..1a64882 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EmptyStaticValues;
import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -64,6 +65,9 @@
assert appView.appInfo().hasLiveness();
assert appView.enableWholeProgramOptimizations();
assert code.context().getDefinition().isClassInitializer();
+ if (!appView.options().enableFieldValueAnalysis) {
+ return EmptyStaticValues.getInstance();
+ }
timing.begin("Analyze class initializer");
StaticFieldValues result =
new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index e050ed4..fdb9d4b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -193,6 +193,14 @@
return null;
}
+ public boolean isSingleStatefulFieldValue() {
+ return false;
+ }
+
+ public boolean isSingleStatelessFieldValue() {
+ return false;
+ }
+
public SingleNullValue asSingleNullValue() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 567d39a..27534c2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -14,14 +14,10 @@
import com.android.tools.r8.ir.analysis.value.objectstate.KnownLengthArrayState;
import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import java.util.concurrent.ConcurrentHashMap;
public class AbstractValueFactory {
- private final TestingOptions testingOptions;
-
private final ConcurrentHashMap<DexType, SingleConstClassValue> singleConstClassValues =
new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues =
@@ -33,10 +29,6 @@
private final ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
new ConcurrentHashMap<>();
- public AbstractValueFactory(InternalOptions options) {
- testingOptions = options.testing;
- }
-
public SingleBoxedBooleanValue createBoxedBooleanFalse() {
return SingleBoxedBooleanValue.getFalseInstance();
}
@@ -83,19 +75,10 @@
if (definitelySetBits != 0 || definitelyUnsetBits != 0) {
// If all bits are known, then create a single number value.
boolean allBitsSet = (definitelySetBits | definitelyUnsetBits) == ALL_BITS_SET_MASK;
- // Account for the temporary hack in the Compose modeling where we create a
- // DefiniteBitsNumberValue with set bits=0b1^32 and unset bits = 0b1^(31)0. This value is used
- // to simulate the effect of `x | 1` in joins.
- if (testingOptions.modelUnknownChangedAndDefaultArgumentsToComposableFunctions) {
- boolean overlappingSetAndUnsetBits = (definitelySetBits & definitelyUnsetBits) != 0;
- if (overlappingSetAndUnsetBits) {
- allBitsSet = false;
- }
- }
if (allBitsSet) {
return createUncheckedSingleNumberValue(definitelySetBits);
}
- return new DefiniteBitsNumberValue(definitelySetBits, definitelyUnsetBits, testingOptions);
+ return new DefiniteBitsNumberValue(definitelySetBits, definitelyUnsetBits);
}
return AbstractValue.unknown();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
index a18281e..778e61b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.android.tools.r8.utils.OptionalBool;
import java.util.Objects;
@@ -17,10 +16,8 @@
private final int definitelySetBits;
private final int definitelyUnsetBits;
- public DefiniteBitsNumberValue(
- int definitelySetBits, int definitelyUnsetBits, TestingOptions testingOptions) {
- assert (definitelySetBits & definitelyUnsetBits) == 0
- || testingOptions.modelUnknownChangedAndDefaultArgumentsToComposableFunctions;
+ public DefiniteBitsNumberValue(int definitelySetBits, int definitelyUnsetBits) {
+ assert (definitelySetBits & definitelyUnsetBits) == 0;
this.definitelySetBits = definitelySetBits;
this.definitelyUnsetBits = definitelyUnsetBits;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
index 7d06ddf..580d2d9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
@@ -40,6 +40,11 @@
}
@Override
+ public boolean isSingleStatefulFieldValue() {
+ return true;
+ }
+
+ @Override
public String toString() {
return "SingleStatefulFieldValue(" + field.toSourceString() + ")";
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
index 7b55767..be620a8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
@@ -25,6 +25,11 @@
}
@Override
+ public boolean isSingleStatelessFieldValue() {
+ return true;
+ }
+
+ @Override
public String toString() {
return "SingleStatelessFieldValue(" + field.toSourceString() + ")";
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java b/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
new file mode 100644
index 0000000..ed6c059
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2024, 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.ir.analysis.value.arithmetic;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.utils.BitUtils;
+
+public class AbstractCalculator {
+
+ public static AbstractValue andIntegers(
+ AppView<?> appView, AbstractValue left, AbstractValue right) {
+ if (left.isZero()) {
+ return left;
+ }
+ if (right.isZero()) {
+ return right;
+ }
+ if (left.isSingleNumberValue() && right.isSingleNumberValue()) {
+ int result =
+ left.asSingleNumberValue().getIntValue() & right.asSingleNumberValue().getIntValue();
+ return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation()
+ && right.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(
+ left.getDefinitelySetIntBits() & right.getDefinitelySetIntBits(),
+ left.getDefinitelyUnsetIntBits() | right.getDefinitelyUnsetIntBits());
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(0, left.getDefinitelyUnsetIntBits());
+ }
+ if (right.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(0, right.getDefinitelyUnsetIntBits());
+ }
+ return AbstractValue.unknown();
+ }
+
+ public static AbstractValue orIntegers(
+ AppView<?> appView, AbstractValue left, AbstractValue right) {
+ if (left.isZero()) {
+ return right;
+ }
+ if (right.isZero()) {
+ return left;
+ }
+ if (left.isSingleNumberValue() && right.isSingleNumberValue()) {
+ int result =
+ left.asSingleNumberValue().getIntValue() | right.asSingleNumberValue().getIntValue();
+ return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation()
+ && right.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(
+ left.getDefinitelySetIntBits() | right.getDefinitelySetIntBits(),
+ left.getDefinitelyUnsetIntBits() & right.getDefinitelyUnsetIntBits());
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(left.getDefinitelySetIntBits(), 0);
+ }
+ if (right.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(right.getDefinitelySetIntBits(), 0);
+ }
+ return AbstractValue.unknown();
+ }
+
+ public static AbstractValue orIntegers(
+ AppView<?> appView,
+ AbstractValue first,
+ AbstractValue second,
+ AbstractValue third,
+ AbstractValue fourth) {
+ return orIntegers(
+ appView, first, orIntegers(appView, second, orIntegers(appView, third, fourth)));
+ }
+
+ public static AbstractValue shlIntegers(
+ AppView<?> appView, AbstractValue left, AbstractValue right) {
+ if (!right.isSingleNumberValue()) {
+ return AbstractValue.unknown();
+ }
+ int rightConst = right.asSingleNumberValue().getIntValue();
+ return shlIntegers(appView, left, rightConst);
+ }
+
+ public static AbstractValue shlIntegers(AppView<?> appView, AbstractValue left, int rightConst) {
+ if (rightConst == 0) {
+ return left;
+ }
+ if (left.isSingleNumberValue()) {
+ int result = left.asSingleNumberValue().getIntValue() << rightConst;
+ return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
+ // Shift the known bits and add that we now know that the lowermost n bits are definitely
+ // unset. Note that when rightConst is 31, 1 << rightConst is Integer.MIN_VALUE. When
+ // subtracting 1 we overflow and get 0111...111, as desired.
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(
+ left.getDefinitelySetIntBits() << rightConst,
+ (left.getDefinitelyUnsetIntBits() << rightConst) | ((1 << rightConst) - 1));
+ }
+ return AbstractValue.unknown();
+ }
+
+ public static AbstractValue shrIntegers(
+ AppView<?> appView, AbstractValue left, AbstractValue right) {
+ if (!right.isSingleNumberValue()) {
+ return AbstractValue.unknown();
+ }
+ int rightConst = right.asSingleNumberValue().getIntValue();
+ return shrIntegers(appView, left, rightConst);
+ }
+
+ public static AbstractValue shrIntegers(AppView<?> appView, AbstractValue left, int rightConst) {
+ if (rightConst == 0) {
+ return left;
+ }
+ if (left.isSingleNumberValue()) {
+ int result = left.asSingleNumberValue().getIntValue() >> rightConst;
+ return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(
+ left.getDefinitelySetIntBits() >> rightConst,
+ left.getDefinitelyUnsetIntBits() >> rightConst);
+ }
+ return AbstractValue.unknown();
+ }
+
+ public static AbstractValue ushrIntegers(
+ AppView<?> appView, AbstractValue left, AbstractValue right) {
+ if (!right.isSingleNumberValue()) {
+ return AbstractValue.unknown();
+ }
+ int rightConst = right.asSingleNumberValue().getIntValue();
+ if (rightConst == 0) {
+ return left;
+ }
+ if (left.isSingleNumberValue()) {
+ int result = left.asSingleNumberValue().getIntValue() >>> rightConst;
+ return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
+ // Shift the known bits information and add that we now know that the uppermost n bits are
+ // definitely unset.
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(
+ left.getDefinitelySetIntBits() >>> rightConst,
+ (left.getDefinitelyUnsetIntBits() >>> rightConst)
+ | (BitUtils.ONLY_SIGN_BIT_SET_MASK >> (rightConst - 1)));
+ }
+ return AbstractValue.unknown();
+ }
+
+ public static AbstractValue xorIntegers(
+ AppView<?> appView, AbstractValue left, AbstractValue right) {
+ if (left.isSingleNumberValue() && right.isSingleNumberValue()) {
+ int result =
+ left.asSingleNumberValue().getIntValue() ^ right.asSingleNumberValue().getIntValue();
+ return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ }
+ if (left.hasDefinitelySetAndUnsetBitsInformation()
+ && right.hasDefinitelySetAndUnsetBitsInformation()) {
+ return appView
+ .abstractValueFactory()
+ .createDefiniteBitsNumberValue(
+ (left.getDefinitelySetIntBits() & right.getDefinitelyUnsetIntBits())
+ | (left.getDefinitelyUnsetIntBits() & right.getDefinitelySetIntBits()),
+ (left.getDefinitelySetIntBits() & right.getDefinitelySetIntBits())
+ | (left.getDefinitelyUnsetIntBits() & right.getDefinitelyUnsetIntBits()));
+ }
+ return AbstractValue.unknown();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
index 1f2b960..d16e2b0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EmptyObjectState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -29,7 +28,7 @@
}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ public AbstractValue getAbstractFieldValue(DexField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
index 28652ca..25a9566 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.lens.GraphLens;
@@ -40,7 +39,7 @@
public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ public AbstractValue getAbstractFieldValue(DexField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
index f05e962..a773fd2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -27,7 +26,7 @@
}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ public AbstractValue getAbstractFieldValue(DexField field) {
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
index 3b73b9d..7acebdd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/NonEmptyObjectState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value.objectstate;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -32,8 +31,8 @@
}
@Override
- public AbstractValue getAbstractFieldValue(DexEncodedField field) {
- return state.getOrDefault(field.getReference(), UnknownValue.getInstance());
+ public AbstractValue getAbstractFieldValue(DexField field) {
+ return state.getOrDefault(field, UnknownValue.getInstance());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
index 3b02fc4..271cd94 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
@@ -47,7 +47,11 @@
return predicate.test(singleValue);
}
- public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
+ public final AbstractValue getAbstractFieldValue(DexEncodedField field) {
+ return getAbstractFieldValue(field.getReference());
+ }
+
+ public abstract AbstractValue getAbstractFieldValue(DexField field);
public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
index dea5836..f53f615 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectStateAnalysis.java
@@ -20,13 +20,13 @@
public static ObjectState computeObjectState(
Value value, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
- assert !value.hasAliasedValue();
- if (value.isDefinedByInstructionSatisfying(
+ Value valueRoot = value.getAliasedValue();
+ if (valueRoot.isDefinedByInstructionSatisfying(
i -> i.isNewArrayEmpty() || i.isNewArrayFilledData() || i.isNewArrayFilled())) {
- return computeNewArrayObjectState(value, appView, context);
+ return computeNewArrayObjectState(valueRoot, appView, context);
}
- if (value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
- return computeNewInstanceObjectState(value, appView, context);
+ if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+ return computeNewInstanceObjectState(valueRoot, appView, context);
}
return ObjectState.empty();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/And.java b/src/main/java/com/android/tools/r8/ir/code/And.java
index 2639a9d..522c7c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/And.java
+++ b/src/main/java/com/android/tools/r8/ir/code/And.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.dex.code.DexInstruction;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
import java.util.Set;
public class And extends LogicalBinop {
@@ -104,37 +105,7 @@
@Override
AbstractValue foldIntegers(AbstractValue left, AbstractValue right, AppView<?> appView) {
- if (left.isZero()) {
- return left;
- }
- if (right.isZero()) {
- return right;
- }
- if (left.isSingleNumberValue() && right.isSingleNumberValue()) {
- int result =
- foldIntegers(
- left.asSingleNumberValue().getIntValue(), right.asSingleNumberValue().getIntValue());
- return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation()
- && right.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- foldIntegers(left.getDefinitelySetIntBits(), right.getDefinitelySetIntBits()),
- left.getDefinitelyUnsetIntBits() | right.getDefinitelyUnsetIntBits());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(0, left.getDefinitelyUnsetIntBits());
- }
- if (right.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(0, right.getDefinitelyUnsetIntBits());
- }
- return AbstractValue.unknown();
+ return AbstractCalculator.andIntegers(appView, left, right);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
index 6b7f62c..c1dee99 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -19,6 +19,8 @@
boolean isInstanceGet();
+ InstanceGet asInstanceGet();
+
boolean isStaticGet();
boolean hasUsedOutValue();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Or.java b/src/main/java/com/android/tools/r8/ir/code/Or.java
index 0cc297a..8fd53b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Or.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Or.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.dex.code.DexOrLong2Addr;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
import java.util.Set;
public class Or extends LogicalBinop {
@@ -103,37 +104,7 @@
@Override
AbstractValue foldIntegers(AbstractValue left, AbstractValue right, AppView<?> appView) {
- if (left.isZero()) {
- return right;
- }
- if (right.isZero()) {
- return left;
- }
- if (left.isSingleNumberValue() && right.isSingleNumberValue()) {
- int result =
- foldIntegers(
- left.asSingleNumberValue().getIntValue(), right.asSingleNumberValue().getIntValue());
- return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation()
- && right.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- foldIntegers(left.getDefinitelySetIntBits(), right.getDefinitelySetIntBits()),
- left.getDefinitelyUnsetIntBits() & right.getDefinitelyUnsetIntBits());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(left.getDefinitelySetIntBits(), 0);
- }
- if (right.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(right.getDefinitelySetIntBits(), 0);
- }
- return AbstractValue.unknown();
+ return AbstractCalculator.orIntegers(appView, left, right);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shl.java b/src/main/java/com/android/tools/r8/ir/code/Shl.java
index a9d9ee4..73c84e8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shl.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shl.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
public class Shl extends LogicalBinop {
@@ -98,28 +99,7 @@
@Override
AbstractValue foldIntegers(AbstractValue left, AbstractValue right, AppView<?> appView) {
- if (!right.isSingleNumberValue()) {
- return AbstractValue.unknown();
- }
- int rightConst = right.asSingleNumberValue().getIntValue();
- if (rightConst == 0) {
- return left;
- }
- if (left.isSingleNumberValue()) {
- int result = foldIntegers(left.asSingleNumberValue().getIntValue(), rightConst);
- return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
- // Shift the known bits and add that we now know that the lowermost n bits are definitely
- // unset. Note that when rightConst is 31, 1 << rightConst is Integer.MIN_VALUE. When
- // subtracting 1 we overflow and get 0111...111, as desired.
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- foldIntegers(left.getDefinitelySetIntBits(), rightConst),
- foldIntegers(left.getDefinitelyUnsetIntBits(), rightConst) | ((1 << rightConst) - 1));
- }
- return AbstractValue.unknown();
+ return AbstractCalculator.shlIntegers(appView, left, right);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shr.java b/src/main/java/com/android/tools/r8/ir/code/Shr.java
index bc59c79..52c7cb0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shr.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
public class Shr extends LogicalBinop {
@@ -98,25 +99,7 @@
@Override
AbstractValue foldIntegers(AbstractValue left, AbstractValue right, AppView<?> appView) {
- if (!right.isSingleNumberValue()) {
- return AbstractValue.unknown();
- }
- int rightConst = right.asSingleNumberValue().getIntValue();
- if (rightConst == 0) {
- return left;
- }
- if (left.isSingleNumberValue()) {
- int result = foldIntegers(left.asSingleNumberValue().getIntValue(), rightConst);
- return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- foldIntegers(left.getDefinitelySetIntBits(), rightConst),
- foldIntegers(left.getDefinitelyUnsetIntBits(), rightConst));
- }
- return AbstractValue.unknown();
+ return AbstractCalculator.shrIntegers(appView, left, right);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Ushr.java b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
index 104b90d..c80f280 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Ushr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.utils.BitUtils;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
public class Ushr extends LogicalBinop {
@@ -99,28 +99,7 @@
@Override
AbstractValue foldIntegers(AbstractValue left, AbstractValue right, AppView<?> appView) {
- if (!right.isSingleNumberValue()) {
- return AbstractValue.unknown();
- }
- int rightConst = right.asSingleNumberValue().getIntValue();
- if (rightConst == 0) {
- return left;
- }
- if (left.isSingleNumberValue()) {
- int result = foldIntegers(left.asSingleNumberValue().getIntValue(), rightConst);
- return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation() && rightConst > 0) {
- // Shift the known bits information and add that we now know that the uppermost n bits are
- // definitely unset.
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- foldIntegers(left.getDefinitelySetIntBits(), rightConst),
- foldIntegers(left.getDefinitelyUnsetIntBits(), rightConst)
- | (BitUtils.ONLY_SIGN_BIT_SET_MASK >> (rightConst - 1)));
- }
- return AbstractValue.unknown();
+ return AbstractCalculator.ushrIntegers(appView, left, right);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Xor.java b/src/main/java/com/android/tools/r8/ir/code/Xor.java
index 5116990..bddba7e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Xor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Xor.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.dex.code.DexXorLong2Addr;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
import java.util.Set;
public class Xor extends LogicalBinop {
@@ -98,23 +99,7 @@
@Override
AbstractValue foldIntegers(AbstractValue left, AbstractValue right, AppView<?> appView) {
- if (left.isSingleNumberValue() && right.isSingleNumberValue()) {
- int result =
- foldIntegers(
- left.asSingleNumberValue().getIntValue(), right.asSingleNumberValue().getIntValue());
- return appView.abstractValueFactory().createSingleNumberValue(result, getOutType());
- }
- if (left.hasDefinitelySetAndUnsetBitsInformation()
- && right.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- (left.getDefinitelySetIntBits() & right.getDefinitelyUnsetIntBits())
- | (left.getDefinitelyUnsetIntBits() & right.getDefinitelySetIntBits()),
- (left.getDefinitelySetIntBits() & right.getDefinitelySetIntBits())
- | (left.getDefinitelyUnsetIntBits() & right.getDefinitelyUnsetIntBits()));
- }
- return AbstractValue.unknown();
+ return AbstractCalculator.xorIntegers(appView, left, right);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 7de5536..035690f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -73,7 +73,7 @@
numberUnboxer.prepareForPrimaryOptimizationPass(timing, executorService);
outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
- if (fieldAccessAnalysis != null) {
+ if (fieldAccessAnalysis != null && fieldAccessAnalysis.fieldAssignmentTracker() != null) {
fieldAccessAnalysis.fieldAssignmentTracker().initialize();
}
@@ -252,11 +252,12 @@
onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
}
- public void waveDone(ProgramMethodSet wave, ExecutorService executorService)
- throws ExecutionException {
+ public void waveDone(ProgramMethodSet wave, ExecutorService executorService) {
delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
delayedOptimizationFeedback.updateVisibleOptimizationInfo();
- fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
+ if (fieldAccessAnalysis.fieldAssignmentTracker() != null) {
+ fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
+ }
appView.withArgumentPropagator(ArgumentPropagator::publishDelayedReprocessingCriteria);
if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
index c050445..906b124 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
@@ -70,12 +70,6 @@
SupportedClasses supportedMethods =
new SupportedClassesGenerator(options, androidJar, minApi, androidPlatformBuild, true)
.run(desugaredLibraryImplementation, desugaredLibrarySpecificationResource);
- System.out.println(
- "Generating lint files for "
- + getDebugIdentifier()
- + " (compile API "
- + compilationLevel
- + ")");
writeLintFiles(compilationLevel, minApi, supportedMethods);
return compilationLevel;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerUtils.java
index 77d819f..b496cf8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerUtils.java
@@ -14,7 +14,7 @@
public static boolean isArrayUsedOnlyForHashCode(
NewArrayFilled newArrayFilled, DexItemFactory factory) {
Value array = newArrayFilled.outValue();
- if (!array.hasSingleUniqueUser() || array.hasPhiUsers()) {
+ if (array == null || !array.hasSingleUniqueUser() || array.hasPhiUsers()) {
return false;
}
InvokeStatic invoke = array.singleUniqueUser().asInvokeStatic();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 9987a33..3c46cf1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -698,7 +698,14 @@
}
private void setAbstractReturnValue(AbstractValue value) {
- assert !abstractReturnValue.isSingleValue() || abstractReturnValue.equals(value)
+ assert !abstractReturnValue.isSingleValue()
+ || abstractReturnValue.equals(value)
+ || (abstractReturnValue.isSingleStatelessFieldValue()
+ && value.isSingleStatefulFieldValue()
+ && abstractReturnValue
+ .asSingleFieldValue()
+ .getField()
+ .isIdenticalTo(value.asSingleFieldValue().getField()))
: "return single value changed from " + abstractReturnValue + " to " + value;
abstractReturnValue = value;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 97ead9d..b73ba99 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis;
@@ -88,9 +89,12 @@
classes -> {
// Disable argument propagation for methods that should not be optimized by setting their
// method state to unknown.
- new ArgumentPropagatorUnoptimizableMethods(
- appView, immediateSubtypingInfo, codeScanner.getMethodStates())
- .initializeUnoptimizableMethodStates(classes);
+ new ArgumentPropagatorUnoptimizableFieldsAndMethods(
+ appView,
+ immediateSubtypingInfo,
+ codeScanner.getFieldStates(),
+ codeScanner.getMethodStates())
+ .run(classes);
// Compute the mapping from virtual methods to their root virtual method and the set of
// monomorphic virtual methods.
@@ -190,7 +194,7 @@
// Find all the code objects that need reprocessing.
new ArgumentPropagatorMethodReprocessingEnqueuer(appView, reprocessingCriteriaCollection)
- .enqueueMethodForReprocessing(
+ .enqueueAndPrepareMethodsForReprocessing(
graphLens, postMethodProcessorBuilder, executorService, timing);
reprocessingCriteriaCollection = null;
@@ -222,8 +226,9 @@
throws ExecutionException {
// Unset the scanner since all code objects have been scanned at this point.
assert appView.isAllCodeProcessed();
- MethodStateCollectionByReference codeScannerResult = codeScanner.getMethodStates();
- appView.testing().argumentPropagatorEventConsumer.acceptCodeScannerResult(codeScannerResult);
+ FieldStateCollection fieldStates = codeScanner.getFieldStates();
+ MethodStateCollectionByReference methodStates = codeScanner.getMethodStates();
+ appView.testing().argumentPropagatorEventConsumer.acceptCodeScannerResult(methodStates);
codeScanner = null;
postMethodProcessorBuilder.rewrittenWithLens(appView);
@@ -233,12 +238,14 @@
appView,
converter,
immediateSubtypingInfo,
- codeScannerResult,
+ fieldStates,
+ methodStates,
stronglyConnectedProgramComponents,
interfaceDispatchOutsideProgram)
.propagateOptimizationInfo(executorService, timing);
+ // TODO(b/296030319): Also publish the computed optimization information for fields.
new ArgumentPropagatorOptimizationInfoPopulator(
- appView, converter, codeScannerResult, postMethodProcessorBuilder)
+ appView, converter, fieldStates, methodStates, postMethodProcessorBuilder)
.populateOptimizationInfo(executorService, timing);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 0eae64d..8df07a0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -4,29 +4,37 @@
package com.android.tools.r8.optimize.argumentpropagation;
+import static com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow.asBaseInFlowOrNull;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectStateAnalysis;
import com.android.tools.r8.ir.code.AbstractValueSupplier;
import com.android.tools.r8.ir.code.AliasedValueConfiguration;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
+import com.android.tools.r8.ir.code.FieldGet;
+import com.android.tools.r8.ir.code.FieldPut;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
@@ -36,10 +44,17 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodStateOrBottom;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReceiverValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValueFactory;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InstanceFieldReadAbstractFunction;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameterFactory;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
@@ -47,6 +62,7 @@
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ParameterReprocessingCriteria;
import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -54,6 +70,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
/**
* Analyzes each {@link IRCode} during the primary optimization to collect information about the
@@ -61,6 +78,7 @@
*
* <p>State pruning is applied on-the-fly to avoid storing redundant information.
*/
+// TODO(b/330130322): Consider extending the flow graph with method-return nodes.
public class ArgumentPropagatorCodeScanner {
private static AliasedValueConfiguration aliasedValueConfiguration =
@@ -70,6 +88,8 @@
private final ArgumentPropagatorCodeScannerModeling modeling;
+ private final FieldValueFactory fieldValueFactory = new FieldValueFactory();
+
private final MethodParameterFactory methodParameterFactory = new MethodParameterFactory();
private final Set<DexMethod> monomorphicVirtualMethods = Sets.newIdentityHashSet();
@@ -84,6 +104,12 @@
private final Map<DexMethod, DexMethod> virtualRootMethods = new IdentityHashMap<>();
/**
+ * The abstract program state for this optimization. Intuitively maps each field to its abstract
+ * value and dynamic type.
+ */
+ private final FieldStateCollection fieldStates = FieldStateCollection.createConcurrent();
+
+ /**
* The abstract program state for this optimization. Intuitively maps each parameter to its
* abstract value and dynamic type.
*/
@@ -110,6 +136,10 @@
virtualRootMethods.putAll(extension);
}
+ public FieldStateCollection getFieldStates() {
+ return fieldStates;
+ }
+
public MethodStateCollectionByReference getMethodStates() {
return methodStates;
}
@@ -118,8 +148,19 @@
return virtualRootMethods.get(method.getReference());
}
+ // TODO(b/296030319): Allow lookups in the FieldStateCollection using DexField keys to avoid the
+ // need for definitionFor here.
+ private boolean isFieldValueAlreadyUnknown(DexField field) {
+ return isFieldValueAlreadyUnknown(appView.definitionFor(field).asProgramField());
+ }
+
+ private boolean isFieldValueAlreadyUnknown(ProgramField field) {
+ return fieldStates.get(field).isUnknown();
+ }
+
protected boolean isMethodParameterAlreadyUnknown(
MethodParameter methodParameter, ProgramMethod method) {
+ assert methodParameter.getMethod().isIdenticalTo(method.getReference());
MethodState methodState =
methodStates.get(
method.getDefinition().belongsToDirectPool() || isMonomorphicVirtualMethod(method)
@@ -153,17 +194,198 @@
AbstractValueSupplier abstractValueSupplier,
Timing timing) {
timing.begin("Argument propagation scanner");
- for (Invoke invoke : code.<Invoke>instructions(Instruction::isInvoke)) {
- if (invoke.isInvokeMethod()) {
- scan(invoke.asInvokeMethod(), abstractValueSupplier, method, timing);
- } else if (invoke.isInvokeCustom()) {
- scan(invoke.asInvokeCustom());
+ for (Instruction instruction : code.instructions()) {
+ if (instruction.isFieldPut()) {
+ scan(instruction.asFieldPut(), abstractValueSupplier, method, timing);
+ } else if (instruction.isInvokeMethod()) {
+ scan(instruction.asInvokeMethod(), abstractValueSupplier, method, timing);
+ } else if (instruction.isInvokeCustom()) {
+ scan(instruction.asInvokeCustom());
}
}
timing.end();
}
private void scan(
+ FieldPut fieldPut,
+ AbstractValueSupplier abstractValueSupplier,
+ ProgramMethod context,
+ Timing timing) {
+ ProgramField field = fieldPut.resolveField(appView, context).getProgramField();
+ if (field == null) {
+ // Nothing to propagate.
+ return;
+ }
+ addTemporaryFieldState(fieldPut, field, abstractValueSupplier, context, timing);
+ }
+
+ private void addTemporaryFieldState(
+ FieldPut fieldPut,
+ ProgramField field,
+ AbstractValueSupplier abstractValueSupplier,
+ ProgramMethod context,
+ Timing timing) {
+ timing.begin("Add field state");
+ fieldStates.addTemporaryFieldState(
+ field,
+ () -> computeFieldState(fieldPut, field, abstractValueSupplier, context, timing),
+ timing,
+ (existingFieldState, fieldStateToAdd) -> {
+ NonEmptyValueState newFieldState =
+ existingFieldState.mutableJoin(
+ appView,
+ fieldStateToAdd,
+ field.getType(),
+ StateCloner.getCloner(),
+ Action.empty());
+ return narrowFieldState(field, newFieldState);
+ });
+ timing.end();
+ }
+
+ private NonEmptyValueState computeFieldState(
+ FieldPut fieldPut,
+ ProgramField resolvedField,
+ AbstractValueSupplier abstractValueSupplier,
+ ProgramMethod context,
+ Timing timing) {
+ timing.begin("Compute field state for field-put");
+ NonEmptyValueState result =
+ computeFieldState(fieldPut, resolvedField, abstractValueSupplier, context);
+ timing.end();
+ return result;
+ }
+
+ private NonEmptyValueState computeFieldState(
+ FieldPut fieldPut,
+ ProgramField field,
+ AbstractValueSupplier abstractValueSupplier,
+ ProgramMethod context) {
+ NonEmptyValueState inFlowState = computeInFlowState(field.getType(), fieldPut.value(), context);
+ if (inFlowState != null) {
+ return inFlowState;
+ }
+
+ if (field.getType().isArrayType()) {
+ Nullability nullability = fieldPut.value().getType().nullability();
+ return ConcreteArrayTypeValueState.create(nullability);
+ }
+
+ AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
+ if (abstractValue.isUnknown()) {
+ abstractValue =
+ getFallbackAbstractValueForField(
+ field,
+ () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
+ }
+ if (field.getType().isClassType()) {
+ DynamicType dynamicType =
+ WideningUtils.widenDynamicNonReceiverType(
+ appView, fieldPut.value().getDynamicType(appView), field.getType());
+ return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
+ } else {
+ assert field.getType().isPrimitiveType();
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
+ }
+ }
+
+ // If the value is an argument of the enclosing method or defined by a field-get, then clearly we
+ // have no information about its abstract value (yet). Instead of treating this as having an
+ // unknown runtime value, we instead record a flow constraint.
+ private InFlow computeInFlow(Value value, ProgramMethod context) {
+ Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
+ if (valueRoot.isArgument()) {
+ MethodParameter inParameter =
+ methodParameterFactory.create(context, valueRoot.getDefinition().asArgument().getIndex());
+ return widenBaseInFlow(inParameter, context);
+ } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
+ FieldGet fieldGet = valueRoot.getDefinition().asFieldGet();
+ ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
+ if (field == null) {
+ return null;
+ }
+ if (fieldGet.isInstanceGet()) {
+ Value receiverValue = fieldGet.asInstanceGet().object();
+ BaseInFlow receiverInFlow = asBaseInFlowOrNull(computeInFlow(receiverValue, context));
+ if (receiverInFlow != null
+ && receiverInFlow.equals(widenBaseInFlow(receiverInFlow, context))) {
+ return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
+ }
+ }
+ return widenBaseInFlow(fieldValueFactory.create(field), context);
+ }
+ return null;
+ }
+
+ private InFlow widenBaseInFlow(BaseInFlow inFlow, ProgramMethod context) {
+ if (inFlow.isFieldValue()) {
+ if (isFieldValueAlreadyUnknown(inFlow.asFieldValue().getField())) {
+ return AbstractFunction.unknown();
+ }
+ } else {
+ assert inFlow.isMethodParameter();
+ if (isMethodParameterAlreadyUnknown(inFlow.asMethodParameter(), context)) {
+ return AbstractFunction.unknown();
+ }
+ }
+ return inFlow;
+ }
+
+ private NonEmptyValueState computeInFlowState(
+ DexType staticType, Value value, ProgramMethod context) {
+ InFlow inFlow = computeInFlow(value, context);
+ if (inFlow == null) {
+ return null;
+ }
+ if (inFlow.isUnknownAbstractFunction()) {
+ return ValueState.unknown();
+ }
+ assert inFlow.isBaseInFlow() || inFlow.isInstanceFieldReadAbstractFunction();
+ return ConcreteValueState.create(staticType, inFlow);
+ }
+
+ // Strengthens the abstract value of static final fields to a (self-)SingleFieldValue when the
+ // abstract value is unknown. The soundness of this is based on the fact that static final fields
+ // will never have their value changed after the <clinit> finishes, so value in a static final
+ // field can always be rematerialized by reading the field.
+ private NonEmptyValueState narrowFieldState(ProgramField field, NonEmptyValueState fieldState) {
+ AbstractValue fallbackAbstractValue =
+ getFallbackAbstractValueForField(field, ObjectState::empty);
+ if (!fallbackAbstractValue.isUnknown()) {
+ AbstractValue abstractValue = fieldState.getAbstractValue(appView);
+ if (!abstractValue.isUnknown()) {
+ return fieldState;
+ }
+ if (field.getType().isArrayType()) {
+ // We do not track an abstract value for array types.
+ return fieldState;
+ }
+ if (field.getType().isClassType()) {
+ DynamicType dynamicType =
+ fieldState.isReferenceState()
+ ? fieldState.asReferenceState().getDynamicType()
+ : DynamicType.unknown();
+ return new ConcreteClassTypeValueState(fallbackAbstractValue, dynamicType);
+ } else {
+ assert field.getType().isPrimitiveType();
+ return new ConcretePrimitiveTypeValueState(fallbackAbstractValue);
+ }
+ }
+ return fieldState;
+ }
+
+ // TODO(b/296030319): Also handle effectively final fields.
+ private AbstractValue getFallbackAbstractValueForField(
+ ProgramField field, Supplier<ObjectState> objectStateSupplier) {
+ if (field.getAccessFlags().isFinal() && field.getAccessFlags().isStatic()) {
+ return appView
+ .abstractValueFactory()
+ .createSingleFieldValue(field.getReference(), objectStateSupplier.get());
+ }
+ return AbstractValue.unknown();
+ }
+
+ private void scan(
InvokeMethod invoke,
AbstractValueSupplier abstractValueSupplier,
ProgramMethod context,
@@ -182,10 +404,7 @@
}
SingleResolutionResult<?> resolutionResult =
- appView
- .appInfo()
- .unsafeResolveMethodDueToDexFormatLegacy(invokedMethod)
- .asSingleResolution();
+ invoke.resolveMethod(appView, context).asSingleResolution();
if (resolutionResult == null) {
// Nothing to propagate; the invoke instruction fails.
return;
@@ -309,7 +528,6 @@
return result;
}
- @SuppressWarnings("UnusedVariable")
// TODO(b/190154391): Add a strategy that widens the dynamic receiver type to allow easily
// experimenting with the performance/size trade-off between precise/imprecise handling of
// dynamic dispatch.
@@ -507,60 +725,41 @@
return ValueState.unknown();
}
- Value argumentRoot = argument.getAliasedValue(aliasedValueConfiguration);
DexType parameterType =
invoke.getInvokedMethod().getArgumentType(argumentIndex, invoke.isInvokeStatic());
- TypeElement parameterTypeElement = parameterType.toTypeElement(appView);
// If the value is an argument of the enclosing method, then clearly we have no information
// about its abstract value. Instead of treating this as having an unknown runtime value, we
// instead record a flow constraint that specifies that all values that flow into the parameter
// of this enclosing method also flows into the corresponding parameter of the methods
// potentially called from this invoke instruction.
- if (argumentRoot.isArgument()) {
- MethodParameter forwardedParameter =
- methodParameterFactory.create(
- context, argumentRoot.getDefinition().asArgument().getIndex());
- if (isMethodParameterAlreadyUnknown(forwardedParameter, context)) {
- return ValueState.unknown();
- }
- if (parameterTypeElement.isClassType()) {
- return new ConcreteClassTypeValueState(forwardedParameter);
- } else if (parameterTypeElement.isArrayType()) {
- return new ConcreteArrayTypeValueState(forwardedParameter);
- } else {
- assert parameterTypeElement.isPrimitiveType();
- return new ConcretePrimitiveTypeValueState(forwardedParameter);
- }
+ NonEmptyValueState inFlowState = computeInFlowState(parameterType, argument, context);
+ if (inFlowState != null) {
+ return inFlowState;
}
// Only track the nullability for array types.
- if (parameterTypeElement.isArrayType()) {
+ if (parameterType.isArrayType()) {
Nullability nullability = argument.getType().nullability();
- return nullability.isMaybeNull()
- ? ValueState.unknown()
- : new ConcreteArrayTypeValueState(nullability);
+ return ConcreteArrayTypeValueState.create(nullability);
}
AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(argument);
// For class types, we track both the abstract value and the dynamic type. If both are unknown,
// then use UnknownParameterState.
- if (parameterTypeElement.isClassType()) {
+ if (parameterType.isClassType()) {
DynamicType dynamicType = argument.getDynamicType(appView);
DynamicType widenedDynamicType =
WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
- return abstractValue.isUnknown() && widenedDynamicType.isUnknown()
- ? ValueState.unknown()
- : new ConcreteClassTypeValueState(abstractValue, widenedDynamicType);
+ return ConcreteClassTypeValueState.create(abstractValue, widenedDynamicType);
+ } else {
+ // For primitive types, we only track the abstract value, thus if the abstract value is
+ // unknown,
+ // we use UnknownParameterState.
+ assert parameterType.isPrimitiveType();
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
}
-
- // For primitive types, we only track the abstract value, thus if the abstract value is unknown,
- // we use UnknownParameterState.
- assert parameterTypeElement.isPrimitiveType();
- return abstractValue.isUnknown()
- ? ValueState.unknown()
- : new ConcretePrimitiveTypeValueState(abstractValue);
}
@SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
index 3e5e433..b4f5f95 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScannerModeling.java
@@ -17,7 +17,10 @@
ArgumentPropagatorCodeScannerModeling(AppView<AppInfoWithLiveness> appView) {
this.composeModeling =
- appView.testing().modelUnknownChangedAndDefaultArgumentsToComposableFunctions
+ appView
+ .options()
+ .getJetpackComposeOptions()
+ .isModelingChangedArgumentsToComposableFunctions()
? new ArgumentPropagatorComposeModeling(appView)
: null;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index b857d7d..5056ee3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.optimize.argumentpropagation;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -14,17 +15,31 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirConstant;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -45,7 +60,7 @@
* Called indirectly from {@link IRConverter} to add all methods that require reprocessing to
* {@param postMethodProcessorBuilder}.
*/
- public void enqueueMethodForReprocessing(
+ public void enqueueAndPrepareMethodsForReprocessing(
ArgumentPropagatorGraphLens graphLens,
PostMethodProcessor.Builder postMethodProcessorBuilder,
ExecutorService executorService,
@@ -69,6 +84,10 @@
}
timing.end();
+ timing.begin("Eliminate dead field accesses");
+ eliminateDeadFieldAccesses(executorService);
+ timing.end();
+
timing.end();
}
@@ -144,6 +163,77 @@
postMethodProcessorBuilder.addAll(methodsToReprocessInClass, currentGraphLens));
}
+ private void eliminateDeadFieldAccesses(ExecutorService executorService)
+ throws ExecutionException {
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz -> {
+ clazz.forEachProgramMethodMatching(
+ m -> m.hasCode() && !m.getCode().isSharedCodeObject(),
+ this::eliminateDeadFieldAccesses);
+ },
+ appView.options().getThreadingModule(),
+ executorService);
+ }
+
+ private void eliminateDeadFieldAccesses(ProgramMethod method) {
+ Code code = method.getDefinition().getCode();
+ if (!code.isLirCode()) {
+ assert appView.isCfByteCodePassThrough(method.getDefinition());
+ return;
+ }
+ LirCode<Integer> lirCode = code.asLirCode();
+ LirCode<Integer> rewrittenLirCode = eliminateDeadFieldAccesses(method, lirCode);
+ if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
+ method.setCode(rewrittenLirCode, appView);
+ }
+ }
+
+ private LirCode<Integer> eliminateDeadFieldAccesses(
+ ProgramMethod method, LirCode<Integer> lirCode) {
+ if (ArrayUtils.none(
+ lirCode.getConstantPool(), constant -> isDeadFieldAccess(constant, method))) {
+ return lirCode;
+ }
+ IRCode irCode = lirCode.buildIR(method, appView);
+ AffectedValues affectedValues = new AffectedValues();
+ BasicBlockIterator blocks = irCode.listIterator();
+ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ if (blocksToRemove.contains(block)) {
+ continue;
+ }
+ InstructionListIterator instructionIterator = block.listIterator(irCode);
+ while (instructionIterator.hasNext()) {
+ FieldInstruction fieldInstruction = instructionIterator.next().asFieldInstruction();
+ if (fieldInstruction == null || !isDeadFieldAccess(fieldInstruction.getField(), method)) {
+ continue;
+ }
+ instructionIterator.replaceCurrentInstructionWithThrowNull(
+ appView, irCode, blocks, blocksToRemove, affectedValues);
+ }
+ }
+ irCode.removeBlocks(blocksToRemove);
+ affectedValues.narrowingWithAssumeRemoval(appView, irCode);
+ irCode.removeRedundantBlocks();
+ return new IRToLirFinalizer(appView)
+ .finalizeCode(irCode, BytecodeMetadataProvider.empty(), Timing.empty());
+ }
+
+ private boolean isDeadFieldAccess(LirConstant constant, ProgramMethod context) {
+ if (!(constant instanceof DexField)) {
+ return false;
+ }
+ DexField fieldReference = (DexField) constant;
+ return isDeadFieldAccess(fieldReference, context);
+ }
+
+ private boolean isDeadFieldAccess(DexField fieldReference, ProgramMethod context) {
+ ProgramField field = appView.appInfo().resolveField(fieldReference, context).getProgramField();
+ return field != null && field.getOptimizationInfo().getAbstractValue().isBottom();
+ }
+
static class AffectedMethodUseRegistry
extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index e4181f2..2b14cf3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -17,11 +18,11 @@
import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
@@ -47,6 +48,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final PrimaryR8IRConverter converter;
+ private final FieldStateCollection fieldStates;
private final MethodStateCollectionByReference methodStates;
private final InternalOptions options;
private final PostMethodProcessor.Builder postMethodProcessorBuilder;
@@ -54,10 +56,12 @@
public ArgumentPropagatorOptimizationInfoPopulator(
AppView<AppInfoWithLiveness> appView,
PrimaryR8IRConverter converter,
+ FieldStateCollection fieldStates,
MethodStateCollectionByReference methodStates,
PostMethodProcessor.Builder postMethodProcessorBuilder) {
this.appView = appView;
this.converter = converter;
+ this.fieldStates = fieldStates;
this.methodStates = methodStates;
this.options = appView.options();
this.postMethodProcessorBuilder = postMethodProcessorBuilder;
@@ -94,11 +98,21 @@
private ProgramMethodSet setOptimizationInfo(DexProgramClass clazz) {
ProgramMethodSet prunedMethods = ProgramMethodSet.create();
+ clazz.forEachProgramField(this::setOptimizationInfo);
clazz.forEachProgramMethod(method -> setOptimizationInfo(method, prunedMethods));
clazz.getMethodCollection().removeMethods(prunedMethods.toDefinitionSet());
return prunedMethods;
}
+ public void setOptimizationInfo(ProgramField field) {
+ ValueState state = fieldStates.remove(field);
+ // TODO(b/296030319): Also publish non-bottom field states.
+ if (state.isBottom()) {
+ getSimpleFeedback()
+ .recordFieldHasAbstractValue(field.getDefinition(), appView, AbstractValue.bottom());
+ }
+ }
+
public void setOptimizationInfo(ProgramMethod method, ProgramMethodSet prunedMethods) {
setOptimizationInfo(method, prunedMethods, methodStates.remove(method));
}
@@ -184,7 +198,7 @@
if (optimizationInfo.returnsArgument()) {
ValueState returnedArgumentState =
monomorphicMethodState.getParameterState(optimizationInfo.getReturnedArgument());
- OptimizationFeedback.getSimple()
+ getSimpleFeedback()
.methodReturnsAbstractValue(
method.getDefinition(), appView, returnedArgumentState.getAbstractValue(appView));
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
index 26a2167..97903a9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.propagation.InFlowPropagator;
import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator;
@@ -31,6 +32,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final PrimaryR8IRConverter converter;
+ private final FieldStateCollection fieldStates;
private final MethodStateCollectionByReference methodStates;
private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
@@ -43,12 +45,14 @@
AppView<AppInfoWithLiveness> appView,
PrimaryR8IRConverter converter,
ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+ FieldStateCollection fieldStates,
MethodStateCollectionByReference methodStates,
List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram) {
this.appView = appView;
this.converter = converter;
this.immediateSubtypingInfo = immediateSubtypingInfo;
+ this.fieldStates = fieldStates;
this.methodStates = methodStates;
this.stronglyConnectedProgramComponents = stronglyConnectedProgramComponents;
this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
@@ -67,7 +71,7 @@
// Solve the parameter flow constraints.
timing.begin("Solve flow constraints");
- new InFlowPropagator(appView, converter, methodStates).run(executorService);
+ new InFlowPropagator(appView, converter, fieldStates, methodStates).run(executorService);
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableFieldsAndMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableFieldsAndMethods.java
new file mode 100644
index 0000000..315c9f7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableFieldsAndMethods.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepFieldInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.Collection;
+
+public class ArgumentPropagatorUnoptimizableFieldsAndMethods {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+ private final FieldStateCollection fieldStates;
+ private final MethodStateCollectionByReference methodStates;
+
+ public ArgumentPropagatorUnoptimizableFieldsAndMethods(
+ AppView<AppInfoWithLiveness> appView,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+ FieldStateCollection fieldStates,
+ MethodStateCollectionByReference methodStates) {
+ this.appView = appView;
+ this.immediateSubtypingInfo = immediateSubtypingInfo;
+ this.fieldStates = fieldStates;
+ this.methodStates = methodStates;
+ }
+
+ public void run(Collection<DexProgramClass> stronglyConnectedComponent) {
+ initializeUnoptimizableFieldStates(stronglyConnectedComponent);
+ initializeUnoptimizableMethodStates(stronglyConnectedComponent);
+ }
+
+ private void initializeUnoptimizableFieldStates(
+ Collection<DexProgramClass> stronglyConnectedComponent) {
+ for (DexProgramClass clazz : stronglyConnectedComponent) {
+ clazz.forEachProgramField(
+ field -> {
+ if (isUnoptimizableField(field)) {
+ disableValuePropagationForField(field);
+ }
+ });
+ }
+ }
+
+ // TODO(b/190154391): Consider if we should bail out for classes that inherit from a missing
+ // class.
+ private void initializeUnoptimizableMethodStates(
+ Collection<DexProgramClass> stronglyConnectedComponent) {
+ ProgramMethodSet unoptimizableVirtualMethods =
+ MethodOverridesCollector.findAllMethodsAndOverridesThatMatches(
+ appView,
+ immediateSubtypingInfo,
+ stronglyConnectedComponent,
+ method -> {
+ if (isUnoptimizableMethod(method)) {
+ if (method.getDefinition().belongsToVirtualPool()
+ && !method.getHolder().isFinal()
+ && !method.getAccessFlags().isFinal()) {
+ return true;
+ } else {
+ disableValuePropagationForMethodParameters(method);
+ }
+ }
+ return false;
+ });
+ unoptimizableVirtualMethods.forEach(this::disableValuePropagationForMethodParameters);
+ }
+
+ private void disableValuePropagationForField(ProgramField field) {
+ fieldStates.set(field, ValueState.unknown());
+ }
+
+ private void disableValuePropagationForMethodParameters(ProgramMethod method) {
+ methodStates.set(method, UnknownMethodState.get());
+ }
+
+ private boolean isUnoptimizableField(ProgramField field) {
+ KeepFieldInfo keepInfo = appView.getKeepInfo(field);
+ InternalOptions options = appView.options();
+ return !keepInfo.isFieldPropagationAllowed(options);
+ }
+
+ private boolean isUnoptimizableMethod(ProgramMethod method) {
+ assert !method.getDefinition().belongsToVirtualPool()
+ || !method.getDefinition().isLibraryMethodOverride().isUnknown()
+ : "Unexpected virtual method without library method override information: "
+ + method.toSourceString();
+ InternalOptions options = appView.options();
+ return method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
+ || !appView.getKeepInfo(method).isArgumentPropagationAllowed(options);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
deleted file mode 100644
index 28aa563..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2021, 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.optimize.argumentpropagation;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.Collection;
-
-public class ArgumentPropagatorUnoptimizableMethods {
-
- private final AppView<AppInfoWithLiveness> appView;
- private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
- private final MethodStateCollectionByReference methodStates;
-
- public ArgumentPropagatorUnoptimizableMethods(
- AppView<AppInfoWithLiveness> appView,
- ImmediateProgramSubtypingInfo immediateSubtypingInfo,
- MethodStateCollectionByReference methodStates) {
- this.appView = appView;
- this.immediateSubtypingInfo = immediateSubtypingInfo;
- this.methodStates = methodStates;
- }
-
- // TODO(b/190154391): Consider if we should bail out for classes that inherit from a missing
- // class.
- public void initializeUnoptimizableMethodStates(
- Collection<DexProgramClass> stronglyConnectedComponent) {
- ProgramMethodSet unoptimizableVirtualMethods =
- MethodOverridesCollector.findAllMethodsAndOverridesThatMatches(
- appView,
- immediateSubtypingInfo,
- stronglyConnectedComponent,
- method -> {
- if (isUnoptimizableMethod(method)) {
- if (method.getDefinition().belongsToVirtualPool()
- && !method.getHolder().isFinal()
- && !method.getAccessFlags().isFinal()) {
- return true;
- } else {
- disableArgumentPropagationForMethod(method);
- }
- }
- return false;
- });
- unoptimizableVirtualMethods.forEach(this::disableArgumentPropagationForMethod);
- }
-
- private void disableArgumentPropagationForMethod(ProgramMethod method) {
- methodStates.set(method, UnknownMethodState.get());
- }
-
- private boolean isUnoptimizableMethod(ProgramMethod method) {
- assert !method.getDefinition().belongsToVirtualPool()
- || !method.getDefinition().isLibraryMethodOverride().isUnknown()
- : "Unexpected virtual method without library method override information: "
- + method.toSourceString();
- AppInfoWithLiveness appInfo = appView.appInfo();
- InternalOptions options = appView.options();
- return method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
- || !appInfo.getKeepInfo().getMethodInfo(method).isArgumentPropagationAllowed(options);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java
index da529db..7b390d6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/AbstractFunction.java
@@ -3,9 +3,56 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
public interface AbstractFunction extends InFlow {
+ static IdentityAbstractFunction identity() {
+ return IdentityAbstractFunction.get();
+ }
+
static UnknownAbstractFunction unknown() {
return UnknownAbstractFunction.get();
}
+
+ /**
+ * Applies the current abstract function to its declared inputs (in {@link #getBaseInFlow()}).
+ *
+ * <p>It is guaranteed by the caller that the given {@param state} is the abstract state for the
+ * field or parameter that caused this function to be reevaluated. If this abstract function takes
+ * a single input, then {@param state} is guaranteed to be the state for the node returned by
+ * {@link #getBaseInFlow()}, and {@param flowGraphStateProvider} should never be used.
+ *
+ * <p>Abstract functions that depend on multiple inputs can lookup the state for each input in
+ * {@param flowGraphStateProvider}. Attempting to lookup the state of a non-declared input is an
+ * error.
+ */
+ ValueState apply(
+ AppView<AppInfoWithLiveness> appView,
+ FlowGraphStateProvider flowGraphStateProvider,
+ ConcreteValueState inState);
+
+ /** Returns true if the given {@param inFlow} is a declared input of this abstract function. */
+ boolean containsBaseInFlow(BaseInFlow inFlow);
+
+ /**
+ * Returns the program field or parameter graph nodes that this function depends on. Upon any
+ * change to the abstract state of any of these nodes this abstract function must be re-evaluated.
+ */
+ Iterable<BaseInFlow> getBaseInFlow();
+
+ @Override
+ default boolean isAbstractFunction() {
+ return true;
+ }
+
+ @Override
+ default AbstractFunction asAbstractFunction() {
+ return this;
+ }
+
+ default boolean isIdentity() {
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
new file mode 100644
index 0000000..ef35ee9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BaseInFlow.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+public interface BaseInFlow extends InFlow {
+
+ static BaseInFlow asBaseInFlowOrNull(InFlow inFlow) {
+ return inFlow != null ? inFlow.asBaseInFlow() : null;
+ }
+
+ @Override
+ default boolean isBaseInFlow() {
+ return true;
+ }
+
+ @Override
+ default BaseInFlow asBaseInFlow() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
index f325f4a..567a559 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
@@ -27,7 +27,7 @@
StateCloner cloner,
Action onChangedAction) {
if (state.isBottom()) {
- assert state == bottomPrimitiveTypeParameter();
+ assert state == bottomPrimitiveTypeState();
return this;
}
if (state.isUnknown()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
index 46a26d7..d973ed8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
@@ -40,18 +40,6 @@
}
@Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (nullability.isBottom()) {
- return bottomArrayTypeParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
- }
-
- @Override
public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
if (getNullability().isDefinitelyNull()) {
return appView.abstractValueFactory().createUncheckedNullValue();
@@ -65,6 +53,11 @@
}
@Override
+ public BottomValueState getCorrespondingBottom() {
+ return bottomArrayTypeState();
+ }
+
+ @Override
public ConcreteParameterStateKind getKind() {
return ConcreteParameterStateKind.ARRAY;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
index bf3d1f9..4653731 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
@@ -46,21 +46,8 @@
}
@Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (abstractValue.isBottom()) {
- assert dynamicType.isBottom();
- return bottomClassTypeParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
- }
-
- @Override
public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
- if (getDynamicType().getNullability().isDefinitelyNull()) {
+ if (getNullability().isDefinitelyNull()) {
assert abstractValue.isNull() || abstractValue.isUnknown();
return appView.abstractValueFactory().createUncheckedNullValue();
}
@@ -68,6 +55,11 @@
}
@Override
+ public BottomValueState getCorrespondingBottom() {
+ return bottomClassTypeState();
+ }
+
+ @Override
public DynamicType getDynamicType() {
return dynamicType;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
index 2d53523..816e303 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
@@ -29,30 +29,22 @@
assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead";
}
- public static NonEmptyValueState create(AbstractValue abstractValue) {
- return abstractValue.isUnknown()
- ? ValueState.unknown()
- : new ConcretePrimitiveTypeValueState(abstractValue);
- }
-
public ConcretePrimitiveTypeValueState(InFlow inFlow) {
this(AbstractValue.bottom(), SetUtils.newHashSet(inFlow));
}
- @Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (abstractValue.isBottom()) {
- return bottomPrimitiveTypeParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
+ public static NonEmptyValueState create(AbstractValue abstractValue) {
+ return create(abstractValue, Collections.emptySet());
+ }
+
+ public static NonEmptyValueState create(AbstractValue abstractValue, Set<InFlow> inFlow) {
+ return abstractValue.isUnknown()
+ ? ValueState.unknown()
+ : new ConcretePrimitiveTypeValueState(abstractValue, inFlow);
}
@Override
- public ValueState mutableCopy() {
+ public ConcretePrimitiveTypeValueState mutableCopy() {
return new ConcretePrimitiveTypeValueState(abstractValue, copyInFlow());
}
@@ -106,6 +98,11 @@
}
@Override
+ public BottomValueState getCorrespondingBottom() {
+ return bottomPrimitiveTypeState();
+ }
+
+ @Override
public ConcreteParameterStateKind getKind() {
return ConcreteParameterStateKind.PRIMITIVE;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
index 186dae0..6169d4a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
@@ -31,20 +31,13 @@
}
@Override
- public ValueState clearInFlow() {
- if (hasInFlow()) {
- if (dynamicType.isBottom()) {
- return bottomReceiverParameter();
- }
- internalClearInFlow();
- }
- assert !isEffectivelyBottom();
- return this;
+ public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
+ return AbstractValue.unknown();
}
@Override
- public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
- return AbstractValue.unknown();
+ public BottomValueState getCorrespondingBottom() {
+ return bottomReceiverParameter();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
index f05f9e6..e32324a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
@@ -6,12 +6,13 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import java.util.function.Function;
public abstract class ConcreteValueState extends NonEmptyValueState {
@@ -28,7 +29,38 @@
this.inFlow = inFlow;
}
- public abstract ValueState clearInFlow();
+ public static NonEmptyValueState create(DexType staticType, AbstractValue abstractValue) {
+ if (staticType.isArrayType()) {
+ return unknown();
+ } else if (staticType.isClassType()) {
+ return ConcreteClassTypeValueState.create(abstractValue, DynamicType.unknown());
+ } else {
+ assert staticType.isPrimitiveType();
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
+ }
+ }
+
+ public static ConcreteValueState create(DexType staticType, InFlow inFlow) {
+ if (staticType.isArrayType()) {
+ return new ConcreteArrayTypeValueState(inFlow);
+ } else if (staticType.isClassType()) {
+ return new ConcreteClassTypeValueState(inFlow);
+ } else {
+ assert staticType.isPrimitiveType();
+ return new ConcretePrimitiveTypeValueState(inFlow);
+ }
+ }
+
+ public ValueState clearInFlow() {
+ if (hasInFlow()) {
+ internalClearInFlow();
+ if (isEffectivelyBottom()) {
+ return getCorrespondingBottom();
+ }
+ }
+ assert !isEffectivelyBottom();
+ return this;
+ }
void internalClearInFlow() {
inFlow = Collections.emptySet();
@@ -51,6 +83,8 @@
return inFlow;
}
+ public abstract BottomValueState getCorrespondingBottom();
+
public abstract ConcreteParameterStateKind getKind();
public abstract boolean isEffectivelyBottom();
@@ -68,16 +102,6 @@
}
@Override
- public NonEmptyValueState mutableJoin(
- AppView<AppInfoWithLiveness> appView,
- Function<ValueState, NonEmptyValueState> stateSupplier,
- DexType staticType,
- StateCloner cloner,
- Action onChangedAction) {
- return mutableJoin(appView, stateSupplier.apply(this), staticType, cloner, onChangedAction);
- }
-
- @Override
public final NonEmptyValueState mutableJoin(
AppView<AppInfoWithLiveness> appView,
ValueState state,
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
new file mode 100644
index 0000000..544c365
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldStateCollection.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramFieldMap;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+public class FieldStateCollection {
+
+ private final ProgramFieldMap<NonEmptyValueState> fieldStates;
+
+ private FieldStateCollection(ProgramFieldMap<NonEmptyValueState> fieldStates) {
+ this.fieldStates = fieldStates;
+ }
+
+ public static FieldStateCollection createConcurrent() {
+ return new FieldStateCollection(ProgramFieldMap.createConcurrent());
+ }
+
+ public NonEmptyValueState addTemporaryFieldState(
+ AppView<AppInfoWithLiveness> appView,
+ ProgramField field,
+ Supplier<NonEmptyValueState> fieldStateSupplier,
+ Timing timing) {
+ return addTemporaryFieldState(
+ field,
+ fieldStateSupplier,
+ timing,
+ (existingFieldState, fieldStateToAdd) ->
+ existingFieldState.mutableJoin(
+ appView,
+ fieldStateToAdd,
+ field.getType(),
+ StateCloner.getCloner(),
+ Action.empty()));
+ }
+
+ /**
+ * This intentionally takes a {@link Supplier<NonEmptyValueState>} to avoid computing the field
+ * state for a given field put when nothing is known about the value of the field.
+ */
+ public NonEmptyValueState addTemporaryFieldState(
+ ProgramField field,
+ Supplier<NonEmptyValueState> fieldStateSupplier,
+ Timing timing,
+ BiFunction<ConcreteValueState, ConcreteValueState, NonEmptyValueState> joiner) {
+ ValueState joinState =
+ fieldStates.compute(
+ field,
+ (f, existingFieldState) -> {
+ if (existingFieldState == null) {
+ return fieldStateSupplier.get();
+ }
+ assert !existingFieldState.isBottom();
+ if (existingFieldState.isUnknown()) {
+ return existingFieldState;
+ }
+ NonEmptyValueState fieldStateToAdd = fieldStateSupplier.get();
+ if (fieldStateToAdd.isUnknown()) {
+ return fieldStateToAdd;
+ }
+ timing.begin("Join temporary field state");
+ ConcreteValueState existingConcreteFieldState = existingFieldState.asConcrete();
+ ConcreteValueState concreteFieldStateToAdd = fieldStateToAdd.asConcrete();
+ NonEmptyValueState joinResult =
+ joiner.apply(existingConcreteFieldState, concreteFieldStateToAdd);
+ timing.end();
+ return joinResult;
+ });
+ assert joinState.isNonEmpty();
+ return joinState.asNonEmpty();
+ }
+
+ public void forEach(BiConsumer<ProgramField, ValueState> consumer) {
+ fieldStates.forEach(consumer);
+ }
+
+ public ValueState get(ProgramField field) {
+ NonEmptyValueState fieldState = fieldStates.get(field);
+ return fieldState != null ? fieldState : ValueState.bottom(field);
+ }
+
+ public ValueState remove(ProgramField field) {
+ ValueState removed = fieldStates.remove(field);
+ return removed != null ? removed : ValueState.bottom(field);
+ }
+
+ public ValueState set(ProgramField field, ValueState state) {
+ if (state.isNonEmpty()) {
+ return set(field, state.asNonEmpty());
+ }
+ return remove(field);
+ }
+
+ public ValueState set(ProgramField field, NonEmptyValueState state) {
+ NonEmptyValueState previous = fieldStates.put(field, state);
+ return previous != null ? previous : ValueState.bottom(field);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
new file mode 100644
index 0000000..f7bd0ec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValue.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.DexField;
+
+// TODO(b/296030319): Change DexField to implement InFlow and use DexField in all places instead of
+// FieldValue to avoid wrappers? This would also remove the need for the FieldValueFactory.
+public class FieldValue implements BaseInFlow {
+
+ private final DexField field;
+
+ public FieldValue(DexField field) {
+ this.field = field;
+ }
+
+ public DexField getField() {
+ return field;
+ }
+
+ @Override
+ public boolean isFieldValue() {
+ return true;
+ }
+
+ @Override
+ public boolean isFieldValue(DexField field) {
+ return this.field.isIdenticalTo(field);
+ }
+
+ @Override
+ public FieldValue asFieldValue() {
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("EqualsGetClass")
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ FieldValue fieldValue = (FieldValue) obj;
+ return field.isIdenticalTo(fieldValue.field);
+ }
+
+ @Override
+ public int hashCode() {
+ return field.hashCode();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValueFactory.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValueFactory.java
new file mode 100644
index 0000000..2ae1d1b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FieldValueFactory.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2024, 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.
+// Copyright (c) 2021, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramField;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class FieldValueFactory {
+
+ private final Map<DexField, FieldValue> fieldValues = new ConcurrentHashMap<>();
+
+ public FieldValue create(ProgramField field) {
+ return fieldValues.computeIfAbsent(field.getReference(), FieldValue::new);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
new file mode 100644
index 0000000..c207a11
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/FlowGraphStateProvider.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.FlowGraph;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Supplier;
+
+public interface FlowGraphStateProvider {
+
+ static FlowGraphStateProvider create(FlowGraph flowGraph, AbstractFunction abstractFunction) {
+ if (!InternalOptions.assertionsEnabled()) {
+ return flowGraph;
+ }
+ // If the abstract function is a canonical function, or the abstract function has a single
+ // declared input, we should never perform any state lookups.
+ if (abstractFunction.isIdentity()
+ || abstractFunction.isUnknownAbstractFunction()
+ || abstractFunction.isUpdateChangedFlagsAbstractFunction()) {
+ return new FlowGraphStateProvider() {
+
+ @Override
+ public ValueState getState(DexField field) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
+ throw new Unreachable();
+ }
+ };
+ }
+ // Otherwise, restrict state lookups to the declared base in flow. This is required for arriving
+ // at the correct fix point.
+ assert abstractFunction.isInstanceFieldReadAbstractFunction();
+ return new FlowGraphStateProvider() {
+
+ @Override
+ public ValueState getState(DexField field) {
+ assert abstractFunction.containsBaseInFlow(new FieldValue(field));
+ return flowGraph.getState(field);
+ }
+
+ @Override
+ public ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
+ assert abstractFunction.containsBaseInFlow(inFlow);
+ return flowGraph.getState(inFlow, defaultStateProvider);
+ }
+ };
+ }
+
+ ValueState getState(DexField field);
+
+ ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider);
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
new file mode 100644
index 0000000..1545a75
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/IdentityAbstractFunction.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class IdentityAbstractFunction implements AbstractFunction {
+
+ private static final IdentityAbstractFunction INSTANCE = new IdentityAbstractFunction();
+
+ private IdentityAbstractFunction() {}
+
+ static IdentityAbstractFunction get() {
+ return INSTANCE;
+ }
+
+ @Override
+ public ValueState apply(
+ AppView<AppInfoWithLiveness> appView,
+ FlowGraphStateProvider flowGraphStateProvider,
+ ConcreteValueState inState) {
+ return inState;
+ }
+
+ @Override
+ public boolean containsBaseInFlow(BaseInFlow inFlow) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public Iterable<BaseInFlow> getBaseInFlow() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isIdentity() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
index b651045..1f28543 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InFlow.java
@@ -3,8 +3,47 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.optimize.compose.UpdateChangedFlagsAbstractFunction;
+
public interface InFlow {
+ default boolean isAbstractFunction() {
+ return false;
+ }
+
+ default AbstractFunction asAbstractFunction() {
+ return null;
+ }
+
+ default boolean isBaseInFlow() {
+ return false;
+ }
+
+ default BaseInFlow asBaseInFlow() {
+ return null;
+ }
+
+ default boolean isFieldValue() {
+ return false;
+ }
+
+ default boolean isFieldValue(DexField field) {
+ return false;
+ }
+
+ default FieldValue asFieldValue() {
+ return null;
+ }
+
+ default boolean isInstanceFieldReadAbstractFunction() {
+ return false;
+ }
+
+ default InstanceFieldReadAbstractFunction asInstanceFieldReadAbstractFunction() {
+ return null;
+ }
+
default boolean isMethodParameter() {
return false;
}
@@ -12,4 +51,16 @@
default MethodParameter asMethodParameter() {
return null;
}
+
+ default boolean isUnknownAbstractFunction() {
+ return false;
+ }
+
+ default boolean isUpdateChangedFlagsAbstractFunction() {
+ return false;
+ }
+
+ default UpdateChangedFlagsAbstractFunction asUpdateChangedFlagsAbstractFunction() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
new file mode 100644
index 0000000..bd97c0b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/InstanceFieldReadAbstractFunction.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Lists;
+
+public class InstanceFieldReadAbstractFunction implements AbstractFunction {
+
+ private final BaseInFlow receiver;
+ private final DexField field;
+
+ public InstanceFieldReadAbstractFunction(BaseInFlow receiver, DexField field) {
+ this.receiver = receiver;
+ this.field = field;
+ }
+
+ @Override
+ public ValueState apply(
+ AppView<AppInfoWithLiveness> appView,
+ FlowGraphStateProvider flowGraphStateProvider,
+ ConcreteValueState predecessorState) {
+ ValueState state = flowGraphStateProvider.getState(receiver, () -> ValueState.bottom(field));
+ if (state.isBottom()) {
+ return ValueState.bottom(field);
+ }
+ if (!state.isClassState()) {
+ return getFallbackState(flowGraphStateProvider);
+ }
+ ConcreteClassTypeValueState classState = state.asClassState();
+ if (classState.getNullability().isDefinitelyNull()) {
+ return ValueState.bottom(field);
+ }
+ AbstractValue abstractValue = state.getAbstractValue(null);
+ if (!abstractValue.hasObjectState()) {
+ return getFallbackState(flowGraphStateProvider);
+ }
+ AbstractValue fieldValue = abstractValue.getObjectState().getAbstractFieldValue(field);
+ if (fieldValue.isUnknown()) {
+ return getFallbackState(flowGraphStateProvider);
+ }
+ return ConcreteValueState.create(field.getType(), fieldValue);
+ }
+
+ @Override
+ public boolean containsBaseInFlow(BaseInFlow inFlow) {
+ return inFlow.equals(receiver) || inFlow.isFieldValue(field);
+ }
+
+ @Override
+ public Iterable<BaseInFlow> getBaseInFlow() {
+ return Lists.newArrayList(receiver, new FieldValue(field));
+ }
+
+ private ValueState getFallbackState(FlowGraphStateProvider flowGraphStateProvider) {
+ ValueState valueState = flowGraphStateProvider.getState(new FieldValue(field), null);
+ assert !valueState.isConcrete() || !valueState.asConcrete().hasInFlow();
+ return valueState;
+ }
+
+ @Override
+ public boolean isInstanceFieldReadAbstractFunction() {
+ return true;
+ }
+
+ @Override
+ public InstanceFieldReadAbstractFunction asInstanceFieldReadAbstractFunction() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
index 9bfeab7..2fe1a2a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.DexMethod;
import java.util.Objects;
-public class MethodParameter implements InFlow {
+public class MethodParameter implements BaseInFlow {
private final DexMethod method;
private final int index;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java
index a715205..97d0a2f 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.ProgramMethod;
-import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -17,10 +16,6 @@
super(methodStates);
}
- public static MethodStateCollectionByReference create() {
- return new MethodStateCollectionByReference(new IdentityHashMap<>());
- }
-
public static MethodStateCollectionByReference createConcurrent() {
return new MethodStateCollectionByReference(new ConcurrentHashMap<>());
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java
index a244616..029a696 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.ProgramMethod;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
public class MethodStateCollectionBySignature extends MethodStateCollection<DexMethodSignature> {
@@ -20,10 +19,6 @@
return new MethodStateCollectionBySignature(new HashMap<>());
}
- public static MethodStateCollectionBySignature createConcurrent() {
- return new MethodStateCollectionBySignature(new ConcurrentHashMap<>());
- }
-
@Override
DexMethodSignature getKey(ProgramMethod method) {
return method.getMethodSignature();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java
index 19bc95a..fd4186b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java
@@ -4,12 +4,6 @@
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import java.util.function.Function;
-
public abstract class NonEmptyValueState extends ValueState {
@Override
@@ -21,19 +15,4 @@
public NonEmptyValueState asNonEmpty() {
return this;
}
-
- public final NonEmptyValueState mutableJoin(
- AppView<AppInfoWithLiveness> appView,
- Function<ValueState, NonEmptyValueState> stateSupplier,
- DexType staticType,
- StateCloner cloner) {
- return mutableJoin(appView, stateSupplier, staticType, cloner, Action.empty());
- }
-
- public abstract NonEmptyValueState mutableJoin(
- AppView<AppInfoWithLiveness> appView,
- Function<ValueState, NonEmptyValueState> stateSupplier,
- DexType staticType,
- StateCloner cloner,
- Action onChangedAction);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java
new file mode 100644
index 0000000..ca1223f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/OrAbstractFunction.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.IterableUtils;
+import java.util.Objects;
+
+/**
+ * Encodes the `x | const` abstract function. This is currently used as part of the modeling of
+ * updateChangedFlags, since the updateChangedFlags function is invoked with `changedFlags | 1` as
+ * an argument.
+ */
+public class OrAbstractFunction implements AbstractFunction {
+
+ public final BaseInFlow inFlow;
+ public final SingleNumberValue constant;
+
+ public OrAbstractFunction(BaseInFlow inFlow, SingleNumberValue constant) {
+ this.inFlow = inFlow;
+ this.constant = constant;
+ }
+
+ @Override
+ public ValueState apply(
+ AppView<AppInfoWithLiveness> appView,
+ FlowGraphStateProvider flowGraphStateProvider,
+ ConcreteValueState inState) {
+ ConcretePrimitiveTypeValueState inPrimitiveState = inState.asPrimitiveState();
+ AbstractValue result =
+ AbstractCalculator.orIntegers(appView, inPrimitiveState.getAbstractValue(), constant);
+ return ConcretePrimitiveTypeValueState.create(result, inPrimitiveState.copyInFlow());
+ }
+
+ @Override
+ public boolean containsBaseInFlow(BaseInFlow otherInFlow) {
+ if (inFlow.isAbstractFunction()) {
+ return inFlow.asAbstractFunction().containsBaseInFlow(otherInFlow);
+ }
+ assert inFlow.isBaseInFlow();
+ return inFlow.equals(otherInFlow);
+ }
+
+ @Override
+ public Iterable<BaseInFlow> getBaseInFlow() {
+ if (inFlow.isAbstractFunction()) {
+ return inFlow.asAbstractFunction().getBaseInFlow();
+ }
+ return IterableUtils.singleton(inFlow);
+ }
+
+ @Override
+ @SuppressWarnings("EqualsGetClass")
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ OrAbstractFunction fn = (OrAbstractFunction) obj;
+ return inFlow.equals(fn.inFlow) && constant.equals(fn.constant);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getClass(), inFlow, constant);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
index 07a87b5..f9ffd98 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownAbstractFunction.java
@@ -3,6 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
public class UnknownAbstractFunction implements AbstractFunction {
private static final UnknownAbstractFunction INSTANCE = new UnknownAbstractFunction();
@@ -12,4 +16,27 @@
static UnknownAbstractFunction get() {
return INSTANCE;
}
+
+ @Override
+ public ValueState apply(
+ AppView<AppInfoWithLiveness> appView,
+ FlowGraphStateProvider flowGraphStateProvider,
+ ConcreteValueState inState) {
+ return ValueState.unknown();
+ }
+
+ @Override
+ public boolean containsBaseInFlow(BaseInFlow inFlow) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public Iterable<BaseInFlow> getBaseInFlow() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isUnknownAbstractFunction() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
index f9e7a1d..6e3601b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
@@ -7,9 +7,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
-import java.util.function.Function;
public class UnknownValueState extends NonEmptyValueState {
@@ -22,7 +22,7 @@
}
@Override
- public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
+ public UnknownValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
return AbstractValue.unknown();
}
@@ -32,24 +32,14 @@
}
@Override
- public ValueState mutableCopy() {
+ public UnknownValueState mutableCopy() {
return this;
}
@Override
- public ValueState mutableJoin(
+ public UnknownValueState mutableJoin(
AppView<AppInfoWithLiveness> appView,
- ValueState parameterState,
- DexType parameterType,
- StateCloner cloner,
- Action onChangedAction) {
- return this;
- }
-
- @Override
- public NonEmptyValueState mutableJoin(
- AppView<AppInfoWithLiveness> appView,
- Function<ValueState, NonEmptyValueState> stateSupplier,
+ ValueState state,
DexType staticType,
StateCloner cloner,
Action onChangedAction) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
index 4cb4c2b..96adb2c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.optimize.argumentpropagation.codescanner;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -14,30 +15,34 @@
public abstract class ValueState {
public static BottomValueState bottom(ProgramField field) {
+ return bottom(field.getReference());
+ }
+
+ public static BottomValueState bottom(DexField field) {
DexType fieldType = field.getType();
if (fieldType.isArrayType()) {
- return bottomArrayTypeParameter();
+ return bottomArrayTypeState();
} else if (fieldType.isClassType()) {
- return bottomClassTypeParameter();
+ return bottomClassTypeState();
} else {
assert fieldType.isPrimitiveType();
- return bottomPrimitiveTypeParameter();
+ return bottomPrimitiveTypeState();
}
}
- public static BottomValueState bottomArrayTypeParameter() {
+ public static BottomArrayTypeValueState bottomArrayTypeState() {
return BottomArrayTypeValueState.get();
}
- public static BottomValueState bottomClassTypeParameter() {
+ public static BottomClassTypeValueState bottomClassTypeState() {
return BottomClassTypeValueState.get();
}
- public static BottomValueState bottomPrimitiveTypeParameter() {
+ public static BottomPrimitiveTypeValueState bottomPrimitiveTypeState() {
return BottomPrimitiveTypeValueState.get();
}
- public static BottomValueState bottomReceiverParameter() {
+ public static BottomReceiverValueState bottomReceiverParameter() {
return BottomReceiverValueState.get();
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
new file mode 100644
index 0000000..294eb8e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -0,0 +1,292 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import com.google.common.collect.Lists;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+
+public class DefaultFieldValueJoiner {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final FieldStateCollection fieldStates;
+ private final List<FlowGraph> flowGraphs;
+
+ public DefaultFieldValueJoiner(
+ AppView<AppInfoWithLiveness> appView,
+ FieldStateCollection fieldStates,
+ List<FlowGraph> flowGraphs) {
+ this.appView = appView;
+ this.fieldStates = fieldStates;
+ this.flowGraphs = flowGraphs;
+ }
+
+ public Map<FlowGraph, Deque<FlowGraphNode>> joinDefaultFieldValuesForFieldsWithReadBeforeWrite(
+ ExecutorService executorService) throws ExecutionException {
+ // Find all the fields where we need to determine if each field read is guaranteed to be
+ // dominated by a write.
+ Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = getFieldsOfInterest();
+
+ // If constructor inlining is disabled, then we focus on whether each instance initializer
+ // definitely assigns the given field before it is read. We do the same for final and static
+ // fields.
+ Map<DexProgramClass, List<ProgramField>> nonFinalInstanceFields =
+ removeFieldsNotSubjectToInitializerAnalysis(fieldsOfInterest);
+ ProgramFieldSet fieldsWithLiveDefaultValue = ProgramFieldSet.createConcurrent();
+ analyzeInitializers(fieldsOfInterest, fieldsWithLiveDefaultValue::add, executorService);
+
+ // For non-final fields where writes in instance initializers may have been subject to
+ // constructor inlining, we find all new-instance instructions (including subtype allocations)
+ // and check if the field is written on each allocation before it is possibly read.
+ analyzeNewInstanceInstructions(nonFinalInstanceFields, fieldsWithLiveDefaultValue::add);
+
+ return updateFlowGraphs(fieldsWithLiveDefaultValue, executorService);
+ }
+
+ private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
+ Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.forEachProgramField(
+ field -> {
+ // We only need to include the fields where including the default value would make a
+ // difference. Then we can assert below in updateFlowGraphs() that adding the default
+ // value changes the field state.
+ // TODO(b/296030319): Implement this for primitive fields.
+ ValueState state = fieldStates.get(field);
+ if (state.isUnknown()) {
+ return;
+ }
+ if (state.isReferenceState()
+ && state.asReferenceState().getNullability().isNullable()) {
+ return;
+ }
+ fieldsOfInterest
+ .computeIfAbsent(field.getHolder(), ignoreKey(ArrayList::new))
+ .add(field);
+ });
+ }
+ return fieldsOfInterest;
+ }
+
+ private Map<DexProgramClass, List<ProgramField>> removeFieldsNotSubjectToInitializerAnalysis(
+ Map<DexProgramClass, List<ProgramField>> fieldsOfInterest) {
+ // When constructor inlining is disabled, we only analyze the initializers of each field holder.
+ if (!appView.options().canInitNewInstanceUsingSuperclassConstructor()) {
+ return Collections.emptyMap();
+ }
+
+ // When constructor inlining is enabled, we can still limit the analysis to the instance
+ // initializers for final fields. We can do the same for static fields as <clinit> is not
+ // subject to inlining.
+ Map<DexProgramClass, List<ProgramField>> nonFinalInstanceFields = new IdentityHashMap<>();
+ MapUtils.removeIf(
+ fieldsOfInterest,
+ (holder, fields) -> {
+ fields.removeIf(
+ field -> {
+ if (!field.getAccessFlags().isFinal() && !field.getAccessFlags().isStatic()) {
+ nonFinalInstanceFields
+ .computeIfAbsent(holder, ignoreKey(ArrayList::new))
+ .add(field);
+ }
+ return false;
+ });
+ return fields.isEmpty();
+ });
+ return nonFinalInstanceFields;
+ }
+
+ private void analyzeInitializers(
+ Map<DexProgramClass, List<ProgramField>> fieldsOfInterest,
+ Consumer<ProgramField> concurrentLiveDefaultValueConsumer,
+ ExecutorService executorService)
+ throws ExecutionException {
+ ThreadUtils.processMap(
+ fieldsOfInterest,
+ (clazz, fields) -> {
+ ProgramFieldSet instanceFieldsWithLiveDefaultValue = ProgramFieldSet.create();
+ ProgramFieldSet staticFieldsWithLiveDefaultValue = ProgramFieldSet.create();
+ partitionFields(
+ fields, instanceFieldsWithLiveDefaultValue, staticFieldsWithLiveDefaultValue);
+ analyzeClassInitializerAssignments(
+ clazz, staticFieldsWithLiveDefaultValue, concurrentLiveDefaultValueConsumer);
+ analyzeInstanceInitializerAssignments(
+ clazz, instanceFieldsWithLiveDefaultValue, concurrentLiveDefaultValueConsumer);
+ },
+ appView.options().getThreadingModule(),
+ executorService);
+ }
+
+ private void partitionFields(
+ Collection<ProgramField> fields,
+ ProgramFieldSet instanceFieldsWithLiveDefaultValue,
+ ProgramFieldSet staticFieldsWithLiveDefaultValue) {
+ for (ProgramField field : fields) {
+ if (field.getAccessFlags().isStatic()) {
+ staticFieldsWithLiveDefaultValue.add(field);
+ } else {
+ instanceFieldsWithLiveDefaultValue.add(field);
+ }
+ }
+ }
+
+ private void analyzeClassInitializerAssignments(
+ DexProgramClass clazz,
+ ProgramFieldSet staticFieldsWithLiveDefaultValue,
+ Consumer<ProgramField> liveDefaultValueConsumer) {
+ if (staticFieldsWithLiveDefaultValue.isEmpty()) {
+ return;
+ }
+ if (clazz.hasClassInitializer()) {
+ IRCode code =
+ clazz
+ .getProgramClassInitializer()
+ .buildIR(appView, MethodConversionOptions.nonConverting());
+ FieldReadBeforeWriteAnalysis analysis = new FieldReadBeforeWriteAnalysis(appView, code);
+ staticFieldsWithLiveDefaultValue.removeIf(analysis::isStaticFieldNeverReadBeforeWrite);
+ }
+ staticFieldsWithLiveDefaultValue.forEach(liveDefaultValueConsumer);
+ }
+
+ private void analyzeInstanceInitializerAssignments(
+ DexProgramClass clazz,
+ ProgramFieldSet instanceFieldsWithLiveDefaultValue,
+ Consumer<ProgramField> liveDefaultValueConsumer) {
+ if (instanceFieldsWithLiveDefaultValue.isEmpty()) {
+ return;
+ }
+ List<ProgramMethod> instanceInitializers =
+ Lists.newArrayList(clazz.programInstanceInitializers());
+ // TODO(b/296030319): Handle multiple instance initializers.
+ if (instanceInitializers.size() == 1) {
+ ProgramMethod instanceInitializer = ListUtils.first(instanceInitializers);
+ IRCode code = instanceInitializer.buildIR(appView, MethodConversionOptions.nonConverting());
+ FieldReadBeforeWriteAnalysis analysis = new FieldReadBeforeWriteAnalysis(appView, code);
+ instanceFieldsWithLiveDefaultValue.removeIf(analysis::isInstanceFieldNeverReadBeforeWrite);
+ }
+ instanceFieldsWithLiveDefaultValue.forEach(liveDefaultValueConsumer);
+ }
+
+ private void analyzeNewInstanceInstructions(
+ Map<DexProgramClass, List<ProgramField>> nonFinalInstanceFields,
+ Consumer<ProgramField> liveDefaultValueConsumer) {
+ // Conservatively treat all fields as maybe read before written.
+ // TODO(b/296030319): Implement analysis by building IR for all methods that instantiate the
+ // relevant classes and analyzing the puts to the newly created instances.
+ for (ProgramField field : IterableUtils.flatten(nonFinalInstanceFields.values())) {
+ liveDefaultValueConsumer.accept(field);
+ }
+ }
+
+ private Map<FlowGraph, Deque<FlowGraphNode>> updateFlowGraphs(
+ ProgramFieldSet fieldsWithLiveDefaultValue, ExecutorService executorService)
+ throws ExecutionException {
+ Collection<Pair<FlowGraph, Deque<FlowGraphNode>>> worklists =
+ ThreadUtils.processItemsWithResultsThatMatches(
+ flowGraphs,
+ flowGraph -> {
+ Deque<FlowGraphNode> worklist = new ArrayDeque<>();
+ flowGraph.forEachFieldNode(
+ node -> {
+ ProgramField field = node.getField();
+ if (fieldsWithLiveDefaultValue.remove(field)) {
+ node.addState(
+ appView,
+ getDefaultValueState(field),
+ () -> {
+ if (node.isUnknown()) {
+ node.clearPredecessors();
+ }
+ node.addToWorkList(worklist);
+ });
+ }
+ });
+ return new Pair<>(flowGraph, worklist);
+ },
+ pair -> !pair.getSecond().isEmpty(),
+ appView.options().getThreadingModule(),
+ executorService);
+ // Unseen fields are not added to any flow graphs, since they are not needed for flow
+ // propagation. Update these fields directly in the field state collection.
+ for (ProgramField field : fieldsWithLiveDefaultValue) {
+ fieldStates.addTemporaryFieldState(
+ appView, field, () -> getDefaultValueState(field), Timing.empty());
+ }
+ return MapUtils.newIdentityHashMap(
+ builder -> worklists.forEach(pair -> builder.put(pair.getFirst(), pair.getSecond())));
+ }
+
+ private ConcreteValueState getDefaultValueState(ProgramField field) {
+ AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+ AbstractValue defaultValue;
+ if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
+ defaultValue = field.getDefinition().getStaticValue().toAbstractValue(abstractValueFactory);
+ } else if (field.getType().isPrimitiveType()) {
+ defaultValue = abstractValueFactory.createZeroValue();
+ } else {
+ defaultValue = abstractValueFactory.createUncheckedNullValue();
+ }
+ NonEmptyValueState fieldStateToAdd;
+ if (field.getType().isArrayType()) {
+ Nullability defaultNullability = Nullability.definitelyNull();
+ fieldStateToAdd = ConcreteArrayTypeValueState.create(defaultNullability);
+ } else if (field.getType().isClassType()) {
+ assert defaultValue.isNull()
+ || defaultValue.isSingleStringValue()
+ || defaultValue.isSingleDexItemBasedStringValue();
+ DynamicType dynamicType =
+ defaultValue.isNull()
+ ? DynamicType.definitelyNull()
+ : DynamicType.createExact(
+ TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
+ fieldStateToAdd = ConcreteClassTypeValueState.create(defaultValue, dynamicType);
+ } else {
+ assert field.getType().isPrimitiveType();
+ fieldStateToAdd = ConcretePrimitiveTypeValueState.create(defaultValue);
+ }
+ // We should always be able to map static field values to an unknown abstract value.
+ if (fieldStateToAdd.isUnknown()) {
+ throw new Unreachable();
+ }
+ return fieldStateToAdd.asConcrete();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteAnalysis.java
new file mode 100644
index 0000000..f11d945
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteAnalysis.java
@@ -0,0 +1,237 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.KnownFieldSet;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DequeUtils;
+import com.android.tools.r8.utils.LazyBox;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+class FieldReadBeforeWriteAnalysis {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final IRCode code;
+ private final ProgramMethod context;
+ private final LazyBox<DominatorTree> lazyDominatorTree;
+ private final List<BasicBlock> returnBlocks;
+
+ private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
+
+ public FieldReadBeforeWriteAnalysis(AppView<AppInfoWithLiveness> appView, IRCode code) {
+ this.appView = appView;
+ this.code = code;
+ this.context = code.context();
+ this.lazyDominatorTree = new LazyBox<>(() -> new DominatorTree(code));
+ this.returnBlocks = code.computeNormalExitBlocks();
+ }
+
+ public boolean isInstanceFieldNeverReadBeforeWrite(ProgramField field) {
+ assert field.getHolder() == context.getHolder();
+ InstancePut instancePut = null;
+ for (InstancePut candidate :
+ code.getThis().<InstancePut>uniqueUsers(Instruction::isInstancePut)) {
+ if (candidate.getField().isIdenticalTo(field.getReference())) {
+ if (instancePut != null) {
+ // In the somewhat unusual case (?) that the same constructor assigns the same field
+ // multiple times, we simply bail out and conservatively report that the field is maybe
+ // read before it is written.
+ return false;
+ }
+ instancePut = candidate;
+ }
+ }
+ // TODO(b/296030319): Improve precision using escape analysis for receiver.
+ return instancePut != null
+ && !isFieldMaybeReadBeforeInstructionInInitializer(field, instancePut)
+ && lazyDominatorTree.computeIfAbsent().dominatesAllOf(instancePut.getBlock(), returnBlocks);
+ }
+
+ public boolean isStaticFieldNeverReadBeforeWrite(ProgramField field) {
+ assert field.getHolder() == context.getHolder();
+ StaticPut staticPut = null;
+ for (StaticPut candidate : code.<StaticPut>instructions(Instruction::isStaticPut)) {
+ if (candidate.getField().isIdenticalTo(field.getReference())) {
+ if (staticPut != null) {
+ // In the somewhat unusual case (?) that the same constructor assigns the same field
+ // multiple times, we simply bail out and conservatively report that the field is maybe
+ // read before it is written.
+ return false;
+ }
+ staticPut = candidate;
+ }
+ }
+ return staticPut != null
+ && !isFieldMaybeReadBeforeInstructionInInitializer(field, staticPut)
+ && lazyDominatorTree.computeIfAbsent().dominatesAllOf(staticPut.getBlock(), returnBlocks);
+ }
+
+ private boolean isFieldMaybeReadBeforeInstructionInInitializer(
+ DexClassAndField field, Instruction instruction) {
+ BasicBlock block = instruction.getBlock();
+
+ // First check if the field may be read in any of the (transitive) predecessor blocks.
+ if (fieldMaybeReadBeforeBlock(field, block)) {
+ return true;
+ }
+
+ // Then check if any of the instructions that precede the given instruction in the current block
+ // may read the field.
+ InstructionIterator instructionIterator = block.iterator();
+ while (instructionIterator.hasNext()) {
+ Instruction current = instructionIterator.next();
+ if (current == instruction) {
+ break;
+ }
+ if (current.readSet(appView, context).contains(field)) {
+ return true;
+ }
+ }
+
+ // Otherwise, the field is not read prior to the given instruction.
+ return false;
+ }
+
+ private boolean fieldMaybeReadBeforeBlock(DexClassAndField field, BasicBlock block) {
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ if (fieldMaybeReadBeforeBlockInclusive(field, predecessor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean fieldMaybeReadBeforeBlockInclusive(DexClassAndField field, BasicBlock block) {
+ return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(field);
+ }
+
+ private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
+ if (fieldsMaybeReadBeforeBlockInclusiveCache == null) {
+ fieldsMaybeReadBeforeBlockInclusiveCache = createFieldsMaybeReadBeforeBlockInclusive();
+ }
+ return fieldsMaybeReadBeforeBlockInclusiveCache;
+ }
+
+ /**
+ * Eagerly creates a mapping from each block to the set of fields that may be read in that block
+ * and its transitive predecessors.
+ */
+ private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() {
+ Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>();
+ Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock());
+ while (!worklist.isEmpty()) {
+ BasicBlock block = worklist.removeFirst();
+ boolean seenBefore = result.containsKey(block);
+ AbstractFieldSet readSet =
+ result.computeIfAbsent(block, ignore -> EmptyFieldSet.getInstance());
+ if (readSet.isTop()) {
+ // We already have unknown information for this block.
+ continue;
+ }
+
+ assert readSet.isKnownFieldSet();
+ KnownFieldSet knownReadSet = readSet.asKnownFieldSet();
+ int oldSize = seenBefore ? knownReadSet.size() : -1;
+
+ // Everything that is read in the predecessor blocks should also be included in the read set
+ // for the current block, so here we join the information from the predecessor blocks into the
+ // current read set.
+ boolean blockOrPredecessorMaybeReadAnyField = false;
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ AbstractFieldSet predecessorReadSet =
+ result.getOrDefault(predecessor, EmptyFieldSet.getInstance());
+ if (predecessorReadSet.isBottom()) {
+ continue;
+ }
+ if (predecessorReadSet.isTop()) {
+ blockOrPredecessorMaybeReadAnyField = true;
+ break;
+ }
+ assert predecessorReadSet.isConcreteFieldSet();
+ if (!knownReadSet.isConcreteFieldSet()) {
+ knownReadSet = new ConcreteMutableFieldSet();
+ }
+ knownReadSet.asConcreteFieldSet().addAll(predecessorReadSet.asConcreteFieldSet());
+ }
+
+ if (!blockOrPredecessorMaybeReadAnyField) {
+ // Finally, we update the read set with the fields that are read by the instructions in the
+ // current block. This can be skipped if the block has already been processed.
+ if (seenBefore) {
+ assert verifyFieldSetContainsAllFieldReadsInBlock(knownReadSet, block, context);
+ } else {
+ for (Instruction instruction : block.getInstructions()) {
+ AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+ if (instructionReadSet.isBottom()) {
+ continue;
+ }
+ if (instructionReadSet.isTop()) {
+ blockOrPredecessorMaybeReadAnyField = true;
+ break;
+ }
+ if (!knownReadSet.isConcreteFieldSet()) {
+ knownReadSet = new ConcreteMutableFieldSet();
+ }
+ knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet());
+ }
+ }
+ }
+
+ boolean changed = false;
+ if (blockOrPredecessorMaybeReadAnyField) {
+ // Record that this block reads all fields.
+ result.put(block, UnknownFieldSet.getInstance());
+ changed = true;
+ } else {
+ if (knownReadSet != readSet) {
+ result.put(block, knownReadSet.asConcreteFieldSet());
+ }
+ if (knownReadSet.size() != oldSize) {
+ assert knownReadSet.size() > oldSize;
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ // Rerun the analysis for all successors because the state of the current block changed.
+ worklist.addAll(block.getSuccessors());
+ }
+ }
+ return result;
+ }
+
+ private boolean verifyFieldSetContainsAllFieldReadsInBlock(
+ KnownFieldSet readSet, BasicBlock block, ProgramMethod context) {
+ for (Instruction instruction : block.getInstructions()) {
+ AbstractFieldSet instructionReadSet = instruction.readSet(appView, context);
+ assert !instructionReadSet.isTop();
+ if (instructionReadSet.isBottom()) {
+ continue;
+ }
+ for (DexEncodedField field : instructionReadSet.asConcreteFieldSet().getFields()) {
+ assert readSet.contains(field);
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
new file mode 100644
index 0000000..01a65d2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraph.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValue;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.optimize.argumentpropagation.utils.BidirectedGraph;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class FlowGraph extends BidirectedGraph<FlowGraphNode> implements FlowGraphStateProvider {
+
+ private final Map<DexField, FlowGraphFieldNode> fieldNodes;
+ private final Map<DexMethod, Int2ReferenceMap<FlowGraphParameterNode>> parameterNodes;
+
+ public FlowGraph(
+ Map<DexField, FlowGraphFieldNode> fieldNodes,
+ Map<DexMethod, Int2ReferenceMap<FlowGraphParameterNode>> parameterNodes) {
+ this.fieldNodes = fieldNodes;
+ this.parameterNodes = parameterNodes;
+ }
+
+ public FlowGraph(Collection<FlowGraphNode> nodes) {
+ this(new IdentityHashMap<>(), new IdentityHashMap<>());
+ for (FlowGraphNode node : nodes) {
+ if (node.isFieldNode()) {
+ FlowGraphFieldNode fieldNode = node.asFieldNode();
+ fieldNodes.put(fieldNode.getField().getReference(), fieldNode);
+ } else {
+ FlowGraphParameterNode parameterNode = node.asParameterNode();
+ parameterNodes
+ .computeIfAbsent(
+ parameterNode.getMethod().getReference(), ignoreKey(Int2ReferenceOpenHashMap::new))
+ .put(parameterNode.getParameterIndex(), parameterNode);
+ }
+ }
+ }
+
+ public static FlowGraphBuilder builder(
+ AppView<AppInfoWithLiveness> appView,
+ IRConverter converter,
+ FieldStateCollection fieldStates,
+ MethodStateCollectionByReference methodStates) {
+ return new FlowGraphBuilder(appView, converter, fieldStates, methodStates);
+ }
+
+ public void forEachFieldNode(Consumer<? super FlowGraphFieldNode> consumer) {
+ fieldNodes.values().forEach(consumer);
+ }
+
+ @Override
+ public void forEachNeighbor(FlowGraphNode node, Consumer<? super FlowGraphNode> consumer) {
+ node.getPredecessors().forEach(consumer);
+ node.getSuccessors().forEach(consumer);
+ }
+
+ @Override
+ public void forEachNode(Consumer<? super FlowGraphNode> consumer) {
+ forEachFieldNode(consumer);
+ parameterNodes.values().forEach(nodesForMethod -> nodesForMethod.values().forEach(consumer));
+ }
+
+ @Override
+ public ValueState getState(DexField field) {
+ return fieldNodes.get(field).getState();
+ }
+
+ @Override
+ public ValueState getState(BaseInFlow inFlow, Supplier<ValueState> defaultStateProvider) {
+ if (inFlow.isFieldValue()) {
+ FieldValue fieldValue = inFlow.asFieldValue();
+ return getState(fieldValue.getField());
+ } else {
+ assert inFlow.isMethodParameter();
+ MethodParameter methodParameter = inFlow.asMethodParameter();
+ FlowGraphParameterNode parameterNode =
+ parameterNodes
+ .getOrDefault(methodParameter.getMethod(), Int2ReferenceMaps.emptyMap())
+ .get(methodParameter.getIndex());
+ if (parameterNode != null) {
+ return parameterNode.getState();
+ }
+ return defaultStateProvider.get();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
new file mode 100644
index 0000000..00d36e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphBuilder.java
@@ -0,0 +1,300 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.graph.ProgramField.asProgramFieldOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValue;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FlowGraphBuilder {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final IRConverter converter;
+ private final FieldStateCollection fieldStates;
+ private final MethodStateCollectionByReference methodStates;
+
+ private final Map<DexField, FlowGraphFieldNode> fieldNodes = new IdentityHashMap<>();
+ private final Map<DexMethod, Int2ReferenceMap<FlowGraphParameterNode>> parameterNodes =
+ new IdentityHashMap<>();
+
+ public FlowGraphBuilder(
+ AppView<AppInfoWithLiveness> appView,
+ IRConverter converter,
+ FieldStateCollection fieldStates,
+ MethodStateCollectionByReference methodStates) {
+ this.appView = appView;
+ this.converter = converter;
+ this.fieldStates = fieldStates;
+ this.methodStates = methodStates;
+ }
+
+ public FlowGraphBuilder addClasses(Collection<DexProgramClass> classes) {
+ for (DexProgramClass clazz : classes) {
+ add(clazz);
+ }
+ return this;
+ }
+
+ public FlowGraph build() {
+ return new FlowGraph(fieldNodes, parameterNodes);
+ }
+
+ private void add(DexProgramClass clazz) {
+ clazz.forEachProgramField(this::addField);
+ clazz.forEachProgramMethod(this::addMethodParameters);
+ }
+
+ private void addField(ProgramField field) {
+ ValueState fieldState = fieldStates.get(field);
+
+ // No need to create nodes for fields with no in-flow or no useful information.
+ if (fieldState.isBottom() || fieldState.isUnknown()) {
+ return;
+ }
+
+ ConcreteValueState concreteFieldState = fieldState.asConcrete();
+
+ // No need to create a node for a field that doesn't depend on any other nodes, unless some
+ // other node depends on this field, in which case that other node will lead to creation of a
+ // node for the current field.
+ if (!concreteFieldState.hasInFlow()) {
+ return;
+ }
+
+ FlowGraphFieldNode node = getOrCreateFieldNode(field, concreteFieldState);
+ for (InFlow inFlow : concreteFieldState.getInFlow()) {
+ if (addInFlow(inFlow, node).shouldBreak()) {
+ assert node.isUnknown();
+ break;
+ }
+ }
+
+ ValueState concreteFieldStateOrBottom = concreteFieldState.clearInFlow();
+ if (concreteFieldStateOrBottom.isBottom()) {
+ fieldStates.remove(field);
+ if (!node.getState().isUnknown()) {
+ assert node.getState() == concreteFieldState;
+ node.setState(concreteFieldStateOrBottom);
+ }
+ }
+ }
+
+ private void addMethodParameters(ProgramMethod method) {
+ MethodState methodState = methodStates.get(method);
+
+ // No need to create nodes for parameters with no in-flow or no useful information.
+ if (methodState.isBottom() || methodState.isUnknown()) {
+ return;
+ }
+
+ // Add nodes for the parameters for which we have non-trivial information.
+ ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
+ List<ValueState> parameterStates = monomorphicMethodState.getParameterStates();
+ for (int parameterIndex = 0; parameterIndex < parameterStates.size(); parameterIndex++) {
+ ValueState parameterState = parameterStates.get(parameterIndex);
+ addMethodParameter(method, parameterIndex, monomorphicMethodState, parameterState);
+ }
+ }
+
+ private void addMethodParameter(
+ ProgramMethod method,
+ int parameterIndex,
+ ConcreteMonomorphicMethodState methodState,
+ ValueState parameterState) {
+ // No need to create nodes for parameters with no in-parameters and parameters we don't know
+ // anything about.
+ if (parameterState.isBottom() || parameterState.isUnknown()) {
+ return;
+ }
+
+ ConcreteValueState concreteParameterState = parameterState.asConcrete();
+
+ // No need to create a node for a parameter that doesn't depend on any other parameters,
+ // unless some other node depends on this parameter, in which case that other node will lead
+ // to the creation of a node for the current parameter.
+ if (!concreteParameterState.hasInFlow()) {
+ return;
+ }
+
+ FlowGraphParameterNode node = getOrCreateParameterNode(method, parameterIndex, methodState);
+ for (InFlow inFlow : concreteParameterState.getInFlow()) {
+ if (addInFlow(inFlow, node).shouldBreak()) {
+ assert node.isUnknown();
+ break;
+ }
+ }
+
+ if (!node.getState().isUnknown()) {
+ assert node.getState() == concreteParameterState;
+ node.setState(concreteParameterState.clearInFlow());
+ }
+ }
+
+ // Returns BREAK if the current node has been set to unknown.
+ private TraversalContinuation<?, ?> addInFlow(InFlow inFlow, FlowGraphNode node) {
+ if (inFlow.isAbstractFunction()) {
+ return addInFlow(inFlow.asAbstractFunction(), node);
+ } else if (inFlow.isFieldValue()) {
+ return addInFlow(inFlow.asFieldValue(), node);
+ } else if (inFlow.isMethodParameter()) {
+ return addInFlow(inFlow.asMethodParameter(), node);
+ } else {
+ throw new Unreachable(inFlow.getClass().getTypeName());
+ }
+ }
+
+ private TraversalContinuation<?, ?> addInFlow(AbstractFunction inFlow, FlowGraphNode node) {
+ for (BaseInFlow baseInFlow : inFlow.getBaseInFlow()) {
+ TraversalContinuation<?, ?> traversalContinuation;
+ if (baseInFlow.isFieldValue()) {
+ traversalContinuation = addInFlow(baseInFlow.asFieldValue(), node, inFlow);
+ } else {
+ assert baseInFlow.isMethodParameter();
+ traversalContinuation = addInFlow(baseInFlow.asMethodParameter(), node, inFlow);
+ }
+ if (traversalContinuation.shouldBreak()) {
+ return traversalContinuation;
+ }
+ }
+ return TraversalContinuation.doContinue();
+ }
+
+ private TraversalContinuation<?, ?> addInFlow(FieldValue inFlow, FlowGraphNode node) {
+ return addInFlow(inFlow, node, AbstractFunction.identity());
+ }
+
+ private TraversalContinuation<?, ?> addInFlow(
+ FieldValue inFlow, FlowGraphNode node, AbstractFunction transferFunction) {
+ assert !node.isUnknown();
+
+ ProgramField field = asProgramFieldOrNull(appView.definitionFor(inFlow.getField()));
+ if (field == null) {
+ assert false;
+ return TraversalContinuation.doContinue();
+ }
+
+ ValueState fieldState = getFieldState(field, fieldStates);
+ if (fieldState.isUnknown()) {
+ // The current node depends on a field for which we don't know anything.
+ node.clearPredecessors();
+ node.setStateToUnknown();
+ return TraversalContinuation.doBreak();
+ }
+
+ FlowGraphFieldNode fieldNode = getOrCreateFieldNode(field, fieldState);
+ node.addPredecessor(fieldNode, transferFunction);
+ return TraversalContinuation.doContinue();
+ }
+
+ private TraversalContinuation<?, ?> addInFlow(MethodParameter inFlow, FlowGraphNode node) {
+ return addInFlow(inFlow, node, AbstractFunction.identity());
+ }
+
+ private TraversalContinuation<?, ?> addInFlow(
+ MethodParameter inFlow, FlowGraphNode node, AbstractFunction transferFunction) {
+ ProgramMethod enclosingMethod = getEnclosingMethod(inFlow);
+ if (enclosingMethod == null) {
+ // This is a parameter of a single caller inlined method. Since this method has been
+ // pruned, the call from inside the method no longer exists, and we can therefore safely
+ // skip it.
+ assert converter.getInliner().verifyIsPrunedDueToSingleCallerInlining(inFlow.getMethod());
+ return TraversalContinuation.doContinue();
+ }
+
+ MethodState enclosingMethodState = getMethodState(enclosingMethod, methodStates);
+ if (enclosingMethodState.isBottom()) {
+ // The current node takes a value from a dead method; no need to propagate any information
+ // from the dead assignment.
+ return TraversalContinuation.doContinue();
+ }
+
+ if (enclosingMethodState.isUnknown()) {
+ // The current node depends on a parameter for which we don't know anything.
+ node.clearPredecessors();
+ node.setStateToUnknown();
+ return TraversalContinuation.doBreak();
+ }
+
+ assert enclosingMethodState.isConcrete();
+ assert enclosingMethodState.asConcrete().isMonomorphic();
+
+ FlowGraphParameterNode predecessor =
+ getOrCreateParameterNode(
+ enclosingMethod, inFlow.getIndex(), enclosingMethodState.asConcrete().asMonomorphic());
+ node.addPredecessor(predecessor, transferFunction);
+ return TraversalContinuation.doContinue();
+ }
+
+ private FlowGraphFieldNode getOrCreateFieldNode(ProgramField field, ValueState fieldState) {
+ return fieldNodes.computeIfAbsent(
+ field.getReference(), ignoreKey(() -> new FlowGraphFieldNode(field, fieldState)));
+ }
+
+ private FlowGraphParameterNode getOrCreateParameterNode(
+ ProgramMethod method, int parameterIndex, ConcreteMonomorphicMethodState methodState) {
+ Int2ReferenceMap<FlowGraphParameterNode> parameterNodesForMethod =
+ parameterNodes.computeIfAbsent(
+ method.getReference(), ignoreKey(Int2ReferenceOpenHashMap::new));
+ return parameterNodesForMethod.compute(
+ parameterIndex,
+ (ignore, parameterNode) ->
+ parameterNode != null
+ ? parameterNode
+ : new FlowGraphParameterNode(
+ method, methodState, parameterIndex, method.getArgumentType(parameterIndex)));
+ }
+
+ private ProgramMethod getEnclosingMethod(MethodParameter methodParameter) {
+ DexMethod methodReference = methodParameter.getMethod();
+ return methodReference.lookupOnProgramClass(
+ asProgramClassOrNull(appView.definitionFor(methodParameter.getMethod().getHolderType())));
+ }
+
+ private ValueState getFieldState(ProgramField field, FieldStateCollection fieldStates) {
+ if (field == null) {
+ // Conservatively return unknown if for some reason we can't find the field.
+ assert false;
+ return ValueState.unknown();
+ }
+ return fieldStates.get(field);
+ }
+
+ private MethodState getMethodState(
+ ProgramMethod method, MethodStateCollectionByReference methodStates) {
+ if (method == null) {
+ // Conservatively return unknown if for some reason we can't find the method.
+ assert false;
+ return MethodState.unknown();
+ }
+ return methodStates.get(method);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphFieldNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphFieldNode.java
new file mode 100644
index 0000000..c25ed35
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphFieldNode.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public class FlowGraphFieldNode extends FlowGraphNode {
+
+ private final ProgramField field;
+ private ValueState fieldState;
+
+ FlowGraphFieldNode(ProgramField field, ValueState fieldState) {
+ this.field = field;
+ this.fieldState = fieldState;
+ }
+
+ public ProgramField getField() {
+ return field;
+ }
+
+ @Override
+ DexType getStaticType() {
+ return field.getType();
+ }
+
+ void addDefaultValue(AppView<AppInfoWithLiveness> appView, Action onChangedAction) {
+ AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+ AbstractValue defaultValue;
+ if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
+ defaultValue = field.getDefinition().getStaticValue().toAbstractValue(abstractValueFactory);
+ } else if (field.getType().isPrimitiveType()) {
+ defaultValue = abstractValueFactory.createZeroValue();
+ } else {
+ defaultValue = abstractValueFactory.createUncheckedNullValue();
+ }
+ NonEmptyValueState fieldStateToAdd;
+ if (field.getType().isArrayType()) {
+ Nullability defaultNullability = Nullability.definitelyNull();
+ fieldStateToAdd = ConcreteArrayTypeValueState.create(defaultNullability);
+ } else if (field.getType().isClassType()) {
+ assert defaultValue.isNull() || defaultValue.isSingleStringValue();
+ DynamicType dynamicType =
+ defaultValue.isNull()
+ ? DynamicType.definitelyNull()
+ : DynamicType.createExact(
+ TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
+ fieldStateToAdd = ConcreteClassTypeValueState.create(defaultValue, dynamicType);
+ } else {
+ assert field.getType().isPrimitiveType();
+ fieldStateToAdd = ConcretePrimitiveTypeValueState.create(defaultValue);
+ }
+ if (fieldStateToAdd.isConcrete()) {
+ addState(appView, fieldStateToAdd.asConcrete(), onChangedAction);
+ } else {
+ // We should always be able to map static field values to an unknown abstract value.
+ assert false;
+ setStateToUnknown();
+ onChangedAction.execute();
+ }
+ }
+
+ @Override
+ ValueState getState() {
+ return fieldState;
+ }
+
+ @Override
+ void setState(ValueState fieldState) {
+ this.fieldState = fieldState;
+ }
+
+ @Override
+ boolean isFieldNode() {
+ return true;
+ }
+
+ @Override
+ FlowGraphFieldNode asFieldNode() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
new file mode 100644
index 0000000..4075465
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphNode.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.google.common.collect.Sets;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
+
+public abstract class FlowGraphNode {
+
+ private final Set<FlowGraphNode> predecessors = Sets.newIdentityHashSet();
+ private final Map<FlowGraphNode, Set<AbstractFunction>> successors = new IdentityHashMap<>();
+
+ private boolean inWorklist = true;
+
+ void addState(
+ AppView<AppInfoWithLiveness> appView, ConcreteValueState stateToAdd, Action onChangedAction) {
+ ValueState oldState = getState();
+ ValueState newState =
+ oldState.mutableJoin(
+ appView, stateToAdd, getStaticType(), StateCloner.getCloner(), onChangedAction);
+ if (newState != oldState) {
+ setState(newState);
+ onChangedAction.execute();
+ }
+ }
+
+ abstract ValueState getState();
+
+ abstract DexType getStaticType();
+
+ abstract void setState(ValueState valueState);
+
+ void setStateToUnknown() {
+ setState(ValueState.unknown());
+ }
+
+ void addPredecessor(FlowGraphNode predecessor, AbstractFunction abstractFunction) {
+ predecessor.successors.computeIfAbsent(this, ignoreKey(HashSet::new)).add(abstractFunction);
+ predecessors.add(predecessor);
+ }
+
+ void clearPredecessors() {
+ for (FlowGraphNode predecessor : predecessors) {
+ predecessor.successors.remove(this);
+ }
+ predecessors.clear();
+ }
+
+ void clearPredecessors(FlowGraphNode cause) {
+ for (FlowGraphNode predecessor : predecessors) {
+ if (predecessor != cause) {
+ predecessor.successors.remove(this);
+ }
+ }
+ predecessors.clear();
+ }
+
+ Set<FlowGraphNode> getPredecessors() {
+ return predecessors;
+ }
+
+ boolean hasPredecessors() {
+ return !predecessors.isEmpty();
+ }
+
+ void clearDanglingSuccessors() {
+ successors.clear();
+ }
+
+ Set<FlowGraphNode> getSuccessors() {
+ return successors.keySet();
+ }
+
+ public void forEachSuccessor(BiConsumer<FlowGraphNode, Set<AbstractFunction>> consumer) {
+ successors.forEach(consumer);
+ }
+
+ public void removeSuccessorIf(BiPredicate<FlowGraphNode, Set<AbstractFunction>> predicate) {
+ successors.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
+ }
+
+ boolean hasSuccessors() {
+ return !successors.isEmpty();
+ }
+
+ boolean isBottom() {
+ return getState().isBottom();
+ }
+
+ boolean isFieldNode() {
+ return false;
+ }
+
+ FlowGraphFieldNode asFieldNode() {
+ return null;
+ }
+
+ boolean isParameterNode() {
+ return false;
+ }
+
+ FlowGraphParameterNode asParameterNode() {
+ return null;
+ }
+
+ boolean isEffectivelyUnknown() {
+ return getState().isConcrete() && getState().asConcrete().isEffectivelyUnknown();
+ }
+
+ boolean isUnknown() {
+ return getState().isUnknown();
+ }
+
+ // No need to enqueue the affected node if it is already in the worklist or if it does not have
+ // any successors (i.e., the successor is a leaf).
+ void addToWorkList(Deque<FlowGraphNode> worklist) {
+ if (!inWorklist && hasSuccessors()) {
+ worklist.add(this);
+ inWorklist = true;
+ }
+ }
+
+ void unsetInWorklist() {
+ assert inWorklist;
+ inWorklist = false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphParameterNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphParameterNode.java
new file mode 100644
index 0000000..b82cecf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphParameterNode.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+
+class FlowGraphParameterNode extends FlowGraphNode {
+
+ private final ProgramMethod method;
+ private final ConcreteMonomorphicMethodState methodState;
+ private final int parameterIndex;
+ private final DexType parameterType;
+
+ FlowGraphParameterNode(
+ ProgramMethod method,
+ ConcreteMonomorphicMethodState methodState,
+ int parameterIndex,
+ DexType parameterType) {
+ this.method = method;
+ this.methodState = methodState;
+ this.parameterIndex = parameterIndex;
+ this.parameterType = parameterType;
+ }
+
+ ProgramMethod getMethod() {
+ return method;
+ }
+
+ int getParameterIndex() {
+ return parameterIndex;
+ }
+
+ @Override
+ DexType getStaticType() {
+ return parameterType;
+ }
+
+ @Override
+ ValueState getState() {
+ return methodState.getParameterState(parameterIndex);
+ }
+
+ @Override
+ void setState(ValueState parameterState) {
+ methodState.setParameterState(parameterIndex, parameterState);
+ }
+
+ @Override
+ boolean isParameterNode() {
+ return true;
+ }
+
+ @Override
+ FlowGraphParameterNode asParameterNode() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphWriter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphWriter.java
new file mode 100644
index 0000000..8f60f27
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FlowGraphWriter.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.google.common.annotations.VisibleForTesting;
+import java.io.PrintStream;
+
+// A simple (unused) writer to aid debugging of field and parameter flow propagation graphs.
+@VisibleForTesting
+public class FlowGraphWriter {
+
+ private final FlowGraph flowGraph;
+
+ public FlowGraphWriter(FlowGraph flowGraph) {
+ this.flowGraph = flowGraph;
+ }
+
+ public void write(PrintStream out) {
+ out.println("digraph {");
+ out.println(" stylesheet = \"/frameworks/g3doc/includes/graphviz-style.css\"");
+ flowGraph.forEachNode(node -> writeNode(out, node));
+ out.println("}");
+ }
+
+ private void writeEdge(PrintStream out) {
+ out.println(" -> ");
+ }
+
+ private void writeNode(PrintStream out, FlowGraphNode node) {
+ if (!node.hasSuccessors()) {
+ writeNodeLabel(out, node);
+ return;
+ }
+ node.forEachSuccessor(
+ (successor, transferFunctions) -> {
+ writeNodeLabel(out, node);
+ writeEdge(out);
+ writeNodeLabel(out, successor);
+ });
+ }
+
+ private void writeNodeLabel(PrintStream out, FlowGraphNode node) {
+ if (node.isFieldNode()) {
+ writeFieldNodeLabel(out, node.asFieldNode());
+ } else {
+ assert node.isParameterNode();
+ writeParameterNodeLabel(out, node.asParameterNode());
+ }
+ }
+
+ private void writeFieldNodeLabel(PrintStream out, FlowGraphFieldNode node) {
+ out.print("\"");
+ ProgramField field = node.getField();
+ out.print(field.getHolderType().getSimpleName());
+ out.print(".");
+ out.print(field.getName().toSourceString());
+ out.print("\"");
+ }
+
+ private void writeParameterNodeLabel(PrintStream out, FlowGraphParameterNode node) {
+ out.print("\"");
+ ProgramMethod method = node.getMethod();
+ out.print(method.getHolderType().getSimpleName());
+ out.print(".");
+ out.print(method.getName().toSourceString());
+ out.print("(");
+ out.print(node.getParameterIndex());
+ out.print(")\"");
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index add2a26..3cd35c2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -1,86 +1,121 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, 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.optimize.argumentpropagation.propagation;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
-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.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
-import com.android.tools.r8.optimize.argumentpropagation.utils.BidirectedGraph;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.ArrayDeque;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.Deque;
-import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
public class InFlowPropagator {
final AppView<AppInfoWithLiveness> appView;
final IRConverter converter;
+ final FieldStateCollection fieldStates;
final MethodStateCollectionByReference methodStates;
public InFlowPropagator(
AppView<AppInfoWithLiveness> appView,
IRConverter converter,
+ FieldStateCollection fieldStates,
MethodStateCollectionByReference methodStates) {
this.appView = appView;
this.converter = converter;
+ this.fieldStates = fieldStates;
this.methodStates = methodStates;
}
public void run(ExecutorService executorService) throws ExecutionException {
- // Build a graph with an edge from parameter p -> parameter p' if all argument information for p
- // must be included in the argument information for p'.
- FlowGraph flowGraph = new FlowGraph(appView.appInfo().classes());
+ // Compute strongly connected components so that we can compute the fixpoint of multiple flow
+ // graphs in parallel.
+ List<FlowGraph> flowGraphs = computeStronglyConnectedFlowGraphs();
+ processFlowGraphs(flowGraphs, executorService);
- List<Set<ParameterNode>> stronglyConnectedComponents =
- flowGraph.computeStronglyConnectedComponents();
- ThreadUtils.processItems(
- stronglyConnectedComponents,
- this::process,
- appView.options().getThreadingModule(),
- executorService);
+ // Account for the fact that fields that are read before they are written also needs to include
+ // the default value in the field state. We only need to analyze if a given field is read before
+ // it is written if the field has a non-trivial state in the flow graph. Therefore, we only
+ // perform this analysis after having computed the initial fixpoint(s). The hypothesis is that
+ // many fields will have reached the unknown state after the initial fixpoint, meaning there is
+ // fewer fields to analyze.
+ updateFieldStates(fieldStates, flowGraphs);
+ Map<FlowGraph, Deque<FlowGraphNode>> worklists =
+ includeDefaultValuesInFieldStates(fieldStates, flowGraphs, executorService);
+
+ // Since the inclusion of default values changes the flow graphs, we need to repeat the
+ // fixpoint.
+ processWorklists(worklists, executorService);
// The algorithm only changes the parameter states of each monomorphic method state. In case any
// of these method states have effectively become unknown, we replace them by the canonicalized
// unknown method state.
postProcessMethodStates(executorService);
+
+ // Copy the result of the flow graph propagation back to the field state collection.
+ updateFieldStates(fieldStates, flowGraphs);
}
- private void process(Set<ParameterNode> stronglyConnectedComponent) {
- // Build a worklist containing all the parameter nodes.
- Deque<ParameterNode> worklist = new ArrayDeque<>(stronglyConnectedComponent);
+ private List<FlowGraph> computeStronglyConnectedFlowGraphs() {
+ // Build a graph with an edge from parameter p -> parameter p' if all argument information for p
+ // must be included in the argument information for p'.
+ FlowGraph flowGraph =
+ FlowGraph.builder(appView, converter, fieldStates, methodStates)
+ .addClasses(appView.appInfo().classes())
+ .build();
+ List<Set<FlowGraphNode>> stronglyConnectedComponents =
+ flowGraph.computeStronglyConnectedComponents();
+ return ListUtils.map(stronglyConnectedComponents, FlowGraph::new);
+ }
+ private Map<FlowGraph, Deque<FlowGraphNode>> includeDefaultValuesInFieldStates(
+ FieldStateCollection fieldStates, List<FlowGraph> flowGraphs, ExecutorService executorService)
+ throws ExecutionException {
+ DefaultFieldValueJoiner joiner = new DefaultFieldValueJoiner(appView, fieldStates, flowGraphs);
+ return joiner.joinDefaultFieldValuesForFieldsWithReadBeforeWrite(executorService);
+ }
+
+ private void processFlowGraphs(List<FlowGraph> flowGraphs, ExecutorService executorService)
+ throws ExecutionException {
+ ThreadUtils.processItems(
+ flowGraphs, this::process, appView.options().getThreadingModule(), executorService);
+ }
+
+ private void processWorklists(
+ Map<FlowGraph, Deque<FlowGraphNode>> worklists, ExecutorService executorService)
+ throws ExecutionException {
+ ThreadUtils.processMap(
+ worklists, this::process, appView.options().getThreadingModule(), executorService);
+ }
+
+ private void process(FlowGraph flowGraph) {
+ // Build a worklist containing all the nodes.
+ Deque<FlowGraphNode> worklist = new ArrayDeque<>();
+ flowGraph.forEachNode(worklist::add);
+ process(flowGraph, worklist);
+ }
+
+ private void process(FlowGraph flowGraph, Deque<FlowGraphNode> worklist) {
// Repeatedly propagate argument information through edges in the flow graph until there are no
// more changes.
// TODO(b/190154391): Consider a path p1 -> p2 -> p3 in the graph. If we process p2 first, then
@@ -88,41 +123,62 @@
// need to reprocess p2 and then p3. If we always process leaves in the graph first, we would
// process p1, then p2, then p3, and then be done.
while (!worklist.isEmpty()) {
- ParameterNode parameterNode = worklist.removeLast();
- parameterNode.unsetPending();
- propagate(
- parameterNode,
- affectedNode -> {
- // No need to enqueue the affected node if it is already in the worklist or if it does
- // not have any successors (i.e., the successor is a leaf).
- if (!affectedNode.isPending() && affectedNode.hasSuccessors()) {
- worklist.add(affectedNode);
- affectedNode.setPending();
- }
- });
+ FlowGraphNode node = worklist.removeLast();
+ node.unsetInWorklist();
+ propagate(flowGraph, node, worklist);
}
}
- private void propagate(
- ParameterNode parameterNode, Consumer<ParameterNode> affectedNodeConsumer) {
- ValueState parameterState = parameterNode.getState();
- if (parameterState.isBottom()) {
+ private void propagate(FlowGraph flowGraph, FlowGraphNode node, Deque<FlowGraphNode> worklist) {
+ if (node.isBottom()) {
return;
}
- List<ParameterNode> newlyUnknownParameterNodes = new ArrayList<>();
- for (ParameterNode successorNode : parameterNode.getSuccessors()) {
- ValueState newParameterState =
- successorNode.addState(
- appView,
- parameterState.asNonEmpty(),
- () -> affectedNodeConsumer.accept(successorNode));
- if (newParameterState.isUnknown()) {
- newlyUnknownParameterNodes.add(successorNode);
+ if (node.isUnknown()) {
+ assert !node.hasPredecessors();
+ for (FlowGraphNode successorNode : node.getSuccessors()) {
+ assert !successorNode.isUnknown();
+ successorNode.clearPredecessors(node);
+ successorNode.setStateToUnknown();
+ successorNode.addToWorkList(worklist);
}
+ node.clearDanglingSuccessors();
+ } else {
+ propagateNode(flowGraph, node, worklist);
}
- for (ParameterNode newlyUnknownParameterNode : newlyUnknownParameterNodes) {
- newlyUnknownParameterNode.clearPredecessors();
- }
+ }
+
+ private void propagateNode(
+ FlowGraph flowGraph, FlowGraphNode node, Deque<FlowGraphNode> worklist) {
+ ConcreteValueState state = node.getState().asConcrete();
+ node.removeSuccessorIf(
+ (successorNode, transferFunctions) -> {
+ assert !successorNode.isUnknown();
+ for (AbstractFunction transferFunction : transferFunctions) {
+ FlowGraphStateProvider flowGraphStateProvider =
+ FlowGraphStateProvider.create(flowGraph, transferFunction);
+ ValueState transferState =
+ transferFunction.apply(appView, flowGraphStateProvider, state);
+ if (transferState.isBottom()) {
+ // Nothing to propagate.
+ } else if (transferState.isUnknown()) {
+ successorNode.setStateToUnknown();
+ successorNode.addToWorkList(worklist);
+ } else {
+ ConcreteValueState concreteTransferState = transferState.asConcrete();
+ successorNode.addState(
+ appView, concreteTransferState, () -> successorNode.addToWorkList(worklist));
+ }
+ // If this successor has become unknown, there is no point in continuing to propagate
+ // flow to it from any of its predecessors. We therefore clear the predecessors to
+ // improve performance of the fixpoint computation.
+ if (successorNode.isUnknown()) {
+ successorNode.clearPredecessors(node);
+ return true;
+ }
+ assert !successorNode.isEffectivelyUnknown();
+ }
+ return false;
+ });
}
private void postProcessMethodStates(ExecutorService executorService) throws ExecutionException {
@@ -151,228 +207,19 @@
}
}
- public class FlowGraph extends BidirectedGraph<ParameterNode> {
-
- private final Map<DexMethod, Int2ReferenceMap<ParameterNode>> nodes = new IdentityHashMap<>();
-
- public FlowGraph(Iterable<DexProgramClass> classes) {
- classes.forEach(this::add);
- }
-
- @Override
- public void forEachNeighbor(ParameterNode node, Consumer<? super ParameterNode> consumer) {
- node.getPredecessors().forEach(consumer);
- node.getSuccessors().forEach(consumer);
- }
-
- @Override
- public void forEachNode(Consumer<? super ParameterNode> consumer) {
- nodes.values().forEach(nodesForMethod -> nodesForMethod.values().forEach(consumer));
- }
-
- private void add(DexProgramClass clazz) {
- clazz.forEachProgramMethod(this::add);
- }
-
- private void add(ProgramMethod method) {
- MethodState methodState = methodStates.get(method);
-
- // No need to create nodes for parameters with no in-flow or no useful information.
- if (methodState.isBottom() || methodState.isUnknown()) {
- return;
- }
-
- // Add nodes for the parameters for which we have non-trivial information.
- ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
- List<ValueState> parameterStates = monomorphicMethodState.getParameterStates();
- for (int parameterIndex = 0; parameterIndex < parameterStates.size(); parameterIndex++) {
- ValueState parameterState = parameterStates.get(parameterIndex);
- add(method, parameterIndex, monomorphicMethodState, parameterState);
- }
- }
-
- private void add(
- ProgramMethod method,
- int parameterIndex,
- ConcreteMonomorphicMethodState methodState,
- ValueState parameterState) {
- // No need to create nodes for parameters with no in-parameters and parameters we don't know
- // anything about.
- if (parameterState.isBottom() || parameterState.isUnknown()) {
- return;
- }
-
- ConcreteValueState concreteParameterState = parameterState.asConcrete();
-
- // No need to create a node for a parameter that doesn't depend on any other parameters
- // (unless some other parameter depends on this parameter).
- if (!concreteParameterState.hasInFlow()) {
- return;
- }
-
- ParameterNode node = getOrCreateParameterNode(method, parameterIndex, methodState);
- for (InFlow inFlow : concreteParameterState.getInFlow()) {
- if (inFlow.isMethodParameter()) {
- if (addInFlow(inFlow.asMethodParameter(), node).shouldBreak()) {
- break;
- }
- } else {
- throw new Unreachable();
- }
- }
-
- if (!node.getState().isUnknown()) {
- assert node.getState() == concreteParameterState;
- node.setState(concreteParameterState.clearInFlow());
- }
- }
-
- private TraversalContinuation<?, ?> addInFlow(MethodParameter inFlow, ParameterNode node) {
- ProgramMethod enclosingMethod = getEnclosingMethod(inFlow);
- if (enclosingMethod == null) {
- // This is a parameter of a single caller inlined method. Since this method has been
- // pruned, the call from inside the method no longer exists, and we can therefore safely
- // skip it.
- assert converter.getInliner().verifyIsPrunedDueToSingleCallerInlining(inFlow.getMethod());
- return TraversalContinuation.doContinue();
- }
-
- MethodState enclosingMethodState = getMethodState(enclosingMethod);
- if (enclosingMethodState.isBottom()) {
- // The current method is called from a dead method; no need to propagate any information
- // from the dead call site.
- return TraversalContinuation.doContinue();
- }
-
- if (enclosingMethodState.isUnknown()) {
- // The parameter depends on another parameter for which we don't know anything.
- node.clearPredecessors();
- node.setState(ValueState.unknown());
- return TraversalContinuation.doBreak();
- }
-
- assert enclosingMethodState.isConcrete();
- assert enclosingMethodState.asConcrete().isMonomorphic();
-
- ParameterNode predecessor =
- getOrCreateParameterNode(
- enclosingMethod,
- inFlow.getIndex(),
- enclosingMethodState.asConcrete().asMonomorphic());
- node.addPredecessor(predecessor);
- return TraversalContinuation.doContinue();
- }
-
- private ParameterNode getOrCreateParameterNode(
- ProgramMethod method, int parameterIndex, ConcreteMonomorphicMethodState methodState) {
- Int2ReferenceMap<ParameterNode> parameterNodesForMethod =
- nodes.computeIfAbsent(method.getReference(), ignoreKey(Int2ReferenceOpenHashMap::new));
- return parameterNodesForMethod.compute(
- parameterIndex,
- (ignore, parameterNode) ->
- parameterNode != null
- ? parameterNode
- : new ParameterNode(
- methodState, parameterIndex, method.getArgumentType(parameterIndex)));
- }
-
- private ProgramMethod getEnclosingMethod(MethodParameter methodParameter) {
- DexMethod methodReference = methodParameter.getMethod();
- return methodReference.lookupOnProgramClass(
- asProgramClassOrNull(appView.definitionFor(methodParameter.getMethod().getHolderType())));
- }
-
- private MethodState getMethodState(ProgramMethod method) {
- if (method == null) {
- // Conservatively return unknown if for some reason we can't find the method.
- assert false;
- return MethodState.unknown();
- }
- return methodStates.get(method);
- }
- }
-
- static class ParameterNode {
-
- private final ConcreteMonomorphicMethodState methodState;
- private final int parameterIndex;
- private final DexType parameterType;
-
- private final Set<ParameterNode> predecessors = Sets.newIdentityHashSet();
- private final Set<ParameterNode> successors = Sets.newIdentityHashSet();
-
- private boolean pending = true;
-
- ParameterNode(
- ConcreteMonomorphicMethodState methodState, int parameterIndex, DexType parameterType) {
- this.methodState = methodState;
- this.parameterIndex = parameterIndex;
- this.parameterType = parameterType;
- }
-
- void addPredecessor(ParameterNode predecessor) {
- predecessor.successors.add(this);
- predecessors.add(predecessor);
- }
-
- void clearPredecessors() {
- for (ParameterNode predecessor : predecessors) {
- predecessor.successors.remove(this);
- }
- predecessors.clear();
- }
-
- Set<ParameterNode> getPredecessors() {
- return predecessors;
- }
-
- ValueState getState() {
- return methodState.getParameterState(parameterIndex);
- }
-
- Set<ParameterNode> getSuccessors() {
- return successors;
- }
-
- boolean hasSuccessors() {
- return !successors.isEmpty();
- }
-
- boolean isPending() {
- return pending;
- }
-
- ValueState addState(
- AppView<AppInfoWithLiveness> appView,
- NonEmptyValueState parameterStateToAdd,
- Action onChangedAction) {
- ValueState oldParameterState = getState();
- ValueState newParameterState =
- oldParameterState.mutableJoin(
- appView,
- parameterStateToAdd,
- parameterType,
- StateCloner.getCloner(),
- onChangedAction);
- if (newParameterState != oldParameterState) {
- setState(newParameterState);
- onChangedAction.execute();
- }
- return newParameterState;
- }
-
- void setPending() {
- assert !isPending();
- pending = true;
- }
-
- void setState(ValueState parameterState) {
- methodState.setParameterState(parameterIndex, parameterState);
- }
-
- void unsetPending() {
- assert pending;
- pending = false;
+ private void updateFieldStates(
+ FieldStateCollection fieldStates, Collection<FlowGraph> flowGraphs) {
+ for (FlowGraph flowGraph : flowGraphs) {
+ flowGraph.forEachFieldNode(
+ node -> {
+ ProgramField field = node.getField();
+ ValueState state = node.getState();
+ ValueState previousState = fieldStates.set(field, state);
+ assert state.isUnknown()
+ || state == previousState
+ || (state.isConcrete() && previousState.isBottom())
+ : "Expected current state to be >= previous state";
+ });
}
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
index 2a54b56..6890b1c 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorComposeModeling.java
@@ -4,12 +4,17 @@
package com.android.tools.r8.optimize.compose;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -17,9 +22,10 @@
import com.android.tools.r8.ir.code.Or;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldValue;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.OrAbstractFunction;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.BitUtils;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.Iterables;
@@ -32,7 +38,10 @@
private final DexString invokeName;
public ArgumentPropagatorComposeModeling(AppView<AppInfoWithLiveness> appView) {
- assert appView.testing().modelUnknownChangedAndDefaultArgumentsToComposableFunctions;
+ assert appView
+ .options()
+ .getJetpackComposeOptions()
+ .isModelingChangedArgumentsToComposableFunctions();
this.appView = appView;
this.rewrittenComposeReferences =
appView
@@ -139,69 +148,83 @@
return null;
}
- // We only model the two last arguments ($$changed and $$default) to the @Composable function.
- // If this argument is not on of these two int arguments, then don't apply any modeling.
- if (argumentIndex <= composerParameterIndex) {
+ // We only model the $$changed argument to the @Composable function.
+ if (argumentIndex != composerParameterIndex + 1) {
return null;
}
assert argument.getType().isInt();
- DexString expectedFieldName;
- ValueState state = ValueState.bottomPrimitiveTypeParameter();
- if (!hasDefaultParameter || argumentIndex == invokedMethod.getArity() - 2) {
- // We are looking at an argument to the $$changed parameter of the @Composable function.
- // We generally expect this argument to be defined by a call to updateChangedFlags().
- if (argument.isDefinedByInstructionSatisfying(Instruction::isInvokeStatic)) {
- InvokeStatic invokeStatic = argument.getDefinition().asInvokeStatic();
- DexMethod maybeUpdateChangedFlagsMethod = invokeStatic.getInvokedMethod();
- if (!maybeUpdateChangedFlagsMethod.isIdenticalTo(
- rewrittenComposeReferences.updatedChangedFlagsMethod)) {
- return null;
- }
- // Assume the call does not impact the $$changed capture and strip the call.
- argument = invokeStatic.getFirstArgument();
+ DexField changedField =
+ appView
+ .dexItemFactory()
+ .createField(
+ context.getHolderType(),
+ appView.dexItemFactory().intType,
+ rewrittenComposeReferences.changedFieldName);
+
+ UpdateChangedFlagsAbstractFunction inFlow = null;
+ // We are looking at an argument to the $$changed parameter of the @Composable function.
+ // We generally expect this argument to be defined by a call to updateChangedFlags().
+ if (argument.isDefinedByInstructionSatisfying(Instruction::isInvokeStatic)) {
+ InvokeStatic invokeStatic = argument.getDefinition().asInvokeStatic();
+ SingleResolutionResult<?> resolutionResult =
+ invokeStatic.resolveMethod(appView, context).asSingleResolution();
+ if (resolutionResult == null) {
+ return null;
}
- // Allow the argument to be defined by `this.$$changed | 1`.
- if (argument.isDefinedByInstructionSatisfying(Instruction::isOr)) {
- Or or = argument.getDefinition().asOr();
- Value maybeNumberOperand =
- or.leftValue().isConstNumber() ? or.leftValue() : or.rightValue();
- Value otherOperand = or.getOperand(1 - or.inValues().indexOf(maybeNumberOperand));
- if (!maybeNumberOperand.isConstNumber(1)) {
- return null;
- }
- // Strip the OR instruction.
- argument = otherOperand;
- // Update the model from bottom to a special value that effectively throws away any known
- // information about the lowermost bit of $$changed.
- state =
- new ConcretePrimitiveTypeValueState(
- appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- BitUtils.ALL_BITS_SET_MASK, BitUtils.ALL_BITS_SET_MASK << 1));
+ DexClassAndMethod invokeSingleTarget =
+ resolutionResult
+ .lookupDispatchTarget(appView, invokeStatic, context)
+ .getSingleDispatchTarget();
+ if (invokeSingleTarget == null) {
+ return null;
}
- expectedFieldName = rewrittenComposeReferences.changedFieldName;
+ inFlow =
+ invokeSingleTarget
+ .getOptimizationInfo()
+ .getAbstractFunction()
+ .asUpdateChangedFlagsAbstractFunction();
+ if (inFlow == null) {
+ return null;
+ }
+ // By accounting for the abstract function we can safely strip the call.
+ argument = invokeStatic.getFirstArgument();
+ }
+ // Allow the argument to be defined by `this.$$changed | 1`.
+ if (argument.isDefinedByInstructionSatisfying(Instruction::isOr)) {
+ Or or = argument.getDefinition().asOr();
+ Value maybeNumberOperand = or.leftValue().isConstNumber() ? or.leftValue() : or.rightValue();
+ Value otherOperand = or.getOperand(1 - or.inValues().indexOf(maybeNumberOperand));
+ if (!maybeNumberOperand.isConstNumber(1)) {
+ return null;
+ }
+ // Strip the OR instruction.
+ argument = otherOperand;
+ // Update the model from bottom to a special value that effectively throws away any known
+ // information about the lowermost bit of $$changed.
+ SingleNumberValue one =
+ appView.abstractValueFactory().createSingleNumberValue(1, TypeElement.getInt());
+ inFlow =
+ new UpdateChangedFlagsAbstractFunction(
+ new OrAbstractFunction(new FieldValue(changedField), one));
} else {
- // We are looking at an argument to the $$default parameter of the @Composable function.
- expectedFieldName = rewrittenComposeReferences.defaultFieldName;
+ inFlow = new UpdateChangedFlagsAbstractFunction(new FieldValue(changedField));
}
- // At this point we expect that the restart lambda is reading either this.$$changed or
- // this.$$default using an instance-get.
+ // At this point we expect that the restart lambda is reading this.$$changed using an
+ // instance-get.
if (!argument.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) {
return null;
}
// Check that the instance-get is reading the capture field that we expect it to.
InstanceGet instanceGet = argument.getDefinition().asInstanceGet();
- if (!instanceGet.getField().getName().isIdenticalTo(expectedFieldName)) {
+ if (!instanceGet.getField().isIdenticalTo(changedField)) {
return null;
}
- // Return the argument model. Note that, for the $$default field, this is always bottom, which
- // is equivalent to modeling that this call does not contribute any new argument information.
- return state;
+ // Return the argument model.
+ return new ConcretePrimitiveTypeValueState(inFlow);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java
index 1c23e06..c2890d8 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java
@@ -6,8 +6,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Iterables;
@@ -35,18 +33,11 @@
ExecutorService executorService,
Timing timing)
throws ExecutionException {
- InternalOptions options = appView.options();
- if (!options.isOptimizing() || !options.isShrinking()) {
- return;
+ if (appView.options().getJetpackComposeOptions().isComposableOptimizationPassEnabled()) {
+ timing.time(
+ "ComposableOptimizationPass",
+ () -> new ComposableOptimizationPass(appView, converter).processWaves(executorService));
}
- TestingOptions testingOptions = options.getTestingOptions();
- if (!testingOptions.enableComposableOptimizationPass
- || !testingOptions.modelUnknownChangedAndDefaultArgumentsToComposableFunctions) {
- return;
- }
- timing.time(
- "ComposableOptimizationPass",
- () -> new ComposableOptimizationPass(appView, converter).processWaves(executorService));
}
void processWaves(ExecutorService executorService) throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
index 258b69b..846dcbe 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -3,9 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.compose;
+import static com.android.tools.r8.graph.ProgramField.asProgramFieldOrNull;
+
import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -20,12 +24,20 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorCodeScanner;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorOptimizationInfoPopulator;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.InFlowPropagator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.LazyBox;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Iterables;
@@ -52,12 +64,12 @@
this.converter = converter;
}
- // TODO(b/302483644): Process wave concurrently.
public Set<ComposableCallGraphNode> processWave(
Set<ComposableCallGraphNode> wave, ExecutorService executorService)
throws ExecutionException {
ProcessorContext processorContext = appView.createProcessorContext();
- wave.forEach(
+ ThreadUtils.processItems(
+ wave,
node -> {
assert !processed.contains(node);
converter.processDesugaredMethod(
@@ -66,17 +78,25 @@
this,
processorContext.createMethodProcessingContext(node.getMethod()),
MethodConversionOptions.forLirPhase(appView));
- });
+ },
+ appView.options().getThreadingModule(),
+ executorService);
processed.addAll(wave);
return optimizeComposableFunctionsCalledFromWave(wave, executorService);
}
- @SuppressWarnings("UnusedVariable")
private Set<ComposableCallGraphNode> optimizeComposableFunctionsCalledFromWave(
Set<ComposableCallGraphNode> wave, ExecutorService executorService)
throws ExecutionException {
+ prepareForInFlowPropagator();
+
+ InFlowPropagator inFlowPropagator =
+ new InFlowPropagator(
+ appView, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates());
+ inFlowPropagator.run(executorService);
+
ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
- new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null);
+ new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null, null);
Set<ComposableCallGraphNode> optimizedComposableFunctions = Sets.newIdentityHashSet();
wave.forEach(
node ->
@@ -92,36 +112,87 @@
return optimizedComposableFunctions;
}
- private MethodState getMethodState(ComposableCallGraphNode node) {
- assert processed.containsAll(node.getCallers());
- MethodState methodState = codeScanner.getMethodStates().get(node.getMethod());
- return widenMethodState(methodState);
+ private void prepareForInFlowPropagator() {
+ FieldStateCollection fieldStates = codeScanner.getFieldStates();
+
+ // Set all field states to unknown since we are not guaranteed to have processes all field
+ // writes.
+ fieldStates.forEach(
+ (field, fieldState) ->
+ fieldStates.addTemporaryFieldState(
+ appView, field, ValueState::unknown, Timing.empty()));
+
+ // Widen all parameter states that have in-flow to unknown, except when the in-flow is an
+ // update-changed-flags abstract function.
+ MethodStateCollectionByReference methodStates = codeScanner.getMethodStates();
+ methodStates.forEach(
+ (method, methodState) -> {
+ if (!methodState.isMonomorphic()) {
+ assert methodState.isUnknown();
+ return;
+ }
+ ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
+ for (int parameterIndex = 0;
+ parameterIndex < monomorphicMethodState.size();
+ parameterIndex++) {
+ ValueState parameterState = monomorphicMethodState.getParameterState(parameterIndex);
+ if (parameterState.isConcrete()) {
+ ConcreteValueState concreteParameterState = parameterState.asConcrete();
+ prepareParameterStateForInFlowPropagator(
+ method, monomorphicMethodState, parameterIndex, concreteParameterState);
+ }
+ }
+ });
}
- /**
- * If a parameter state of the current method state encodes that it is greater than (lattice wise)
- * than another parameter in the program, then widen the parameter state to unknown. This is
- * needed since we are not guaranteed to have seen all possible call sites of the callers of this
- * method.
- */
- private MethodState widenMethodState(MethodState methodState) {
- assert !methodState.isBottom();
- assert !methodState.isPolymorphic();
- if (methodState.isMonomorphic()) {
- ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
- for (int i = 0; i < monomorphicMethodState.size(); i++) {
- if (monomorphicMethodState.getParameterState(i).isConcrete()) {
- ConcreteValueState concreteParameterState =
- monomorphicMethodState.getParameterState(i).asConcrete();
- if (concreteParameterState.hasInFlow()) {
- monomorphicMethodState.setParameterState(i, ValueState.unknown());
- }
- }
- }
- } else {
- assert methodState.isUnknown();
+ private void prepareParameterStateForInFlowPropagator(
+ DexMethod method,
+ ConcreteMonomorphicMethodState methodState,
+ int parameterIndex,
+ ConcreteValueState parameterState) {
+ if (!parameterState.hasInFlow()) {
+ return;
}
- return methodState;
+
+ UpdateChangedFlagsAbstractFunction transferFunction = null;
+ if (parameterState.getInFlow().size() == 1) {
+ transferFunction =
+ Iterables.getOnlyElement(parameterState.getInFlow())
+ .asUpdateChangedFlagsAbstractFunction();
+ }
+ if (transferFunction == null) {
+ methodState.setParameterState(parameterIndex, ValueState.unknown());
+ return;
+ }
+
+ // This is a call to a composable function from a restart function.
+ Iterable<BaseInFlow> baseInFlow = transferFunction.getBaseInFlow();
+ assert Iterables.size(baseInFlow) == 1;
+ BaseInFlow singleBaseInFlow = IterableUtils.first(baseInFlow);
+ assert singleBaseInFlow.isFieldValue();
+
+ ProgramField field =
+ asProgramFieldOrNull(appView.definitionFor(singleBaseInFlow.asFieldValue().getField()));
+ assert field != null;
+
+ // If the only input to the $$changed parameter of the Composable function is in-flow then skip.
+ if (methodState.getParameterState(parameterIndex).getAbstractValue(appView).isBottom()) {
+ methodState.setParameterState(parameterIndex, ValueState.unknown());
+ return;
+ }
+
+ codeScanner
+ .getFieldStates()
+ .addTemporaryFieldState(
+ appView,
+ field,
+ () -> new ConcretePrimitiveTypeValueState(new MethodParameter(method, parameterIndex)),
+ Timing.empty());
+ }
+
+ private MethodState getMethodState(ComposableCallGraphNode node) {
+ assert processed.containsAll(node.getCallers());
+ return codeScanner.getMethodStates().get(node.getMethod());
}
public void scan(ProgramMethod method, IRCode code, Timing timing) {
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
index e441188..e38f9a4 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeReferences.java
@@ -12,7 +12,6 @@
public class ComposeReferences {
public final DexString changedFieldName;
- public final DexString defaultFieldName;
public final DexType composableType;
public final DexType composerType;
@@ -21,7 +20,6 @@
public ComposeReferences(DexItemFactory factory) {
changedFieldName = factory.createString("$$changed");
- defaultFieldName = factory.createString("$$default");
composableType = factory.createType("Landroidx/compose/runtime/Composable;");
composerType = factory.createType("Landroidx/compose/runtime/Composer;");
@@ -35,12 +33,10 @@
public ComposeReferences(
DexString changedFieldName,
- DexString defaultFieldName,
DexType composableType,
DexType composerType,
DexMethod updatedChangedFlagsMethod) {
this.changedFieldName = changedFieldName;
- this.defaultFieldName = defaultFieldName;
this.composableType = composableType;
this.composerType = composerType;
this.updatedChangedFlagsMethod = updatedChangedFlagsMethod;
@@ -49,7 +45,6 @@
public ComposeReferences rewrittenWithLens(GraphLens graphLens, GraphLens codeLens) {
return new ComposeReferences(
changedFieldName,
- defaultFieldName,
graphLens.lookupClassType(composableType, codeLens),
graphLens.lookupClassType(composerType, codeLens),
graphLens.getRenamedMethodSignature(updatedChangedFlagsMethod, codeLens));
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/JetpackComposeOptions.java b/src/main/java/com/android/tools/r8/optimize/compose/JetpackComposeOptions.java
new file mode 100644
index 0000000..69ec5e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/JetpackComposeOptions.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2024, 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.optimize.compose;
+
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SystemPropertyUtils;
+
+public class JetpackComposeOptions {
+
+ private final InternalOptions options;
+
+ public boolean enableComposableOptimizationPass =
+ SystemPropertyUtils.parseSystemPropertyOrDefault(
+ "com.android.tools.r8.jetpackcompose.enableComposableOptimizationPass", false);
+
+ public boolean enableModelingOfChangedArguments =
+ SystemPropertyUtils.parseSystemPropertyOrDefault(
+ "com.android.tools.r8.jetpackcompose.enableModelingOfChangedArguments", false);
+
+ public JetpackComposeOptions(InternalOptions options) {
+ this.options = options;
+ }
+
+ public void enableAllOptimizations(boolean enable) {
+ enableComposableOptimizationPass = enable;
+ enableModelingOfChangedArguments = enable;
+ }
+
+ public boolean isAnyOptimizationsEnabled() {
+ return isComposableOptimizationPassEnabled()
+ || isModelingChangedArgumentsToComposableFunctions();
+ }
+
+ public boolean isComposableOptimizationPassEnabled() {
+ return isModelingChangedArgumentsToComposableFunctions() && enableComposableOptimizationPass;
+ }
+
+ public boolean isModelingChangedArgumentsToComposableFunctions() {
+ return options.isOptimizing() && options.isShrinking() && enableModelingOfChangedArguments;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java b/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
index 5ae767a..2d940e9 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/UpdateChangedFlagsAbstractFunction.java
@@ -3,15 +3,150 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize.compose;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.BaseInFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.OrAbstractFunction;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.IterableUtils;
+import java.util.Objects;
public class UpdateChangedFlagsAbstractFunction implements AbstractFunction {
- @SuppressWarnings("UnusedVariable")
+ private static final int changedLowBitMask = 0b001_001_001_001_001_001_001_001_001_001_0;
+ private static final int changedHighBitMask = changedLowBitMask << 1;
+ private static final int changedMask = ~(changedLowBitMask | changedHighBitMask);
+
private final InFlow inFlow;
public UpdateChangedFlagsAbstractFunction(InFlow inFlow) {
this.inFlow = inFlow;
}
+
+ @Override
+ public ValueState apply(
+ AppView<AppInfoWithLiveness> appView,
+ FlowGraphStateProvider flowGraphStateProvider,
+ ConcreteValueState baseInState) {
+ ValueState inState;
+ if (inFlow.isAbstractFunction()) {
+ AbstractFunction orFunction = inFlow.asAbstractFunction();
+ assert orFunction instanceof OrAbstractFunction;
+ inState = orFunction.apply(appView, flowGraphStateProvider, baseInState);
+ } else {
+ inState = baseInState;
+ }
+ if (!inState.isPrimitiveState()) {
+ assert inState.isBottom() || inState.isUnknown();
+ return inState;
+ }
+ AbstractValue result = apply(appView, inState.asPrimitiveState().getAbstractValue());
+ return ConcretePrimitiveTypeValueState.create(result);
+ }
+
+ /**
+ * Applies the following function to the given {@param abstractValue}.
+ *
+ * <pre>
+ * private const val changedLowBitMask = 0b001_001_001_001_001_001_001_001_001_001_0
+ * private const val changedHighBitMask = changedLowBitMask shl 1
+ * private const val changedMask = (changedLowBitMask or changedHighBitMask).inv()
+ *
+ * internal fun updateChangedFlags(flags: Int): Int {
+ * val lowBits = flags and changedLowBitMask
+ * val highBits = flags and changedHighBitMask
+ * return ((flags and changedMask) or
+ * (lowBits or (highBits shr 1)) or ((lowBits shl 1) and highBits))
+ * }
+ * </pre>
+ */
+ private AbstractValue apply(AppView<AppInfoWithLiveness> appView, AbstractValue flagsValue) {
+ if (flagsValue.isSingleNumberValue()) {
+ return apply(appView, flagsValue.asSingleNumberValue().getIntValue());
+ }
+ AbstractValueFactory factory = appView.abstractValueFactory();
+ // Load constants.
+ AbstractValue changedLowBitMaskValue =
+ factory.createUncheckedSingleNumberValue(changedLowBitMask);
+ AbstractValue changedHighBitMaskValue =
+ factory.createUncheckedSingleNumberValue(changedHighBitMask);
+ AbstractValue changedMaskValue = factory.createUncheckedSingleNumberValue(changedMask);
+ // Evaluate expression.
+ AbstractValue lowBitsValue =
+ AbstractCalculator.andIntegers(appView, flagsValue, changedLowBitMaskValue);
+ AbstractValue highBitsValue =
+ AbstractCalculator.andIntegers(appView, flagsValue, changedHighBitMaskValue);
+ AbstractValue changedBitsValue =
+ AbstractCalculator.andIntegers(appView, flagsValue, changedMaskValue);
+ return AbstractCalculator.orIntegers(
+ appView,
+ changedBitsValue,
+ lowBitsValue,
+ AbstractCalculator.shrIntegers(appView, highBitsValue, 1),
+ AbstractCalculator.andIntegers(
+ appView, AbstractCalculator.shlIntegers(appView, lowBitsValue, 1), highBitsValue));
+ }
+
+ private SingleNumberValue apply(AppView<AppInfoWithLiveness> appView, int flags) {
+ int lowBits = flags & changedLowBitMask;
+ int highBits = flags & changedHighBitMask;
+ int changedBits = flags & changedMask;
+ int result = changedBits | lowBits | (highBits >> 1) | ((lowBits << 1) & highBits);
+ return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ }
+
+ @Override
+ public boolean containsBaseInFlow(BaseInFlow otherInFlow) {
+ if (inFlow.isAbstractFunction()) {
+ return inFlow.asAbstractFunction().containsBaseInFlow(otherInFlow);
+ }
+ assert inFlow.isBaseInFlow();
+ return inFlow.equals(otherInFlow);
+ }
+
+ @Override
+ public Iterable<BaseInFlow> getBaseInFlow() {
+ if (inFlow.isAbstractFunction()) {
+ return inFlow.asAbstractFunction().getBaseInFlow();
+ }
+ assert inFlow.isBaseInFlow();
+ return IterableUtils.singleton(inFlow.asBaseInFlow());
+ }
+
+ @Override
+ public boolean isUpdateChangedFlagsAbstractFunction() {
+ return true;
+ }
+
+ @Override
+ public UpdateChangedFlagsAbstractFunction asUpdateChangedFlagsAbstractFunction() {
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("EqualsGetClass")
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ UpdateChangedFlagsAbstractFunction fn = (UpdateChangedFlagsAbstractFunction) obj;
+ return inFlow.equals(fn.inFlow);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getClass(), inFlow);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 40e2beb..0fa843d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -334,7 +334,7 @@
AnnotatedKind kind,
Mode mode,
InternalOptions options) {
- return options.testing.modelUnknownChangedAndDefaultArgumentsToComposableFunctions
+ return options.getJetpackComposeOptions().isAnyOptimizationsEnabled()
&& mode.isInitialTreeShaking()
&& kind.isMethod()
&& annotation
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index db53845..5981161 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -40,6 +40,10 @@
return new Builder(this);
}
+ public boolean isFieldPropagationAllowed(GlobalKeepInfoConfiguration configuration) {
+ return isOptimizationAllowed(configuration) && isShrinkingAllowed(configuration);
+ }
+
public boolean isFieldTypeStrengtheningAllowed(GlobalKeepInfoConfiguration configuration) {
return internalIsFieldTypeStrengtheningAllowed();
}
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 b8b3ddf..28f377b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -82,6 +82,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.optimize.accessmodification.AccessModifierOptions;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
+import com.android.tools.r8.optimize.compose.JetpackComposeOptions;
import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalOptions;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
@@ -401,6 +402,8 @@
// Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
public boolean enableFieldBitAccessAnalysis =
System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
+ public boolean enableFieldAssignmentTracker = true;
+ public boolean enableFieldValueAnalysis = true;
public boolean enableUnusedInterfaceRemoval = true;
public boolean enableDevirtualization = true;
public boolean enableEnumUnboxing = true;
@@ -929,6 +932,7 @@
private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
private final ClassInlinerOptions classInlinerOptions = new ClassInlinerOptions();
private final InlinerOptions inlinerOptions = new InlinerOptions(this);
+ private final JetpackComposeOptions jetpackComposeOptions = new JetpackComposeOptions(this);
private final HorizontalClassMergerOptions horizontalClassMergerOptions =
new HorizontalClassMergerOptions();
private final VerticalClassMergerOptions verticalClassMergerOptions =
@@ -982,6 +986,10 @@
return horizontalClassMergerOptions;
}
+ public JetpackComposeOptions getJetpackComposeOptions() {
+ return jetpackComposeOptions;
+ }
+
public VerticalClassMergerOptions getVerticalClassMergerOptions() {
return verticalClassMergerOptions;
}
@@ -2387,13 +2395,6 @@
System.getProperty("com.android.tools.r8.disableMarkingClassesFinal") != null;
public boolean testEnableTestAssertions = false;
public boolean keepMetadataInR8IfNotRewritten = true;
- public boolean enableComposableOptimizationPass =
- SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault(
- "com.android.tools.r8.enableComposableOptimizationPass", false);
- public boolean modelUnknownChangedAndDefaultArgumentsToComposableFunctions =
- SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault(
- "com.android.tools.r8.modelUnknownChangedAndDefaultArgumentsToComposableFunctions",
- false);
// Flag to allow processing of resources in D8. A data resource consumer still needs to be
// specified.
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
index 2952b30..c778df3 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
@@ -88,6 +88,10 @@
return remove(field.getReference());
}
+ public boolean remove(ProgramField field) {
+ return remove(field.getReference());
+ }
+
public boolean removeIf(Predicate<? super ProgramField> predicate) {
return backing.values().removeIf(predicate);
}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index d22f197..208d106 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
@@ -31,6 +32,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
+import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
@@ -46,6 +48,7 @@
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@@ -78,6 +81,18 @@
return jar;
}
+ private Path getTestResources() throws Exception {
+ Path resourceOutput = temp.newFile("base_resources.ap_").toPath();
+ AndroidResourceTestingUtils.writePrecompiledManifestAndResourcePB(resourceOutput);
+ return resourceOutput;
+ }
+
+ private Path getFeatureTestResources(TemporaryFolder temp) throws Exception {
+ Path resourceOutput = temp.newFile("feature_resources.ap_").toPath();
+ AndroidResourceTestingUtils.writePrecompiledManifestAndResourcePB(resourceOutput);
+ return resourceOutput;
+ }
+
@Test(expected = CompilationFailedException.class)
public void emptyBuilder() throws Throwable {
// The builder must have a program consumer.
@@ -178,6 +193,68 @@
}
@Test
+ public void passAndroidResources() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ Path input = getJarWithA();
+ Path library = ToolHelper.getDefaultAndroidJar();
+ Path output = working.resolve("classes.dex");
+ Path resourceInput = getTestResources();
+ Path resourceOutput = working.resolve("resources_out.ap_");
+ assertFalse(Files.exists(output));
+ assertFalse(Files.exists(resourceOutput));
+ ProcessResult result =
+ ToolHelper.forkR8(
+ working,
+ input.toAbsolutePath().toString(),
+ "--lib",
+ library.toAbsolutePath().toString(),
+ "--android-resources",
+ resourceInput.toAbsolutePath().toString(),
+ resourceOutput.toAbsolutePath().toString(),
+ "--no-tree-shaking");
+ assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+ assertTrue(Files.exists(output));
+ System.out.println(result.stdout);
+ assertTrue(Files.exists(resourceOutput));
+ }
+
+ @Test
+ public void passFeatureResources() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ Path input = getJarWithA();
+ Path inputFeature = getJarWithB();
+ Path library = ToolHelper.getDefaultAndroidJar();
+ Path output = working.resolve("classes.dex");
+ Path featureOutput = working.resolve("feature.zip");
+ Path resourceInput = getTestResources();
+ Path resourceOutput = working.resolve("resources_out.ap_");
+ TemporaryFolder featureSplitTemp = ToolHelper.getTemporaryFolderForTest();
+ featureSplitTemp.create();
+ Path featureReasourceInput = getFeatureTestResources(featureSplitTemp);
+ Path featureResourceOutput = working.resolve("feature_resources_out.ap_");
+ assertFalse(Files.exists(output));
+ assertFalse(Files.exists(featureOutput));
+ String pathSeparator = File.pathSeparator;
+ ProcessResult result =
+ ToolHelper.forkR8(
+ working,
+ input.toAbsolutePath().toString(),
+ "--lib",
+ library.toAbsolutePath().toString(),
+ "--android-resources",
+ resourceInput.toAbsolutePath().toString(),
+ resourceOutput.toAbsolutePath().toString(),
+ "--feature",
+ inputFeature.toAbsolutePath() + pathSeparator + featureReasourceInput.toAbsolutePath(),
+ featureOutput.toAbsolutePath() + pathSeparator + featureResourceOutput.toAbsolutePath(),
+ "--no-tree-shaking");
+ assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+ assertTrue(Files.exists(output));
+ assertTrue(Files.exists(featureOutput));
+ assertTrue(Files.exists(resourceOutput));
+ }
+
+ @Test
public void featureOnlyOneArgument() throws Throwable {
Path working = temp.getRoot().toPath();
Path input = getJarWithA();
diff --git a/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
index d056f65..7c40462 100644
--- a/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
@@ -116,9 +116,9 @@
.addOptionsModification(
options -> {
options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces();
- options.testing.enableComposableOptimizationPass = enableComposeOptimizations;
- options.testing.modelUnknownChangedAndDefaultArgumentsToComposableFunctions =
- enableComposeOptimizations;
+ options
+ .getJetpackComposeOptions()
+ .enableAllOptimizations(enableComposeOptimizations);
})
.setMinApi(AndroidApiLevel.N)
.allowDiagnosticMessages()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
index 556a914..4bab75f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ArgumentInfoCollectionTest.java
@@ -130,7 +130,7 @@
public void testCombineRemoveRewritten() {
InternalOptions options = new InternalOptions();
DexItemFactory factory = options.dexItemFactory();
- AbstractValueFactory abstractValueFactory = new AbstractValueFactory(options);
+ AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
ArgumentInfoCollection.Builder builder1 = ArgumentInfoCollection.builder();
builder1.addArgumentInfo(
diff --git a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
index aaadb51..a9686e1 100644
--- a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
@@ -83,6 +83,11 @@
.addOptionsModification(o -> o.testing.allowInvokeErrors = true)
.addDontObfuscate()
.addKeepMainRule(Main.class)
+ // Without a keep rule, we conclude that I.m() is not called and remove it. That is not
+ // true, however, since there is an invalid invoke to I.m(). To preserve the error we
+ // could consider rewriting invoke to `throw new ICCE()`, as this would also ensure that
+ // the compiled program runs on all runtimes with the same behavior.
+ .addKeepClassAndMembersRules(I.class)
.run(parameters.getRuntime(), Main.class);
if (parameters.getRuntime().asCf().isNewerThan(CfVm.JDK8)) {
runResult.assertFailureWithErrorThatMatches(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationTest.java
new file mode 100644
index 0000000..e1074d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2024, 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.ir.optimize.callsites;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RestartLambdaPropagationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject restartableMethodSubject =
+ mainClassSubject.uniqueMethodWithOriginalName("restartableMethod");
+ assertThat(restartableMethodSubject, isPresent());
+ assertEquals(
+ parameters.isDexRuntime() ? 1 : 2,
+ restartableMethodSubject.getParameters().size());
+ assertEquals(
+ parameters.isDexRuntime(),
+ restartableMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstNumber(42)));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Postponing!", "Restarting!", "42", "Stopping!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Runnable restarter = restartableMethod(true, 42);
+ restarter.run();
+ }
+
+ @NeverInline
+ static Runnable restartableMethod(boolean doRestart, int flags) {
+ if (doRestart) {
+ System.out.println("Postponing!");
+ return () -> {
+ System.out.println("Restarting!");
+ Runnable restarter = restartableMethod(false, flags);
+ if (restarter == null) {
+ System.out.println("Stopping!");
+ } else {
+ throw new RuntimeException();
+ }
+ };
+ }
+ System.out.println(flags);
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SwitchWithSimpleCasesInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SwitchWithSimpleCasesInliningTest.java
new file mode 100644
index 0000000..44eb259
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SwitchWithSimpleCasesInliningTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2024, 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.ir.optimize.inliner.conditionalsimpleinlining;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SwitchWithSimpleCasesInliningTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ // TODO(b/331337747): Should be true.
+ assertFalse(
+ mainMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isConstString)
+ .allMatch(i -> i.isConstString("O")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "true", "false", "true", "true", "false", "true", "false", "false", "false", "false",
+ "true", "true", "false", "true", "false");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // Known.
+ System.out.println(isEnabled("A"));
+ System.out.println(isEnabled("B"));
+ System.out.println(isEnabled("C"));
+ System.out.println(isEnabled("D"));
+ System.out.println(isEnabled("E"));
+ System.out.println(isEnabled("F"));
+ System.out.println(isEnabled("G"));
+ System.out.println(isEnabled("H"));
+ System.out.println(isEnabled("I"));
+ System.out.println(isEnabled("J"));
+ System.out.println(isEnabled("K"));
+ System.out.println(isEnabled("L"));
+ System.out.println(isEnabled("M"));
+ System.out.println(isEnabled("N"));
+ // Unknown.
+ System.out.println(isEnabled("O"));
+ }
+
+ public static boolean isEnabled(String feature) {
+ switch (feature) {
+ case "A":
+ return true;
+ case "B":
+ return false;
+ case "C":
+ return true;
+ case "D":
+ return true;
+ case "E":
+ return false;
+ case "F":
+ return true;
+ case "G":
+ return false;
+ case "H":
+ return false;
+ case "I":
+ return false;
+ case "J":
+ return false;
+ case "K":
+ return true;
+ case "L":
+ return true;
+ case "M":
+ return false;
+ case "N":
+ return true;
+ default:
+ return hasProperty(feature);
+ }
+ }
+
+ @NeverInline
+ public static boolean hasProperty(String property) {
+ return System.getProperty(property) != null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java
new file mode 100644
index 0000000..d484c87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/BottomFieldStatePropagationTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BottomFieldStatePropagationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableConstantArgumentAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(NullPointerException.class);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Builder builder = alwaysFalse() ? new Builder(args.length) : null;
+ test(builder);
+ }
+
+ static boolean alwaysFalse() {
+ return false;
+ }
+
+ @KeepConstantArguments
+ @NeverInline
+ static void test(Builder builder) {
+ // Argument propagation should prove that the field access is unreachable due to the field
+ // value being bottom.
+ unreachable(builder.f);
+ }
+
+ @KeepConstantArguments
+ @NeverInline
+ static void unreachable(int i) {
+ System.out.println(i);
+ }
+ }
+
+ static class Builder {
+
+ int f;
+
+ Builder(int f) {
+ this.f = f;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
new file mode 100644
index 0000000..6272631
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/FieldStateArgumentPropagationTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldStateArgumentPropagationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> {
+ options.enableFieldAssignmentTracker = false;
+ options.enableFieldValueAnalysis = false;
+ })
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject printMethodSubject =
+ mainClassSubject.uniqueMethodWithOriginalName("print");
+ assertThat(printMethodSubject, isPresent());
+ assertEquals(0, printMethodSubject.getProgramMethod().getArity());
+
+ MethodSubject printlnMethodSubject =
+ mainClassSubject.uniqueMethodWithOriginalName("println");
+ assertThat(printlnMethodSubject, isPresent());
+ assertEquals(0, printlnMethodSubject.getProgramMethod().getArity());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ print(Greetings.HELLO.f);
+ println(Greetings.WORLD.f);
+ }
+
+ @NeverInline
+ static void print(String message) {
+ System.out.print(message);
+ }
+
+ @NeverInline
+ static void println(String message) {
+ System.out.println(message);
+ }
+ }
+
+ static class Greetings {
+
+ static final Greetings HELLO;
+ static final Greetings WORLD;
+
+ static {
+ HELLO = new Greetings("Hello");
+ WORLD = new Greetings(", world!");
+ }
+
+ final String f;
+
+ Greetings(String f) {
+ this.f = f;
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index c74d4d5..eb5b8e2 100644
--- a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -31,6 +31,7 @@
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -41,6 +42,8 @@
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
import org.junit.Assert;
import org.junit.rules.TemporaryFolder;
@@ -103,6 +106,20 @@
return dollarIndex > 0 && name.charAt(dollarIndex - 1) == 'R';
}
+ public static void writePrecompiledManifestAndResourcePB(Path resourceOutput) {
+ try (ZipOutputStream out =
+ new ZipOutputStream(
+ Files.newOutputStream(
+ resourceOutput, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+ ZipUtils.writeToZipStream(
+ out, "AndroidManifest.xml", CompiledProto.SIMPLE_MANIFEST, ZipEntry.STORED);
+ ZipUtils.writeToZipStream(
+ out, "resources.pb", CompiledProto.SIMPLE_RESOURCE_TABLE, ZipEntry.STORED);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public static String SIMPLE_MANIFEST_WITH_APP_NAME =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
@@ -368,7 +385,7 @@
return this;
}
- AndroidTestResourceBuilder setPackageId(int packageId) {
+ public AndroidTestResourceBuilder setPackageId(int packageId) {
this.packageId = packageId;
return this;
}
diff --git a/src/test/testbase/java/com/android/tools/r8/androidresources/CompiledProto.java b/src/test/testbase/java/com/android/tools/r8/androidresources/CompiledProto.java
new file mode 100644
index 0000000..9cda6e0
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/androidresources/CompiledProto.java
@@ -0,0 +1,814 @@
+// Copyright (c) 2024, 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.androidresources;
+
+public class CompiledProto {
+
+ // A simple resource table with just the string app_name referenced from the manifest below
+ public static final byte[] SIMPLE_RESOURCE_TABLE =
+ new byte[] {
+ (byte) 10,
+ (byte) 98,
+ (byte) 10,
+ (byte) 96,
+ (byte) 1,
+ (byte) 0,
+ (byte) 28,
+ (byte) 0,
+ (byte) 96,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 2,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 1,
+ (byte) 0,
+ (byte) 0,
+ (byte) 36,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 3,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 52,
+ (byte) 52,
+ (byte) 47,
+ (byte) 116,
+ (byte) 109,
+ (byte) 112,
+ (byte) 47,
+ (byte) 106,
+ (byte) 117,
+ (byte) 110,
+ (byte) 105,
+ (byte) 116,
+ (byte) 53,
+ (byte) 57,
+ (byte) 53,
+ (byte) 50,
+ (byte) 50,
+ (byte) 51,
+ (byte) 52,
+ (byte) 52,
+ (byte) 55,
+ (byte) 54,
+ (byte) 56,
+ (byte) 51,
+ (byte) 57,
+ (byte) 49,
+ (byte) 56,
+ (byte) 53,
+ (byte) 53,
+ (byte) 53,
+ (byte) 53,
+ (byte) 47,
+ (byte) 114,
+ (byte) 101,
+ (byte) 115,
+ (byte) 47,
+ (byte) 118,
+ (byte) 97,
+ (byte) 108,
+ (byte) 117,
+ (byte) 101,
+ (byte) 115,
+ (byte) 47,
+ (byte) 115,
+ (byte) 116,
+ (byte) 114,
+ (byte) 105,
+ (byte) 110,
+ (byte) 103,
+ (byte) 115,
+ (byte) 46,
+ (byte) 120,
+ (byte) 109,
+ (byte) 108,
+ (byte) 0,
+ (byte) 0,
+ (byte) 0,
+ (byte) 18,
+ (byte) 102,
+ (byte) 10,
+ (byte) 2,
+ (byte) 8,
+ (byte) 127,
+ (byte) 18,
+ (byte) 20,
+ (byte) 116,
+ (byte) 104,
+ (byte) 101,
+ (byte) 112,
+ (byte) 97,
+ (byte) 99,
+ (byte) 107,
+ (byte) 97,
+ (byte) 103,
+ (byte) 101,
+ (byte) 49,
+ (byte) 50,
+ (byte) 55,
+ (byte) 46,
+ (byte) 102,
+ (byte) 111,
+ (byte) 111,
+ (byte) 98,
+ (byte) 97,
+ (byte) 114,
+ (byte) 26,
+ (byte) 74,
+ (byte) 10,
+ (byte) 2,
+ (byte) 8,
+ (byte) 1,
+ (byte) 18,
+ (byte) 6,
+ (byte) 115,
+ (byte) 116,
+ (byte) 114,
+ (byte) 105,
+ (byte) 110,
+ (byte) 103,
+ (byte) 26,
+ (byte) 60,
+ (byte) 10,
+ (byte) 0,
+ (byte) 18,
+ (byte) 8,
+ (byte) 97,
+ (byte) 112,
+ (byte) 112,
+ (byte) 95,
+ (byte) 110,
+ (byte) 97,
+ (byte) 109,
+ (byte) 101,
+ (byte) 26,
+ (byte) 2,
+ (byte) 18,
+ (byte) 0,
+ (byte) 50,
+ (byte) 42,
+ (byte) 10,
+ (byte) 0,
+ (byte) 18,
+ (byte) 38,
+ (byte) 10,
+ (byte) 6,
+ (byte) 8,
+ (byte) 1,
+ (byte) 18,
+ (byte) 2,
+ (byte) 8,
+ (byte) 2,
+ (byte) 34,
+ (byte) 28,
+ (byte) 18,
+ (byte) 26,
+ (byte) 10,
+ (byte) 24,
+ (byte) 77,
+ (byte) 111,
+ (byte) 115,
+ (byte) 116,
+ (byte) 32,
+ (byte) 105,
+ (byte) 109,
+ (byte) 112,
+ (byte) 111,
+ (byte) 114,
+ (byte) 116,
+ (byte) 97,
+ (byte) 110,
+ (byte) 116,
+ (byte) 32,
+ (byte) 97,
+ (byte) 112,
+ (byte) 112,
+ (byte) 32,
+ (byte) 101,
+ (byte) 118,
+ (byte) 101,
+ (byte) 114,
+ (byte) 46,
+ (byte) 34,
+ (byte) 51,
+ (byte) 10,
+ (byte) 35,
+ (byte) 65,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 32,
+ (byte) 65,
+ (byte) 115,
+ (byte) 115,
+ (byte) 101,
+ (byte) 116,
+ (byte) 32,
+ (byte) 80,
+ (byte) 97,
+ (byte) 99,
+ (byte) 107,
+ (byte) 97,
+ (byte) 103,
+ (byte) 105,
+ (byte) 110,
+ (byte) 103,
+ (byte) 32,
+ (byte) 84,
+ (byte) 111,
+ (byte) 111,
+ (byte) 108,
+ (byte) 32,
+ (byte) 40,
+ (byte) 97,
+ (byte) 97,
+ (byte) 112,
+ (byte) 116,
+ (byte) 41,
+ (byte) 18,
+ (byte) 12,
+ (byte) 50,
+ (byte) 46,
+ (byte) 49,
+ (byte) 57,
+ (byte) 45,
+ (byte) 57,
+ (byte) 50,
+ (byte) 56,
+ (byte) 57,
+ (byte) 51,
+ (byte) 53,
+ (byte) 56
+ };
+
+ // A simple manifest with just the app name embedded (referenced through the resource table)
+ // "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ // "<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ // package="com.android.tools.r8">
+ // <application android:label="@string/app_name">
+ // </application>
+ // "</manifest>"
+ public static final byte[] SIMPLE_MANIFEST =
+ new byte[] {
+ (byte) 10,
+ (byte) -113,
+ (byte) 4,
+ (byte) 10,
+ (byte) 57,
+ (byte) 10,
+ (byte) 7,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 18,
+ (byte) 42,
+ (byte) 104,
+ (byte) 116,
+ (byte) 116,
+ (byte) 112,
+ (byte) 58,
+ (byte) 47,
+ (byte) 47,
+ (byte) 115,
+ (byte) 99,
+ (byte) 104,
+ (byte) 101,
+ (byte) 109,
+ (byte) 97,
+ (byte) 115,
+ (byte) 46,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 46,
+ (byte) 99,
+ (byte) 111,
+ (byte) 109,
+ (byte) 47,
+ (byte) 97,
+ (byte) 112,
+ (byte) 107,
+ (byte) 47,
+ (byte) 114,
+ (byte) 101,
+ (byte) 115,
+ (byte) 47,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 26,
+ (byte) 2,
+ (byte) 8,
+ (byte) 2,
+ (byte) 26,
+ (byte) 8,
+ (byte) 109,
+ (byte) 97,
+ (byte) 110,
+ (byte) 105,
+ (byte) 102,
+ (byte) 101,
+ (byte) 115,
+ (byte) 116,
+ (byte) 34,
+ (byte) 31,
+ (byte) 18,
+ (byte) 7,
+ (byte) 112,
+ (byte) 97,
+ (byte) 99,
+ (byte) 107,
+ (byte) 97,
+ (byte) 103,
+ (byte) 101,
+ (byte) 26,
+ (byte) 20,
+ (byte) 99,
+ (byte) 111,
+ (byte) 109,
+ (byte) 46,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 46,
+ (byte) 116,
+ (byte) 111,
+ (byte) 111,
+ (byte) 108,
+ (byte) 115,
+ (byte) 46,
+ (byte) 114,
+ (byte) 56,
+ (byte) 34,
+ (byte) 82,
+ (byte) 10,
+ (byte) 42,
+ (byte) 104,
+ (byte) 116,
+ (byte) 116,
+ (byte) 112,
+ (byte) 58,
+ (byte) 47,
+ (byte) 47,
+ (byte) 115,
+ (byte) 99,
+ (byte) 104,
+ (byte) 101,
+ (byte) 109,
+ (byte) 97,
+ (byte) 115,
+ (byte) 46,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 46,
+ (byte) 99,
+ (byte) 111,
+ (byte) 109,
+ (byte) 47,
+ (byte) 97,
+ (byte) 112,
+ (byte) 107,
+ (byte) 47,
+ (byte) 114,
+ (byte) 101,
+ (byte) 115,
+ (byte) 47,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 18,
+ (byte) 17,
+ (byte) 99,
+ (byte) 111,
+ (byte) 109,
+ (byte) 112,
+ (byte) 105,
+ (byte) 108,
+ (byte) 101,
+ (byte) 83,
+ (byte) 100,
+ (byte) 107,
+ (byte) 86,
+ (byte) 101,
+ (byte) 114,
+ (byte) 115,
+ (byte) 105,
+ (byte) 111,
+ (byte) 110,
+ (byte) 26,
+ (byte) 2,
+ (byte) 51,
+ (byte) 49,
+ (byte) 34,
+ (byte) 2,
+ (byte) 8,
+ (byte) 2,
+ (byte) 40,
+ (byte) -14,
+ (byte) -118,
+ (byte) -124,
+ (byte) 8,
+ (byte) 50,
+ (byte) 4,
+ (byte) 58,
+ (byte) 2,
+ (byte) 48,
+ (byte) 31,
+ (byte) 34,
+ (byte) 40,
+ (byte) 18,
+ (byte) 24,
+ (byte) 112,
+ (byte) 108,
+ (byte) 97,
+ (byte) 116,
+ (byte) 102,
+ (byte) 111,
+ (byte) 114,
+ (byte) 109,
+ (byte) 66,
+ (byte) 117,
+ (byte) 105,
+ (byte) 108,
+ (byte) 100,
+ (byte) 86,
+ (byte) 101,
+ (byte) 114,
+ (byte) 115,
+ (byte) 105,
+ (byte) 111,
+ (byte) 110,
+ (byte) 67,
+ (byte) 111,
+ (byte) 100,
+ (byte) 101,
+ (byte) 26,
+ (byte) 2,
+ (byte) 51,
+ (byte) 49,
+ (byte) 34,
+ (byte) 2,
+ (byte) 8,
+ (byte) 2,
+ (byte) 50,
+ (byte) 4,
+ (byte) 58,
+ (byte) 2,
+ (byte) 48,
+ (byte) 31,
+ (byte) 34,
+ (byte) 80,
+ (byte) 10,
+ (byte) 42,
+ (byte) 104,
+ (byte) 116,
+ (byte) 116,
+ (byte) 112,
+ (byte) 58,
+ (byte) 47,
+ (byte) 47,
+ (byte) 115,
+ (byte) 99,
+ (byte) 104,
+ (byte) 101,
+ (byte) 109,
+ (byte) 97,
+ (byte) 115,
+ (byte) 46,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 46,
+ (byte) 99,
+ (byte) 111,
+ (byte) 109,
+ (byte) 47,
+ (byte) 97,
+ (byte) 112,
+ (byte) 107,
+ (byte) 47,
+ (byte) 114,
+ (byte) 101,
+ (byte) 115,
+ (byte) 47,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 18,
+ (byte) 25,
+ (byte) 99,
+ (byte) 111,
+ (byte) 109,
+ (byte) 112,
+ (byte) 105,
+ (byte) 108,
+ (byte) 101,
+ (byte) 83,
+ (byte) 100,
+ (byte) 107,
+ (byte) 86,
+ (byte) 101,
+ (byte) 114,
+ (byte) 115,
+ (byte) 105,
+ (byte) 111,
+ (byte) 110,
+ (byte) 67,
+ (byte) 111,
+ (byte) 100,
+ (byte) 101,
+ (byte) 110,
+ (byte) 97,
+ (byte) 109,
+ (byte) 101,
+ (byte) 26,
+ (byte) 2,
+ (byte) 49,
+ (byte) 50,
+ (byte) 40,
+ (byte) -13,
+ (byte) -118,
+ (byte) -124,
+ (byte) 8,
+ (byte) 34,
+ (byte) 40,
+ (byte) 18,
+ (byte) 24,
+ (byte) 112,
+ (byte) 108,
+ (byte) 97,
+ (byte) 116,
+ (byte) 102,
+ (byte) 111,
+ (byte) 114,
+ (byte) 109,
+ (byte) 66,
+ (byte) 117,
+ (byte) 105,
+ (byte) 108,
+ (byte) 100,
+ (byte) 86,
+ (byte) 101,
+ (byte) 114,
+ (byte) 115,
+ (byte) 105,
+ (byte) 111,
+ (byte) 110,
+ (byte) 78,
+ (byte) 97,
+ (byte) 109,
+ (byte) 101,
+ (byte) 26,
+ (byte) 2,
+ (byte) 49,
+ (byte) 50,
+ (byte) 34,
+ (byte) 2,
+ (byte) 8,
+ (byte) 2,
+ (byte) 50,
+ (byte) 4,
+ (byte) 58,
+ (byte) 2,
+ (byte) 48,
+ (byte) 12,
+ (byte) 42,
+ (byte) 13,
+ (byte) 18,
+ (byte) 5,
+ (byte) 10,
+ (byte) 32,
+ (byte) 32,
+ (byte) 32,
+ (byte) 32,
+ (byte) 26,
+ (byte) 4,
+ (byte) 8,
+ (byte) 3,
+ (byte) 16,
+ (byte) 41,
+ (byte) 42,
+ (byte) -110,
+ (byte) 1,
+ (byte) 10,
+ (byte) -119,
+ (byte) 1,
+ (byte) 26,
+ (byte) 11,
+ (byte) 97,
+ (byte) 112,
+ (byte) 112,
+ (byte) 108,
+ (byte) 105,
+ (byte) 99,
+ (byte) 97,
+ (byte) 116,
+ (byte) 105,
+ (byte) 111,
+ (byte) 110,
+ (byte) 34,
+ (byte) 107,
+ (byte) 10,
+ (byte) 42,
+ (byte) 104,
+ (byte) 116,
+ (byte) 116,
+ (byte) 112,
+ (byte) 58,
+ (byte) 47,
+ (byte) 47,
+ (byte) 115,
+ (byte) 99,
+ (byte) 104,
+ (byte) 101,
+ (byte) 109,
+ (byte) 97,
+ (byte) 115,
+ (byte) 46,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 46,
+ (byte) 99,
+ (byte) 111,
+ (byte) 109,
+ (byte) 47,
+ (byte) 97,
+ (byte) 112,
+ (byte) 107,
+ (byte) 47,
+ (byte) 114,
+ (byte) 101,
+ (byte) 115,
+ (byte) 47,
+ (byte) 97,
+ (byte) 110,
+ (byte) 100,
+ (byte) 114,
+ (byte) 111,
+ (byte) 105,
+ (byte) 100,
+ (byte) 18,
+ (byte) 5,
+ (byte) 108,
+ (byte) 97,
+ (byte) 98,
+ (byte) 101,
+ (byte) 108,
+ (byte) 26,
+ (byte) 16,
+ (byte) 64,
+ (byte) 115,
+ (byte) 116,
+ (byte) 114,
+ (byte) 105,
+ (byte) 110,
+ (byte) 103,
+ (byte) 47,
+ (byte) 97,
+ (byte) 112,
+ (byte) 112,
+ (byte) 95,
+ (byte) 110,
+ (byte) 97,
+ (byte) 109,
+ (byte) 101,
+ (byte) 34,
+ (byte) 2,
+ (byte) 8,
+ (byte) 4,
+ (byte) 40,
+ (byte) -127,
+ (byte) -128,
+ (byte) -124,
+ (byte) 8,
+ (byte) 50,
+ (byte) 27,
+ (byte) 10,
+ (byte) 25,
+ (byte) 16,
+ (byte) -128,
+ (byte) -128,
+ (byte) -124,
+ (byte) -8,
+ (byte) 7,
+ (byte) 26,
+ (byte) 15,
+ (byte) 115,
+ (byte) 116,
+ (byte) 114,
+ (byte) 105,
+ (byte) 110,
+ (byte) 103,
+ (byte) 47,
+ (byte) 97,
+ (byte) 112,
+ (byte) 112,
+ (byte) 95,
+ (byte) 110,
+ (byte) 97,
+ (byte) 109,
+ (byte) 101,
+ (byte) 48,
+ (byte) 3,
+ (byte) 42,
+ (byte) 13,
+ (byte) 18,
+ (byte) 5,
+ (byte) 10,
+ (byte) 32,
+ (byte) 32,
+ (byte) 32,
+ (byte) 32,
+ (byte) 26,
+ (byte) 4,
+ (byte) 8,
+ (byte) 4,
+ (byte) 16,
+ (byte) 50,
+ (byte) 26,
+ (byte) 4,
+ (byte) 8,
+ (byte) 4,
+ (byte) 16,
+ (byte) 4,
+ (byte) 42,
+ (byte) 9,
+ (byte) 18,
+ (byte) 1,
+ (byte) 10,
+ (byte) 26,
+ (byte) 4,
+ (byte) 8,
+ (byte) 5,
+ (byte) 16,
+ (byte) 18,
+ (byte) 26,
+ (byte) 2,
+ (byte) 8,
+ (byte) 2
+ };
+}
diff --git a/tools/test.py b/tools/test.py
index 4ffed63..9805e8e 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -516,6 +516,9 @@
))
rotate_test_reports()
+ if options.print_times:
+ gradle_args.append('-Pprint_times=true')
+
# Now run tests on selected runtime(s).
if options.runtimes:
if options.dex_vm != 'default':
@@ -548,8 +551,6 @@
# Legacy testing populates the runtimes based on dex_vm.
vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
- if options.print_times:
- gradle_args.append('-Pprint_times=true')
for art_vm in vms_to_test:
vm_suffix = "_" + options.dex_vm_kind if art_vm != "default" else ""
runtimes = ['dex-' + art_vm]