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]