Merge commit 'c9ec33d96b64bc2820eef206914e993a6f9c60e0' into dev-release
Change-Id: I3687dd49a93681717da3b58d4547b1df39135e94
diff --git a/.gitignore b/.gitignore
index 8eab4b8..3da002f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -223,6 +223,8 @@
third_party/opensource-apps/applymapping.tar.gz
third_party/opensource-apps/chanu
third_party/opensource-apps/chanu.tar.gz
+third_party/opensource-apps/compose-examples/changed-bitwise-value-propagation
+third_party/opensource-apps/compose-examples/changed-bitwise-value-propagation.tar.gz
third_party/opensource-apps/empty-activity
third_party/opensource-apps/empty-activity.tar.gz
third_party/opensource-apps/empty-compose-activity
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index 1d566dc..1ec0e8b 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -417,6 +417,14 @@
"third_party",
"binary_compatibility_tests",
"compiler_api_tests.tar.gz.sha1").toFile())
+ val composeExamplesChangedBitwiseValuePropagation = ThirdPartyDependency(
+ "compose-examples-changed-bitwise-value-propagation",
+ Paths.get(
+ "third_party", "opensource-apps", "compose-examples",
+ "changed-bitwise-value-propagation").toFile(),
+ Paths.get(
+ "third_party", "opensource-apps", "compose-examples",
+ "changed-bitwise-value-propagation.tar.gz.sha1").toFile())
val coreLambdaStubs = ThirdPartyDependency(
"coreLambdaStubs",
Paths.get("third_party", "core-lambda-stubs").toFile(),
diff --git a/d8_r8/keepanno/build.gradle.kts b/d8_r8/keepanno/build.gradle.kts
index 376472a..3d981f6 100644
--- a/d8_r8/keepanno/build.gradle.kts
+++ b/d8_r8/keepanno/build.gradle.kts
@@ -34,4 +34,9 @@
dependsOn(gradle.includedBuild("shared").task(":downloadDeps"))
from(sourceSets.main.get().output)
}
+
+ val keepAnnoAnnotationsDoc by registering(Javadoc::class) {
+ source = sourceSets.main.get().allJava
+ include("com/android/tools/r8/keepanno/annotations/*")
+ }
}
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts
index c441284..ddb244f 100644
--- a/d8_r8/main/build.gradle.kts
+++ b/d8_r8/main/build.gradle.kts
@@ -21,7 +21,7 @@
`kotlin-dsl`
id("dependencies-plugin")
id("net.ltgt.errorprone") version "3.0.1"
- id("org.spdx.sbom") version "0.4.0-r8-patch01"
+ id("org.spdx.sbom") version "0.4.0-r8-patch02"
}
java {
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 35c7e74..30432af 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -2,7 +2,7 @@
# Do not modify manually.
#
# For the schema of this file, see BuildbucketCfg message:
-# https://luci-config.appspot.com/schemas/projects:buildbucket.cfg
+# https://config.luci.app/schemas/projects:buildbucket.cfg
buckets {
name: "ci"
@@ -1336,6 +1336,78 @@
}
}
builders {
+ name: "linux-jdk21"
+ swarming_host: "chrome-swarming.appspot.com"
+ swarming_tags: "vpython:native-python-wrapper"
+ dimensions: "cpu:x86-64"
+ dimensions: "normal:true"
+ dimensions: "os:Ubuntu-20.04"
+ dimensions: "pool:luci.r8.ci"
+ exe {
+ cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+ cipd_version: "refs/heads/master"
+ cmd: "luciexe"
+ }
+ properties:
+ '{'
+ ' "builder_group": "internal.client.r8",'
+ ' "recipe": "rex",'
+ ' "test_options": ['
+ ' "--runtimes=jdk21",'
+ ' "--command_cache_dir=/tmp/ccache",'
+ ' "--tool=r8",'
+ ' "--no_internal",'
+ ' "--one_line_per_test",'
+ ' "--archive_failures"'
+ ' ]'
+ '}'
+ priority: 26
+ execution_timeout_secs: 21600
+ expiration_secs: 126000
+ build_numbers: YES
+ service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+ experiments {
+ key: "luci.recipes.use_python3"
+ value: 100
+ }
+ }
+ builders {
+ name: "linux-jdk21_release"
+ swarming_host: "chrome-swarming.appspot.com"
+ swarming_tags: "vpython:native-python-wrapper"
+ dimensions: "cpu:x86-64"
+ dimensions: "normal:true"
+ dimensions: "os:Ubuntu-20.04"
+ dimensions: "pool:luci.r8.ci"
+ exe {
+ cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+ cipd_version: "refs/heads/master"
+ cmd: "luciexe"
+ }
+ properties:
+ '{'
+ ' "builder_group": "internal.client.r8",'
+ ' "recipe": "rex",'
+ ' "test_options": ['
+ ' "--runtimes=jdk21",'
+ ' "--command_cache_dir=/tmp/ccache",'
+ ' "--tool=r8",'
+ ' "--no_internal",'
+ ' "--one_line_per_test",'
+ ' "--archive_failures"'
+ ' ]'
+ '}'
+ priority: 26
+ execution_timeout_secs: 21600
+ expiration_secs: 126000
+ build_numbers: YES
+ service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+ experiments {
+ key: "luci.recipes.use_python3"
+ value: 100
+ }
+ }
+ builders {
name: "linux-jdk8"
swarming_host: "chrome-swarming.appspot.com"
swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/global/generated/luci-logdog.cfg b/infra/config/global/generated/luci-logdog.cfg
index 57dada0..6f58579 100644
--- a/infra/config/global/generated/luci-logdog.cfg
+++ b/infra/config/global/generated/luci-logdog.cfg
@@ -2,7 +2,7 @@
# Do not modify manually.
#
# For the schema of this file, see ProjectConfig message:
-# https://luci-config.appspot.com/schemas/projects:luci-logdog.cfg
+# https://config.luci.app/schemas/projects:luci-logdog.cfg
reader_auth_groups: "all"
writer_auth_groups: "luci-logdog-r8-writers"
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index 0f163d5..05b414b 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -2,7 +2,7 @@
# Do not modify manually.
#
# For the schema of this file, see Project message:
-# https://luci-config.appspot.com/schemas/projects:luci-milo.cfg
+# https://config.luci.app/schemas/projects:luci-milo.cfg
consoles {
id: "main"
@@ -51,6 +51,11 @@
short_name: "jdk17"
}
builders {
+ name: "buildbucket/luci.r8.ci/linux-jdk21"
+ category: "R8"
+ short_name: "jdk21"
+ }
+ builders {
name: "buildbucket/luci.r8.ci/linux-android-4.0.4"
category: "R8"
short_name: "4.0.4"
@@ -186,6 +191,11 @@
short_name: "jdk17"
}
builders {
+ name: "buildbucket/luci.r8.ci/linux-jdk21_release"
+ category: "Release|R8"
+ short_name: "jdk21"
+ }
+ builders {
name: "buildbucket/luci.r8.ci/linux-android-4.0.4_release"
category: "Release|R8"
short_name: "4.0.4"
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg
index ff77a2b..92de734 100644
--- a/infra/config/global/generated/luci-notify.cfg
+++ b/infra/config/global/generated/luci-notify.cfg
@@ -2,7 +2,7 @@
# Do not modify manually.
#
# For the schema of this file, see ProjectConfig message:
-# https://luci-config.appspot.com/schemas/projects:luci-notify.cfg
+# https://config.luci.app/schemas/projects:luci-notify.cfg
notifiers {
notifications {
@@ -420,6 +420,30 @@
}
builders {
bucket: "ci"
+ name: "linux-jdk21"
+ repository: "https://r8.googlesource.com/r8"
+ }
+}
+notifiers {
+ notifications {
+ on_failure: true
+ on_new_failure: true
+ notify_blamelist {}
+ }
+ builders {
+ bucket: "ci"
+ name: "linux-jdk21_release"
+ repository: "https://r8.googlesource.com/r8"
+ }
+}
+notifiers {
+ notifications {
+ on_failure: true
+ on_new_failure: true
+ notify_blamelist {}
+ }
+ builders {
+ bucket: "ci"
name: "linux-jdk8"
repository: "https://r8.googlesource.com/r8"
}
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index 60c9a25..55b6ba6 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -2,7 +2,7 @@
# Do not modify manually.
#
# For the schema of this file, see ProjectConfig message:
-# https://luci-config.appspot.com/schemas/projects:luci-scheduler.cfg
+# https://config.luci.app/schemas/projects:luci-scheduler.cfg
job {
id: "archive"
@@ -545,6 +545,35 @@
}
}
job {
+ id: "linux-jdk21"
+ realm: "ci"
+ acl_sets: "ci"
+ triggering_policy {
+ kind: GREEDY_BATCHING
+ max_concurrent_invocations: 1
+ }
+ buildbucket {
+ server: "cr-buildbucket.appspot.com"
+ bucket: "ci"
+ builder: "linux-jdk21"
+ }
+}
+job {
+ id: "linux-jdk21_release"
+ realm: "ci"
+ acl_sets: "ci"
+ triggering_policy {
+ kind: GREEDY_BATCHING
+ max_concurrent_invocations: 1
+ max_batch_size: 1
+ }
+ buildbucket {
+ server: "cr-buildbucket.appspot.com"
+ bucket: "ci"
+ builder: "linux-jdk21_release"
+ }
+}
+job {
id: "linux-jdk8"
realm: "ci"
acl_sets: "ci"
@@ -767,6 +796,17 @@
}
}
trigger {
+ id: "branch-gitiles-8.3-forward"
+ realm: "ci"
+ acl_sets: "ci"
+ triggers: "linux-jdk21_release"
+ gitiles {
+ repo: "https://r8.googlesource.com/r8"
+ refs: "regexp:refs/heads/([8]\\.[3-9]+(\\.[0-9]+)?|[9]\\.[0-9]+(\\.[0-9]+)?)"
+ path_regexps: "src/main/java/com/android/tools/r8/Version.java"
+ }
+}
+trigger {
id: "branch-gitiles-trigger"
realm: "ci"
acl_sets: "ci"
@@ -815,6 +855,7 @@
triggers: "linux-internal"
triggers: "linux-jdk11"
triggers: "linux-jdk17"
+ triggers: "linux-jdk21"
triggers: "linux-jdk8"
triggers: "linux-jdk9"
triggers: "linux-kotlin_dev"
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index dcf12da..d9a3a1e 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -2,12 +2,12 @@
# Do not modify manually.
#
# For the schema of this file, see ProjectCfg message:
-# https://luci-config.appspot.com/schemas/projects:project.cfg
+# https://config.luci.app/schemas/projects:project.cfg
name: "r8"
access: "group:all"
lucicfg {
- version: "1.39.20"
+ version: "1.40.0"
package_dir: ".."
config_dir: "generated"
entry_point: "main.star"
diff --git a/infra/config/global/generated/realms.cfg b/infra/config/global/generated/realms.cfg
index de02ce6..e05c5b4 100644
--- a/infra/config/global/generated/realms.cfg
+++ b/infra/config/global/generated/realms.cfg
@@ -2,7 +2,7 @@
# Do not modify manually.
#
# For the schema of this file, see RealmsCfg message:
-# https://luci-config.appspot.com/schemas/projects:realms.cfg
+# https://config.luci.app/schemas/projects:realms.cfg
realms {
name: "@root"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 11fbd09..35e29c7 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -109,6 +109,14 @@
)
luci.gitiles_poller(
+ name = "branch-gitiles-8.3-forward",
+ bucket = "ci",
+ repo = "https://r8.googlesource.com/r8",
+ refs = ["refs/heads/([8]\\.[3-9]+(\\.[0-9]+)?|[9]\\.[0-9]+(\\.[0-9]+)?)"],
+ path_regexps = ["src/main/java/com/android/tools/r8/Version.java"]
+)
+
+luci.gitiles_poller(
name = "branch-gitiles-8.1-forward",
bucket = "ci",
repo = "https://r8.googlesource.com/r8",
@@ -327,7 +335,9 @@
r8_tester_with_default("linux-jdk17",
["--runtimes=jdk17", "--command_cache_dir=/tmp/ccache"],
release_trigger=["branch-gitiles-3.3-forward"])
-
+r8_tester_with_default("linux-jdk21",
+ ["--runtimes=jdk21", "--command_cache_dir=/tmp/ccache"],
+ release_trigger=["branch-gitiles-8.3-forward"])
r8_tester_with_default("linux-android-4.0.4",
["--dex_vm=4.0.4", "--all_tests", "--command_cache_dir=/tmp/ccache"],
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
index 37713a9..9657a22 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -1,6 +1,11 @@
// Copyright (c) 2022, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
package com.android.tools.r8.keepanno.annotations;
import java.lang.annotation.ElementType;
@@ -11,11 +16,16 @@
/**
* A binding of a keep item.
*
- * <p>A binding allows referencing the exact instance of a match from a condition in other
- * conditions and/or targets. It can also be used to reduce duplication of targets by sharing
- * patterns.
+ * <p>Bindings allow referencing the exact instance of a match from a condition in other conditions
+ * and/or targets. It can also be used to reduce duplication of targets by sharing patterns.
*
- * <p>See KeepTarget for documentation on specifying an item pattern.
+ * <p>An item can be:
+ *
+ * <ul>
+ * <li>a pattern on classes;
+ * <li>a pattern on methods; or
+ * <li>a pattern on fields.
+ * </ul>
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.CLASS)
@@ -24,25 +34,268 @@
/** Name with which other bindings, conditions or targets can reference the bound item pattern. */
String bindingName();
+ /**
+ * Specify the kind of this item pattern.
+ *
+ * <p>Possible values are:
+ *
+ * <ul>
+ * <li>ONLY_CLASS
+ * <li>ONLY_MEMBERS
+ * <li>CLASS_AND_MEMBERS
+ * </ul>
+ *
+ * <p>If unspecified the default for an item with no member patterns is ONLY_CLASS and if it does
+ * have member patterns the default is ONLY_MEMBERS
+ */
KeepItemKind kind() default KeepItemKind.DEFAULT;
+ /**
+ * Define the class pattern by reference to a binding.
+ *
+ * <p>Mutually exclusive with the following other properties defining class:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class.
+ */
String classFromBinding() default "";
+ /**
+ * Define the class-name pattern by fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ */
String className() default "";
+ /**
+ * Define the class-name pattern by reference to a Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ */
Class<?> classConstant() default Object.class;
+ /**
+ * Define the instance-of pattern as classes that are instances of the fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ String instanceOfClassName() default "";
+
+ /**
+ * Define the instance-of pattern as classes that are instances of the fully qualified class name.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ String instanceOfClassNameExclusive() default "";
+
+ /**
+ * Define the instance-of pattern as classes that are instances the referenced Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ Class<?> instanceOfClassConstant() default Object.class;
+
+ /**
+ * Define the instance-of pattern as classes that are instances the referenced Class constant.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ Class<?> instanceOfClassConstantExclusive() default Object.class;
+
+ /**
+ * Define the instance-of pattern as classes extending the fully qualified class name.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>This property is deprecated, use instanceOfClassName instead.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
String extendsClassName() default "";
+ /**
+ * Define the instance-of pattern as classes extending the referenced Class constant.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>This property is deprecated, use instanceOfClassConstant instead.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
Class<?> extendsClassConstant() default Object.class;
+ /**
+ * Define the member-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field and method properties as use restricts the match to both
+ * types of members.
+ */
+ MemberAccessFlags[] memberAccess() default {};
+
+ /**
+ * Define the method-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * method-access flags.
+ */
+ MethodAccessFlags[] methodAccess() default {};
+
+ /**
+ * Define the method-name pattern by an exact method name.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any method
+ * name.
+ */
String methodName() default "";
+ /**
+ * Define the method return-type pattern by a fully qualified type or 'void'.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any return
+ * type.
+ */
String methodReturnType() default "";
- String[] methodParameters() default {""};
+ /**
+ * Define the method parameters pattern by a list of fully qualified types.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * parameters.
+ */
+ String[] methodParameters() default {"<default>"};
+ /**
+ * Define the field-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any
+ * field-access flags.
+ */
+ FieldAccessFlags[] fieldAccess() default {};
+
+ /**
+ * Define the field-name pattern by an exact field name.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any field
+ * name.
+ */
String fieldName() default "";
+ /**
+ * Define the field-type pattern by a fully qualified type.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any type.
+ */
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index a0dbcf4..7777fd8 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -1,6 +1,11 @@
// Copyright (c) 2022, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
package com.android.tools.r8.keepanno.annotations;
import java.lang.annotation.ElementType;
@@ -11,31 +16,272 @@
/**
* A condition for a keep edge.
*
- * <p>See KeepTarget for documentation on specifying an item pattern.
+ * <p>The condition denotes an item used as a precondition of a rule. An item can be:
+ *
+ * <ul>
+ * <li>a pattern on classes;
+ * <li>a pattern on methods; or
+ * <li>a pattern on fields.
+ * </ul>
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface KeepCondition {
+ /**
+ * Define the class pattern by reference to a binding.
+ *
+ * <p>Mutually exclusive with the following other properties defining class:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class.
+ */
String classFromBinding() default "";
+ /**
+ * Define the class-name pattern by fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ */
String className() default "";
+ /**
+ * Define the class-name pattern by reference to a Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ */
Class<?> classConstant() default Object.class;
+ /**
+ * Define the instance-of pattern as classes that are instances of the fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ String instanceOfClassName() default "";
+
+ /**
+ * Define the instance-of pattern as classes that are instances of the fully qualified class name.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ String instanceOfClassNameExclusive() default "";
+
+ /**
+ * Define the instance-of pattern as classes that are instances the referenced Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ Class<?> instanceOfClassConstant() default Object.class;
+
+ /**
+ * Define the instance-of pattern as classes that are instances the referenced Class constant.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ Class<?> instanceOfClassConstantExclusive() default Object.class;
+
+ /**
+ * Define the instance-of pattern as classes extending the fully qualified class name.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>This property is deprecated, use instanceOfClassName instead.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
String extendsClassName() default "";
+ /**
+ * Define the instance-of pattern as classes extending the referenced Class constant.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>This property is deprecated, use instanceOfClassConstant instead.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
Class<?> extendsClassConstant() default Object.class;
+ /**
+ * Define the member pattern in full by a reference to a binding.
+ *
+ * <p>Mutually exclusive with all other class and member pattern properties. When a member binding
+ * is referenced this item is defined to be that item, including its class and member patterns.
+ */
String memberFromBinding() default "";
+ /**
+ * Define the member-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field and method properties as use restricts the match to both
+ * types of members.
+ */
+ MemberAccessFlags[] memberAccess() default {};
+
+ /**
+ * Define the method-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * method-access flags.
+ */
+ MethodAccessFlags[] methodAccess() default {};
+
+ /**
+ * Define the method-name pattern by an exact method name.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any method
+ * name.
+ */
String methodName() default "";
+ /**
+ * Define the method return-type pattern by a fully qualified type or 'void'.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any return
+ * type.
+ */
String methodReturnType() default "";
- String[] methodParameters() default {""};
+ /**
+ * Define the method parameters pattern by a list of fully qualified types.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * parameters.
+ */
+ String[] methodParameters() default {"<default>"};
+ /**
+ * Define the field-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any
+ * field-access flags.
+ */
+ FieldAccessFlags[] fieldAccess() default {};
+
+ /**
+ * Define the field-name pattern by an exact field name.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any field
+ * name.
+ */
String fieldName() default "";
+ /**
+ * Define the field-type pattern by a fully qualified type.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any type.
+ */
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
index 24b9c4f..65454fe 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
@@ -1,6 +1,11 @@
// Copyright (c) 2023, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
package com.android.tools.r8.keepanno.annotations;
import java.lang.annotation.ElementType;
@@ -21,37 +26,103 @@
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface KeepForApi {
+
+ /** Optional description to document the reason for this annotation. */
String description() default "";
- /** Additional targets to be kept as part of the API surface. */
+ /**
+ * Additional targets to be kept as part of the API surface.
+ *
+ * <p>Defaults to no additional targets.
+ */
KeepTarget[] additionalTargets() default {};
/**
- * The target kind to be kept.
+ * Specify the kind of this item pattern.
*
- * <p>Default kind is CLASS_AND_MEMBERS, meaning the annotated class and/or member is to be kept.
+ * <p>Default kind is CLASS_AND_MEMBERS , meaning the annotated class and/or member is to be kept.
* When annotating a class this can be set to ONLY_CLASS to avoid patterns on any members. That
* can be useful when the API members are themselves explicitly annotated.
*
* <p>It is not possible to use ONLY_CLASS if annotating a member. Also, it is never valid to use
- * kind ONLY_MEMBERS as the API surface must keep the class if any member it to be accessible.
+ * kind ONLY_MEMBERS as the API surface must keep the class if any member is to be accessible.
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
- // Member patterns. See KeepTarget for documentation.
+ /**
+ * Define the member-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field and method properties as use restricts the match to both
+ * types of members.
+ */
MemberAccessFlags[] memberAccess() default {};
+ /**
+ * Define the method-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * method-access flags.
+ */
MethodAccessFlags[] methodAccess() default {};
+ /**
+ * Define the method-name pattern by an exact method name.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any method
+ * name.
+ */
String methodName() default "";
+ /**
+ * Define the method return-type pattern by a fully qualified type or 'void'.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any return
+ * type.
+ */
String methodReturnType() default "";
- String[] methodParameters() default {""};
+ /**
+ * Define the method parameters pattern by a list of fully qualified types.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * parameters.
+ */
+ String[] methodParameters() default {"<default>"};
+ /**
+ * Define the field-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any
+ * field-access flags.
+ */
FieldAccessFlags[] fieldAccess() default {};
+ /**
+ * Define the field-name pattern by an exact field name.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any field
+ * name.
+ */
String fieldName() default "";
+ /**
+ * Define the field-type pattern by a fully qualified type.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any type.
+ */
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index c9626f2..59e7e7c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -1,6 +1,11 @@
// Copyright (c) 2022, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
package com.android.tools.r8.keepanno.annotations;
import java.lang.annotation.ElementType;
@@ -11,45 +16,67 @@
/**
* A target for a keep edge.
*
- * <p>The target denotes a keep item along with options for what to keep:
+ * <p>The target denotes an item along with options for what to keep. An item can be:
*
* <ul>
* <li>a pattern on classes;
* <li>a pattern on methods; or
* <li>a pattern on fields.
* </ul>
- *
- * <p>The structure of a target item is the same as for a condition item but has the additional keep
- * options.
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface KeepTarget {
+ /**
+ * Specify the kind of this item pattern.
+ *
+ * <p>Possible values are:
+ *
+ * <ul>
+ * <li>ONLY_CLASS
+ * <li>ONLY_MEMBERS
+ * <li>CLASS_AND_MEMBERS
+ * </ul>
+ *
+ * <p>If unspecified the default for an item with no member patterns is ONLY_CLASS and if it does
+ * have member patterns the default is ONLY_MEMBERS
+ */
KeepItemKind kind() default KeepItemKind.DEFAULT;
/**
* Define the options that do not need to be preserved for the target.
*
- * <p>Mutually exclusive with `disallow`.
+ * <p>Mutually exclusive with the property `disallow` also defining options.
*
- * <p>If none are specified the default is "allow none" / "disallow all".
+ * <p>If nothing is specified for options the default is "allow none" / "disallow all".
*/
KeepOption[] allow() default {};
/**
* Define the options that *must* be preserved for the target.
*
- * <p>Mutually exclusive with `allow`.
+ * <p>Mutually exclusive with the property `allow` also defining options.
*
- * <p>If none are specified the default is "allow none" / "disallow all".
+ * <p>If nothing is specified for options the default is "allow none" / "disallow all".
*/
KeepOption[] disallow() default {};
/**
- * Define the class-name pattern by reference to a binding.
+ * Define the class pattern by reference to a binding.
*
- * <p>Mutually exclusive with `className` and `classConstant`.
+ * <p>Mutually exclusive with the following other properties defining class:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * </ul>
*
* <p>If none are specified the default is to match any class.
*/
@@ -58,111 +85,237 @@
/**
* Define the class-name pattern by fully qualified class name.
*
- * <p>Mutually exclusive with `classFromBinding` and `classConstant`.
+ * <p>Mutually exclusive with the following other properties defining class-name:
*
- * <p>If none are specified the default is to match any class.
+ * <ul>
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
*/
String className() default "";
/**
* Define the class-name pattern by reference to a Class constant.
*
- * <p>Mutually exclusive with `classFromBinding` and `className`.
+ * <p>Mutually exclusive with the following other properties defining class-name:
*
- * <p>If none are specified the default is to match any class.
+ * <ul>
+ * <li>className
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
*/
Class<?> classConstant() default Object.class;
/**
- * Define the extends pattern by fully qualified class name.
+ * Define the instance-of pattern as classes that are instances of the fully qualified class name.
*
- * <p>Mutually exclusive with `extendsClassConstant`.
+ * <p>Mutually exclusive with the following other properties defining instance-of:
*
- * <p>If none are specified the default is to match any extends clause.
+ * <ul>
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ String instanceOfClassName() default "";
+
+ /**
+ * Define the instance-of pattern as classes that are instances of the fully qualified class name.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ String instanceOfClassNameExclusive() default "";
+
+ /**
+ * Define the instance-of pattern as classes that are instances the referenced Class constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ Class<?> instanceOfClassConstant() default Object.class;
+
+ /**
+ * Define the instance-of pattern as classes that are instances the referenced Class constant.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>extendsClassName
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
+ */
+ Class<?> instanceOfClassConstantExclusive() default Object.class;
+
+ /**
+ * Define the instance-of pattern as classes extending the fully qualified class name.
+ *
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
+ *
+ * <p>This property is deprecated, use instanceOfClassName instead.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
*/
String extendsClassName() default "";
/**
- * Define the extends pattern by Class constant.
+ * Define the instance-of pattern as classes extending the referenced Class constant.
*
- * <p>Mutually exclusive with `extendsClassName`.
+ * <p>The pattern is exclusive in that it does not match classes that are instances of the
+ * pattern, but only those that are instances of classes that are subclasses of the pattern.
*
- * <p>If none are specified the default is to match any extends clause.
+ * <p>This property is deprecated, use instanceOfClassConstant instead.
+ *
+ * <p>Mutually exclusive with the following other properties defining instance-of:
+ *
+ * <ul>
+ * <li>instanceOfClassName
+ * <li>instanceOfClassNameExclusive
+ * <li>instanceOfClassConstant
+ * <li>instanceOfClassConstantExclusive
+ * <li>extendsClassName
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class instance.
*/
Class<?> extendsClassConstant() default Object.class;
/**
* Define the member pattern in full by a reference to a binding.
*
- * <p>Mutually exclusive with all other pattern properties. When a member binding is referenced
- * this item is defined to be that item, including its class and member patterns.
+ * <p>Mutually exclusive with all other class and member pattern properties. When a member binding
+ * is referenced this item is defined to be that item, including its class and member patterns.
*/
String memberFromBinding() default "";
/**
- * Define the member pattern by matching on access flags.
+ * Define the member-access pattern by matching on access flags.
*
- * <p>Mutually exclusive with all field and method patterns as use restricts the match to both
+ * <p>Mutually exclusive with all field and method properties as use restricts the match to both
* types of members.
*/
MemberAccessFlags[] memberAccess() default {};
/**
- * Define the method pattern by matching on access flags.
+ * Define the method-access pattern by matching on access flags.
*
- * <p>Mutually exclusive with any field properties.
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * method-access flags.
*/
MethodAccessFlags[] methodAccess() default {};
/**
* Define the method-name pattern by an exact method name.
*
- * <p>Mutually exclusive with any field properties.
+ * <p>Mutually exclusive with all field properties.
*
- * <p>If none and other properties define this as a method the default matches any method name.
+ * <p>If none, and other properties define this item as a method, the default matches any method
+ * name.
*/
String methodName() default "";
/**
* Define the method return-type pattern by a fully qualified type or 'void'.
*
- * <p>Mutually exclusive with any field properties.
+ * <p>Mutually exclusive with all field properties.
*
- * <p>If none and other properties define this as a method the default matches any return type.
+ * <p>If none, and other properties define this item as a method, the default matches any return
+ * type.
*/
String methodReturnType() default "";
/**
* Define the method parameters pattern by a list of fully qualified types.
*
- * <p>Mutually exclusive with any field properties.
+ * <p>Mutually exclusive with all field properties.
*
- * <p>If none and other properties define this as a method the default matches any parameters.
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * parameters.
*/
- String[] methodParameters() default {""};
+ String[] methodParameters() default {"<default>"};
/**
- * Define the field pattern by matching on field access flags.
+ * Define the field-access pattern by matching on access flags.
*
- * <p>Mutually exclusive with any method properties.
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any
+ * field-access flags.
*/
FieldAccessFlags[] fieldAccess() default {};
/**
* Define the field-name pattern by an exact field name.
*
- * <p>Mutually exclusive with any method properties.
+ * <p>Mutually exclusive with all method properties.
*
- * <p>If none and other properties define this as a field the default matches any field name.
+ * <p>If none, and other properties define this item as a field, the default matches any field
+ * name.
*/
String fieldName() default "";
/**
* Define the field-type pattern by a fully qualified type.
*
- * <p>Mutually exclusive with any method properties.
+ * <p>Mutually exclusive with all method properties.
*
- * <p>If none and other properties define this as a field the default matches any field type.
+ * <p>If none, and other properties define this item as a field, the default matches any type.
*/
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
index cd00e00..b1ad804 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -1,6 +1,11 @@
// Copyright (c) 2023, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
package com.android.tools.r8.keepanno.annotations;
import java.lang.annotation.ElementType;
@@ -11,6 +16,12 @@
/**
* Annotation to mark a class, field or method as being accessed from native code via JNI.
*
+ * <p>Note: Before using this annotation, consider if instead you can annotate the code that is
+ * doing reflection with {@link UsesReflection}. Annotating the reflecting code is generally more
+ * clear and maintainable, and it also naturally gives rise to edges that describe just the
+ * reflected aspects of the program. The {@link UsedByReflection} annotation is suitable for cases
+ * where the reflecting code is not under user control, or in migrating away from rules.
+ *
* <p>When a class is annotated, member patterns can be used to define which members are to be kept.
* When no member patterns are specified the default pattern is to match just the class.
*
@@ -20,6 +31,8 @@
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface UsedByNative {
+
+ /** Optional description to document the reason for this annotation. */
String description() default "";
/**
@@ -29,11 +42,15 @@
*/
KeepCondition[] preconditions() default {};
- /** Additional targets to be kept in addition to the annotated class/members. */
+ /**
+ * Additional targets to be kept in addition to the annotated class/members.
+ *
+ * <p>Defaults to no additional targets.
+ */
KeepTarget[] additionalTargets() default {};
/**
- * The target kind to be kept.
+ * Specify the kind of this item pattern.
*
* <p>When annotating a class without member patterns, the default kind is {@link
* KeepItemKind#ONLY_CLASS}.
@@ -47,20 +64,80 @@
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
- // Member patterns. See KeepTarget for documentation.
+ /**
+ * Define the member-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field and method properties as use restricts the match to both
+ * types of members.
+ */
MemberAccessFlags[] memberAccess() default {};
+ /**
+ * Define the method-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * method-access flags.
+ */
MethodAccessFlags[] methodAccess() default {};
+ /**
+ * Define the method-name pattern by an exact method name.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any method
+ * name.
+ */
String methodName() default "";
+ /**
+ * Define the method return-type pattern by a fully qualified type or 'void'.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any return
+ * type.
+ */
String methodReturnType() default "";
- String[] methodParameters() default {""};
+ /**
+ * Define the method parameters pattern by a list of fully qualified types.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * parameters.
+ */
+ String[] methodParameters() default {"<default>"};
+ /**
+ * Define the field-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any
+ * field-access flags.
+ */
FieldAccessFlags[] fieldAccess() default {};
+ /**
+ * Define the field-name pattern by an exact field name.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any field
+ * name.
+ */
String fieldName() default "";
+ /**
+ * Define the field-type pattern by a fully qualified type.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any type.
+ */
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
index 3b065ca..27489cb 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -1,6 +1,11 @@
// Copyright (c) 2023, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
package com.android.tools.r8.keepanno.annotations;
import java.lang.annotation.ElementType;
@@ -9,7 +14,7 @@
import java.lang.annotation.Target;
/**
- * Annotation to mark a class, field or method as being reflectively accessed.
+ * Annotation to mark a class, field or method as being accessed reflectively.
*
* <p>Note: Before using this annotation, consider if instead you can annotate the code that is
* doing reflection with {@link UsesReflection}. Annotating the reflecting code is generally more
@@ -26,6 +31,8 @@
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface UsedByReflection {
+
+ /** Optional description to document the reason for this annotation. */
String description() default "";
/**
@@ -35,11 +42,15 @@
*/
KeepCondition[] preconditions() default {};
- /** Additional targets to be kept in addition to the annotated class/members. */
+ /**
+ * Additional targets to be kept in addition to the annotated class/members.
+ *
+ * <p>Defaults to no additional targets.
+ */
KeepTarget[] additionalTargets() default {};
/**
- * The target kind to be kept.
+ * Specify the kind of this item pattern.
*
* <p>When annotating a class without member patterns, the default kind is {@link
* KeepItemKind#ONLY_CLASS}.
@@ -53,20 +64,80 @@
*/
KeepItemKind kind() default KeepItemKind.DEFAULT;
- // Member patterns. See KeepTarget for documentation.
+ /**
+ * Define the member-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field and method properties as use restricts the match to both
+ * types of members.
+ */
MemberAccessFlags[] memberAccess() default {};
+ /**
+ * Define the method-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * method-access flags.
+ */
MethodAccessFlags[] methodAccess() default {};
+ /**
+ * Define the method-name pattern by an exact method name.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any method
+ * name.
+ */
String methodName() default "";
+ /**
+ * Define the method return-type pattern by a fully qualified type or 'void'.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any return
+ * type.
+ */
String methodReturnType() default "";
- String[] methodParameters() default {""};
+ /**
+ * Define the method parameters pattern by a list of fully qualified types.
+ *
+ * <p>Mutually exclusive with all field properties.
+ *
+ * <p>If none, and other properties define this item as a method, the default matches any
+ * parameters.
+ */
+ String[] methodParameters() default {"<default>"};
+ /**
+ * Define the field-access pattern by matching on access flags.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any
+ * field-access flags.
+ */
FieldAccessFlags[] fieldAccess() default {};
+ /**
+ * Define the field-name pattern by an exact field name.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any field
+ * name.
+ */
String fieldName() default "";
+ /**
+ * Define the field-type pattern by a fully qualified type.
+ *
+ * <p>Mutually exclusive with all method properties.
+ *
+ * <p>If none, and other properties define this item as a field, the default matches any type.
+ */
String fieldType() default "";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
index 0af2ec6..56a215d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
@@ -1,6 +1,11 @@
// Copyright (c) 2022, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
package com.android.tools.r8.keepanno.annotations;
import java.lang.annotation.ElementType;
@@ -9,7 +14,7 @@
import java.lang.annotation.Target;
/**
- * Annotation to declare the reflective usages made by an item.
+ * Annotation to declare the reflective usages made by a class, method or field.
*
* <p>The annotation's 'value' is a list of targets to be kept if the annotated item is used. The
* annotated item is a precondition for keeping any of the specified targets. Thus, if an annotated
@@ -19,14 +24,15 @@
* <p>The annotation's 'additionalPreconditions' is optional and can specify additional conditions
* that should be satisfied for the annotation to be in effect.
*
- * <p>The translation of the @UsesReflection annotation into a @KeepEdge is as follows:
+ * <p>The translation of the {@link UsesReflection} annotation into a {@link KeepEdge} is as
+ * follows:
*
* <p>Assume the item of the annotation is denoted by 'CTX' and referred to as its context.
*
* <pre>
- * @UsesReflection(value = targets, [additionalPreconditions = preconditions])
- * ==>
- * @KeepEdge(
+ * @UsesReflection(value = targets, [additionalPreconditions = preconditions])
+ * ==>
+ * @KeepEdge(
* consequences = targets,
* preconditions = {createConditionFromContext(CTX)} + preconditions
* )
@@ -56,10 +62,17 @@
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface UsesReflection {
+
+ /** Optional description to document the reason for this annotation. */
String description() default "";
+ /** Consequences that must be kept if the annotation is in effect. */
KeepTarget[] value();
+ /**
+ * Additional preconditions for the annotation to be in effect.
+ *
+ * <p>Defaults to no additional preconditions.
+ */
KeepCondition[] additionalPreconditions() default {};
-
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index d3fc1ba..bc466c5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -18,25 +18,28 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflection;
+import com.android.tools.r8.keepanno.ast.KeepBindingReference;
import com.android.tools.r8.keepanno.ast.KeepBindings;
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepCheck;
import com.android.tools.r8.keepanno.ast.KeepCheck.KeepCheckKind;
-import com.android.tools.r8.keepanno.ast.KeepClassReference;
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
-import com.android.tools.r8.keepanno.ast.KeepExtendsPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepInstanceOfPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
@@ -51,7 +54,6 @@
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -81,6 +83,11 @@
return declarations;
}
+ private static KeepClassItemReference classReferenceFromName(String className) {
+ return KeepClassItemReference.fromClassNamePattern(
+ KeepQualifiedClassNamePattern.exact(className));
+ }
+
private static class KeepEdgeClassVisitor extends ClassVisitor {
private final Parent<KeepDeclaration> parent;
private String className;
@@ -116,9 +123,9 @@
return new KeepEdgeVisitor(parent::accept, this::setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
- KeepItemPattern classItem =
- KeepItemPattern.builder()
- .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
+ KeepClassItemPattern classItem =
+ KeepClassItemPattern.builder()
+ .setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
.build();
return new UsesReflectionVisitor(parent::accept, this::setContext, classItem);
}
@@ -176,7 +183,7 @@
this.methodDescriptor = methodDescriptor;
}
- private KeepItemPattern createItemContext() {
+ private KeepMemberItemPattern createMethodItemContext() {
String returnTypeDescriptor = Type.getReturnType(methodDescriptor).getDescriptor();
Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor);
KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
@@ -188,8 +195,8 @@
? KeepMethodReturnTypePattern.voidType()
: KeepMethodReturnTypePattern.fromType(
KeepTypePattern.fromDescriptor(returnTypeDescriptor));
- return KeepItemPattern.builder()
- .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
+ return KeepMemberItemPattern.builder()
+ .setClassReference(classReferenceFromName(className))
.setMemberPattern(
KeepMethodPattern.builder()
.setNamePattern(KeepMethodNamePattern.exact(methodName))
@@ -209,22 +216,23 @@
return new KeepEdgeVisitor(parent::accept, this::setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
- return new UsesReflectionVisitor(parent::accept, this::setContext, createItemContext());
+ return new UsesReflectionVisitor(
+ parent::accept, this::setContext, createMethodItemContext());
}
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
- return new ForApiMemberVisitor(parent::accept, this::setContext, createItemContext());
+ return new ForApiMemberVisitor(parent::accept, this::setContext, createMethodItemContext());
}
if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionMemberVisitor(
- descriptor, parent::accept, this::setContext, createItemContext());
+ descriptor, parent::accept, this::setContext, createMethodItemContext());
}
if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
return new CheckRemovedMemberVisitor(
descriptor,
parent::accept,
this::setContext,
- createItemContext(),
+ createMethodItemContext(),
KeepCheckKind.REMOVED);
}
if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
@@ -232,7 +240,7 @@
descriptor,
parent::accept,
this::setContext,
- createItemContext(),
+ createMethodItemContext(),
KeepCheckKind.OPTIMIZED_OUT);
}
return null;
@@ -259,11 +267,11 @@
this.fieldDescriptor = fieldDescriptor;
}
- private KeepItemPattern createItemContext() {
+ private KeepMemberItemPattern createMemberItemContext() {
KeepFieldTypePattern typePattern =
KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(fieldDescriptor));
- return KeepItemPattern.builder()
- .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
+ return KeepMemberItemPattern.builder()
+ .setClassReference(classReferenceFromName(className))
.setMemberPattern(
KeepFieldPattern.builder()
.setNamePattern(KeepFieldNamePattern.exact(fieldName))
@@ -287,15 +295,15 @@
return new KeepEdgeVisitor(parent, this::setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
- return new UsesReflectionVisitor(parent, this::setContext, createItemContext());
+ return new UsesReflectionVisitor(parent, this::setContext, createMemberItemContext());
}
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
- return new ForApiMemberVisitor(parent, this::setContext, createItemContext());
+ return new ForApiMemberVisitor(parent, this::setContext, createMemberItemContext());
}
if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionMemberVisitor(
- descriptor, parent, this::setContext, createItemContext());
+ descriptor, parent, this::setContext, createMemberItemContext());
}
return null;
}
@@ -342,9 +350,9 @@
private static class UserBindingsHelper {
private final KeepBindings.Builder builder = KeepBindings.builder();
- private final Map<String, BindingSymbol> userNames = new HashMap<>();
+ private final Map<String, KeepBindingSymbol> userNames = new HashMap<>();
- public BindingSymbol resolveUserBinding(String name) {
+ public KeepBindingSymbol resolveUserBinding(String name) {
return userNames.computeIfAbsent(name, builder::create);
}
@@ -352,8 +360,8 @@
builder.addBinding(resolveUserBinding(name), item);
}
- public BindingSymbol defineFreshBinding(String name, KeepItemPattern item) {
- BindingSymbol symbol = builder.generateFreshSymbol(name);
+ public KeepBindingSymbol defineFreshBinding(String name, KeepItemPattern item) {
+ KeepBindingSymbol symbol = builder.generateFreshSymbol(name);
builder.addBinding(symbol, item);
return symbol;
}
@@ -444,7 +452,7 @@
@Override
public String getAnnotationName() {
- return ForApi.CLASS.getSimpleName();
+ return ForApi.SIMPLE_NAME;
}
@Override
@@ -483,17 +491,20 @@
if (item.isBindingReference()) {
throw new KeepEdgeException("@KeepForApi cannot reference bindings");
}
- KeepItemPattern itemPattern = item.asItemPattern();
- String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
- String itemDescriptor =
- itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
+ KeepClassItemPattern classItemPattern = item.asClassItemPattern();
+ if (classItemPattern == null) {
+ assert item.isMemberItemReference();
+ classItemPattern = item.asMemberItemPattern().getClassReference().asClassItemPattern();
+ }
+ String descriptor = KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className);
+ String itemDescriptor = classItemPattern.getClassNamePattern().getExactDescriptor();
if (!descriptor.equals(itemDescriptor)) {
throw new KeepEdgeException("@KeepForApi must reference its class context " + className);
}
- if (itemPattern.isMemberItemPattern() && items.size() == 1) {
+ if (classItemPattern.isMemberItemPattern() && items.size() == 1) {
throw new KeepEdgeException("@KeepForApi kind must include its class");
}
- if (!itemPattern.getExtendsPattern().isAny()) {
+ if (!classItemPattern.getInstanceOfPattern().isAny()) {
throw new KeepEdgeException("@KeepForApi cannot define an 'extends' pattern.");
}
consequences.addTarget(KeepTarget.builder().setItemReference(item).build());
@@ -522,27 +533,28 @@
ForApiMemberVisitor(
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
- KeepItemPattern context) {
+ KeepMemberItemPattern context) {
this.parent = parent;
addContext.accept(metaInfoBuilder);
- BindingSymbol contextBinding = bindingsHelper.defineFreshBinding("CONTEXT", context);
// Create a binding for the context such that the class and member are shared.
+ KeepClassItemPattern classContext = context.getClassReference().asClassItemPattern();
+ KeepBindingSymbol bindingSymbol = bindingsHelper.defineFreshBinding("CONTEXT", classContext);
+ KeepClassItemReference classReference =
+ KeepBindingReference.forClass(bindingSymbol).toClassItemReference();
consequences.addTarget(
KeepTarget.builder()
.setItemPattern(
- KeepItemPattern.builder()
- .setClassReference(KeepClassReference.fromBindingReference(contextBinding))
+ KeepMemberItemPattern.builder()
+ .copyFrom(context)
+ .setClassReference(classReference)
.build())
.build());
- consequences.addTarget(
- KeepTarget.builder()
- .setItemReference(KeepItemReference.fromBindingReference(contextBinding))
- .build());
+ consequences.addTarget(KeepTarget.builder().setItemReference(classReference).build());
}
@Override
public String getAnnotationName() {
- return ForApi.CLASS.getSimpleName();
+ return ForApi.SIMPLE_NAME;
}
@Override
@@ -658,9 +670,12 @@
throw new KeepEdgeException("@" + getAnnotationName() + " cannot reference bindings");
}
KeepItemPattern itemPattern = item.asItemPattern();
- String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
- String itemDescriptor =
- itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
+ KeepClassItemPattern holderPattern =
+ itemPattern.isClassItemPattern()
+ ? itemPattern.asClassItemPattern()
+ : itemPattern.asMemberItemPattern().getClassReference().asClassItemPattern();
+ String descriptor = KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className);
+ String itemDescriptor = holderPattern.getClassNamePattern().getExactDescriptor();
if (!descriptor.equals(itemDescriptor)) {
throw new KeepEdgeException(
"@" + getAnnotationName() + " must reference its class context " + className);
@@ -668,7 +683,7 @@
if (itemPattern.isMemberItemPattern() && items.size() == 1) {
throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its class");
}
- if (!itemPattern.getExtendsPattern().isAny()) {
+ if (!holderPattern.getInstanceOfPattern().isAny()) {
throw new KeepEdgeException(
"@" + getAnnotationName() + " cannot define an 'extends' pattern.");
}
@@ -730,9 +745,6 @@
super.visitEnum(name, descriptor, value);
}
switch (value) {
- case Kind.DEFAULT:
- // The default value is obtained by not assigning a kind (e.g., null in the builder).
- break;
case Kind.ONLY_CLASS:
case Kind.ONLY_MEMBERS:
case Kind.CLASS_AND_MEMBERS:
@@ -766,14 +778,10 @@
throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its member");
}
assert context.isMemberItemPattern();
+ KeepMemberItemPattern memberContext = context.asMemberItemPattern();
if (Kind.CLASS_AND_MEMBERS.equals(kind)) {
consequences.addTarget(
- KeepTarget.builder()
- .setItemPattern(
- KeepItemPattern.builder()
- .setClassReference(context.getClassReference())
- .build())
- .build());
+ KeepTarget.builder().setItemReference(memberContext.getClassReference()).build());
}
consequences.addTarget(KeepTarget.builder().setItemPattern(context).build());
parent.accept(
@@ -803,7 +811,7 @@
@Override
public String getAnnotationName() {
- return UsesReflection.CLASS.getSimpleName();
+ return UsesReflection.SIMPLE_NAME;
}
@Override
@@ -1127,69 +1135,160 @@
}
}
- private static class ClassDeclaration extends SingleDeclaration<KeepClassReference> {
+ private static class ClassNameDeclaration
+ extends SingleDeclaration<KeepQualifiedClassNamePattern> {
+
+ @Override
+ String kind() {
+ return "class-name";
+ }
+
+ @Override
+ KeepQualifiedClassNamePattern getDefaultValue() {
+ return KeepQualifiedClassNamePattern.any();
+ }
+
+ @Override
+ KeepQualifiedClassNamePattern parse(String name, Object value) {
+ if (name.equals(Item.classConstant) && value instanceof Type) {
+ return KeepQualifiedClassNamePattern.exact(((Type) value).getClassName());
+ }
+ if (name.equals(Item.className) && value instanceof String) {
+ return KeepQualifiedClassNamePattern.exact(((String) value));
+ }
+ return null;
+ }
+ }
+
+ private static class InstanceOfDeclaration extends SingleDeclaration<KeepInstanceOfPattern> {
+
+ @Override
+ String kind() {
+ return "instance-of";
+ }
+
+ @Override
+ KeepInstanceOfPattern getDefaultValue() {
+ return KeepInstanceOfPattern.any();
+ }
+
+ @Override
+ KeepInstanceOfPattern parse(String name, Object value) {
+ if (name.equals(Item.instanceOfClassConstant) && value instanceof Type) {
+ return KeepInstanceOfPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()))
+ .build();
+ }
+ if (name.equals(Item.instanceOfClassName) && value instanceof String) {
+ return KeepInstanceOfPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((String) value)))
+ .build();
+ }
+ if (name.equals(Item.instanceOfClassConstantExclusive) && value instanceof Type) {
+ return KeepInstanceOfPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()))
+ .setInclusive(false)
+ .build();
+ }
+ if (name.equals(Item.instanceOfClassNameExclusive) && value instanceof String) {
+ return KeepInstanceOfPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((String) value)))
+ .setInclusive(false)
+ .build();
+ }
+ if (name.equals(Item.extendsClassConstant) && value instanceof Type) {
+ return KeepInstanceOfPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()))
+ .setInclusive(false)
+ .build();
+ }
+ if (name.equals(Item.extendsClassName) && value instanceof String) {
+ return KeepInstanceOfPattern.builder()
+ .classPattern(KeepQualifiedClassNamePattern.exact(((String) value)))
+ .setInclusive(false)
+ .build();
+ }
+ return null;
+ }
+ }
+
+ private static class ClassDeclaration extends Declaration<KeepClassItemReference> {
private final Supplier<UserBindingsHelper> getBindingsHelper;
+ private KeepClassItemReference boundClassItemReference = null;
+ private final ClassNameDeclaration classNameDeclaration = new ClassNameDeclaration();
+ private final InstanceOfDeclaration instanceOfDeclaration = new InstanceOfDeclaration();
+
public ClassDeclaration(Supplier<UserBindingsHelper> getBindingsHelper) {
this.getBindingsHelper = getBindingsHelper;
}
+ private boolean isBindingReferenceDefined() {
+ return boundClassItemReference != null;
+ }
+
+ private boolean classPatternsAreDefined() {
+ return !classNameDeclaration.isDefault() || !instanceOfDeclaration.isDefault();
+ }
+
+ private void checkAllowedDefinitions() {
+ if (isBindingReferenceDefined() && classPatternsAreDefined()) {
+ throw new KeepEdgeException(
+ "Cannot reference a class binding and class patterns for a single class item");
+ }
+ }
+
@Override
String kind() {
return "class";
}
- KeepClassReference wrap(KeepQualifiedClassNamePattern namePattern) {
- return KeepClassReference.fromClassNamePattern(namePattern);
+ @Override
+ boolean isDefault() {
+ return !isBindingReferenceDefined() && !classPatternsAreDefined();
}
@Override
- KeepClassReference getDefaultValue() {
- return wrap(KeepQualifiedClassNamePattern.any());
+ KeepClassItemReference getValue() {
+ if (isBindingReferenceDefined()) {
+ return boundClassItemReference;
+ }
+ if (classPatternsAreDefined()) {
+ return KeepClassItemPattern.builder()
+ .setClassNamePattern(classNameDeclaration.getValue())
+ .setInstanceOfPattern(instanceOfDeclaration.getValue())
+ .build()
+ .toClassItemReference();
+ }
+ assert isDefault();
+ return KeepClassItemPattern.any().toClassItemReference();
+ }
+
+ public void setBindingReference(KeepClassItemReference bindingReference) {
+ if (isBindingReferenceDefined()) {
+ throw new KeepEdgeException(
+ "Cannot reference multiple class bindings for a single class item");
+ }
+ this.boundClassItemReference = bindingReference;
}
@Override
- KeepClassReference parse(String name, Object value) {
+ boolean tryParse(String name, Object value) {
if (name.equals(Item.classFromBinding) && value instanceof String) {
- BindingSymbol symbol = getBindingsHelper.get().resolveUserBinding((String) value);
- return KeepClassReference.fromBindingReference(symbol);
+ KeepBindingSymbol symbol = getBindingsHelper.get().resolveUserBinding((String) value);
+ setBindingReference(KeepBindingReference.forClass(symbol).toClassItemReference());
+ return true;
}
- if (name.equals(Item.classConstant) && value instanceof Type) {
- return wrap(KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()));
+ if (classNameDeclaration.tryParse(name, value)) {
+ checkAllowedDefinitions();
+ return true;
}
- if (name.equals(Item.className) && value instanceof String) {
- return wrap(KeepQualifiedClassNamePattern.exact(((String) value)));
+ if (instanceOfDeclaration.tryParse(name, value)) {
+ checkAllowedDefinitions();
+ return true;
}
- return null;
- }
- }
-
- private static class ExtendsDeclaration extends SingleDeclaration<KeepExtendsPattern> {
-
- @Override
- String kind() {
- return "extends";
- }
-
- @Override
- KeepExtendsPattern getDefaultValue() {
- return KeepExtendsPattern.any();
- }
-
- @Override
- KeepExtendsPattern parse(String name, Object value) {
- if (name.equals(Item.extendsClassConstant) && value instanceof Type) {
- return KeepExtendsPattern.builder()
- .classPattern(KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()))
- .build();
- }
- if (name.equals(Item.extendsClassName) && value instanceof String) {
- return KeepExtendsPattern.builder()
- .classPattern(KeepQualifiedClassNamePattern.exact(((String) value)))
- .build();
- }
- return null;
+ return false;
}
}
@@ -1230,18 +1329,12 @@
@Override
boolean tryParse(String name, Object value) {
if (name.equals(Item.methodName) && value instanceof String) {
- String methodName = (String) value;
- if (!Item.methodNameDefaultValue.equals(methodName)) {
- getBuilder().setNamePattern(KeepMethodNamePattern.exact(methodName));
- }
+ getBuilder().setNamePattern(KeepMethodNamePattern.exact((String) value));
return true;
}
if (name.equals(Item.methodReturnType) && value instanceof String) {
- String returnType = (String) value;
- if (!Item.methodReturnTypeDefaultValue.equals(returnType)) {
- getBuilder()
- .setReturnTypePattern(KeepEdgeReaderUtils.methodReturnTypeFromString(returnType));
- }
+ getBuilder()
+ .setReturnTypePattern(KeepEdgeReaderUtils.methodReturnTypeFromString((String) value));
return true;
}
return false;
@@ -1257,9 +1350,6 @@
return new StringArrayVisitor(
annotationName,
params -> {
- if (Arrays.asList(Item.methodParametersDefaultValue).equals(params)) {
- return;
- }
KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
for (String param : params) {
builder.addParameterTypePattern(KeepEdgeReaderUtils.typePatternFromString(param));
@@ -1309,20 +1399,14 @@
@Override
boolean tryParse(String name, Object value) {
if (name.equals(Item.fieldName) && value instanceof String) {
- String fieldName = (String) value;
- if (!Item.fieldNameDefaultValue.equals(fieldName)) {
- getBuilder().setNamePattern(KeepFieldNamePattern.exact(fieldName));
- }
+ getBuilder().setNamePattern(KeepFieldNamePattern.exact((String) value));
return true;
}
if (name.equals(Item.fieldType) && value instanceof String) {
- String fieldType = (String) value;
- if (!Item.fieldTypeDefaultValue.equals(fieldType)) {
- getBuilder()
- .setTypePattern(
- KeepFieldTypePattern.fromType(
- KeepEdgeReaderUtils.typePatternFromString(fieldType)));
- }
+ getBuilder()
+ .setTypePattern(
+ KeepFieldTypePattern.fromType(
+ KeepEdgeReaderUtils.typePatternFromString((String) value)));
return true;
}
return false;
@@ -1406,7 +1490,6 @@
private String memberBindingReference = null;
private String kind = null;
private final ClassDeclaration classDeclaration = new ClassDeclaration(this::getBindingsHelper);
- private final ExtendsDeclaration extendsDeclaration = new ExtendsDeclaration();
private final MemberDeclaration memberDeclaration;
public abstract UserBindingsHelper getBindingsHelper();
@@ -1426,28 +1509,20 @@
// If kind is set then visitEnd ensures that this cannot be a binding reference.
assert !itemReference.isBindingReference();
KeepItemPattern itemPattern = itemReference.asItemPattern();
- KeepItemPattern classPattern;
- KeepItemPattern memberPattern;
+ KeepClassItemReference classReference;
+ KeepMemberItemPattern memberPattern;
if (itemPattern.isClassItemPattern()) {
- classPattern = itemPattern;
+ classReference = itemPattern.asClassItemPattern().toClassItemReference();
memberPattern =
- KeepItemPattern.builder()
- .copyFrom(itemPattern)
+ KeepMemberItemPattern.builder()
+ .setClassReference(classReference)
.setMemberPattern(KeepMemberPattern.allMembers())
.build();
} else {
- memberPattern = itemPattern;
- classPattern =
- KeepItemPattern.builder()
- .copyFrom(itemPattern)
- .setMemberPattern(KeepMemberPattern.none())
- .build();
+ memberPattern = itemPattern.asMemberItemPattern();
+ classReference = memberPattern.getClassReference();
}
- assert classPattern.isClassItemPattern();
- assert memberPattern.isMemberItemPattern();
- return ImmutableList.of(
- KeepItemReference.fromItemPattern(classPattern),
- KeepItemReference.fromItemPattern(memberPattern));
+ return ImmutableList.of(classReference, memberPattern.toItemReference());
} else {
return Collections.singletonList(itemReference);
}
@@ -1464,31 +1539,32 @@
assert kind != null;
if (Kind.CLASS_AND_MEMBERS.equals(kind)) {
KeepItemPattern itemPattern = itemReference.asItemPattern();
- KeepMemberPattern memberPattern;
- KeepItemPattern classPattern;
+ // Ensure we have a member item linked to the correct class.
+ KeepMemberItemPattern memberItemPattern;
if (itemPattern.isClassItemPattern()) {
- memberPattern = KeepMemberPattern.allMembers();
- classPattern = itemPattern;
+ memberItemPattern =
+ KeepMemberItemPattern.builder()
+ .setClassReference(itemPattern.asClassItemPattern().toClassItemReference())
+ .build();
} else {
- memberPattern = itemPattern.getMemberPattern();
- classPattern =
- KeepItemPattern.builder()
- .copyFrom(itemPattern)
- .setMemberPattern(KeepMemberPattern.none())
+ memberItemPattern = itemPattern.asMemberItemPattern();
+ }
+ // If the class is not a binding, introduce the binding and rewrite the member.
+ KeepClassItemReference classItemReference = memberItemPattern.getClassReference();
+ if (classItemReference.isClassItemPattern()) {
+ KeepClassItemPattern classItemPattern = classItemReference.asClassItemPattern();
+ KeepBindingSymbol symbol =
+ getBindingsHelper().defineFreshBinding("CLASS", classItemPattern);
+ classItemReference = KeepBindingReference.forClass(symbol).toClassItemReference();
+ memberItemPattern =
+ KeepMemberItemPattern.builder()
+ .copyFrom(memberItemPattern)
+ .setClassReference(classItemReference)
.build();
}
- BindingSymbol symbol = getBindingsHelper().defineFreshBinding("CLASS", classPattern);
- KeepItemPattern memberItemPattern =
- KeepItemPattern.builder()
- .copyFrom(itemPattern)
- .setClassReference(KeepClassReference.fromBindingReference(symbol))
- .setMemberPattern(memberPattern)
- .build();
- assert classPattern.isClassItemPattern();
- assert memberItemPattern.isMemberItemPattern();
- return ImmutableList.of(
- KeepItemReference.fromItemPattern(classPattern),
- KeepItemReference.fromItemPattern(memberItemPattern));
+ assert classItemReference.isBindingReference();
+ assert memberItemPattern.getClassReference().equals(classItemReference);
+ return ImmutableList.of(classItemReference, memberItemPattern.toItemReference());
} else {
return Collections.singletonList(itemReference);
}
@@ -1515,9 +1591,6 @@
super.visitEnum(name, descriptor, value);
}
switch (value) {
- case Kind.DEFAULT:
- // The default value is obtained by not assigning a kind (e.g., null in the builder).
- break;
case Kind.ONLY_CLASS:
case Kind.ONLY_MEMBERS:
case Kind.CLASS_AND_MEMBERS:
@@ -1535,7 +1608,6 @@
return;
}
if (classDeclaration.tryParse(name, value)
- || extendsDeclaration.tryParse(name, value)
|| memberDeclaration.tryParse(name, value)) {
return;
}
@@ -1554,32 +1626,33 @@
@Override
public void visitEnd() {
if (memberBindingReference != null) {
- if (!classDeclaration.getValue().equals(classDeclaration.getDefaultValue())
+ if (!classDeclaration.isDefault()
|| !memberDeclaration.getValue().isNone()
- || !extendsDeclaration.getValue().isAny()
|| kind != null) {
throw new KeepEdgeException(
"Cannot define an item explicitly and via a member-binding reference");
}
- BindingSymbol symbol = getBindingsHelper().resolveUserBinding(memberBindingReference);
- itemReference = KeepItemReference.fromBindingReference(symbol);
+ KeepBindingSymbol symbol = getBindingsHelper().resolveUserBinding(memberBindingReference);
+ itemReference = KeepBindingReference.forMember(symbol).toItemReference();
} else {
KeepMemberPattern memberPattern = memberDeclaration.getValue();
// If the kind is not set (default) then the content of the members determines the kind.
if (kind == null) {
kind = memberPattern.isNone() ? Kind.ONLY_CLASS : Kind.ONLY_MEMBERS;
}
- // If the kind is a member kind and no member pattern is set then set members to all.
- if (!kind.equals(Kind.ONLY_CLASS) && memberPattern.isNone()) {
- memberPattern = KeepMemberPattern.allMembers();
+
+ KeepClassItemReference classReference = classDeclaration.getValue();
+ if (kind.equals(Kind.ONLY_CLASS)) {
+ itemReference = classReference;
+ } else {
+ KeepItemPattern itemPattern =
+ KeepMemberItemPattern.builder()
+ .setClassReference(classReference)
+ .setMemberPattern(
+ memberPattern.isNone() ? KeepMemberPattern.allMembers() : memberPattern)
+ .build();
+ itemReference = itemPattern.toItemReference();
}
- itemReference =
- KeepItemReference.fromItemPattern(
- KeepItemPattern.builder()
- .setClassReference(classDeclaration.getValue())
- .setExtendsPattern(extendsDeclaration.getValue())
- .setMemberPattern(memberPattern)
- .build());
}
}
}
@@ -1600,7 +1673,7 @@
@Override
public String getAnnotationName() {
- return Binding.CLASS.getSimpleName();
+ return Binding.SIMPLE_NAME;
}
@Override
@@ -1724,7 +1797,7 @@
@Override
public String getAnnotationName() {
- return Target.CLASS.getSimpleName();
+ return Target.SIMPLE_NAME;
}
@Override
@@ -1762,7 +1835,7 @@
@Override
public String getAnnotationName() {
- return Condition.CLASS.getSimpleName();
+ return Condition.SIMPLE_NAME;
}
@Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
index e18bf1e..7b0cf28 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
@@ -15,6 +15,14 @@
*/
public class KeepEdgeReaderUtils {
+ public static String getBinaryNameFromClassTypeName(String classTypeName) {
+ return classTypeName.replace('.', '/');
+ }
+
+ public static String getDescriptorFromClassTypeName(String classTypeName) {
+ return "L" + getBinaryNameFromClassTypeName(classTypeName) + ";";
+ }
+
public static KeepTypePattern typePatternFromString(String string) {
if (string.equals("<any>")) {
return KeepTypePattern.any();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index ec3fb30..568e6fe 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -8,13 +8,14 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
-import com.android.tools.r8.keepanno.ast.KeepClassReference;
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern.KeepFieldNameExactPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern.KeepMethodNameExactPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -138,21 +139,34 @@
}
private void writeItem(AnnotationVisitor itemVisitor, KeepItemPattern item) {
- KeepClassReference classReference = item.getClassReference();
- if (classReference.isBindingReference()) {
- throw new Unimplemented();
+ if (item.isClassItemPattern()) {
+ writeClassItem(item.asClassItemPattern(), itemVisitor);
+ } else {
+ writeMemberItem(item.asMemberItemPattern(), itemVisitor);
}
- KeepQualifiedClassNamePattern namePattern = classReference.asClassNamePattern();
+ }
+
+ private void writeClassItem(
+ KeepClassItemPattern classItemPattern, AnnotationVisitor itemVisitor) {
+ KeepQualifiedClassNamePattern namePattern = classItemPattern.getClassNamePattern();
if (namePattern.isExact()) {
Type typeConstant = Type.getType(namePattern.getExactDescriptor());
itemVisitor.visit(AnnotationConstants.Item.classConstant, typeConstant);
} else {
throw new Unimplemented();
}
- if (!item.getExtendsPattern().isAny()) {
+ if (!classItemPattern.getInstanceOfPattern().isAny()) {
throw new Unimplemented();
}
- writeMember(item.getMemberPattern(), itemVisitor);
+ }
+
+ private void writeMemberItem(
+ KeepMemberItemPattern memberItemPattern, AnnotationVisitor itemVisitor) {
+ if (memberItemPattern.getClassReference().isBindingReference()) {
+ throw new Unimplemented();
+ }
+ writeClassItem(memberItemPattern.getClassReference().asClassItemPattern(), itemVisitor);
+ writeMember(memberItemPattern.getMemberPattern(), itemVisitor);
itemVisitor.visitEnd();
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index f6c6a74..7ca4306 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -1,18 +1,12 @@
// Copyright (c) 2023, 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.keepanno.ast;
-import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
-import com.android.tools.r8.keepanno.annotations.KeepBinding;
-import com.android.tools.r8.keepanno.annotations.KeepCondition;
-import com.android.tools.r8.keepanno.annotations.KeepEdge;
-import com.android.tools.r8.keepanno.annotations.KeepForApi;
-import com.android.tools.r8.keepanno.annotations.KeepItemKind;
-import com.android.tools.r8.keepanno.annotations.KeepOption;
-import com.android.tools.r8.keepanno.annotations.KeepTarget;
-import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
-import com.android.tools.r8.keepanno.annotations.MethodAccessFlags;
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.keepanno.ast;
/**
* Utility class for referencing the various keep annotations and their structure.
@@ -21,32 +15,9 @@
* annotations which overlap in name with the actual semantic AST types.
*/
public final class AnnotationConstants {
-
- public static String getDescriptor(Class<?> clazz) {
- return getDescriptorFromClassTypeName(clazz.getTypeName());
- }
-
- public static String getBinaryNameFromClassTypeName(String classTypeName) {
- return classTypeName.replace('.', '/');
- }
-
- public static String getDescriptorFromClassTypeName(String classTypeName) {
- return "L" + getBinaryNameFromClassTypeName(classTypeName) + ";";
- }
-
- public static boolean isKeepAnnotation(String descriptor, boolean visible) {
- if (visible) {
- return false;
- }
- return descriptor.equals(Edge.DESCRIPTOR)
- || descriptor.equals(UsesReflection.DESCRIPTOR)
- || descriptor.equals(Condition.DESCRIPTOR)
- || descriptor.equals(Target.DESCRIPTOR);
- }
-
public static final class Edge {
- public static final Class<KeepEdge> CLASS = KeepEdge.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String SIMPLE_NAME = "KeepEdge";
+ public static final String DESCRIPTOR = "Lcom/android/tools/r8/keepanno/annotations/KeepEdge;";
public static final String description = "description";
public static final String bindings = "bindings";
public static final String preconditions = "preconditions";
@@ -54,158 +25,134 @@
}
public static final class ForApi {
- public static final Class<KeepForApi> CLASS = KeepForApi.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String SIMPLE_NAME = "KeepForApi";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/KeepForApi;";
public static final String description = "description";
public static final String additionalTargets = "additionalTargets";
public static final String memberAccess = "memberAccess";
}
public static final class UsesReflection {
- public static final Class<com.android.tools.r8.keepanno.annotations.UsesReflection> CLASS =
- com.android.tools.r8.keepanno.annotations.UsesReflection.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String SIMPLE_NAME = "UsesReflection";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/UsesReflection;";
public static final String description = "description";
public static final String value = "value";
public static final String additionalPreconditions = "additionalPreconditions";
}
public static final class UsedByReflection {
- public static final Class<com.android.tools.r8.keepanno.annotations.UsedByReflection> CLASS =
- com.android.tools.r8.keepanno.annotations.UsedByReflection.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String SIMPLE_NAME = "UsedByReflection";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/UsedByReflection;";
public static final String description = "description";
public static final String preconditions = "preconditions";
public static final String additionalTargets = "additionalTargets";
- public static final String memberAccess = "memberAccess";
}
public static final class UsedByNative {
- public static final Class<com.android.tools.r8.keepanno.annotations.UsedByNative> CLASS =
- com.android.tools.r8.keepanno.annotations.UsedByNative.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String SIMPLE_NAME = "UsedByNative";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/UsedByNative;";
// Content is the same as UsedByReflection.
}
public static final class CheckRemoved {
- public static final Class<com.android.tools.r8.keepanno.annotations.CheckRemoved> CLASS =
- com.android.tools.r8.keepanno.annotations.CheckRemoved.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
- public static final String description = "description";
+ public static final String SIMPLE_NAME = "CheckRemoved";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/CheckRemoved;";
}
public static final class CheckOptimizedOut {
- public static final Class<com.android.tools.r8.keepanno.annotations.CheckOptimizedOut> CLASS =
- com.android.tools.r8.keepanno.annotations.CheckOptimizedOut.class;
-
- @SuppressWarnings("MutablePublicArray")
- public static final String DESCRIPTOR = getDescriptor(CLASS);
-
- public static final String description = "description";
+ public static final String SIMPLE_NAME = "CheckOptimizedOut";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/CheckOptimizedOut;";
}
- // Implicit hidden item which is "super type" of Condition and Target.
+ /** Item properties common to binding items, conditions and targets. */
public static final class Item {
public static final String classFromBinding = "classFromBinding";
public static final String memberFromBinding = "memberFromBinding";
-
public static final String className = "className";
public static final String classConstant = "classConstant";
-
+ public static final String instanceOfClassName = "instanceOfClassName";
+ public static final String instanceOfClassNameExclusive = "instanceOfClassNameExclusive";
+ public static final String instanceOfClassConstant = "instanceOfClassConstant";
+ public static final String instanceOfClassConstantExclusive =
+ "instanceOfClassConstantExclusive";
public static final String extendsClassName = "extendsClassName";
public static final String extendsClassConstant = "extendsClassConstant";
-
public static final String memberAccess = "memberAccess";
-
public static final String methodAccess = "methodAccess";
public static final String methodName = "methodName";
public static final String methodReturnType = "methodReturnType";
public static final String methodParameters = "methodParameters";
-
public static final String fieldAccess = "fieldAccess";
public static final String fieldName = "fieldName";
public static final String fieldType = "fieldType";
-
- // Default values for the optional entries. The defaults should be chosen such that they do
- // not coincide with any actual valid value. E.g., the empty string in place of a name or type.
- // These must be 1:1 with the value defined on the actual annotation definition.
- public static final String classNameDefault = "";
- public static final Class<?> classConstantDefault = Object.class;
-
- public static final String extendsClassNameDefault = "";
- public static final Class<?> extendsClassConstantDefault = Object.class;
-
- public static final String methodNameDefaultValue = "";
- public static final String methodReturnTypeDefaultValue = "";
-
- @SuppressWarnings("MutablePublicArray")
- public static final String[] methodParametersDefaultValue = new String[] {""};
-
- public static final String fieldNameDefaultValue = "";
- public static final String fieldTypeDefaultValue = "";
}
public static final class Binding {
- public static final Class<KeepBinding> CLASS = KeepBinding.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String SIMPLE_NAME = "KeepBinding";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/KeepBinding;";
public static final String bindingName = "bindingName";
}
public static final class Condition {
- public static final Class<KeepCondition> CLASS = KeepCondition.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String SIMPLE_NAME = "KeepCondition";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/KeepCondition;";
}
public static final class Target {
- public static final Class<KeepTarget> CLASS = KeepTarget.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
-
+ public static final String SIMPLE_NAME = "KeepTarget";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/KeepTarget;";
public static final String kind = "kind";
public static final String allow = "allow";
public static final String disallow = "disallow";
}
public static final class Kind {
- public static final Class<KeepItemKind> CLASS = KeepItemKind.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
-
- public static final String DEFAULT = "DEFAULT";
+ public static final String SIMPLE_NAME = "KeepItemKind";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/KeepItemKind;";
public static final String ONLY_CLASS = "ONLY_CLASS";
public static final String ONLY_MEMBERS = "ONLY_MEMBERS";
public static final String CLASS_AND_MEMBERS = "CLASS_AND_MEMBERS";
}
public static final class Option {
- public static final Class<KeepOption> CLASS = KeepOption.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
-
+ public static final String SIMPLE_NAME = "KeepOption";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/KeepOption;";
public static final String SHRINKING = "SHRINKING";
- public static final String OBFUSCATION = "OBFUSCATION";
public static final String OPTIMIZATION = "OPTIMIZATION";
+ public static final String OBFUSCATION = "OBFUSCATION";
public static final String ACCESS_MODIFICATION = "ACCESS_MODIFICATION";
public static final String ANNOTATION_REMOVAL = "ANNOTATION_REMOVAL";
}
public static final class MemberAccess {
- public static final Class<MemberAccessFlags> CLASS = MemberAccessFlags.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
-
+ public static final String SIMPLE_NAME = "MemberAccessFlags";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/MemberAccessFlags;";
public static final String NEGATION_PREFIX = "NON_";
-
public static final String PUBLIC = "PUBLIC";
public static final String PROTECTED = "PROTECTED";
public static final String PACKAGE_PRIVATE = "PACKAGE_PRIVATE";
public static final String PRIVATE = "PRIVATE";
-
public static final String STATIC = "STATIC";
public static final String FINAL = "FINAL";
public static final String SYNTHETIC = "SYNTHETIC";
}
public static final class MethodAccess {
- public static final Class<MethodAccessFlags> CLASS = MethodAccessFlags.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
-
+ public static final String SIMPLE_NAME = "MethodAccessFlags";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/MethodAccessFlags;";
public static final String SYNCHRONIZED = "SYNCHRONIZED";
public static final String BRIDGE = "BRIDGE";
public static final String NATIVE = "NATIVE";
@@ -214,9 +161,9 @@
}
public static final class FieldAccess {
- public static final Class<FieldAccessFlags> CLASS = FieldAccessFlags.class;
- public static final String DESCRIPTOR = getDescriptor(CLASS);
-
+ public static final String SIMPLE_NAME = "FieldAccessFlags";
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/FieldAccessFlags;";
public static final String VOLATILE = "VOLATILE";
public static final String TRANSIENT = "TRANSIENT";
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindingReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindingReference.java
new file mode 100644
index 0000000..6097e39
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindingReference.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
+
+public abstract class KeepBindingReference {
+
+ public static KeepClassBindingReference forClass(KeepBindingSymbol name) {
+ return new KeepClassBindingReference(name);
+ }
+
+ public static KeepMemberBindingReference forMember(KeepBindingSymbol name) {
+ return new KeepMemberBindingReference(name);
+ }
+
+ public static KeepBindingReference forItem(KeepBindingSymbol name, KeepItemPattern item) {
+ return item.isClassItemPattern() ? forClass(name) : forMember(name);
+ }
+
+ private final KeepBindingSymbol name;
+
+ KeepBindingReference(KeepBindingSymbol name) {
+ this.name = name;
+ }
+
+ public abstract KeepItemReference toItemReference();
+
+ public KeepBindingSymbol getName() {
+ return name;
+ }
+
+ public final boolean isClassType() {
+ return asClassBindingReference() != null;
+ }
+
+ public final boolean isMemberType() {
+ return asMemberBindingReference() != null;
+ }
+
+ public KeepClassBindingReference asClassBindingReference() {
+ return null;
+ }
+
+ public KeepMemberBindingReference asMemberBindingReference() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return name.toString();
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java
index a3ba447..8b759ee 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java
@@ -18,9 +18,9 @@
private static final KeepBindings NONE_INSTANCE = new KeepBindings(Collections.emptyMap());
- private final Map<BindingSymbol, Binding> bindings;
+ private final Map<KeepBindingSymbol, Binding> bindings;
- private KeepBindings(Map<BindingSymbol, Binding> bindings) {
+ private KeepBindings(Map<KeepBindingSymbol, Binding> bindings) {
assert bindings != null;
this.bindings = bindings;
}
@@ -29,7 +29,11 @@
return NONE_INSTANCE;
}
- public Binding get(BindingSymbol bindingReference) {
+ public Binding get(KeepBindingReference bindingReference) {
+ return bindings.get(bindingReference.getName());
+ }
+
+ public Binding get(KeepBindingSymbol bindingReference) {
return bindings.get(bindingReference);
}
@@ -41,7 +45,7 @@
return bindings.isEmpty();
}
- public void forEach(BiConsumer<BindingSymbol, KeepItemPattern> fn) {
+ public void forEach(BiConsumer<KeepBindingSymbol, KeepItemPattern> fn) {
bindings.forEach((name, binding) -> fn.accept(name, binding.getItem()));
}
@@ -125,11 +129,11 @@
}
}
- public static class BindingSymbol {
+ public static class KeepBindingSymbol {
private final String hint;
private String suffix = "";
- public BindingSymbol(String hint) {
+ public KeepBindingSymbol(String hint) {
this.hint = hint;
}
@@ -155,24 +159,24 @@
public static class Builder {
- private final Map<String, BindingSymbol> reserved = new HashMap<>();
- private final Map<BindingSymbol, KeepItemPattern> bindings = new IdentityHashMap<>();
+ private final Map<String, KeepBindingSymbol> reserved = new HashMap<>();
+ private final Map<KeepBindingSymbol, KeepItemPattern> bindings = new IdentityHashMap<>();
- public BindingSymbol generateFreshSymbol(String hint) {
+ public KeepBindingSymbol generateFreshSymbol(String hint) {
// Allocate a fresh non-forgeable symbol. The actual name is chosen at build time.
- return new BindingSymbol(hint);
+ return new KeepBindingSymbol(hint);
}
- public BindingSymbol create(String name) {
- BindingSymbol symbol = new BindingSymbol(name);
- BindingSymbol old = reserved.put(name, symbol);
+ public KeepBindingSymbol create(String name) {
+ KeepBindingSymbol symbol = new KeepBindingSymbol(name);
+ KeepBindingSymbol old = reserved.put(name, symbol);
if (old != null) {
throw new KeepEdgeException("Multiple bindings with name '" + name + "'");
}
return symbol;
}
- public Builder addBinding(BindingSymbol symbol, KeepItemPattern itemPattern) {
+ public Builder addBinding(KeepBindingSymbol symbol, KeepItemPattern itemPattern) {
if (symbol == null || itemPattern == null) {
throw new KeepEdgeException("Invalid binding of '" + symbol + "'");
}
@@ -183,25 +187,17 @@
return this;
}
- public BindingSymbol getClassBinding(BindingSymbol bindingSymbol) {
- KeepItemPattern pattern = bindings.get(bindingSymbol);
- if (pattern.isClassItemPattern()) {
- return bindingSymbol;
- }
- return pattern.getClassReference().asBindingReference();
- }
-
@SuppressWarnings("ReferenceEquality")
public KeepBindings build() {
if (bindings.isEmpty()) {
return NONE_INSTANCE;
}
- Map<BindingSymbol, Binding> definitions = new HashMap<>(bindings.size());
- for (BindingSymbol symbol : bindings.keySet()) {
+ Map<KeepBindingSymbol, Binding> definitions = new HashMap<>(bindings.size());
+ for (KeepBindingSymbol symbol : bindings.keySet()) {
// The reserved symbols are a subset of all symbols. Those that are not yet reserved denote
// symbols that must be "unique" in the set of symbols, but that do not have a specific
// name. Now that all symbols are known we can give each of these a unique name.
- BindingSymbol defined = reserved.get(symbol.toString());
+ KeepBindingSymbol defined = reserved.get(symbol.toString());
if (defined != symbol) {
// For each undefined symbol we try to use the "hint" as its name, if the name is already
// reserved for another symbol then we search for the first non-reserved name with an
@@ -218,18 +214,20 @@
return new KeepBindings(definitions);
}
- private Binding verifyAndCreateBinding(BindingSymbol bindingDefinitionSymbol) {
+ private Binding verifyAndCreateBinding(KeepBindingSymbol bindingDefinitionSymbol) {
KeepItemPattern pattern = bindings.get(bindingDefinitionSymbol);
- for (BindingSymbol bindingReference : pattern.getBindingReferences()) {
+ for (KeepBindingReference bindingReference : pattern.getBindingReferences()) {
// Currently, it is not possible to define mutually recursive items, so we only need
// to check against self.
- if (bindingReference.equals(bindingDefinitionSymbol)) {
+ if (bindingReference.getName().equals(bindingDefinitionSymbol)) {
throw new KeepEdgeException("Recursive binding for name '" + bindingReference + "'");
}
- if (!bindings.containsKey(bindingReference)) {
+ if (!bindings.containsKey(bindingReference.getName())) {
throw new KeepEdgeException(
- "Undefined binding for name '"
- + bindingReference
+ "Undefined binding for binding '"
+ + bindingReference.getName()
+ + "' or type '"
+ + (bindingReference.isClassType() ? "class" : "member")
+ "' referenced in binding of '"
+ bindingDefinitionSymbol
+ "'");
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassBindingReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassBindingReference.java
new file mode 100644
index 0000000..19fc080
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassBindingReference.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
+
+public final class KeepClassBindingReference extends KeepBindingReference {
+
+ KeepClassBindingReference(KeepBindingSymbol name) {
+ super(name);
+ }
+
+ @Override
+ public KeepClassBindingReference asClassBindingReference() {
+ return this;
+ }
+
+ public KeepClassItemReference toClassItemReference() {
+ return KeepClassItemReference.fromBindingReference(this);
+ }
+
+ @Override
+ public KeepItemReference toItemReference() {
+ return toClassItemReference();
+ }
+
+ @Override
+ public String toString() {
+ return "class-ref(" + super.toString() + ")";
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
new file mode 100644
index 0000000..5c63faf
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemPattern.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+public class KeepClassItemPattern extends KeepItemPattern {
+
+ public static KeepClassItemPattern any() {
+ return builder().build();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private KeepQualifiedClassNamePattern classNamePattern = KeepQualifiedClassNamePattern.any();
+ private KeepInstanceOfPattern instanceOfPattern = KeepInstanceOfPattern.any();
+
+ private Builder() {}
+
+ public Builder copyFrom(KeepClassItemPattern pattern) {
+ return setClassNamePattern(pattern.getClassNamePattern())
+ .setInstanceOfPattern(pattern.getInstanceOfPattern());
+ }
+
+ public Builder setClassNamePattern(KeepQualifiedClassNamePattern classNamePattern) {
+ this.classNamePattern = classNamePattern;
+ return this;
+ }
+
+ public Builder setInstanceOfPattern(KeepInstanceOfPattern instanceOfPattern) {
+ this.instanceOfPattern = instanceOfPattern;
+ return this;
+ }
+
+ public KeepClassItemPattern build() {
+ return new KeepClassItemPattern(classNamePattern, instanceOfPattern);
+ }
+ }
+
+ private final KeepQualifiedClassNamePattern classNamePattern;
+ private final KeepInstanceOfPattern instanceOfPattern;
+
+ public KeepClassItemPattern(
+ KeepQualifiedClassNamePattern classNamePattern, KeepInstanceOfPattern instanceOfPattern) {
+ assert classNamePattern != null;
+ assert instanceOfPattern != null;
+ this.classNamePattern = classNamePattern;
+ this.instanceOfPattern = instanceOfPattern;
+ }
+
+ @Override
+ public KeepClassItemPattern asClassItemPattern() {
+ return this;
+ }
+
+ @Override
+ public KeepItemReference toItemReference() {
+ return toClassItemReference();
+ }
+
+ public final KeepClassItemReference toClassItemReference() {
+ return KeepClassItemReference.fromClassItemPattern(this);
+ }
+
+ @Override
+ public Collection<KeepBindingReference> getBindingReferences() {
+ return Collections.emptyList();
+ }
+
+ public KeepQualifiedClassNamePattern getClassNamePattern() {
+ return classNamePattern;
+ }
+
+ public KeepInstanceOfPattern getInstanceOfPattern() {
+ return instanceOfPattern;
+ }
+
+ public boolean isAny() {
+ return classNamePattern.isAny() && instanceOfPattern.isAny();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof KeepClassItemPattern)) {
+ return false;
+ }
+ KeepClassItemPattern that = (KeepClassItemPattern) obj;
+ return classNamePattern.equals(that.classNamePattern)
+ && instanceOfPattern.equals(that.instanceOfPattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(classNamePattern, instanceOfPattern);
+ }
+
+ @Override
+ public String toString() {
+ return "KeepClassItemPattern"
+ + "{ class="
+ + classNamePattern
+ + ", instance-of="
+ + instanceOfPattern
+ + '}';
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemReference.java
new file mode 100644
index 0000000..5a3c7d5
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassItemReference.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public abstract class KeepClassItemReference extends KeepItemReference {
+
+ public static KeepClassItemReference fromBindingReference(
+ KeepClassBindingReference bindingReference) {
+ return new ClassBinding(bindingReference);
+ }
+
+ public static KeepClassItemReference fromClassItemPattern(KeepClassItemPattern classItemPattern) {
+ return new ClassItem(classItemPattern);
+ }
+
+ public static KeepClassItemReference fromClassNamePattern(
+ KeepQualifiedClassNamePattern classNamePattern) {
+ return new ClassItem(
+ KeepClassItemPattern.builder().setClassNamePattern(classNamePattern).build());
+ }
+
+ @Override
+ public final KeepClassItemReference asClassItemReference() {
+ return this;
+ }
+
+ public abstract Collection<KeepBindingReference> getBindingReferences();
+
+ private static class ClassBinding extends KeepClassItemReference {
+ private final KeepClassBindingReference bindingReference;
+
+ private ClassBinding(KeepClassBindingReference bindingReference) {
+ assert bindingReference != null;
+ this.bindingReference = bindingReference;
+ }
+
+ @Override
+ public KeepClassBindingReference asBindingReference() {
+ return bindingReference;
+ }
+
+ @Override
+ public Collection<KeepBindingReference> getBindingReferences() {
+ return Collections.singletonList(bindingReference);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ClassBinding)) {
+ return false;
+ }
+ ClassBinding that = (ClassBinding) o;
+ return bindingReference.equals(that.bindingReference);
+ }
+
+ @Override
+ public int hashCode() {
+ return bindingReference.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return bindingReference.toString();
+ }
+ }
+
+ private static class ClassItem extends KeepClassItemReference {
+ private final KeepClassItemPattern classItemPattern;
+
+ private ClassItem(KeepClassItemPattern classItemPattern) {
+ assert classItemPattern != null;
+ this.classItemPattern = classItemPattern;
+ }
+
+ @Override
+ public KeepItemPattern asItemPattern() {
+ return classItemPattern;
+ }
+
+ @Override
+ public KeepClassItemPattern asClassItemPattern() {
+ return classItemPattern;
+ }
+
+ @Override
+ public Collection<KeepBindingReference> getBindingReferences() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ClassItem)) {
+ return false;
+ }
+ ClassItem someItem = (ClassItem) o;
+ return classItemPattern.equals(someItem.classItemPattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return classItemPattern.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return classItemPattern.toString();
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassReference.java
deleted file mode 100644
index 4f0f3f3..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassReference.java
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) 2022, 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.keepanno.ast;
-
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.function.Predicate;
-
-public abstract class KeepClassReference {
-
- public static KeepClassReference fromBindingReference(BindingSymbol bindingReference) {
- return new BindingReference(bindingReference);
- }
-
- public static KeepClassReference fromClassNamePattern(
- KeepQualifiedClassNamePattern classNamePattern) {
- return new SomeItem(classNamePattern);
- }
-
- public boolean isBindingReference() {
- return asBindingReference() != null;
- }
-
- public boolean isClassNamePattern() {
- return asClassNamePattern() != null;
- }
-
- public BindingSymbol asBindingReference() {
- return null;
- }
-
- public KeepQualifiedClassNamePattern asClassNamePattern() {
- return null;
- }
-
- public abstract Collection<BindingSymbol> getBindingReferences();
-
- public boolean isAny(Predicate<BindingSymbol> onReference) {
- return isBindingReference()
- ? onReference.test(asBindingReference())
- : asClassNamePattern().isAny();
- }
-
- private static class BindingReference extends KeepClassReference {
- private final BindingSymbol bindingReference;
-
- private BindingReference(BindingSymbol bindingReference) {
- assert bindingReference != null;
- this.bindingReference = bindingReference;
- }
-
- @Override
- public BindingSymbol asBindingReference() {
- return bindingReference;
- }
-
- @Override
- public Collection<BindingSymbol> getBindingReferences() {
- return Collections.singletonList(bindingReference);
- }
-
- @Override
- @SuppressWarnings("EqualsGetClass")
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- BindingReference that = (BindingReference) o;
- return bindingReference.equals(that.bindingReference);
- }
-
- @Override
- public int hashCode() {
- return bindingReference.hashCode();
- }
-
- @Override
- public String toString() {
- return bindingReference.toString();
- }
- }
-
- private static class SomeItem extends KeepClassReference {
- private final KeepQualifiedClassNamePattern classNamePattern;
-
- private SomeItem(KeepQualifiedClassNamePattern classNamePattern) {
- assert classNamePattern != null;
- this.classNamePattern = classNamePattern;
- }
-
- @Override
- public KeepQualifiedClassNamePattern asClassNamePattern() {
- return classNamePattern;
- }
-
- @Override
- public Collection<BindingSymbol> getBindingReferences() {
- return Collections.emptyList();
- }
-
- @Override
- @SuppressWarnings("EqualsGetClass")
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- SomeItem someItem = (SomeItem) o;
- return classNamePattern.equals(someItem.classNamePattern);
- }
-
- @Override
- public int hashCode() {
- return classNamePattern.hashCode();
- }
-
- @Override
- public String toString() {
- return classNamePattern.toString();
- }
- }
-}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
index de4b742..e9f7be2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
@@ -29,7 +29,7 @@
}
public Builder setItemPattern(KeepItemPattern itemPattern) {
- return setItemReference(KeepItemReference.fromItemPattern(itemPattern));
+ return setItemReference(itemPattern.toItemReference());
}
public KeepCondition build() {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 3b11a28..34dcab5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -22,9 +22,11 @@
* CONTEXT ::= class-descriptor | method-descriptor | field-descriptor
* DESCRIPTION ::= string-content
*
- * BINDINGS ::= (BINDING_NAME = ITEM_PATTERN)*
- * BINDING_NAME ::= string-content
- * BINDING_REFERENCE ::= BINDING_NAME
+ * BINDINGS ::= (BINDING_SYMBOL = ITEM_PATTERN)*
+ * BINDING_SYMBOL ::= string-content
+ * BINDING_REFERENCE ::= CLASS_BINDING_REFERENCE | MEMBER_BINDING_REFERENCE
+ * CLASS_BINDING_REFERENCE ::= class BINDING_SYMBOL
+ * MEMBER_BINDING_REFERENCE ::= member BINDING_SYMBOL
*
* PRECONDITIONS ::= always | CONDITION+
* CONDITION ::= ITEM_REFERENCE
@@ -34,10 +36,13 @@
* OPTIONS ::= keep-all | OPTION+
* OPTION ::= shrinking | optimizing | obfuscating | access-modification | annotation-removal
*
- * ITEM_REFERENCE ::= BINDING_REFERENCE | ITEM_PATTERN
- * CLASS_REFERENCE ::= BINDING_REFERENCE | QUALIFIED_CLASS_NAME_PATTERN
+ * ITEM_REFERENCE ::= CLASS_ITEM_REFERENCE | MEMBER_ITEM_REFERENCE
+ * CLASS_ITEM_REFERENCE ::= CLASS_BINDING_REFERENCE | CLASS_ITEM_PATTERN
+ * MEMBER_ITEM_REFERENCE ::= MEMBER_BINDING_REFERENCE | MEMBER_ITEM_PATTERN
*
- * ITEM_PATTERN ::= class CLASS_REFERENCE extends EXTENDS_PATTERN { MEMBER_PATTERN }
+ * ITEM_PATTERN ::= CLASS_ITEM_PATTERN | MEMBER_ITEM_PATTERN
+ * CLASS_ITEM_PATTERN ::= class QUALIFIED_CLASS_NAME_PATTERN instance-of INSTANCE_OF_PATTERN
+ * MEMBER_ITEM_PATTERN ::= CLASS_ITEM_REFERENCE { MEMBER_PATTERN }
*
* TYPE_PATTERN ::= any | exact type-descriptor
* PACKAGE_PATTERN ::= any | exact package-name
@@ -45,11 +50,12 @@
* QUALIFIED_CLASS_NAME_PATTERN
* ::= any
* | PACKAGE_PATTERN UNQUALIFIED_CLASS_NAME_PATTERN
- * | BINDING_REFERENCE
*
* UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name
*
- * EXTENDS_PATTERN ::= any | QUALIFIED_CLASS_NAME_PATTERN
+ * INSTANCE_OF_PATTERN ::= INSTANCE_OF_PATTERN_INCLUSIVE | INSTANCE_OF_PATTERN_EXCLUSIVE
+ * INSTANCE_OF_PATTERN_INCLUSIVE ::= QUALIFIED_CLASS_NAME_PATTERN
+ * INSTANCE_OF_PATTERN_EXCLUSIVE ::= QUALIFIED_CLASS_NAME_PATTERN
*
* MEMBER_PATTERN ::= none | all | FIELD_PATTERN | METHOD_PATTERN
*
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
deleted file mode 100644
index c6b3db3..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2022, 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.keepanno.ast;
-
-/** Pattern for matching the "extends" or "implements" clause of a class. */
-public abstract class KeepExtendsPattern {
-
- public static KeepExtendsPattern any() {
- return Some.getAnyInstance();
- }
-
- public static class Builder {
-
- private KeepExtendsPattern pattern = KeepExtendsPattern.any();
-
- private Builder() {}
-
- public Builder classPattern(KeepQualifiedClassNamePattern pattern) {
- this.pattern = new Some(pattern);
- return this;
- }
-
- public KeepExtendsPattern build() {
- return pattern;
- }
- }
-
- private static class Some extends KeepExtendsPattern {
-
- private static final KeepExtendsPattern ANY_INSTANCE =
- new Some(KeepQualifiedClassNamePattern.any());
-
- private static KeepExtendsPattern getAnyInstance() {
- return ANY_INSTANCE;
- }
-
- private final KeepQualifiedClassNamePattern pattern;
-
- public Some(KeepQualifiedClassNamePattern pattern) {
- assert pattern != null;
- this.pattern = pattern;
- }
-
- @Override
- public boolean isAny() {
- return pattern.isAny();
- }
-
- @Override
- public KeepQualifiedClassNamePattern asClassNamePattern() {
- return pattern;
- }
-
- @Override
- @SuppressWarnings("EqualsGetClass")
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Some that = (Some) o;
- return pattern.equals(that.pattern);
- }
-
- @Override
- public int hashCode() {
- return pattern.hashCode();
- }
-
- @Override
- public String toString() {
- return pattern.toString();
- }
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- private KeepExtendsPattern() {}
-
- public abstract boolean isAny();
-
- public abstract KeepQualifiedClassNamePattern asClassNamePattern();
-}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepInstanceOfPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepInstanceOfPattern.java
new file mode 100644
index 0000000..79e2f43
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepInstanceOfPattern.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+/** Pattern for matching the instance-of properties of a class. */
+public abstract class KeepInstanceOfPattern {
+
+ public static KeepInstanceOfPattern any() {
+ return Some.getAnyInstance();
+ }
+
+ public static class Builder {
+
+ private KeepQualifiedClassNamePattern namePattern = KeepQualifiedClassNamePattern.any();
+ private boolean isInclusive = true;
+
+ private Builder() {}
+
+ public Builder classPattern(KeepQualifiedClassNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return this;
+ }
+
+ public Builder setInclusive(boolean isInclusive) {
+ this.isInclusive = isInclusive;
+ return this;
+ }
+
+ public KeepInstanceOfPattern build() {
+ if (namePattern.isAny()) {
+ if (!isInclusive) {
+ throw new KeepEdgeException(
+ "Invalid instance-of pattern matching any class exclusive. "
+ + "This pattern matches nothing.");
+ }
+ return any();
+ }
+ return new Some(namePattern, isInclusive);
+ }
+ }
+
+ private static class Some extends KeepInstanceOfPattern {
+
+ private static final KeepInstanceOfPattern ANY_INSTANCE =
+ new Some(KeepQualifiedClassNamePattern.any(), true);
+
+ private static KeepInstanceOfPattern getAnyInstance() {
+ return ANY_INSTANCE;
+ }
+
+ private final KeepQualifiedClassNamePattern namePattern;
+ private final boolean isInclusive;
+
+ public Some(KeepQualifiedClassNamePattern namePattern, boolean isInclusive) {
+ assert namePattern != null;
+ this.namePattern = namePattern;
+ this.isInclusive = isInclusive;
+ }
+
+ @Override
+ public boolean isAny() {
+ return namePattern.isAny();
+ }
+
+ @Override
+ public boolean isInclusive() {
+ return isInclusive;
+ }
+
+ @Override
+ public KeepQualifiedClassNamePattern getClassNamePattern() {
+ return namePattern;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Some)) {
+ return false;
+ }
+ Some that = (Some) o;
+ return namePattern.equals(that.namePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return namePattern.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ String nameString = namePattern.toString();
+ return isInclusive ? nameString : ("excl(" + nameString + ")");
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private KeepInstanceOfPattern() {}
+
+ public abstract boolean isAny();
+
+ public abstract KeepQualifiedClassNamePattern getClassNamePattern();
+
+ public abstract boolean isInclusive();
+
+ public final boolean isExclusive() {
+ return !isInclusive();
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
index 7634c68..c88c7c4 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -3,9 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
import java.util.Collection;
-import java.util.Objects;
/**
* A pattern for matching items in the program.
@@ -17,135 +15,34 @@
* classes or it is a pattern on members. The distinction is defined by having a "none" member
* pattern.
*/
-public class KeepItemPattern {
+public abstract class KeepItemPattern {
public static KeepItemPattern anyClass() {
- return builder().setMemberPattern(KeepMemberPattern.none()).build();
+ return KeepClassItemPattern.any();
}
public static KeepItemPattern anyMember() {
- return builder().setMemberPattern(KeepMemberPattern.allMembers()).build();
- }
-
- public static Builder builder() {
- return new Builder();
+ return KeepMemberItemPattern.any();
}
public boolean isClassItemPattern() {
- return memberPattern.isNone();
+ return asClassItemPattern() != null;
}
public boolean isMemberItemPattern() {
- return !isClassItemPattern();
+ return asMemberItemPattern() != null;
}
- public static class Builder {
-
- private KeepClassReference classReference =
- KeepClassReference.fromClassNamePattern(KeepQualifiedClassNamePattern.any());
- private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any();
- private KeepMemberPattern memberPattern = KeepMemberPattern.none();
-
- private Builder() {}
-
- public Builder copyFrom(KeepItemPattern pattern) {
- return setClassReference(pattern.getClassReference())
- .setExtendsPattern(pattern.getExtendsPattern())
- .setMemberPattern(pattern.getMemberPattern());
- }
-
- public Builder setClassReference(KeepClassReference classReference) {
- this.classReference = classReference;
- return this;
- }
-
- public Builder setClassPattern(KeepQualifiedClassNamePattern qualifiedClassNamePattern) {
- return setClassReference(KeepClassReference.fromClassNamePattern(qualifiedClassNamePattern));
- }
-
- public Builder setExtendsPattern(KeepExtendsPattern extendsPattern) {
- this.extendsPattern = extendsPattern;
- return this;
- }
-
- public Builder setMemberPattern(KeepMemberPattern memberPattern) {
- this.memberPattern = memberPattern;
- return this;
- }
-
- public KeepItemPattern build() {
- return new KeepItemPattern(classReference, extendsPattern, memberPattern);
- }
+ public KeepClassItemPattern asClassItemPattern() {
+ return null;
}
- private final KeepClassReference classReference;
- private final KeepExtendsPattern extendsPattern;
- private final KeepMemberPattern memberPattern;
- // TODO: class annotations
-
- private KeepItemPattern(
- KeepClassReference classReference,
- KeepExtendsPattern extendsPattern,
- KeepMemberPattern memberPattern) {
- assert classReference != null;
- assert extendsPattern != null;
- assert memberPattern != null;
- this.classReference = classReference;
- this.extendsPattern = extendsPattern;
- this.memberPattern = memberPattern;
+ public KeepMemberItemPattern asMemberItemPattern() {
+ return null;
}
- public KeepClassReference getClassReference() {
- return classReference;
- }
+ public abstract Collection<KeepBindingReference> getBindingReferences();
- public KeepExtendsPattern getExtendsPattern() {
- return extendsPattern;
- }
-
- public KeepMemberPattern getMemberPattern() {
- return memberPattern;
- }
-
- public Collection<BindingSymbol> getBindingReferences() {
- return classReference.getBindingReferences();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof KeepItemPattern)) {
- return false;
- }
- KeepItemPattern that = (KeepItemPattern) obj;
- return classReference.equals(that.classReference)
- && extendsPattern.equals(that.extendsPattern)
- && memberPattern.equals(that.memberPattern);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(classReference, extendsPattern, memberPattern);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- if (isClassItemPattern()) {
- builder.append("KeepClassPattern");
- } else {
- assert isMemberItemPattern();
- builder.append("KeepMemberPattern");
- }
- builder.append("{ class=").append(classReference);
- if (!extendsPattern.isAny()) {
- builder.append(", extends=").append(extendsPattern);
- }
- if (!memberPattern.isNone()) {
- builder.append(", members=").append(memberPattern);
- }
- return builder.append('}').toString();
- }
+ public abstract KeepItemReference toItemReference();
}
+
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java
index 21bb63c..2883d5c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java
@@ -3,27 +3,50 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
-
+/**
+ * A reference to an item pattern.
+ *
+ * <p>A reference can either be a binding-reference to an item pattern or the item pattern itself.
+ */
public abstract class KeepItemReference {
- public static KeepItemReference fromBindingReference(BindingSymbol bindingReference) {
- return new BindingReference(bindingReference);
+ public final boolean isClassItemReference() {
+ return asClassItemReference() != null;
}
- public static KeepItemReference fromItemPattern(KeepItemPattern itemPattern) {
- return new SomeItem(itemPattern);
+ public final boolean isMemberItemReference() {
+ return asMemberItemReference() != null;
}
- public boolean isBindingReference() {
+ public KeepClassItemReference asClassItemReference() {
+ return null;
+ }
+
+ public KeepMemberItemReference asMemberItemReference() {
+ return null;
+ }
+
+ // Helpers below.
+
+ /* Returns true if the reference is a binding to a class or member. */
+ public final boolean isBindingReference() {
return asBindingReference() != null;
}
- public boolean isItemPattern() {
+ /* Returns true if the reference is an item pattern for a class or member. */
+ public final boolean isItemPattern() {
return asItemPattern() != null;
}
- public BindingSymbol asBindingReference() {
+ public final boolean isClassItemPattern() {
+ return asClassItemPattern() != null;
+ }
+
+ public final boolean isMemberItemPattern() {
+ return asMemberItemPattern() != null;
+ }
+
+ public KeepBindingReference asBindingReference() {
return null;
}
@@ -31,89 +54,11 @@
return null;
}
- public abstract KeepItemPattern lookupItemPattern(KeepBindings bindings);
-
- private static class BindingReference extends KeepItemReference {
- private final BindingSymbol bindingReference;
-
- private BindingReference(BindingSymbol bindingReference) {
- assert bindingReference != null;
- this.bindingReference = bindingReference;
- }
-
- @Override
- public BindingSymbol asBindingReference() {
- return bindingReference;
- }
-
- @Override
- public KeepItemPattern lookupItemPattern(KeepBindings bindings) {
- return bindings.get(bindingReference).getItem();
- }
-
- @Override
- @SuppressWarnings("EqualsGetClass")
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- BindingReference that = (BindingReference) o;
- return bindingReference.equals(that.bindingReference);
- }
-
- @Override
- public int hashCode() {
- return bindingReference.hashCode();
- }
-
- @Override
- public String toString() {
- return "reference='" + bindingReference + "'";
- }
+ public KeepClassItemPattern asClassItemPattern() {
+ return null;
}
- private static class SomeItem extends KeepItemReference {
- private final KeepItemPattern itemPattern;
-
- private SomeItem(KeepItemPattern itemPattern) {
- assert itemPattern != null;
- this.itemPattern = itemPattern;
- }
-
- @Override
- public KeepItemPattern asItemPattern() {
- return itemPattern;
- }
-
- @Override
- public KeepItemPattern lookupItemPattern(KeepBindings bindings) {
- return asItemPattern();
- }
-
- @Override
- @SuppressWarnings("EqualsGetClass")
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- SomeItem someItem = (SomeItem) o;
- return itemPattern.equals(someItem.itemPattern);
- }
-
- @Override
- public int hashCode() {
- return itemPattern.hashCode();
- }
-
- @Override
- public String toString() {
- return itemPattern.toString();
- }
+ public KeepMemberItemPattern asMemberItemPattern() {
+ return null;
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberBindingReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberBindingReference.java
new file mode 100644
index 0000000..443ea55
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberBindingReference.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
+
+public final class KeepMemberBindingReference extends KeepBindingReference {
+
+ KeepMemberBindingReference(KeepBindingSymbol name) {
+ super(name);
+ }
+
+ @Override
+ public KeepMemberBindingReference asMemberBindingReference() {
+ return this;
+ }
+
+ @Override
+ public KeepItemReference toItemReference() {
+ return KeepMemberItemReference.fromBindingReference(this);
+ }
+
+ @Override
+ public String toString() {
+ return "member-ref(" + super.toString() + ")";
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemPattern.java
new file mode 100644
index 0000000..5184528
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemPattern.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+import java.util.Collection;
+import java.util.Objects;
+
+public class KeepMemberItemPattern extends KeepItemPattern {
+
+ public static KeepMemberItemPattern any() {
+ return builder().build();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private KeepClassItemReference classReference =
+ KeepClassItemPattern.any().toClassItemReference();
+ private KeepMemberPattern memberPattern = KeepMemberPattern.allMembers();
+
+ private Builder() {}
+
+ public Builder copyFrom(KeepMemberItemPattern pattern) {
+ return setClassReference(pattern.getClassReference())
+ .setMemberPattern(pattern.getMemberPattern());
+ }
+
+ public Builder setClassReference(KeepClassItemReference classReference) {
+ this.classReference = classReference;
+ return this;
+ }
+
+ public Builder setMemberPattern(KeepMemberPattern memberPattern) {
+ this.memberPattern = memberPattern;
+ return this;
+ }
+
+ public KeepMemberItemPattern build() {
+ return new KeepMemberItemPattern(classReference, memberPattern);
+ }
+ }
+
+ private final KeepClassItemReference classReference;
+ private final KeepMemberPattern memberPattern;
+
+ private KeepMemberItemPattern(
+ KeepClassItemReference classReference, KeepMemberPattern memberPattern) {
+ assert classReference != null;
+ assert memberPattern != null;
+ this.classReference = classReference;
+ this.memberPattern = memberPattern;
+ }
+
+ @Override
+ public KeepMemberItemPattern asMemberItemPattern() {
+ return this;
+ }
+
+ @Override
+ public KeepItemReference toItemReference() {
+ return KeepMemberItemReference.fromMemberItemPattern(this);
+ }
+
+ public KeepClassItemReference getClassReference() {
+ return classReference;
+ }
+
+ public KeepMemberPattern getMemberPattern() {
+ return memberPattern;
+ }
+
+ public Collection<KeepBindingReference> getBindingReferences() {
+ return classReference.getBindingReferences();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof KeepMemberItemPattern)) {
+ return false;
+ }
+ KeepMemberItemPattern that = (KeepMemberItemPattern) obj;
+ return classReference.equals(that.classReference) && memberPattern.equals(that.memberPattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(classReference, memberPattern);
+ }
+
+ @Override
+ public String toString() {
+ return "KeepMemberItemPattern"
+ + "{ class="
+ + classReference
+ + ", members="
+ + memberPattern
+ + '}';
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemReference.java
new file mode 100644
index 0000000..d2ab55d
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberItemReference.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+public abstract class KeepMemberItemReference extends KeepItemReference {
+
+ public static KeepMemberItemReference fromBindingReference(
+ KeepMemberBindingReference bindingReference) {
+ return new MemberBinding(bindingReference);
+ }
+
+ public static KeepMemberItemReference fromMemberItemPattern(KeepMemberItemPattern itemPattern) {
+ return new MemberItem(itemPattern);
+ }
+
+ @Override
+ public final KeepMemberItemReference asMemberItemReference() {
+ return this;
+ }
+
+ private static final class MemberBinding extends KeepMemberItemReference {
+
+ private final KeepMemberBindingReference bindingReference;
+
+ private MemberBinding(KeepMemberBindingReference bindingReference) {
+ this.bindingReference = bindingReference;
+ }
+
+ @Override
+ public KeepBindingReference asBindingReference() {
+ return bindingReference;
+ }
+
+ @Override
+ public String toString() {
+ return bindingReference.toString();
+ }
+ }
+
+ private static final class MemberItem extends KeepMemberItemReference {
+
+ private final KeepMemberItemPattern itemPattern;
+
+ public MemberItem(KeepMemberItemPattern itemPattern) {
+ this.itemPattern = itemPattern;
+ }
+
+ @Override
+ public KeepItemPattern asItemPattern() {
+ return itemPattern;
+ }
+
+ @Override
+ public KeepMemberItemPattern asMemberItemPattern() {
+ return itemPattern;
+ }
+
+ @Override
+ public String toString() {
+ return itemPattern.toString();
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
index c67fd9a..aec5fbf 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
@@ -6,6 +6,7 @@
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
public abstract class KeepMethodParametersPattern {
@@ -88,6 +89,13 @@
public int hashCode() {
return parameterPatterns.hashCode();
}
+
+ @Override
+ public String toString() {
+ return "("
+ + parameterPatterns.stream().map(Object::toString).collect(Collectors.joining(", "))
+ + ")";
+ }
}
private static class Any extends KeepMethodParametersPattern {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
index 4d450a2..1d8b32b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
@@ -20,7 +20,7 @@
}
public Builder setItemPattern(KeepItemPattern itemPattern) {
- return setItemReference(KeepItemReference.fromItemPattern(itemPattern));
+ return setItemReference(itemPattern.toItemReference());
}
public Builder setOptions(KeepOptions options) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
index 9bd2c09..baf9f6c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
@@ -3,16 +3,20 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.keeprules;
+import com.android.tools.r8.keepanno.ast.KeepBindingReference;
import com.android.tools.r8.keepanno.ast.KeepBindings;
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
-import com.android.tools.r8.keepanno.ast.KeepClassReference;
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
+import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemReference;
+import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepPreconditions;
import com.android.tools.r8.keepanno.ast.KeepTarget;
+import java.util.HashMap;
+import java.util.Map;
/**
* Normalize a keep edge with respect to its bindings. This will systematically introduce a binding
@@ -31,6 +35,7 @@
private final KeepEdge edge;
+ private final Map<KeepBindingSymbol, KeepItemPattern> normalizedUserBindings = new HashMap<>();
private final KeepBindings.Builder bindingsBuilder = KeepBindings.builder();
private final KeepPreconditions.Builder preconditionsBuilder = KeepPreconditions.builder();
private final KeepConsequences.Builder consequencesBuilder = KeepConsequences.builder();
@@ -43,7 +48,9 @@
edge.getBindings()
.forEach(
(name, pattern) -> {
- bindingsBuilder.addBinding(name, normalizeItemPattern(pattern));
+ KeepItemPattern normalizedItem = normalizeItemPattern(pattern);
+ bindingsBuilder.addBinding(name, normalizedItem);
+ normalizedUserBindings.put(name, normalizedItem);
});
// TODO(b/248408342): Normalize the preconditions by identifying vacuously true conditions.
edge.getPreconditions()
@@ -51,7 +58,7 @@
condition ->
preconditionsBuilder.addCondition(
KeepCondition.builder()
- .setItemReference(normalizeItem(condition.getItem()))
+ .setItemReference(normalizeItemReference(condition.getItem()))
.build()));
edge.getConsequences()
.forEachTarget(
@@ -59,7 +66,7 @@
consequencesBuilder.addTarget(
KeepTarget.builder()
.setOptions(target.getOptions())
- .setItemReference(normalizeItem(target.getItem()))
+ .setItemReference(normalizeItemReference(target.getItem()))
.build());
});
return KeepEdge.builder()
@@ -70,58 +77,55 @@
.build();
}
- private KeepItemReference normalizeItem(KeepItemReference item) {
+ private KeepBindingSymbol synthesizeFreshBindingSymbol(KeepItemPattern item) {
+ KeepBindingSymbol bindingName = bindingsBuilder.generateFreshSymbol(syntheticBindingPrefix);
+ bindingsBuilder.addBinding(bindingName, item);
+ return bindingName;
+ }
+
+ private KeepBindingReference synthesizeFreshBindingReference(KeepItemPattern item) {
+ KeepBindingSymbol bindingName = synthesizeFreshBindingSymbol(item);
+ return KeepBindingReference.forItem(bindingName, item);
+ }
+
+ private KeepItemReference normalizeItemReference(KeepItemReference item) {
if (item.isBindingReference()) {
+ KeepBindingReference bindingReference = item.asBindingReference();
+ if (bindingReference.isClassType()) {
+ // A class-type reference is allowed to reference a member-typed binding.
+ // In this case, the normalized reference is to the class of the member.
+ KeepItemPattern boundItemPattern = normalizedUserBindings.get(bindingReference.getName());
+ if (boundItemPattern.isMemberItemPattern()) {
+ return boundItemPattern.asMemberItemPattern().getClassReference();
+ }
+ }
return item;
}
- KeepItemPattern itemPattern = item.asItemPattern();
- if (itemPattern.isClassItemPattern() && itemPattern.getClassReference().isBindingReference()) {
- BindingSymbol classBinding =
- bindingsBuilder.getClassBinding(itemPattern.getClassReference().asBindingReference());
- return KeepItemReference.fromBindingReference(classBinding);
- }
- KeepItemPattern newItemPattern = normalizeItemPattern(itemPattern);
- BindingSymbol bindingName = bindingsBuilder.generateFreshSymbol(syntheticBindingPrefix);
- bindingsBuilder.addBinding(bindingName, newItemPattern);
- return KeepItemReference.fromBindingReference(bindingName);
+ KeepItemPattern newItemPattern = normalizeItemPattern(item.asItemPattern());
+ return synthesizeFreshBindingReference(newItemPattern).toItemReference();
}
private KeepItemPattern normalizeItemPattern(KeepItemPattern pattern) {
- // If the pattern is just a class pattern it is in normal form.
if (pattern.isClassItemPattern()) {
+ // If the pattern is just a class pattern it is in normal form.
return pattern;
}
- KeepClassReference bindingReference = bindingForClassItem(pattern);
- return getMemberItemPattern(pattern, bindingReference);
+ return normalizeMemberItemPattern(pattern.asMemberItemPattern());
}
- private KeepClassReference bindingForClassItem(KeepItemPattern pattern) {
- KeepClassReference classReference = pattern.getClassReference();
+ private KeepMemberItemPattern normalizeMemberItemPattern(
+ KeepMemberItemPattern memberItemPattern) {
+ KeepClassItemReference classReference = memberItemPattern.getClassReference();
if (classReference.isBindingReference()) {
// If the class is already defined via a binding then no need to introduce a new one and
- // change the item.
- return classReference;
+ // change the member item pattern.
+ return memberItemPattern;
}
- BindingSymbol bindingName = bindingsBuilder.generateFreshSymbol(syntheticBindingPrefix);
- KeepClassReference bindingReference = KeepClassReference.fromBindingReference(bindingName);
- KeepItemPattern newClassPattern = getClassItemPattern(pattern);
- bindingsBuilder.addBinding(bindingName, newClassPattern);
- return bindingReference;
- }
-
- public static KeepItemPattern getClassItemPattern(KeepItemPattern fromPattern) {
- return KeepItemPattern.builder()
- .setClassReference(fromPattern.getClassReference())
- .setExtendsPattern(fromPattern.getExtendsPattern())
- .build();
- }
-
- private KeepItemPattern getMemberItemPattern(
- KeepItemPattern fromPattern, KeepClassReference classReference) {
- assert fromPattern.isMemberItemPattern();
- return KeepItemPattern.builder()
- .setClassReference(classReference)
- .setMemberPattern(fromPattern.getMemberPattern())
+ KeepBindingSymbol bindingName =
+ synthesizeFreshBindingSymbol(classReference.asClassItemPattern());
+ return KeepMemberItemPattern.builder()
+ .copyFrom(memberItemPattern)
+ .setClassReference(KeepBindingReference.forClass(bindingName).toClassItemReference())
.build();
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 5ad2ae3..41f593e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -3,10 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.keeprules;
+import com.android.tools.r8.keepanno.ast.KeepBindingReference;
import com.android.tools.r8.keepanno.ast.KeepBindings;
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepCheck;
import com.android.tools.r8.keepanno.ast.KeepCheck.KeepCheckKind;
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.ast.KeepEdge;
@@ -14,8 +16,10 @@
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.keepanno.ast.KeepInstanceOfPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemReference;
+import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -71,19 +75,21 @@
boolean isRemovedPattern = check.getKind() == KeepCheckKind.REMOVED;
List<PgRule> rules = new ArrayList<>(isRemovedPattern ? 2 : 1);
Holder holder;
- Map<BindingSymbol, KeepMemberPattern> memberPatterns;
- List<BindingSymbol> targetMembers;
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns;
+ List<KeepBindingSymbol> targetMembers;
KeepBindings.Builder builder = KeepBindings.builder();
- BindingSymbol symbol = builder.generateFreshSymbol("CLASS");
+ KeepBindingSymbol symbol = builder.generateFreshSymbol("CLASS");
if (itemPattern.isClassItemPattern()) {
- builder.addBinding(symbol, check.getItemPattern());
+ builder.addBinding(symbol, itemPattern);
memberPatterns = Collections.emptyMap();
targetMembers = Collections.emptyList();
} else {
- builder.addBinding(symbol, KeepEdgeNormalizer.getClassItemPattern(check.getItemPattern()));
- KeepMemberPattern memberPattern = itemPattern.getMemberPattern();
+ KeepMemberItemPattern memberItemPattern = itemPattern.asMemberItemPattern();
+ assert memberItemPattern.getClassReference().isClassItemPattern();
+ builder.addBinding(symbol, memberItemPattern.getClassReference().asClassItemPattern());
+ KeepMemberPattern memberPattern = memberItemPattern.getMemberPattern();
// This does not actually allocate a binding as the mapping is maintained in 'memberPatterns'.
- BindingSymbol memberSymbol = new BindingSymbol("MEMBERS");
+ KeepBindingSymbol memberSymbol = new KeepBindingSymbol("MEMBERS");
memberPatterns = Collections.singletonMap(memberSymbol, memberPattern);
targetMembers = Collections.singletonList(memberSymbol);
}
@@ -104,7 +110,7 @@
if (itemPattern.isClassItemPattern()) {
// A check removal on a class means that the entire class is removed, thus soft-pin the
// class and *all* of its members.
- BindingSymbol memberSymbol = new BindingSymbol("MEMBERS");
+ KeepBindingSymbol memberSymbol = new KeepBindingSymbol("MEMBERS");
rules.add(
new PgUnconditionalRule(
check.getMetaInfo(),
@@ -129,36 +135,86 @@
return rules;
}
- /**
- * Utility to package up a class binding with its name and item pattern.
- *
- * <p>This is useful as the normalizer will have introduced class reference indirections so a
- * given item may need to.
- */
+ /** Utility to package up a class binding with its name and item pattern. */
public static class Holder {
- final KeepItemPattern itemPattern;
- final KeepQualifiedClassNamePattern namePattern;
+ private final KeepClassItemPattern itemPattern;
- static Holder create(BindingSymbol bindingName, KeepBindings bindings) {
- KeepItemPattern itemPattern = bindings.get(bindingName).getItem();
- assert itemPattern.isClassItemPattern();
- KeepQualifiedClassNamePattern namePattern = getClassNamePattern(itemPattern, bindings);
- return new Holder(itemPattern, namePattern);
+ static Holder create(KeepBindingSymbol bindingName, KeepBindings bindings) {
+ KeepClassItemPattern itemPattern = bindings.get(bindingName).getItem().asClassItemPattern();
+ return new Holder(itemPattern);
}
- private Holder(KeepItemPattern itemPattern, KeepQualifiedClassNamePattern namePattern) {
+ private Holder(KeepClassItemPattern itemPattern) {
+ assert itemPattern != null;
this.itemPattern = itemPattern;
- this.namePattern = namePattern;
+ }
+
+ public KeepClassItemPattern getClassItemPattern() {
+ return itemPattern;
+ }
+
+ public KeepQualifiedClassNamePattern getNamePattern() {
+ return getClassItemPattern().getClassNamePattern();
+ }
+
+ public void onTargetHolders(Consumer<Holder> fn) {
+ KeepInstanceOfPattern instanceOfPattern = itemPattern.getInstanceOfPattern();
+ if (instanceOfPattern.isAny()) {
+ // An any-pattern does not give rise to 'extends' and maps as is.
+ fn.accept(this);
+ return;
+ }
+ if (instanceOfPattern.isExclusive()) {
+ // An exclusive-pattern maps to the "extends" clause as is.
+ fn.accept(this);
+ return;
+ }
+ if (getNamePattern().isExact()) {
+ // This case is a pattern of "Foo instance-of Bar" and only makes sense if Foo==Bar.
+ // In any case we can conservatively cover this case by ignoring the instance-of clause.
+ Holder holderWithoutExtends =
+ new Holder(
+ KeepClassItemPattern.builder()
+ .copyFrom(itemPattern)
+ .setInstanceOfPattern(KeepInstanceOfPattern.any())
+ .build());
+ fn.accept(holderWithoutExtends);
+ return;
+ }
+ if (getNamePattern().isAny()) {
+ // This case is a pattern of "* instance-of Bar" and we match that as two rules, one of
+ // which is just the rule on the instance-of moved to the class name.
+ Holder holderWithInstanceOfAsName =
+ new Holder(
+ KeepClassItemPattern.builder()
+ .copyFrom(itemPattern)
+ .setClassNamePattern(instanceOfPattern.getClassNamePattern())
+ .setInstanceOfPattern(KeepInstanceOfPattern.any())
+ .build());
+ fn.accept(this);
+ fn.accept(holderWithInstanceOfAsName);
+ return;
+ }
+ // The remaining case is the general "*Foo* instance-of *Bar*" case. Here it unfolds to two
+ // cases matching anything of the form "*Foo*" and the other being the exclusive extends.
+ Holder holderWithNoInstanceOf =
+ new Holder(
+ KeepClassItemPattern.builder()
+ .copyFrom(itemPattern)
+ .setInstanceOfPattern(KeepInstanceOfPattern.any())
+ .build());
+ fn.accept(this);
+ fn.accept(holderWithNoInstanceOf);
}
}
private static class BindingUsers {
final Holder holder;
- final Set<BindingSymbol> conditionRefs = new HashSet<>();
- final Map<KeepOptions, Set<BindingSymbol>> targetRefs = new HashMap<>();
+ final Set<KeepBindingSymbol> conditionRefs = new HashSet<>();
+ final Map<KeepOptions, Set<KeepBindingSymbol>> targetRefs = new HashMap<>();
- static BindingUsers create(BindingSymbol bindingName, KeepBindings bindings) {
+ static BindingUsers create(KeepBindingSymbol bindingName, KeepBindings bindings) {
return new BindingUsers(Holder.create(bindingName, bindings));
}
@@ -168,14 +224,14 @@
public void addCondition(KeepCondition condition) {
assert condition.getItem().isBindingReference();
- conditionRefs.add(condition.getItem().asBindingReference());
+ conditionRefs.add(condition.getItem().asBindingReference().getName());
}
public void addTarget(KeepTarget target) {
assert target.getItem().isBindingReference();
targetRefs
.computeIfAbsent(target.getOptions(), k -> new HashSet<>())
- .add(target.getItem().asBindingReference());
+ .add(target.getItem().asBindingReference().getName());
}
}
@@ -186,11 +242,11 @@
// First step after normalizing is to group up all conditions and targets on their target class.
// Here we use the normalized binding as the notion of identity on a class.
KeepBindings bindings = edge.getBindings();
- Map<BindingSymbol, BindingUsers> bindingUsers = new HashMap<>();
+ Map<KeepBindingSymbol, BindingUsers> bindingUsers = new HashMap<>();
edge.getPreconditions()
.forEach(
condition -> {
- BindingSymbol classReference =
+ KeepBindingSymbol classReference =
getClassItemBindingReference(condition.getItem(), bindings);
assert classReference != null;
bindingUsers
@@ -200,7 +256,7 @@
edge.getConsequences()
.forEachTarget(
target -> {
- BindingSymbol classReference =
+ KeepBindingSymbol classReference =
getClassItemBindingReference(target.getItem(), bindings);
assert classReference != null;
bindingUsers
@@ -263,16 +319,18 @@
return rules;
}
- private static List<BindingSymbol> computeConditions(
- Set<BindingSymbol> conditions,
+ private static List<KeepBindingSymbol> computeConditions(
+ Set<KeepBindingSymbol> conditions,
KeepBindings bindings,
- Map<BindingSymbol, KeepMemberPattern> memberPatterns) {
- List<BindingSymbol> conditionMembers = new ArrayList<>();
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns) {
+ List<KeepBindingSymbol> conditionMembers = new ArrayList<>();
conditions.forEach(
conditionReference -> {
KeepItemPattern item = bindings.get(conditionReference).getItem();
if (!item.isClassItemPattern()) {
- KeepMemberPattern old = memberPatterns.put(conditionReference, item.getMemberPattern());
+ KeepMemberItemPattern memberItemPattern = item.asMemberItemPattern();
+ KeepMemberPattern old =
+ memberPatterns.put(conditionReference, memberItemPattern.getMemberPattern());
conditionMembers.add(conditionReference);
assert old == null;
}
@@ -283,31 +341,37 @@
@FunctionalInterface
private interface OnTargetCallback {
void accept(
- Map<BindingSymbol, KeepMemberPattern> memberPatterns,
- List<BindingSymbol> memberTargets,
+ Holder targetHolder,
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
+ List<KeepBindingSymbol> memberTargets,
TargetKeepKind keepKind);
}
private static void computeTargets(
- Set<BindingSymbol> targets,
+ Holder targetHolder,
+ Set<KeepBindingSymbol> targets,
KeepBindings bindings,
- Map<BindingSymbol, KeepMemberPattern> memberPatterns,
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
OnTargetCallback callback) {
TargetKeepKind keepKind = TargetKeepKind.JUST_MEMBERS;
- List<BindingSymbol> targetMembers = new ArrayList<>();
- for (BindingSymbol targetReference : targets) {
+ List<KeepBindingSymbol> targetMembers = new ArrayList<>();
+ for (KeepBindingSymbol targetReference : targets) {
KeepItemPattern item = bindings.get(targetReference).getItem();
if (item.isClassItemPattern()) {
keepKind = TargetKeepKind.CLASS_AND_MEMBERS;
} else {
- memberPatterns.putIfAbsent(targetReference, item.getMemberPattern());
+ KeepMemberItemPattern memberItemPattern = item.asMemberItemPattern();
+ memberPatterns.putIfAbsent(targetReference, memberItemPattern.getMemberPattern());
targetMembers.add(targetReference);
}
}
if (targetMembers.isEmpty()) {
keepKind = TargetKeepKind.CLASS_OR_MEMBERS;
}
- callback.accept(memberPatterns, targetMembers, keepKind);
+ final TargetKeepKind finalKeepKind = keepKind;
+ targetHolder.onTargetHolders(
+ newTargetHolder ->
+ callback.accept(newTargetHolder, memberPatterns, targetMembers, finalKeepKind));
}
private static void createUnconditionalRules(
@@ -316,18 +380,19 @@
KeepEdgeMetaInfo metaInfo,
KeepBindings bindings,
KeepOptions options,
- Set<BindingSymbol> targets) {
+ Set<KeepBindingSymbol> targets) {
computeTargets(
+ holder,
targets,
bindings,
new HashMap<>(),
- (memberPatterns, targetMembers, targetKeepKind) -> {
+ (targetHolder, memberPatterns, targetMembers, targetKeepKind) -> {
if (targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS)) {
// Members dependent on the class, so they go to the implicitly dependent rule.
rules.add(
new PgDependentMembersRule(
metaInfo,
- holder,
+ targetHolder,
options,
memberPatterns,
Collections.emptyList(),
@@ -336,7 +401,12 @@
} else {
rules.add(
new PgUnconditionalRule(
- metaInfo, holder, options, memberPatterns, targetMembers, targetKeepKind));
+ metaInfo,
+ targetHolder,
+ options,
+ memberPatterns,
+ targetMembers,
+ targetKeepKind));
}
});
}
@@ -348,29 +418,31 @@
Holder targetHolder,
KeepBindings bindings,
KeepOptions options,
- Set<BindingSymbol> conditions,
- Set<BindingSymbol> targets) {
- if (conditionHolder.namePattern.isExact()
- && conditionHolder.itemPattern.equals(targetHolder.itemPattern)) {
+ Set<KeepBindingSymbol> conditions,
+ Set<KeepBindingSymbol> targets) {
+ if (conditionHolder.getNamePattern().isExact()
+ && conditionHolder.getClassItemPattern().equals(targetHolder.getClassItemPattern())) {
// If the targets are conditional on its holder, the rule can be simplified as a dependent
// rule. Note that this is only valid on an *exact* class matching as otherwise any
// wildcard is allowed to be matched independently on the left and right of the edge.
createDependentRules(rules, targetHolder, metaInfo, bindings, options, conditions, targets);
return;
}
- Map<BindingSymbol, KeepMemberPattern> memberPatterns = new HashMap<>();
- List<BindingSymbol> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns = new HashMap<>();
+ List<KeepBindingSymbol> conditionMembers =
+ computeConditions(conditions, bindings, memberPatterns);
computeTargets(
+ targetHolder,
targets,
bindings,
memberPatterns,
- (ignore, targetMembers, targetKeepKind) ->
+ (newTargetHolder, ignore, targetMembers, targetKeepKind) ->
rules.add(
new PgConditionalRule(
metaInfo,
options,
conditionHolder,
- targetHolder,
+ newTargetHolder,
memberPatterns,
conditionMembers,
targetMembers,
@@ -379,27 +451,29 @@
private static void createDependentRules(
List<PgRule> rules,
- Holder holder,
+ Holder initialHolder,
KeepEdgeMetaInfo metaInfo,
KeepBindings bindings,
KeepOptions options,
- Set<BindingSymbol> conditions,
- Set<BindingSymbol> targets) {
- Map<BindingSymbol, KeepMemberPattern> memberPatterns = new HashMap<>();
- List<BindingSymbol> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
+ Set<KeepBindingSymbol> conditions,
+ Set<KeepBindingSymbol> targets) {
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns = new HashMap<>();
+ List<KeepBindingSymbol> conditionMembers =
+ computeConditions(conditions, bindings, memberPatterns);
computeTargets(
+ initialHolder,
targets,
bindings,
memberPatterns,
- (ignore, targetMembers, targetKeepKind) -> {
- List<BindingSymbol> nonAllMemberTargets = new ArrayList<>(targetMembers.size());
- for (BindingSymbol targetMember : targetMembers) {
+ (holder, ignore, targetMembers, targetKeepKind) -> {
+ List<KeepBindingSymbol> nonAllMemberTargets = new ArrayList<>(targetMembers.size());
+ for (KeepBindingSymbol targetMember : targetMembers) {
KeepMemberPattern memberPattern = memberPatterns.get(targetMember);
if (memberPattern.isGeneralMember() && conditionMembers.contains(targetMember)) {
// This pattern is on "members in general" and it is bound by a condition.
// Since backrefs can't reference a *-member we split this target in two, one for
// fields and one for methods.
- HashMap<BindingSymbol, KeepMemberPattern> copyWithMethod =
+ HashMap<KeepBindingSymbol, KeepMemberPattern> copyWithMethod =
new HashMap<>(memberPatterns);
copyWithMethod.put(targetMember, copyMethodFromMember(memberPattern));
rules.add(
@@ -411,7 +485,7 @@
conditionMembers,
Collections.singletonList(targetMember),
targetKeepKind));
- HashMap<BindingSymbol, KeepMemberPattern> copyWithField =
+ HashMap<KeepBindingSymbol, KeepMemberPattern> copyWithField =
new HashMap<>(memberPatterns);
copyWithField.put(targetMember, copyFieldFromMember(memberPattern));
rules.add(
@@ -454,18 +528,10 @@
return KeepFieldPattern.builder().setAccessPattern(accessPattern).build();
}
- private static KeepQualifiedClassNamePattern getClassNamePattern(
- KeepItemPattern itemPattern, KeepBindings bindings) {
- return itemPattern.getClassReference().isClassNamePattern()
- ? itemPattern.getClassReference().asClassNamePattern()
- : getClassNamePattern(
- bindings.get(itemPattern.getClassReference().asBindingReference()).getItem(), bindings);
- }
-
- private static BindingSymbol getClassItemBindingReference(
+ private static KeepBindingSymbol getClassItemBindingReference(
KeepItemReference itemReference, KeepBindings bindings) {
- BindingSymbol classReference = null;
- for (BindingSymbol reference : getTransitiveBindingReferences(itemReference, bindings)) {
+ KeepBindingSymbol classReference = null;
+ for (KeepBindingSymbol reference : getTransitiveBindingReferences(itemReference, bindings)) {
if (bindings.get(reference).getItem().isClassItemPattern()) {
if (classReference != null) {
throw new KeepEdgeException("Unexpected reference to multiple class bindings");
@@ -476,30 +542,29 @@
return classReference;
}
- private static Set<BindingSymbol> getTransitiveBindingReferences(
+ private static Set<KeepBindingSymbol> getTransitiveBindingReferences(
KeepItemReference itemReference, KeepBindings bindings) {
- Set<BindingSymbol> references = new HashSet<>(2);
- Deque<BindingSymbol> worklist = new ArrayDeque<>();
+ Set<KeepBindingSymbol> symbols = new HashSet<>(2);
+ Deque<KeepBindingReference> worklist = new ArrayDeque<>();
worklist.addAll(getBindingReference(itemReference));
while (!worklist.isEmpty()) {
- BindingSymbol bindingReference = worklist.pop();
- if (references.add(bindingReference)) {
+ KeepBindingReference bindingReference = worklist.pop();
+ if (symbols.add(bindingReference.getName())) {
worklist.addAll(getBindingReference(bindings.get(bindingReference).getItem()));
}
}
- return references;
+ return symbols;
}
- private static Collection<BindingSymbol> getBindingReference(KeepItemReference itemReference) {
+ private static Collection<KeepBindingReference> getBindingReference(
+ KeepItemReference itemReference) {
if (itemReference.isBindingReference()) {
return Collections.singletonList(itemReference.asBindingReference());
}
return getBindingReference(itemReference.asItemPattern());
}
- private static Collection<BindingSymbol> getBindingReference(KeepItemPattern itemPattern) {
- return itemPattern.getClassReference().isBindingReference()
- ? Collections.singletonList(itemPattern.getClassReference().asBindingReference())
- : Collections.emptyList();
+ private static Collection<KeepBindingReference> getBindingReference(KeepItemPattern itemPattern) {
+ return itemPattern.getBindingReferences();
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
index 5d55174..96e4bde 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
@@ -10,11 +10,10 @@
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.printClassHeader;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.printMemberClause;
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
-import com.android.tools.r8.keepanno.ast.KeepClassReference;
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
-import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepOptions;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
@@ -96,14 +95,10 @@
}
// Helper to print the class-name pattern in a class-item.
- // The item is assumed to either be a binding (where the binding is a class with
- // the supplied class-name pattern), or a class-item that has the class-name pattern itself (e.g.,
- // without a binding indirection).
- public static BiConsumer<StringBuilder, KeepClassReference> classReferencePrinter(
+ public static BiConsumer<StringBuilder, KeepQualifiedClassNamePattern> classNamePrinter(
KeepQualifiedClassNamePattern classNamePattern) {
- return (builder, classReference) -> {
- assert classReference.isBindingReference()
- || classReference.asClassNamePattern().equals(classNamePattern);
+ return (builder, className) -> {
+ assert className.equals(classNamePattern);
RulePrintingUtils.printClassName(
classNamePattern, RulePrinter.withoutBackReferences(builder));
};
@@ -123,10 +118,10 @@
if (hasCondition()) {
builder.append(RulePrintingUtils.IF).append(' ');
printConditionHolder(builder);
- List<BindingSymbol> members = getConditionMembers();
+ List<KeepBindingSymbol> members = getConditionMembers();
if (!members.isEmpty()) {
builder.append(" {");
- for (BindingSymbol member : members) {
+ for (KeepBindingSymbol member : members) {
builder.append(' ');
printConditionMember(builder, member);
}
@@ -141,10 +136,10 @@
printKeepOptions(builder);
builder.append(' ');
printTargetHolder(builder);
- List<BindingSymbol> members = getTargetMembers();
+ List<KeepBindingSymbol> members = getTargetMembers();
if (!members.isEmpty()) {
builder.append(" {");
- for (BindingSymbol member : members) {
+ for (KeepBindingSymbol member : members) {
builder.append(' ');
printTargetMember(builder, member);
}
@@ -156,25 +151,25 @@
return false;
}
- List<BindingSymbol> getConditionMembers() {
+ List<KeepBindingSymbol> getConditionMembers() {
throw new KeepEdgeException("Unreachable");
}
abstract String getConsequenceKeepType();
- abstract List<BindingSymbol> getTargetMembers();
+ abstract List<KeepBindingSymbol> getTargetMembers();
void printConditionHolder(StringBuilder builder) {
throw new KeepEdgeException("Unreachable");
}
- void printConditionMember(StringBuilder builder, BindingSymbol member) {
+ void printConditionMember(StringBuilder builder, KeepBindingSymbol member) {
throw new KeepEdgeException("Unreachable");
}
abstract void printTargetHolder(StringBuilder builder);
- abstract void printTargetMember(StringBuilder builder, BindingSymbol member);
+ abstract void printTargetMember(StringBuilder builder, KeepBindingSymbol member);
/**
* Representation of an unconditional rule to keep a class and methods.
@@ -187,22 +182,22 @@
*/
static class PgUnconditionalRule extends PgRule {
private final KeepQualifiedClassNamePattern holderNamePattern;
- private final KeepItemPattern holderPattern;
+ private final KeepClassItemPattern holderPattern;
private final TargetKeepKind targetKeepKind;
- private final List<BindingSymbol> targetMembers;
- private final Map<BindingSymbol, KeepMemberPattern> memberPatterns;
+ private final List<KeepBindingSymbol> targetMembers;
+ private final Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns;
public PgUnconditionalRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
- Map<BindingSymbol, KeepMemberPattern> memberPatterns,
- List<BindingSymbol> targetMembers,
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
+ List<KeepBindingSymbol> targetMembers,
TargetKeepKind targetKeepKind) {
super(metaInfo, options);
assert !targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS);
- this.holderNamePattern = holder.namePattern;
- this.holderPattern = holder.itemPattern;
+ this.holderNamePattern = holder.getNamePattern();
+ this.holderPattern = holder.getClassItemPattern();
this.targetKeepKind = targetKeepKind;
this.memberPatterns = memberPatterns;
this.targetMembers = targetMembers;
@@ -214,20 +209,20 @@
}
@Override
- List<BindingSymbol> getTargetMembers() {
+ List<KeepBindingSymbol> getTargetMembers() {
return targetMembers;
}
@Override
void printTargetHolder(StringBuilder builder) {
- printClassHeader(builder, holderPattern, classReferencePrinter(holderNamePattern));
+ printClassHeader(builder, holderPattern, classNamePrinter(holderNamePattern));
if (getTargetMembers().isEmpty()) {
printNonEmptyMembersPatternAsDefaultInitWorkaround(builder, targetKeepKind);
}
}
@Override
- void printTargetMember(StringBuilder builder, BindingSymbol memberReference) {
+ void printTargetMember(StringBuilder builder, KeepBindingSymbol memberReference) {
KeepMemberPattern memberPattern = memberPatterns.get(memberReference);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
@@ -245,11 +240,11 @@
*/
static class PgConditionalRule extends PgRule {
- final KeepItemPattern classCondition;
- final KeepItemPattern classTarget;
- final Map<BindingSymbol, KeepMemberPattern> memberPatterns;
- final List<BindingSymbol> memberConditions;
- private final List<BindingSymbol> memberTargets;
+ final KeepClassItemPattern classCondition;
+ final KeepClassItemPattern classTarget;
+ final Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns;
+ final List<KeepBindingSymbol> memberConditions;
+ private final List<KeepBindingSymbol> memberTargets;
private final TargetKeepKind keepKind;
public PgConditionalRule(
@@ -257,13 +252,13 @@
KeepOptions options,
Holder classCondition,
Holder classTarget,
- Map<BindingSymbol, KeepMemberPattern> memberPatterns,
- List<BindingSymbol> memberConditions,
- List<BindingSymbol> memberTargets,
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
+ List<KeepBindingSymbol> memberConditions,
+ List<KeepBindingSymbol> memberTargets,
TargetKeepKind keepKind) {
super(metaInfo, options);
- this.classCondition = classCondition.itemPattern;
- this.classTarget = classTarget.itemPattern;
+ this.classCondition = classCondition.getClassItemPattern();
+ this.classTarget = classTarget.getClassItemPattern();
this.memberPatterns = memberPatterns;
this.memberConditions = memberConditions;
this.memberTargets = memberTargets;
@@ -276,7 +271,7 @@
}
@Override
- List<BindingSymbol> getConditionMembers() {
+ List<KeepBindingSymbol> getConditionMembers() {
return memberConditions;
}
@@ -286,7 +281,7 @@
}
@Override
- void printConditionMember(StringBuilder builder, BindingSymbol member) {
+ void printConditionMember(StringBuilder builder, KeepBindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
@@ -305,19 +300,18 @@
}
@Override
- List<BindingSymbol> getTargetMembers() {
+ List<KeepBindingSymbol> getTargetMembers() {
return memberTargets;
}
@Override
- void printTargetMember(StringBuilder builder, BindingSymbol member) {
+ void printTargetMember(StringBuilder builder, KeepBindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
- private void printClassName(StringBuilder builder, KeepClassReference clazz) {
- RulePrintingUtils.printClassName(
- clazz.asClassNamePattern(), RulePrinter.withoutBackReferences(builder));
+ private void printClassName(StringBuilder builder, KeepQualifiedClassNamePattern clazzName) {
+ RulePrintingUtils.printClassName(clazzName, RulePrinter.withoutBackReferences(builder));
}
}
@@ -338,27 +332,27 @@
static class PgDependentMembersRule extends PgRule {
private final KeepQualifiedClassNamePattern holderNamePattern;
- private final KeepItemPattern holderPattern;
- private final Map<BindingSymbol, KeepMemberPattern> memberPatterns;
- private final List<BindingSymbol> memberConditions;
- private final List<BindingSymbol> memberTargets;
+ private final KeepClassItemPattern holderPattern;
+ private final Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns;
+ private final List<KeepBindingSymbol> memberConditions;
+ private final List<KeepBindingSymbol> memberTargets;
private final TargetKeepKind keepKind;
private int nextBackReferenceNumber = 1;
private String holderBackReferencePattern = null;
- private final Map<BindingSymbol, String> membersBackReferencePatterns = new HashMap<>();
+ private final Map<KeepBindingSymbol, String> membersBackReferencePatterns = new HashMap<>();
public PgDependentMembersRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
- Map<BindingSymbol, KeepMemberPattern> memberPatterns,
- List<BindingSymbol> memberConditions,
- List<BindingSymbol> memberTargets,
+ Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
+ List<KeepBindingSymbol> memberConditions,
+ List<KeepBindingSymbol> memberTargets,
TargetKeepKind keepKind) {
super(metaInfo, options);
- this.holderNamePattern = holder.namePattern;
- this.holderPattern = holder.itemPattern;
+ this.holderNamePattern = holder.getNamePattern();
+ this.holderPattern = holder.getClassItemPattern();
this.memberPatterns = memberPatterns;
this.memberConditions = memberConditions;
this.memberTargets = memberTargets;
@@ -384,12 +378,12 @@
}
@Override
- List<BindingSymbol> getConditionMembers() {
+ List<KeepBindingSymbol> getConditionMembers() {
return memberConditions;
}
@Override
- List<BindingSymbol> getTargetMembers() {
+ List<KeepBindingSymbol> getTargetMembers() {
return memberTargets;
}
@@ -407,7 +401,7 @@
}
@Override
- void printConditionMember(StringBuilder builder, BindingSymbol member) {
+ void printConditionMember(StringBuilder builder, KeepBindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
BackReferencePrinter printer =
RulePrinter.withBackReferences(builder, this::getNextBackReferenceNumber);
@@ -420,9 +414,8 @@
printClassHeader(
builder,
holderPattern,
- (b, reference) -> {
- assert reference.isBindingReference()
- || reference.asClassNamePattern().equals(holderNamePattern);
+ (b, className) -> {
+ assert className.equals(holderNamePattern);
if (hasCondition()) {
b.append(holderBackReferencePattern);
} else {
@@ -437,7 +430,7 @@
}
@Override
- void printTargetMember(StringBuilder builder, BindingSymbol member) {
+ void printTargetMember(StringBuilder builder, KeepBindingSymbol member) {
if (hasCondition()) {
String backref = membersBackReferencePatterns.get(member);
if (backref != null) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 604fe5b..3669002 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -4,14 +4,13 @@
package com.android.tools.r8.keepanno.keeprules;
import com.android.tools.r8.keepanno.ast.AccessVisibility;
-import com.android.tools.r8.keepanno.ast.KeepClassReference;
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
-import com.android.tools.r8.keepanno.ast.KeepExtendsPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
-import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepInstanceOfPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
@@ -90,15 +89,15 @@
public static StringBuilder printClassHeader(
StringBuilder builder,
- KeepItemPattern classPattern,
- BiConsumer<StringBuilder, KeepClassReference> printClassReference) {
+ KeepClassItemPattern classPattern,
+ BiConsumer<StringBuilder, KeepQualifiedClassNamePattern> printClassName) {
builder.append("class ");
- printClassReference.accept(builder, classPattern.getClassReference());
- KeepExtendsPattern extendsPattern = classPattern.getExtendsPattern();
+ printClassName.accept(builder, classPattern.getClassNamePattern());
+ KeepInstanceOfPattern extendsPattern = classPattern.getInstanceOfPattern();
if (!extendsPattern.isAny()) {
builder.append(" extends ");
printClassName(
- extendsPattern.asClassNamePattern(), RulePrinter.withoutBackReferences(builder));
+ extendsPattern.getClassNamePattern(), RulePrinter.withoutBackReferences(builder));
}
return builder;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
deleted file mode 100644
index 1b8d1a6..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright (c) 2022, 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.keepanno.processor;
-
-import static org.objectweb.asm.Opcodes.ACC_FINAL;
-import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-import static org.objectweb.asm.Opcodes.ACC_SUPER;
-
-import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
-import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Item;
-import com.android.tools.r8.keepanno.ast.KeepCondition;
-import com.android.tools.r8.keepanno.ast.KeepConsequences;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.ast.KeepEdge.Builder;
-import com.android.tools.r8.keepanno.ast.KeepEdgeException;
-import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
-import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
-import com.android.tools.r8.keepanno.ast.KeepPreconditions;
-import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepTarget;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.function.Consumer;
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.Filer;
-import javax.annotation.processing.RoundEnvironment;
-import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.annotation.processing.SupportedSourceVersion;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.SimpleAnnotationValueVisitor7;
-import javax.lang.model.util.SimpleTypeVisitor7;
-import javax.tools.Diagnostic.Kind;
-import javax.tools.JavaFileObject;
-import org.objectweb.asm.ClassWriter;
-
-@SupportedAnnotationTypes("com.android.tools.r8.keepanno.annotations.*")
-@SupportedSourceVersion(SourceVersion.RELEASE_7)
-public class KeepEdgeProcessor extends AbstractProcessor {
-
- public static String getClassTypeNameForSynthesizedEdges(String classTypeName) {
- return classTypeName + "$$KeepEdges";
- }
-
- @Override
- @SuppressWarnings("DoNotClaimAnnotations")
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- Map<String, List<KeepEdge>> collectedEdges = new HashMap<>();
- for (TypeElement annotation : annotations) {
- for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
- KeepEdge edge = processKeepEdge(element);
- if (edge != null) {
- TypeElement enclosingType = getEnclosingTypeElement(element);
- String enclosingTypeName = enclosingType.getQualifiedName().toString();
- collectedEdges.computeIfAbsent(enclosingTypeName, k -> new ArrayList<>()).add(edge);
- }
- }
- }
- for (Entry<String, List<KeepEdge>> entry : collectedEdges.entrySet()) {
- String enclosingTypeName = entry.getKey();
- String edgeTargetClass = getClassTypeNameForSynthesizedEdges(enclosingTypeName);
- byte[] writtenEdge = writeEdges(entry.getValue(), edgeTargetClass);
- Filer filer = processingEnv.getFiler();
- try {
- JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
- classFile.openOutputStream().write(writtenEdge);
- } catch (IOException e) {
- error(e.getMessage());
- }
- }
- return true;
- }
-
- private static byte[] writeEdges(List<KeepEdge> edges, String classTypeName) {
- String classBinaryName = AnnotationConstants.getBinaryNameFromClassTypeName(classTypeName);
- ClassWriter classWriter = new ClassWriter(0);
- classWriter.visit(
- KeepEdgeReader.ASM_VERSION,
- ACC_PUBLIC | ACC_FINAL | ACC_SUPER,
- classBinaryName,
- null,
- "java/lang/Object",
- null);
- classWriter.visitSource("SynthesizedKeepEdge", null);
- for (KeepEdge edge : edges) {
- KeepEdgeWriter.writeEdge(edge, classWriter);
- }
- classWriter.visitEnd();
- return classWriter.toByteArray();
- }
-
- @SuppressWarnings("BadImport")
- private KeepEdge processKeepEdge(Element element) {
- AnnotationMirror mirror = getAnnotationMirror(element, AnnotationConstants.Edge.CLASS);
- if (mirror == null) {
- return null;
- }
- Builder edgeBuilder = KeepEdge.builder();
- processPreconditions(edgeBuilder, mirror);
- processConsequences(edgeBuilder, mirror);
- return edgeBuilder.build();
- }
-
- @SuppressWarnings("BadImport")
- private void processPreconditions(Builder edgeBuilder, AnnotationMirror mirror) {
- AnnotationValue preconditions = getAnnotationValue(mirror, Edge.preconditions);
- if (preconditions == null) {
- return;
- }
- KeepPreconditions.Builder preconditionsBuilder = KeepPreconditions.builder();
- new AnnotationListValueVisitor(
- value -> {
- KeepCondition.Builder conditionBuilder = KeepCondition.builder();
- processCondition(conditionBuilder, AnnotationMirrorValueVisitor.getMirror(value));
- preconditionsBuilder.addCondition(conditionBuilder.build());
- })
- .onValue(preconditions);
- edgeBuilder.setPreconditions(preconditionsBuilder.build());
- }
-
- @SuppressWarnings("BadImport")
- private void processConsequences(Builder edgeBuilder, AnnotationMirror mirror) {
- AnnotationValue consequences = getAnnotationValue(mirror, Edge.consequences);
- if (consequences == null) {
- return;
- }
- KeepConsequences.Builder consequencesBuilder = KeepConsequences.builder();
- new AnnotationListValueVisitor(
- value -> {
- KeepTarget.Builder targetBuilder = KeepTarget.builder();
- processTarget(targetBuilder, AnnotationMirrorValueVisitor.getMirror(value));
- consequencesBuilder.addTarget(targetBuilder.build());
- })
- .onValue(consequences);
- edgeBuilder.setConsequences(consequencesBuilder.build());
- }
-
- private String getTypeNameForClassConstantElement(DeclaredType type) {
- // The processor API does not expose the descriptor or typename, so we need to depend on the
- // sun.tools internals to extract it. If not, this code will not work for inner classes as
- // we cannot recover the $ separator.
- try {
- Object tsym = type.getClass().getField("tsym").get(type);
- Object flatname = tsym.getClass().getField("flatname").get(tsym);
- return flatname.toString();
- } catch (NoSuchFieldException | IllegalAccessException e) {
- throw new KeepEdgeException("Unable to obtain the class type name for: " + type);
- }
- }
-
- private void processCondition(KeepCondition.Builder builder, AnnotationMirror mirror) {
- KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
- processItem(itemBuilder, mirror);
- builder.setItemPattern(itemBuilder.build());
- }
-
- private void processTarget(KeepTarget.Builder builder, AnnotationMirror mirror) {
- KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
- processItem(itemBuilder, mirror);
- builder.setItemPattern(itemBuilder.build());
- }
-
- private void processItem(KeepItemPattern.Builder builder, AnnotationMirror mirror) {
- AnnotationValue classConstantValue = getAnnotationValue(mirror, Item.classConstant);
- if (classConstantValue != null) {
- DeclaredType type = AnnotationClassValueVisitor.getType(classConstantValue);
- String typeName = getTypeNameForClassConstantElement(type);
- builder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
- }
- AnnotationValue methodNameValue = getAnnotationValue(mirror, Item.methodName);
- AnnotationValue fieldNameValue = getAnnotationValue(mirror, Item.fieldName);
- if (methodNameValue != null && fieldNameValue != null) {
- throw new KeepEdgeException("Cannot define both a method and a field name pattern");
- }
- if (methodNameValue != null) {
- String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
- builder.setMemberPattern(
- KeepMethodPattern.builder()
- .setNamePattern(KeepMethodNamePattern.exact(methodName))
- .build());
- } else if (fieldNameValue != null) {
- String fieldName = AnnotationStringValueVisitor.getString(fieldNameValue);
- builder.setMemberPattern(
- KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build());
- }
- }
-
- private void error(String message) {
- processingEnv.getMessager().printMessage(Kind.ERROR, message);
- }
-
- private static TypeElement getEnclosingTypeElement(Element element) {
- while (true) {
- if (element == null || element instanceof TypeElement) {
- return (TypeElement) element;
- }
- element = element.getEnclosingElement();
- }
- }
-
- private static AnnotationMirror getAnnotationMirror(Element element, Class<?> clazz) {
- String clazzName = clazz.getName();
- for (AnnotationMirror m : element.getAnnotationMirrors()) {
- if (m.getAnnotationType().toString().equals(clazzName)) {
- return m;
- }
- }
- return null;
- }
-
- private static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
- for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
- annotationMirror.getElementValues().entrySet()) {
- if (entry.getKey().getSimpleName().toString().equals(key)) {
- return entry.getValue();
- }
- }
- return null;
- }
-
- /// Annotation Visitors
-
- private abstract static class AnnotationValueVisitorBase<T>
- extends SimpleAnnotationValueVisitor7<T, Object> {
- @Override
- protected T defaultAction(Object o1, Object o2) {
- throw new IllegalStateException();
- }
-
- public T onValue(AnnotationValue value) {
- return value.accept(this, null);
- }
- }
-
- private static class AnnotationListValueVisitor
- extends AnnotationValueVisitorBase<AnnotationListValueVisitor> {
-
- private final Consumer<AnnotationValue> fn;
-
- public AnnotationListValueVisitor(Consumer<AnnotationValue> fn) {
- this.fn = fn;
- }
-
- @Override
- public AnnotationListValueVisitor visitArray(
- List<? extends AnnotationValue> values, Object ignore) {
- values.forEach(fn);
- return this;
- }
- }
-
- private static class AnnotationMirrorValueVisitor
- extends AnnotationValueVisitorBase<AnnotationMirrorValueVisitor> {
-
- private AnnotationMirror mirror = null;
-
- public static AnnotationMirror getMirror(AnnotationValue value) {
- return new AnnotationMirrorValueVisitor().onValue(value).mirror;
- }
-
- @Override
- public AnnotationMirrorValueVisitor visitAnnotation(AnnotationMirror mirror, Object o) {
- this.mirror = mirror;
- return this;
- }
- }
-
- private static class AnnotationStringValueVisitor
- extends AnnotationValueVisitorBase<AnnotationStringValueVisitor> {
- private String string;
-
- public static String getString(AnnotationValue value) {
- return new AnnotationStringValueVisitor().onValue(value).string;
- }
-
- @Override
- public AnnotationStringValueVisitor visitString(String string, Object ignore) {
- this.string = string;
- return this;
- }
- }
-
- private static class AnnotationClassValueVisitor
- extends AnnotationValueVisitorBase<AnnotationClassValueVisitor> {
- private DeclaredType type = null;
-
- public static DeclaredType getType(AnnotationValue value) {
- return new AnnotationClassValueVisitor().onValue(value).type;
- }
-
- @Override
- public AnnotationClassValueVisitor visitType(TypeMirror t, Object ignore) {
- ClassTypeVisitor classTypeVisitor = new ClassTypeVisitor();
- t.accept(classTypeVisitor, null);
- type = classTypeVisitor.type;
- return this;
- }
- }
-
- private static class TypeVisitorBase<T> extends SimpleTypeVisitor7<T, Object> {
- @Override
- protected T defaultAction(TypeMirror typeMirror, Object ignore) {
- throw new IllegalStateException();
- }
- }
-
- private static class ClassTypeVisitor extends TypeVisitorBase<ClassTypeVisitor> {
- private DeclaredType type = null;
-
- @Override
- public ClassTypeVisitor visitDeclared(DeclaredType t, Object ignore) {
- this.type = t;
- return this;
- }
- }
-}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index df0df7b..3d23d9f 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -1,5 +1,5 @@
{
- "identifier": "com.tools.android:desugar_jdk_libs_configuration:2.0.4",
+ "identifier": "com.tools.android:desugar_jdk_libs_configuration:2.1.0",
"configuration_format_version": 101,
"required_compilation_api_level": 30,
"synthesized_library_classes_package_prefix": "j$.",
@@ -7,6 +7,12 @@
"common_flags": [
{
"api_level_below_or_equal": 10000,
+ "amend_library_method": [
+ "public java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+ ]
+ },
+ {
+ "api_level_below_or_equal": 10000,
"api_level_greater_or_equal": 26,
"rewrite_prefix": {
"java.time.DesugarLocalDate": "j$.time.DesugarLocalDate",
@@ -57,6 +63,14 @@
"rewrite_prefix": {
"java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
},
+ "emulate_interface": {
+ "java.util.Collection": {
+ "rewrittenType": "j$.util.Collection",
+ "emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+ ]
+ }
+ },
"retarget_method": {
"java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
"java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -360,12 +374,6 @@
]
},
{
- "api_level_below_or_equal": 33,
- "amend_library_method": [
- "public java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
- ]
- },
- {
"api_level_below_or_equal": 32,
"api_level_greater_or_equal": 26,
"covariant_retarget_method": {
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
index d69ed9a..7303240 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
@@ -1,5 +1,5 @@
{
- "identifier": "com.tools.android:desugar_jdk_libs_configuration_minimal:2.0.4",
+ "identifier": "com.tools.android:desugar_jdk_libs_configuration_minimal:2.1.0",
"configuration_format_version": 101,
"required_compilation_api_level": 24,
"synthesized_library_classes_package_prefix": "j$.",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 29e4e26..0f81af4 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -1,5 +1,5 @@
{
- "identifier": "com.tools.android:desugar_jdk_libs_configuration_nio:2.0.4",
+ "identifier": "com.tools.android:desugar_jdk_libs_configuration_nio:2.1.0",
"configuration_format_version": 101,
"required_compilation_api_level": 30,
"synthesized_library_classes_package_prefix": "j$.",
@@ -8,6 +8,7 @@
{
"api_level_below_or_equal": 10000,
"amend_library_method": [
+ "public java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
"public static java.nio.file.Path java.nio.file.Path#of(java.lang.String, java.lang.String[])",
"public static java.nio.file.Path java.nio.file.Path#of(java.net.URI)"
]
@@ -76,6 +77,14 @@
"java.io.DesugarInputStream": "j$.io.DesugarInputStream",
"java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
},
+ "emulate_interface": {
+ "java.util.Collection": {
+ "rewrittenType": "j$.util.Collection",
+ "emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+ ]
+ }
+ },
"retarget_method": {
"java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
"java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -532,12 +541,6 @@
}
},
{
- "api_level_below_or_equal": 33,
- "amend_library_method": [
- "public java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
- ]
- },
- {
"api_level_below_or_equal": 32,
"api_level_greater_or_equal": 26,
"covariant_retarget_method": {
@@ -588,7 +591,9 @@
"api_level_below_or_equal": 10000,
"api_level_greater_or_equal": 26,
"rewrite_prefix": {
- "sun.nio.cs.UTF_8": "j$.sun.nio.cs.UTF_8"
+ "sun.nio.cs.UTF_8": "j$.sun.nio.cs.UTF_8",
+ "sun.nio.cs.Unicode": "j$.sun.nio.cs.Unicode",
+ "sun.nio.cs.HistoricallyNamedCharset": "j$.sun.nio.cs.HistoricallyNamedCharset"
},
"retarget_static_field": {
"sun.nio.cs.UTF_8 sun.nio.cs.UTF_8#INSTANCE": "java.nio.charset.Charset java.nio.charset.StandardCharsets#UTF_8"
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index 3084ee0..186856b 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -9,23 +9,18 @@
// This class is used by the Android Studio Gradle plugin and is thus part of the R8 API.
@KeepForApi
public class CompatProguardCommandBuilder extends R8Command.Builder {
+
public CompatProguardCommandBuilder() {
this(true);
}
+ public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
+ setProguardCompatibility(forceProguardCompatibility);
+ }
+
public CompatProguardCommandBuilder(
boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
super(diagnosticsHandler);
setProguardCompatibility(forceProguardCompatibility);
}
-
- public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
- this(forceProguardCompatibility, false);
- }
-
- public CompatProguardCommandBuilder(
- boolean forceProguardCompatibility, boolean disableVerticalClassMerging) {
- setProguardCompatibility(forceProguardCompatibility);
- setDisableVerticalClassMerging(disableVerticalClassMerging);
- }
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 211dfb5..2250d81 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -755,7 +755,7 @@
// Assert some of R8 optimizations are disabled.
assert !internal.inlinerOptions().enableInlining;
assert !internal.enableClassInlining;
- assert !internal.enableVerticalClassMerging;
+ assert internal.getVerticalClassMergerOptions().isDisabled();
assert !internal.enableEnumValueOptimization;
assert !internal.outline.enabled;
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 97e6cb2..08b1657 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -32,7 +32,7 @@
public class FeatureSplit {
public static final FeatureSplit BASE =
- new FeatureSplit(null, null) {
+ new FeatureSplit(null, null, null, null) {
@Override
public boolean isBase() {
return true;
@@ -40,7 +40,7 @@
};
public static final FeatureSplit BASE_STARTUP =
- new FeatureSplit(null, null) {
+ new FeatureSplit(null, null, null, null) {
@Override
public boolean isBase() {
return true;
@@ -52,13 +52,20 @@
}
};
- private final ProgramConsumer programConsumer;
+ private ProgramConsumer programConsumer;
private final List<ProgramResourceProvider> programResourceProviders;
+ private final AndroidResourceProvider androidResourceProvider;
+ private final AndroidResourceConsumer androidResourceConsumer;
private FeatureSplit(
- ProgramConsumer programConsumer, List<ProgramResourceProvider> programResourceProviders) {
+ ProgramConsumer programConsumer,
+ List<ProgramResourceProvider> programResourceProviders,
+ AndroidResourceProvider androidResourceProvider,
+ AndroidResourceConsumer androidResourceConsumer) {
this.programConsumer = programConsumer;
this.programResourceProviders = programResourceProviders;
+ this.androidResourceProvider = androidResourceProvider;
+ this.androidResourceConsumer = androidResourceConsumer;
}
public boolean isBase() {
@@ -69,6 +76,10 @@
return false;
}
+ void internalSetProgramConsumer(ProgramConsumer consumer) {
+ this.programConsumer = consumer;
+ }
+
public List<ProgramResourceProvider> getProgramResourceProviders() {
return programResourceProviders;
}
@@ -81,6 +92,14 @@
return new Builder(handler);
}
+ public AndroidResourceProvider getAndroidResourceProvider() {
+ return androidResourceProvider;
+ }
+
+ public AndroidResourceConsumer getAndroidResourceConsumer() {
+ return androidResourceConsumer;
+ }
+
/**
* Builder for constructing a FeatureSplit.
*
@@ -90,10 +109,13 @@
public static class Builder {
private ProgramConsumer programConsumer;
private final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>();
+ private AndroidResourceProvider androidResourceProvider;
+ private AndroidResourceConsumer androidResourceConsumer;
@SuppressWarnings("UnusedVariable")
private final DiagnosticsHandler handler;
+
private Builder(DiagnosticsHandler handler) {
this.handler = handler;
}
@@ -121,9 +143,23 @@
return this;
}
+ public Builder setAndroidResourceProvider(AndroidResourceProvider androidResourceProvider) {
+ this.androidResourceProvider = androidResourceProvider;
+ return this;
+ }
+
+ public Builder setAndroidResourceConsumer(AndroidResourceConsumer androidResourceConsumer) {
+ this.androidResourceConsumer = androidResourceConsumer;
+ return this;
+ }
+
/** Build and return the {@link FeatureSplit} */
public FeatureSplit build() {
- return new FeatureSplit(programConsumer, programResourceProviders);
+ return new FeatureSplit(
+ programConsumer,
+ programResourceProviders,
+ androidResourceProvider,
+ androidResourceConsumer);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 664eae4..726013e 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.dump.DumpOptions;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.inspector.Inspector;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
@@ -22,7 +21,6 @@
import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ProgramClassCollection;
import com.android.tools.r8.utils.Reporter;
@@ -197,16 +195,12 @@
// Assert some of R8 optimizations are disabled.
assert !internal.inlinerOptions().enableInlining;
assert !internal.enableClassInlining;
- assert !internal.enableVerticalClassMerging;
+ assert internal.getVerticalClassMergerOptions().isDisabled();
assert !internal.enableEnumValueOptimization;
assert !internal.outline.enabled;
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
- HorizontalClassMergerOptions horizontalClassMergerOptions =
- internal.horizontalClassMergerOptions();
- horizontalClassMergerOptions.disable();
- assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL);
- assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
+ internal.horizontalClassMergerOptions().disable();
assert internal.desugarState == DesugarState.ON;
assert internal.enableInheritanceClassInDexDistributor;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d0264fc..6e8edf7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -34,7 +34,6 @@
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.graph.lens.AppliedGraphLens;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.inspector.internal.InspectorImpl;
@@ -69,7 +68,7 @@
import com.android.tools.r8.naming.RecordInvokeDynamicInvokeCustomRewriter;
import com.android.tools.r8.naming.RecordRewritingNamingLens;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
-import com.android.tools.r8.optimize.LegacyAccessModifier;
+import com.android.tools.r8.optimize.BridgeHoistingToSharedSyntheticSuperClass;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
@@ -105,8 +104,6 @@
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.shaking.TreePrunerConfiguration;
-import com.android.tools.r8.shaking.VerticalClassMerger;
-import com.android.tools.r8.shaking.VerticalClassMergerGraphLens;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.synthesis.SyntheticFinalization;
import com.android.tools.r8.synthesis.SyntheticItems;
@@ -114,13 +111,13 @@
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.SelfRetraceTest;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.verticalclassmerging.VerticalClassMerger;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayOutputStream;
@@ -131,7 +128,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
@@ -263,7 +262,7 @@
if (options.quiet) {
System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
}
- if (this.getClass().desiredAssertionStatus()) {
+ if (this.getClass().desiredAssertionStatus() && !options.quiet) {
options.reporter.info(
new StringDiagnostic(
"Running R8 version " + Version.LABEL + " with assertions enabled."));
@@ -347,7 +346,7 @@
timing.end();
timing.begin("Strip unused code");
timing.begin("Before enqueuer");
- RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
+ RuntimeTypeCheckInfo.Builder initialRuntimeTypeCheckInfoBuilder =
new RuntimeTypeCheckInfo.Builder(appView);
List<ProguardConfigurationRule> synthesizedProguardRules;
try {
@@ -391,7 +390,7 @@
appView,
profileCollectionAdditions,
subtypingInfo,
- classMergingEnqueuerExtensionBuilder);
+ initialRuntimeTypeCheckInfoBuilder);
timing.end();
timing.begin("After enqueuer");
assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness);
@@ -480,9 +479,6 @@
// to clear the cache, so that we will recompute the type lattice elements.
appView.dexItemFactory().clearTypeElementsCache();
- // TODO(b/132677331): Remove legacy access modifier.
- LegacyAccessModifier.run(appViewWithLiveness, executorService, timing);
-
// This pass attempts to reduce the number of nests and nest size to allow further passes, and
// should therefore be run after the publicizer.
new NestReducer(appViewWithLiveness).run(executorService, timing);
@@ -498,43 +494,16 @@
.setMustRetargetInvokesToTargetMethod()
.run(executorService, timing);
- boolean isKotlinLibraryCompilationWithInlinePassThrough =
- options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
-
- RuntimeTypeCheckInfo runtimeTypeCheckInfo =
- classMergingEnqueuerExtensionBuilder.build(appView.graphLens());
- classMergingEnqueuerExtensionBuilder = null;
+ BridgeHoistingToSharedSyntheticSuperClass.run(appViewWithLiveness, executorService, timing);
assert ArtProfileCompletenessChecker.verify(appView);
- if (!isKotlinLibraryCompilationWithInlinePassThrough
- && options.getProguardConfiguration().isOptimizing()) {
- if (options.enableVerticalClassMerging) {
- timing.begin("VerticalClassMerger");
- VerticalClassMergerGraphLens lens =
- new VerticalClassMerger(
- getDirectApp(appViewWithLiveness),
- appViewWithLiveness,
- executorService,
- timing)
- .run();
- if (lens != null) {
- runtimeTypeCheckInfo = runtimeTypeCheckInfo.rewriteWithLens(lens);
- }
- timing.end();
- } else {
- appView.setVerticallyMergedClasses(VerticallyMergedClasses.empty());
- }
- assert appView.verticallyMergedClasses() != null;
-
- assert ArtProfileCompletenessChecker.verify(appView);
-
- HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
- .runIfNecessary(executorService, timing, runtimeTypeCheckInfo);
- }
- appViewWithLiveness
- .appInfo()
- .notifyHorizontalClassMergerFinished(HorizontalClassMerger.Mode.INITIAL);
+ VerticalClassMerger.runIfNecessary(appViewWithLiveness, executorService, timing);
+ HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
+ .runIfNecessary(
+ executorService,
+ timing,
+ initialRuntimeTypeCheckInfoBuilder.build(appView.graphLens()));
// TODO(b/225838009): Horizontal merging currently assumes pre-phase CF conversion.
appView.testing().enterLirSupportedPhase(appView, executorService);
@@ -571,10 +540,12 @@
// At this point all code has been mapped according to the graph lens. We cannot remove the
// graph lens entirely, though, since it is needed for mapping all field and method signatures
// back to the original program.
- timing.begin("AppliedGraphLens construction");
- appView.setGraphLens(new AppliedGraphLens(appView));
+ timing.time(
+ "AppliedGraphLens construction",
+ () -> appView.setGraphLens(new AppliedGraphLens(appView)));
timing.end();
- timing.end();
+
+ RuntimeTypeCheckInfo.Builder finalRuntimeTypeCheckInfoBuilder = null;
if (options.shouldRerunEnqueuer()) {
timing.begin("Post optimization code stripping");
try {
@@ -596,8 +567,8 @@
keptGraphConsumer,
prunedTypes);
if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
- classMergingEnqueuerExtensionBuilder = new RuntimeTypeCheckInfo.Builder(appView);
- classMergingEnqueuerExtensionBuilder.attach(enqueuer);
+ finalRuntimeTypeCheckInfoBuilder = new RuntimeTypeCheckInfo.Builder(appView);
+ finalRuntimeTypeCheckInfoBuilder.attach(enqueuer);
}
EnqueuerResult enqueuerResult =
enqueuer.traceApplication(appView.rootSet(), executorService, timing);
@@ -753,10 +724,10 @@
timing.end();
// Perform repackaging.
- if (options.isRepackagingEnabled()) {
- new Repackaging(appView.withLiveness()).run(executorService, timing);
- }
if (appView.hasLiveness()) {
+ if (options.isRepackagingEnabled()) {
+ new Repackaging(appView.withLiveness()).run(executorService, timing);
+ }
assert Repackaging.verifyIdentityRepackaging(appView.withLiveness(), executorService);
}
@@ -773,10 +744,9 @@
.runIfNecessary(
executorService,
timing,
- classMergingEnqueuerExtensionBuilder != null
- ? classMergingEnqueuerExtensionBuilder.build(appView.graphLens())
+ finalRuntimeTypeCheckInfoBuilder != null
+ ? finalRuntimeTypeCheckInfoBuilder.build(appView.graphLens())
: null);
- appView.appInfo().notifyHorizontalClassMergerFinished(HorizontalClassMerger.Mode.FINAL);
// Perform minification.
if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -788,7 +758,7 @@
appView.clearApplyMappingSeedMapper();
} else if (options.isMinifying()) {
timing.begin("Minification");
- appView.setNamingLens(new Minifier(appView.withLiveness()).run(executorService, timing));
+ new Minifier(appView.withLiveness()).run(executorService, timing);
timing.end();
} else {
timing.begin("MinifyIdentifiers");
@@ -854,20 +824,24 @@
new DesugaredLibraryKeepRuleGenerator(appView).runIfNecessary(timing);
- List<Pair<Integer, byte[]>> dexFileContent = new ArrayList<>();
- if (options.androidResourceProvider != null && options.androidResourceConsumer != null) {
+ Map<String, byte[]> dexFileContent = new ConcurrentHashMap<>();
+ if (options.androidResourceProvider != null
+ && options.androidResourceConsumer != null
+ // We trace the dex directly in the enqueuer.
+ && !options.resourceShrinkerConfiguration.isOptimizedShrinking()) {
options.programConsumer =
- new ForwardingConsumer((DexIndexedConsumer) options.programConsumer) {
- @Override
- public void accept(
- int fileIndex,
- ByteDataView data,
- Set<String> descriptors,
- DiagnosticsHandler handler) {
- dexFileContent.add(new Pair<>(fileIndex, data.copyByteData()));
- super.accept(fileIndex, data, descriptors, handler);
- }
- };
+ wrapConsumerStoreBytesInList(
+ dexFileContent, (DexIndexedConsumer) options.programConsumer, "base");
+ if (options.featureSplitConfiguration != null) {
+ int featureIndex = 0;
+ for (FeatureSplit featureSplit : options.featureSplitConfiguration.getFeatureSplits()) {
+ featureSplit.internalSetProgramConsumer(
+ wrapConsumerStoreBytesInList(
+ dexFileContent,
+ (DexIndexedConsumer) featureSplit.getProgramConsumer(),
+ "feature" + featureIndex));
+ }
+ }
}
assert appView.verifyMovedMethodsHaveOriginalMethodPosition();
@@ -877,7 +851,7 @@
writeApplication(appView, inputApp, executorService);
if (options.androidResourceProvider != null && options.androidResourceConsumer != null) {
- shrinkResources(dexFileContent);
+ shrinkResources(dexFileContent, appView);
}
assert appView.getDontWarnConfiguration().validate(options);
@@ -894,70 +868,142 @@
}
}
- private void shrinkResources(List<Pair<Integer, byte[]>> dexFileContent) {
+ private static ForwardingConsumer wrapConsumerStoreBytesInList(
+ Map<String, byte[]> dexFileContent,
+ DexIndexedConsumer programConsumer,
+ String classesPrefix) {
+
+ return new ForwardingConsumer(programConsumer) {
+ @Override
+ public void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ dexFileContent.put(classesPrefix + "_classes" + fileIndex + ".dex", data.copyByteData());
+ super.accept(fileIndex, data, descriptors, handler);
+ }
+ };
+ }
+
+ private void shrinkResources(
+ Map<String, byte[]> dexFileContent, AppView<AppInfoWithClassHierarchy> appView) {
LegacyResourceShrinker.Builder resourceShrinkerBuilder = LegacyResourceShrinker.builder();
Reporter reporter = options.reporter;
- dexFileContent.forEach(p -> resourceShrinkerBuilder.addDexInput(p.getFirst(), p.getSecond()));
+ dexFileContent.forEach(resourceShrinkerBuilder::addDexInput);
try {
- Collection<AndroidResourceInput> androidResources =
- options.androidResourceProvider.getAndroidResources();
- for (AndroidResourceInput androidResource : androidResources) {
- try {
- byte[] bytes = androidResource.getByteStream().readAllBytes();
- Path path = Paths.get(androidResource.getPath().location());
- switch (androidResource.getKind()) {
- case MANIFEST:
- resourceShrinkerBuilder.setManifest(path, bytes);
- break;
- case RES_FOLDER_FILE:
- resourceShrinkerBuilder.addResFolderInput(path, bytes);
- break;
- case RESOURCE_TABLE:
- resourceShrinkerBuilder.setResourceTable(path, bytes);
- break;
- case XML_FILE:
- resourceShrinkerBuilder.addXmlInput(path, bytes);
- break;
- case UNKNOWN:
- break;
+ addResourcesToBuilder(
+ resourceShrinkerBuilder, reporter, options.androidResourceProvider, null);
+ if (options.featureSplitConfiguration != null) {
+ for (FeatureSplit featureSplit : options.featureSplitConfiguration.getFeatureSplits()) {
+ if (featureSplit.getAndroidResourceProvider() != null) {
+ addResourcesToBuilder(
+ resourceShrinkerBuilder,
+ reporter,
+ featureSplit.getAndroidResourceProvider(),
+ featureSplit);
}
- } catch (IOException e) {
- reporter.error(new ExceptionDiagnostic(e, androidResource.getOrigin()));
}
}
LegacyResourceShrinker shrinker = resourceShrinkerBuilder.build();
- ShrinkerResult shrinkerResult = shrinker.run();
- AndroidResourceConsumer androidResourceConsumer = options.androidResourceConsumer;
+ ShrinkerResult shrinkerResult;
+ if (options.resourceShrinkerConfiguration.isOptimizedShrinking()) {
+ shrinkerResult =
+ shrinker.shrinkModel(appView.getResourceShrinkerState().getR8ResourceShrinkerModel());
+ } else {
+ shrinkerResult = shrinker.run();
+ }
Set<String> toKeep = shrinkerResult.getResFolderEntriesToKeep();
- for (AndroidResourceInput androidResource : androidResources) {
- switch (androidResource.getKind()) {
- case MANIFEST:
- case UNKNOWN:
- androidResourceConsumer.accept(
- new R8PassThroughAndroidResource(androidResource, reporter), reporter);
- break;
- case RESOURCE_TABLE:
- androidResourceConsumer.accept(
- new R8AndroidResourceWithData(
- androidResource, reporter, shrinkerResult.getResourceTableInProtoFormat()),
- reporter);
- break;
- case RES_FOLDER_FILE:
- case XML_FILE:
- if (toKeep.contains(androidResource.getPath().location())) {
- androidResourceConsumer.accept(
- new R8PassThroughAndroidResource(androidResource, reporter), reporter);
- }
- break;
+ writeResourcesToConsumer(
+ reporter,
+ shrinkerResult,
+ toKeep,
+ options.androidResourceProvider,
+ options.androidResourceConsumer,
+ null);
+ if (options.featureSplitConfiguration != null) {
+ for (FeatureSplit featureSplit : options.featureSplitConfiguration.getFeatureSplits()) {
+ if (featureSplit.getAndroidResourceProvider() != null) {
+ writeResourcesToConsumer(
+ reporter,
+ shrinkerResult,
+ toKeep,
+ featureSplit.getAndroidResourceProvider(),
+ featureSplit.getAndroidResourceConsumer(),
+ featureSplit);
+ }
}
}
- androidResourceConsumer.finished(reporter);
} catch (ParserConfigurationException | SAXException | ResourceException | IOException e) {
reporter.error(new ExceptionDiagnostic(e));
}
}
+ private static void writeResourcesToConsumer(
+ Reporter reporter,
+ ShrinkerResult shrinkerResult,
+ Set<String> toKeep,
+ AndroidResourceProvider androidResourceProvider,
+ AndroidResourceConsumer androidResourceConsumer,
+ FeatureSplit featureSplit)
+ throws ResourceException {
+ for (AndroidResourceInput androidResource : androidResourceProvider.getAndroidResources()) {
+ switch (androidResource.getKind()) {
+ case MANIFEST:
+ case UNKNOWN:
+ androidResourceConsumer.accept(
+ new R8PassThroughAndroidResource(androidResource, reporter), reporter);
+ break;
+ case RESOURCE_TABLE:
+ androidResourceConsumer.accept(
+ new R8AndroidResourceWithData(
+ androidResource,
+ reporter,
+ shrinkerResult.getResourceTableInProtoFormat(featureSplit)),
+ reporter);
+ break;
+ case RES_FOLDER_FILE:
+ case XML_FILE:
+ if (toKeep.contains(androidResource.getPath().location())) {
+ androidResourceConsumer.accept(
+ new R8PassThroughAndroidResource(androidResource, reporter), reporter);
+ }
+ break;
+ }
+ }
+ androidResourceConsumer.finished(reporter);
+ }
+
+ private static void addResourcesToBuilder(
+ LegacyResourceShrinker.Builder resourceShrinkerBuilder,
+ Reporter reporter,
+ AndroidResourceProvider androidResourceProvider,
+ FeatureSplit featureSplit)
+ throws ResourceException {
+ for (AndroidResourceInput androidResource : androidResourceProvider.getAndroidResources()) {
+ try {
+ byte[] bytes = androidResource.getByteStream().readAllBytes();
+ Path path = Paths.get(androidResource.getPath().location());
+ switch (androidResource.getKind()) {
+ case MANIFEST:
+ resourceShrinkerBuilder.addManifest(path, bytes);
+ break;
+ case RES_FOLDER_FILE:
+ resourceShrinkerBuilder.addResFolderInput(path, bytes);
+ break;
+ case RESOURCE_TABLE:
+ resourceShrinkerBuilder.addResourceTable(path, bytes, featureSplit);
+ break;
+ case XML_FILE:
+ resourceShrinkerBuilder.addXmlInput(path, bytes);
+ break;
+ case UNKNOWN:
+ break;
+ }
+ } catch (IOException e) {
+ reporter.error(new ExceptionDiagnostic(e, androidResource.getOrigin()));
+ }
+ }
+ }
+
private static boolean allReferencesAssignedApiLevel(
AppView<? extends AppInfoWithClassHierarchy> appView) {
if (!appView.options().apiModelingOptions().isCheckAllApiReferencesAreSet()) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index b4ac86e..09a8ceb 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -123,7 +123,6 @@
private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>();
private boolean disableTreeShaking = false;
private boolean disableMinification = false;
- private boolean disableVerticalClassMerging = false;
private boolean forceProguardCompatibility = false;
private Optional<Boolean> includeDataResources = Optional.empty();
private StringConsumer proguardUsageConsumer = null;
@@ -140,6 +139,8 @@
private SemanticVersion fakeCompilerVersion = null;
private AndroidResourceProvider androidResourceProvider = null;
private AndroidResourceConsumer androidResourceConsumer = null;
+ private ResourceShrinkerConfiguration resourceShrinkerConfiguration =
+ ResourceShrinkerConfiguration.DEFAULT_CONFIGURATION;
private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder =
ProguardConfigurationParserOptions.builder().readEnvironment();
@@ -168,10 +169,6 @@
// Internal
- void setDisableVerticalClassMerging(boolean disableVerticalClassMerging) {
- this.disableVerticalClassMerging = disableVerticalClassMerging;
- }
-
@Override
Builder self() {
return this;
@@ -539,6 +536,19 @@
return this;
}
+ /**
+ * API for configuring resource shrinking.
+ *
+ * <p>Set the configuration properties on the provided builder.
+ */
+ public Builder setResourceShrinkerConfiguration(
+ Function<ResourceShrinkerConfiguration.Builder, ResourceShrinkerConfiguration>
+ configurationBuilder) {
+ this.resourceShrinkerConfiguration =
+ configurationBuilder.apply(ResourceShrinkerConfiguration.builder(getReporter()));
+ return this;
+ }
+
@Override
void validate() {
if (isPrintHelp()) {
@@ -566,8 +576,8 @@
}
}
for (FeatureSplit featureSplit : featureSplits) {
- assert featureSplit.getProgramConsumer() instanceof DexIndexedConsumer;
- if (!(getProgramConsumer() instanceof DexIndexedConsumer)) {
+ verifyResourceSplitOrProgramSplit(featureSplit);
+ if (getProgramConsumer() != null && !(getProgramConsumer() instanceof DexIndexedConsumer)) {
reporter.error("R8 does not support class file output when using feature splits");
}
}
@@ -587,6 +597,11 @@
super.validate();
}
+ private static void verifyResourceSplitOrProgramSplit(FeatureSplit featureSplit) {
+ assert featureSplit.getProgramConsumer() instanceof DexIndexedConsumer
+ || featureSplit.getAndroidResourceProvider() != null;
+ }
+
@Override
R8Command makeCommand() {
// If printing versions ignore everything else.
@@ -663,7 +678,6 @@
desugaring,
configuration.isShrinking(),
configuration.isObfuscating(),
- disableVerticalClassMerging,
forceProguardCompatibility,
includeDataResources,
proguardMapConsumer,
@@ -694,7 +708,8 @@
getClassConflictResolver(),
getCancelCompilationChecker(),
androidResourceProvider,
- androidResourceConsumer);
+ androidResourceConsumer,
+ resourceShrinkerConfiguration);
if (inputDependencyGraphConsumer != null) {
inputDependencyGraphConsumer.finished();
@@ -867,7 +882,6 @@
private final ProguardConfiguration proguardConfiguration;
private final boolean enableTreeShaking;
private final boolean enableMinification;
- private final boolean disableVerticalClassMerging;
private final boolean forceProguardCompatibility;
private final Optional<Boolean> includeDataResources;
private final StringConsumer proguardMapConsumer;
@@ -885,6 +899,7 @@
private final boolean enableMissingLibraryApiModeling;
private final AndroidResourceProvider androidResourceProvider;
private final AndroidResourceConsumer androidResourceConsumer;
+ private final ResourceShrinkerConfiguration resourceShrinkerConfiguration;
/** Get a new {@link R8Command.Builder}. */
public static Builder builder() {
@@ -950,7 +965,6 @@
DesugarState enableDesugaring,
boolean enableTreeShaking,
boolean enableMinification,
- boolean disableVerticalClassMerging,
boolean forceProguardCompatibility,
Optional<Boolean> includeDataResources,
StringConsumer proguardMapConsumer,
@@ -981,7 +995,8 @@
ClassConflictResolver classConflictResolver,
CancelCompilationChecker cancelCompilationChecker,
AndroidResourceProvider androidResourceProvider,
- AndroidResourceConsumer androidResourceConsumer) {
+ AndroidResourceConsumer androidResourceConsumer,
+ ResourceShrinkerConfiguration resourceShrinkerConfiguration) {
super(
inputApp,
mode,
@@ -1010,7 +1025,6 @@
this.proguardConfiguration = proguardConfiguration;
this.enableTreeShaking = enableTreeShaking;
this.enableMinification = enableMinification;
- this.disableVerticalClassMerging = disableVerticalClassMerging;
this.forceProguardCompatibility = forceProguardCompatibility;
this.includeDataResources = includeDataResources;
this.proguardMapConsumer = proguardMapConsumer;
@@ -1028,6 +1042,7 @@
this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
this.androidResourceProvider = androidResourceProvider;
this.androidResourceConsumer = androidResourceConsumer;
+ this.resourceShrinkerConfiguration = resourceShrinkerConfiguration;
}
private R8Command(boolean printHelp, boolean printVersion) {
@@ -1036,7 +1051,6 @@
proguardConfiguration = null;
enableTreeShaking = false;
enableMinification = false;
- disableVerticalClassMerging = false;
forceProguardCompatibility = false;
includeDataResources = null;
proguardMapConsumer = null;
@@ -1054,6 +1068,7 @@
enableMissingLibraryApiModeling = false;
androidResourceProvider = null;
androidResourceConsumer = null;
+ resourceShrinkerConfiguration = null;
}
public DexItemFactory getDexItemFactory() {
@@ -1108,13 +1123,11 @@
assert internal.isOptimizing() || horizontalClassMergerOptions.isRestrictedToSynthetics();
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
- assert internal.enableVerticalClassMerging || !internal.isOptimizing();
if (!internal.isShrinking()) {
// If R8 is not shrinking, there is no point in running various optimizations since the
// optimized classes will still remain in the program (the application size could increase).
internal.enableEnumUnboxing = false;
- internal.enableVerticalClassMerging = false;
}
// Amend the proguard-map consumer with options from the proguard configuration.
@@ -1193,9 +1206,6 @@
// EXPERIMENTAL flags.
assert !internal.forceProguardCompatibility;
internal.forceProguardCompatibility = forceProguardCompatibility;
- if (disableVerticalClassMerging) {
- internal.enableVerticalClassMerging = false;
- }
internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
@@ -1230,6 +1240,7 @@
internal.androidResourceProvider = androidResourceProvider;
internal.androidResourceConsumer = androidResourceConsumer;
+ internal.resourceShrinkerConfiguration = resourceShrinkerConfiguration;
if (!DETERMINISTIC_DEBUGGING) {
assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinkerConfiguration.java b/src/main/java/com/android/tools/r8/ResourceShrinkerConfiguration.java
new file mode 100644
index 0000000..1a5b781
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ResourceShrinkerConfiguration.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2023, 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;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+/**
+ * Resource shrinker configuration. Allows building an immutable structure of resource shrinker
+ * settings.
+ *
+ * <p>A {@link ResourceShrinkerConfiguration} can be added to a {@link R8Command} change the way
+ * resource shrinking is performed.
+ *
+ * <p>To build a {@link ResourceShrinkerConfiguration} use the {@link
+ * ResourceShrinkerConfiguration.Builder} class, available through the {@link R8Command.Builder}.
+ * For example:
+ *
+ * <pre>
+ * R8Command command = R8Command.builder()
+ * .addProgramFiles(path1, path2)
+ * .setMode(CompilationMode.RELEASE)
+ * .setProgramConsumer(programConsumer)
+ * .setResourceShrinkerConfiguration(builder -> builder
+ * .enableOptimizedShrinkingWithR8()
+ * .build())
+ * .build();
+ * </pre>
+ */
+@KeepForApi
+public class ResourceShrinkerConfiguration {
+ public static ResourceShrinkerConfiguration DEFAULT_CONFIGURATION =
+ new ResourceShrinkerConfiguration(false, true);
+
+ private final boolean optimizedShrinking;
+ private final boolean preciseShrinking;
+
+ private ResourceShrinkerConfiguration(boolean optimizedShrinking, boolean preciseShrinking) {
+ this.optimizedShrinking = optimizedShrinking;
+ this.preciseShrinking = preciseShrinking;
+ }
+
+ public static Builder builder(DiagnosticsHandler handler) {
+ return new Builder();
+ }
+
+ public boolean isOptimizedShrinking() {
+ return optimizedShrinking;
+ }
+
+ public boolean isPreciseShrinking() {
+ return preciseShrinking;
+ }
+
+ /**
+ * Builder for constructing a ResourceShrinkerConfiguration.
+ *
+ * <p>A builder is obtained by calling setResourceShrinkerConfiguration on a {@link
+ * R8Command.Builder}.
+ */
+ @KeepForApi
+ public static class Builder {
+
+ private boolean optimizedShrinking = false;
+ private boolean preciseShrinking = true;
+
+ private Builder() {}
+
+ /**
+ * Enable R8 based resource shrinking.
+ *
+ * <p>If this is not set, r8 will use resource shrinking legacy mode where resource shrinking is
+ * done after code has been generated. This is consistent with a setup where resource shrinking
+ * is run seperately from R8.
+ *
+ * <p>Setting this option allows R8 to shrink resources as part of its normal compilation,
+ * tracing resources throughout the pipeline.
+ */
+ public Builder enableOptimizedShrinkingWithR8() {
+ assert preciseShrinking;
+ this.optimizedShrinking = true;
+ return this;
+ }
+
+ /**
+ * Disable precise shrinking.
+ *
+ * <p>The resource table will not be rewritten. Unused entries in the res folder will be
+ * replaced by small dummy files.
+ */
+ @Deprecated
+ public Builder disablePreciseShrinking() {
+ assert !optimizedShrinking;
+ this.preciseShrinking = false;
+ return this;
+ }
+
+ /** Build and return the {@link ResourceShrinkerConfiguration} */
+ public ResourceShrinkerConfiguration build() {
+ return new ResourceShrinkerConfiguration(optimizedShrinking, preciseShrinking);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index e49137e..5a2fa56 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -52,9 +52,6 @@
public final List<String> proguardConfig;
public boolean printHelpAndExit;
- // Flags to disable experimental features.
- public boolean disableVerticalClassMerging;
-
CompatProguardOptions(
List<String> proguardConfig,
String output,
@@ -67,8 +64,7 @@
MapIdProvider mapIdProvider,
SourceFileProvider sourceFileProvider,
String depsFileOutput,
- boolean printHelpAndExit,
- boolean disableVerticalClassMerging) {
+ boolean printHelpAndExit) {
this.output = output;
this.mode = mode;
this.minApi = minApi;
@@ -81,7 +77,6 @@
this.sourceFileProvider = sourceFileProvider;
this.depsFileOutput = depsFileOutput;
this.printHelpAndExit = printHelpAndExit;
- this.disableVerticalClassMerging = disableVerticalClassMerging;
}
public static CompatProguardOptions parse(String[] args) {
@@ -98,8 +93,6 @@
MapIdProvider mapIdProvider = null;
SourceFileProvider sourceFileProvider = null;
String depsFileOutput = null;
- // Flags to disable experimental features.
- boolean disableVerticalClassMerging = false;
ImmutableList.Builder<String> builder = ImmutableList.builder();
if (args.length > 0) {
@@ -139,8 +132,6 @@
sourceFileProvider = SourceFileTemplateProvider.create(args[++i], handler);
} else if (arg.equals("--deps-file")) {
depsFileOutput = args[++i];
- } else if (arg.equals("--no-vertical-class-merging")) {
- disableVerticalClassMerging = true;
} else if (arg.equals("--core-library")
|| arg.equals("--minimal-main-dex")
|| arg.equals("--no-locals")) {
@@ -177,8 +168,7 @@
mapIdProvider,
sourceFileProvider,
depsFileOutput,
- printHelpAndExit,
- disableVerticalClassMerging);
+ printHelpAndExit);
}
public static void print() {
@@ -218,8 +208,7 @@
return;
}
CompatProguardCommandBuilder builder =
- new CompatProguardCommandBuilder(
- options.forceProguardCompatibility, options.disableVerticalClassMerging);
+ new CompatProguardCommandBuilder(options.forceProguardCompatibility);
builder
.setOutput(Paths.get(options.output), OutputMode.DexIndexed, options.includeDataResources)
.addProguardConfiguration(options.proguardConfig, CommandLineOrigin.INSTANCE)
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 8232b0c..47f7ed6 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -169,7 +169,7 @@
Diagnostic message = new StringDiagnostic("Dumped compilation inputs to: " + dumpOutput);
if (dumpInputFlags.shouldFailCompilation()) {
throw options.reporter.fatalError(message);
- } else {
+ } else if (dumpInputFlags.shouldLogDumpInfoMessage()) {
options.reporter.info(message);
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index e6c4db1..105ea1c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -836,7 +836,7 @@
options.itemFactory));
}
- if (options.emitNestAnnotationsInDex) {
+ if (options.canUseNestBasedAccess()) {
if (clazz.isNestHost()) {
annotations.add(
DexAnnotation.createNestMembersAnnotation(
diff --git a/src/main/java/com/android/tools/r8/dump/CompilerDump.java b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
index 7655a6e..7febf31 100644
--- a/src/main/java/com/android/tools/r8/dump/CompilerDump.java
+++ b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
@@ -45,6 +45,10 @@
return directory.resolve("proguard.config");
}
+ public boolean hasDesugaredLibrary() {
+ return Files.exists(directory.resolve("desugared-library.json"));
+ }
+
public Path getDesugaredLibraryFile() {
return directory.resolve("desugared-library.json");
}
diff --git a/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
index b541ff1..01dc9d4 100644
--- a/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
@@ -5,7 +5,8 @@
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
/** Common interface type for all diagnostics related to desugaring. */
-@KeepForApi
+@KeepForApi(kind = KeepItemKind.ONLY_CLASS)
public interface DesugarDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
index 6156e58..81b9215 100644
--- a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.errors;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
/** Common interface type for all diagnostics related to interface-method desugaring. */
-@KeepForApi
+@KeepForApi(kind = KeepItemKind.ONLY_CLASS)
public interface InterfaceDesugarDiagnostic extends DesugarDiagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
index 95cf81e..d454b19 100644
--- a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
@@ -5,7 +5,8 @@
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
/** Base interface for diagnostics related to proguard keep rules. */
-@KeepForApi
+@KeepForApi(kind = KeepItemKind.ONLY_CLASS)
public interface ProguardKeepRuleDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
index b252ad4..ead08b8 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -71,8 +71,7 @@
OptionalBool callerIsStartupMethod = isStartupMethod(caller, startupProfile);
if (callerIsStartupMethod.isTrue()) {
// If the caller is a startup method, then only allow inlining if the callee is also a
- // startup
- // method.
+ // startup method.
if (isStartupMethod(callee, startupProfile).isFalse()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 07512e5..dc2094f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.origin.GlobalSyntheticOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -293,4 +294,9 @@
? FieldResolutionResult.createSingleFieldResolutionResult(clazz, clazz, definition)
: FieldResolutionResult.unknown();
}
+
+ public void notifyHorizontalClassMergerFinished(
+ HorizontalClassMerger.Mode horizontalClassMergerMode) {
+ // Intentionally empty.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index d121559..1b9b0b0 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.utils.TraversalContinuation.doContinue;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -95,11 +94,6 @@
commit, getClassToFeatureSplitMap(), getMainDexInfo(), getMissingClasses());
}
- public void notifyHorizontalClassMergerFinished(
- HorizontalClassMerger.Mode horizontalClassMergerMode) {
- // Intentionally empty.
- }
-
public void notifyMinifierFinished() {
// Intentionally empty.
}
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 6c86022..3e3fe4c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.contexts.CompilationContext;
@@ -13,7 +14,6 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.InitClassLens;
import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
@@ -64,6 +64,7 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.threads.ThreadTask;
import com.android.tools.r8.utils.threads.ThreadTaskUtils;
+import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
@@ -78,9 +79,13 @@
public class AppView<T extends AppInfo> implements DexDefinitionSupplier, LibraryModeledPredicate {
- private enum WholeProgramOptimizations {
+ public enum WholeProgramOptimizations {
ON,
- OFF
+ OFF;
+
+ public boolean isOn() {
+ return this == ON;
+ }
}
private T appInfo;
@@ -144,6 +149,8 @@
private SeedMapper applyMappingSeedMapper;
+ R8ResourceShrinkerState resourceShrinkerState = null;
+
// When input has been (partially) desugared these are the classes which has been library
// desugared. This information is populated in the IR converter.
private Set<DexType> alreadyLibraryDesugared = null;
@@ -526,6 +533,10 @@
return wholeProgramOptimizations == WholeProgramOptimizations.ON;
}
+ public WholeProgramOptimizations getWholeProgramOptimizations() {
+ return wholeProgramOptimizations;
+ }
+
/**
* Create a new processor context.
*
@@ -691,6 +702,7 @@
}
public void setCfByteCodePassThrough(Set<DexMethod> cfByteCodePassThrough) {
+ assert options().enableCfByteCodePassThrough;
this.cfByteCodePassThrough = cfByteCodePassThrough;
}
@@ -813,11 +825,9 @@
HorizontallyMergedClasses horizontallyMergedClasses, HorizontalClassMerger.Mode mode) {
assert !hasHorizontallyMergedClasses() || mode.isFinal();
this.horizontallyMergedClasses = horizontallyMergedClasses().extend(horizontallyMergedClasses);
- if (mode.isFinal()) {
- testing()
- .horizontallyMergedClassesConsumer
- .accept(dexItemFactory(), horizontallyMergedClasses());
- }
+ testing()
+ .horizontallyMergedClassesConsumer
+ .accept(dexItemFactory(), horizontallyMergedClasses(), mode);
}
public boolean hasVerticallyMergedClasses() {
@@ -828,7 +838,7 @@
* Get the result of vertical class merging. Returns null if vertical class merging has not been
* run.
*/
- public VerticallyMergedClasses verticallyMergedClasses() {
+ public VerticallyMergedClasses getVerticallyMergedClasses() {
return verticallyMergedClasses;
}
@@ -861,6 +871,14 @@
testing().unboxedEnumsConsumer.accept(dexItemFactory(), unboxedEnums);
}
+ public R8ResourceShrinkerState getResourceShrinkerState() {
+ return resourceShrinkerState;
+ }
+
+ public void setResourceShrinkerState(R8ResourceShrinkerState resourceShrinkerState) {
+ this.resourceShrinkerState = resourceShrinkerState;
+ }
+
public boolean validateUnboxedEnumsHaveBeenPruned() {
for (DexType unboxedEnum : unboxedEnums.computeAllUnboxedEnums()) {
assert appInfo.definitionForWithoutExistenceAssert(unboxedEnum) == null
@@ -913,9 +931,10 @@
}
public boolean isCfByteCodePassThrough(DexEncodedMethod method) {
- if (!options().isGeneratingClassFiles()) {
+ if (!options().enableCfByteCodePassThrough) {
return false;
}
+ assert options().isGeneratingClassFiles();
if (cfByteCodePassThrough.contains(method.getReference())) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index bc71dd6..fe5b2c4 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -296,13 +296,12 @@
}
@Override
- public int estimatedSizeForInlining() {
- return countNonStackOperations(Integer.MAX_VALUE);
- }
-
- @Override
- public boolean estimatedSizeForInliningAtMost(int threshold) {
- return countNonStackOperations(threshold) <= threshold;
+ public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) {
+ int estimatedSizeForInlining = countNonStackOperations(threshold);
+ if (estimatedSizeForInlining <= threshold) {
+ return estimatedSizeForInlining;
+ }
+ return -1;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
index 4b48516..8635555 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
@@ -5,12 +5,12 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.ThrowingConsumer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
-import java.util.function.Consumer;
abstract class ClassHierarchyTraversal<
T extends DexClass, CHT extends ClassHierarchyTraversal<T, CHT>> {
@@ -61,7 +61,8 @@
return self();
}
- public void visit(Iterable<? extends DexClass> sources, Consumer<T> visitor) {
+ public <E extends Throwable> void visit(
+ Iterable<? extends DexClass> sources, ThrowingConsumer<T, E> visitor) throws E {
Iterator<? extends DexClass> sourceIterator = sources.iterator();
// Visit the program classes in the order that is implemented by addDependentsToWorklist().
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 36d8464..dfc2117 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -141,13 +141,17 @@
}
/** Estimate the number of IR instructions emitted by buildIR(). */
- public int estimatedSizeForInlining() {
- return Integer.MAX_VALUE;
+ public final int estimatedSizeForInlining() {
+ return getEstimatedSizeForInliningIfLessThanOrEquals(Integer.MAX_VALUE);
}
/** Compute estimatedSizeForInlining() <= threshold. */
- public boolean estimatedSizeForInliningAtMost(int threshold) {
- return estimatedSizeForInlining() <= threshold;
+ public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) {
+ throw new Unreachable(getClass().getTypeName());
+ }
+
+ public final boolean estimatedSizeForInliningAtMost(int threshold) {
+ return getEstimatedSizeForInliningIfLessThanOrEquals(threshold) >= 0;
}
public abstract int estimatedDexCodeSizeUpperBoundInBytes();
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 906a0be..2974981 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -101,7 +101,6 @@
return this;
}
- @SuppressWarnings("ReferenceEquality")
private static boolean hasDefaultInstanceInitializerCode(
ProgramMethod method, AppView<?> appView) {
if (!method.getDefinition().isInstanceInitializer()) {
@@ -126,7 +125,7 @@
Iterator<CfInstruction> instructionIterator = cfCode.getInstructions().iterator();
// Allow skipping CfPosition instructions in instance initializers that only call Object.<init>.
Predicate<CfInstruction> instructionOfInterest =
- method.getHolder().getSuperType() == dexItemFactory.objectType
+ method.getHolder().getSuperType().isIdenticalTo(dexItemFactory.objectType)
? instruction -> !instruction.isLabel() && !instruction.isPosition()
: instruction -> !instruction.isLabel();
CfLoad load = IteratorUtils.nextUntil(instructionIterator, instructionOfInterest).asLoad();
@@ -136,7 +135,7 @@
CfInvoke invoke = instructionIterator.next().asInvoke();
if (invoke == null
|| !invoke.isInvokeConstructor(dexItemFactory)
- || invoke.getMethod() != getParentConstructor(method, dexItemFactory)) {
+ || invoke.getMethod().isNotIdenticalTo(getParentConstructor(method, dexItemFactory))) {
return false;
}
return instructionIterator.next().isReturnVoid();
@@ -235,8 +234,17 @@
}
@Override
+ public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) {
+ int estimatedSizeForInlining = estimatedDexCodeSizeUpperBoundInBytes();
+ if (estimatedSizeForInlining <= threshold) {
+ return estimatedSizeForInlining;
+ }
+ return -1;
+ }
+
+ @Override
public TryHandler[] getHandlers() {
- return new TryHandler[0];
+ return TryHandler.EMPTY_ARRAY;
}
@Override
@@ -278,7 +286,7 @@
@Override
public Try[] getTries() {
- return new Try[0];
+ return Try.EMPTY_ARRAY;
}
@Override
@@ -413,7 +421,7 @@
@Override
public DexWritableCacheKey getCacheLookupKey(ProgramMethod method, DexItemFactory factory) {
- return new AmendedDexWritableCodeKey<DexMethod>(
+ return new AmendedDexWritableCodeKey<>(
this,
getParentConstructor(method, factory),
getIncomingRegisterSize(method),
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
new file mode 100644
index 0000000..b8fe0c2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, 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.graph;
+
+public class DefaultUseRegistry<T extends Definition> extends UseRegistry<T> {
+
+ public DefaultUseRegistry(AppView<?> appView, T context) {
+ super(appView, context);
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ // Intentionally empty.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistryWithResult.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistryWithResult.java
new file mode 100644
index 0000000..5b3fec0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistryWithResult.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2023, 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.graph;
+
+public class DefaultUseRegistryWithResult<R, T extends Definition>
+ extends UseRegistryWithResult<R, T> {
+
+ public DefaultUseRegistryWithResult(AppView<?> appView, T context) {
+ super(appView, context);
+ }
+
+ public DefaultUseRegistryWithResult(AppView<?> appView, T context, R defaultResult) {
+ super(appView, context, defaultResult);
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ // Intentionally empty.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e8c908a..2a80c0b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -734,6 +734,10 @@
return superType;
}
+ public void setSuperType(DexType superType) {
+ this.superType = superType;
+ }
+
public boolean hasClassInitializer() {
return getClassInitializer() != null;
}
@@ -1194,6 +1198,8 @@
/** Returns kotlin class info if the class is synthesized by kotlin compiler. */
public abstract KotlinClassLevelInfo getKotlinInfo();
+ public abstract ClassKind<?> getKind();
+
public final String getSimpleName() {
return getType().getSimpleName();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 2937cd2..9c11896 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -126,6 +126,11 @@
}
@Override
+ public ClassKind<DexClasspathClass> getKind() {
+ return ClassKind.CLASSPATH;
+ }
+
+ @Override
public DexClasspathClass get() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e800b21..00febce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -286,11 +286,6 @@
}
@Override
- public int estimatedSizeForInlining() {
- return codeSizeInBytes();
- }
-
- @Override
public int estimatedDexCodeSizeUpperBoundInBytes() {
return codeSizeInBytes();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 2a85b22..f7cc258 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -106,23 +106,4 @@
public void setApiLevelForDefinition(ComputedApiLevel apiLevelForDefinition) {
this.apiLevelForDefinition = apiLevelForDefinition;
}
-
- public boolean hasComputedApiReferenceLevel() {
- return !getApiLevel().isNotSetApiLevel();
- }
-
- @Override
- @SuppressWarnings("EqualsGetClass")
- public final boolean equals(Object other) {
- if (other == this) {
- return true;
- }
- return other.getClass() == getClass()
- && ((DexEncodedMember<?, ?>) other).getReference().equals(getReference());
- }
-
- @Override
- public final int hashCode() {
- return getReference().hashCode();
- }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c1f8ac7..730fb6c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -31,15 +31,11 @@
import com.android.tools.r8.cf.code.CfThrow;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.dex.code.DexConstString;
-import com.android.tools.r8.dex.code.DexInstanceOf;
import com.android.tools.r8.dex.code.DexInstruction;
import com.android.tools.r8.dex.code.DexInvokeDirect;
import com.android.tools.r8.dex.code.DexInvokeStatic;
import com.android.tools.r8.dex.code.DexNewInstance;
-import com.android.tools.r8.dex.code.DexReturn;
import com.android.tools.r8.dex.code.DexThrow;
-import com.android.tools.r8.dex.code.DexXorIntLit8;
-import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
@@ -48,7 +44,6 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
@@ -63,7 +58,6 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.ConsumerUtils;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.RetracerForCodePrinting;
import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -660,75 +654,43 @@
this.kotlinMemberInfo = kotlinMemberInfo;
}
- public boolean isKotlinFunction() {
- return kotlinMemberInfo.isFunction();
- }
-
- public boolean isKotlinExtensionFunction() {
- return kotlinMemberInfo.isFunction() && kotlinMemberInfo.asFunction().isExtensionFunction();
- }
-
public boolean isOnlyInlinedIntoNestMembers() {
return compilationState == PROCESSED_INLINING_CANDIDATE_SAME_NEST;
}
public boolean isInliningCandidate(
- ProgramMethod container,
- Reason inliningReason,
- AppInfoWithClassHierarchy appInfo,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ProgramMethod context,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
checkIfObsolete();
- return isInliningCandidate(
- container.getHolderType(), inliningReason, appInfo, whyAreYouNotInliningReporter);
- }
-
- @SuppressWarnings("ReferenceEquality")
- public boolean isInliningCandidate(
- DexType containerType,
- Reason inliningReason,
- AppInfoWithClassHierarchy appInfo,
- WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- checkIfObsolete();
-
- if (inliningReason == Reason.FORCE) {
- // Make sure we would be able to inline this normally.
- if (!isInliningCandidate(
- containerType, Reason.SIMPLE, appInfo, whyAreYouNotInliningReporter)) {
- // If not, raise a flag, because some optimizations that depend on force inlining would
- // silently produce an invalid code, which is worse than an internal error.
- throw new InternalCompilerError("FORCE inlining on non-inlinable: " + toSourceString());
- }
- return true;
- }
-
- // TODO(b/128967328): inlining candidate should satisfy all states if multiple states are there.
+ AppInfoWithClassHierarchy appInfo = appView.appInfo();
switch (compilationState) {
case PROCESSED_INLINING_CANDIDATE_ANY:
return true;
case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
- if (appInfo.isSubtype(containerType, getReference().holder)) {
+ if (appInfo.isSubtype(context.getHolderType(), getHolderType())) {
return true;
}
whyAreYouNotInliningReporter.reportCallerNotSubtype();
return false;
case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
- if (containerType.isSamePackage(getReference().holder)) {
+ if (context.isSamePackage(getHolderType())) {
return true;
}
whyAreYouNotInliningReporter.reportCallerNotSamePackage();
return false;
case PROCESSED_INLINING_CANDIDATE_SAME_NEST:
- if (NestUtils.sameNest(containerType, getReference().holder, appInfo)) {
+ if (NestUtils.sameNest(context.getHolderType(), getHolderType(), appInfo)) {
return true;
}
whyAreYouNotInliningReporter.reportCallerNotSameNest();
return false;
case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
- if (containerType == getReference().holder) {
+ if (context.getHolderType().isIdenticalTo(getHolderType())) {
return true;
}
whyAreYouNotInliningReporter.reportCallerNotSameClass();
@@ -968,12 +930,6 @@
null);
}
- public Code buildInstanceOfCode(DexType type, boolean negate, InternalOptions options) {
- return options.isGeneratingClassFiles()
- ? buildInstanceOfCfCode(type, negate)
- : buildInstanceOfDexCode(type, negate);
- }
-
public CfCode buildInstanceOfCfCode(DexType type, boolean negate) {
CfInstruction[] instructions = new CfInstruction[3 + BooleanUtils.intValue(negate) * 2];
int i = 0;
@@ -991,17 +947,6 @@
Arrays.asList(instructions));
}
- public DexCode buildInstanceOfDexCode(DexType type, boolean negate) {
- DexInstruction[] instructions = new DexInstruction[2 + BooleanUtils.intValue(negate)];
- int i = 0;
- instructions[i++] = new DexInstanceOf(0, 0, type);
- if (negate) {
- instructions[i++] = new DexXorIntLit8(0, 0, 1);
- }
- instructions[i] = new DexReturn(0);
- return generateCodeFromTemplate(1, 0, instructions);
- }
-
public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
Builder builder =
builder(this)
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 75f9968..92ceda8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -126,7 +126,6 @@
new ConcurrentHashMap<>();
public final LRUCacheTable<InterfaceCollection, InterfaceCollection, InterfaceCollection>
leastUpperBoundOfInterfacesTable = LRUCacheTable.create(8, 8);
-
boolean sorted = false;
// Internal type containing only the null value.
@@ -215,6 +214,7 @@
public final DexString compareToMethodName = createString("compareTo");
public final DexString compareToIgnoreCaseMethodName = createString("compareToIgnoreCase");
public final DexString cloneMethodName = createString("clone");
+ public final DexString formatMethodName = createString("format");
public final DexString substringName = createString("substring");
public final DexString trimName = createString("trim");
@@ -299,6 +299,7 @@
public final DexString serviceLoaderDescriptor = createString("Ljava/util/ServiceLoader;");
public final DexString serviceLoaderConfigurationErrorDescriptor =
createString("Ljava/util/ServiceConfigurationError;");
+ public final DexString localeDescriptor = createString("Ljava/util/Locale;");
public final DexString listDescriptor = createString("Ljava/util/List;");
public final DexString setDescriptor = createString("Ljava/util/Set;");
public final DexString mapDescriptor = createString("Ljava/util/Map;");
@@ -604,8 +605,10 @@
public final DexType javaUtilComparatorType = createStaticallyKnownType("Ljava/util/Comparator;");
public final DexType javaUtilConcurrentTimeUnitType =
createStaticallyKnownType("Ljava/util/concurrent/TimeUnit;");
+ public final DexType javaUtilFormattableType =
+ createStaticallyKnownType("Ljava/util/Formattable;");
public final DexType javaUtilListType = createStaticallyKnownType("Ljava/util/List;");
- public final DexType javaUtilLocaleType = createStaticallyKnownType("Ljava/util/Locale;");
+ public final DexType javaUtilLocaleType = createStaticallyKnownType(localeDescriptor);
public final DexType javaUtilLoggingLevelType =
createStaticallyKnownType("Ljava/util/logging/Level;");
public final DexType javaUtilLoggingLoggerType =
@@ -887,6 +890,38 @@
return createMethod(boxType, proto, valueOfMethodName);
}
+ public BoxUnboxPrimitiveMethodRoundtrip getBoxUnboxPrimitiveMethodRoundtrip(DexType type) {
+ if (type.isPrimitiveType()) {
+ return new BoxUnboxPrimitiveMethodRoundtrip(
+ getBoxPrimitiveMethod(type), getUnboxPrimitiveMethod(type));
+ } else if (primitiveToBoxed.containsValue(type)) {
+ return new BoxUnboxPrimitiveMethodRoundtrip(
+ getUnboxPrimitiveMethod(type), getBoxPrimitiveMethod(type));
+ } else {
+ return null;
+ }
+ }
+
+ public static class BoxUnboxPrimitiveMethodRoundtrip {
+
+ private final DexMethod boxIfPrimitiveElseUnbox;
+ private final DexMethod unboxIfPrimitiveElseBox;
+
+ public BoxUnboxPrimitiveMethodRoundtrip(
+ DexMethod boxIfPrimitiveElseUnbox, DexMethod unboxIfPrimitiveElseBox) {
+ this.boxIfPrimitiveElseUnbox = boxIfPrimitiveElseUnbox;
+ this.unboxIfPrimitiveElseBox = unboxIfPrimitiveElseBox;
+ }
+
+ public DexMethod getBoxIfPrimitiveElseUnbox() {
+ return boxIfPrimitiveElseUnbox;
+ }
+
+ public DexMethod getUnboxIfPrimitiveElseBox() {
+ return unboxIfPrimitiveElseBox;
+ }
+ }
+
public DexType getBoxedForPrimitiveType(DexType primitive) {
assert primitive.isPrimitiveType();
return primitiveToBoxed.get(primitive);
@@ -989,6 +1024,8 @@
objectsMethods.requireNonNull,
objectsMethods.requireNonNullWithMessage,
objectsMethods.requireNonNullWithMessageSupplier,
+ stringMembers.format,
+ stringMembers.formatWithLocale,
stringMembers.valueOf)
.addAll(boxedValueOfMethods())
.addAll(stringBufferMethods.appendMethods)
@@ -2181,6 +2218,9 @@
public final DexMethod compareToIgnoreCase;
public final DexMethod hashCode;
+
+ public final DexMethod format;
+ public final DexMethod formatWithLocale;
public final DexMethod valueOf;
public final DexMethod toString;
public final DexMethod intern;
@@ -2227,6 +2267,19 @@
needsOneString);
hashCode = createMethod(stringType, createProto(intType), hashCodeMethodName);
+ format =
+ createMethod(
+ stringDescriptor,
+ formatMethodName,
+ stringDescriptor,
+ new DexString[] {stringDescriptor, objectArrayDescriptor});
+ formatWithLocale =
+ createMethod(
+ stringDescriptor,
+ formatMethodName,
+ stringDescriptor,
+ new DexString[] {localeDescriptor, stringDescriptor, objectArrayDescriptor});
+
valueOf = createMethod(
stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
toString = createMethod(
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index a145cba..7dc2241 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -144,6 +144,11 @@
}
@Override
+ public ClassKind<DexLibraryClass> getKind() {
+ return ClassKind.LIBRARY;
+ }
+
+ @Override
public DexLibraryClass get() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index c4ee457..a58d4a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -31,6 +31,10 @@
return identical(this, other);
}
+ public final boolean isNotIdenticalTo(DexMethod other) {
+ return !isIdenticalTo(other);
+ }
+
public final DexProto proto;
DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 029fd1e..6f5c0fe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -574,6 +574,11 @@
return kotlinInfo;
}
+ @Override
+ public ClassKind<DexProgramClass> getKind() {
+ return ClassKind.PROGRAM;
+ }
+
public void setKotlinInfo(KotlinClassLevelInfo kotlinInfo) {
assert kotlinInfo != null;
assert this.kotlinInfo == getNoKotlinInfo();
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 9721adc..12b0e3b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -26,6 +26,10 @@
return identical(this, other);
}
+ public final boolean isNotIdenticalTo(DexProto other) {
+ return !isIdenticalTo(other);
+ }
+
public static final DexProto SENTINEL = new DexProto(null, null);
public final DexType returnType;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index ecac370..4809783 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -40,6 +40,10 @@
return identical(this, other);
}
+ public final boolean isNotIdenticalTo(DexType other) {
+ return !isIdenticalTo(other);
+ }
+
public static final DexType[] EMPTY_ARRAY = {};
// Bundletool is merging classes that may originate from a build with an old version of R8.
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index c7fe5c9..0257cfd 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -247,13 +247,8 @@
}
@Override
- public int estimatedSizeForInlining() {
- return asCfCode().estimatedSizeForInlining();
- }
-
- @Override
- public boolean estimatedSizeForInliningAtMost(int threshold) {
- return asCfCode().estimatedSizeForInliningAtMost(threshold);
+ public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) {
+ return asCfCode().getEstimatedSizeForInliningIfLessThanOrEquals(threshold);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 12da839..8ede6f7 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -267,6 +267,11 @@
return this;
}
+ public Builder setAbstract() {
+ flags.setAbstract();
+ return this;
+ }
+
public Builder setBridge() {
flags.setBridge();
return this;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
index 99cd8d2..beb2fb3 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -119,6 +119,10 @@
virtualInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer);
}
+ public boolean isVirtualInvokesDestroyed() {
+ return isThrowingMap(virtualInvokes);
+ }
+
public MethodAccessInfoCollection rewrittenWithLens(
DexDefinitionSupplier definitions, GraphLens lens, Timing timing) {
timing.begin("Rewrite MethodAccessInfoCollection");
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index 4905063..dc2bbdc 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -126,6 +126,15 @@
}
@Override
+ public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) {
+ int estimatedSizeForInlining = estimatedDexCodeSizeUpperBoundInBytes();
+ if (estimatedSizeForInlining <= threshold) {
+ return estimatedSizeForInlining;
+ }
+ return -1;
+ }
+
+ @Override
public TryHandler[] getHandlers() {
return new TryHandler[0];
}
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index a58f744..ddfbba1 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -142,6 +142,15 @@
}
@Override
+ public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) {
+ int estimatedSizeForInlining = estimatedDexCodeSizeUpperBoundInBytes();
+ if (estimatedSizeForInlining <= threshold) {
+ return estimatedSizeForInlining;
+ }
+ return -1;
+ }
+
+ @Override
public TryHandler[] getHandlers() {
return TryHandler.EMPTY_ARRAY;
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index 7947f84..3c2e9f6 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -21,7 +21,7 @@
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.FieldResolutionResult;
+import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -69,7 +69,7 @@
@Override
public void traceStaticFieldRead(
DexField field,
- FieldResolutionResult resolutionResult,
+ SingleFieldResolutionResult<?> resolutionResult,
ProgramMethod context,
EnqueuerWorklist worklist) {
if (isUsingJavaAssertionsDisabledField(field) || isUsingKotlinAssertionsEnabledField(field)) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
index d8dadc5..0642216 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
@@ -6,7 +6,9 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
public interface EnqueuerFieldAccessAnalysis {
@@ -27,7 +29,7 @@
default void traceStaticFieldRead(
DexField field,
- FieldResolutionResult resolutionResult,
+ SingleFieldResolutionResult<?> resolutionResult,
ProgramMethod context,
EnqueuerWorklist worklist) {}
;
@@ -38,4 +40,10 @@
ProgramMethod context,
EnqueuerWorklist worklist) {}
;
+
+ /**
+ * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
+ * perform some post-processing.
+ */
+ default void done(Enqueuer enqueuer) {}
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
index 48ca34d..ed49777 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
@@ -74,7 +75,7 @@
@Override
public void traceStaticFieldRead(
DexField field,
- FieldResolutionResult resolutionResult,
+ SingleFieldResolutionResult<?> resolutionResult,
ProgramMethod context,
EnqueuerWorklist worklist) {
if (isUnsafeToUseFieldOnDalvik(field)) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
new file mode 100644
index 0000000..d4ebe2f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -0,0 +1,212 @@
+// Copyright (c) 2023, 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.graph.analysis;
+
+import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
+import com.android.tools.r8.AndroidResourceInput;
+import com.android.tools.r8.AndroidResourceInput.Kind;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ResourceAccessAnalysis implements EnqueuerFieldAccessAnalysis {
+
+ private final R8ResourceShrinkerState resourceShrinkerState;
+ private final Map<DexProgramClass, RClassFieldToValueStore> fieldToValueMapping =
+ new IdentityHashMap<>();
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+ @SuppressWarnings("UnusedVariable")
+ private ResourceAccessAnalysis(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Enqueuer enqueuer,
+ R8ResourceShrinkerState resourceShrinkerState) {
+ this.appView = appView;
+ this.resourceShrinkerState = resourceShrinkerState;
+ appView.setResourceShrinkerState(resourceShrinkerState);
+ try {
+ for (AndroidResourceInput androidResource :
+ appView.options().androidResourceProvider.getAndroidResources()) {
+ if (androidResource.getKind() == Kind.RESOURCE_TABLE) {
+ resourceShrinkerState.setResourceTableInput(androidResource.getByteStream());
+ break;
+ }
+ }
+ } catch (ResourceException e) {
+ throw appView.reporter().fatalError("Failed initializing resource table");
+ }
+ }
+
+ public static void register(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+ if (enabled(appView, enqueuer)) {
+ enqueuer.registerFieldAccessAnalysis(
+ new ResourceAccessAnalysis(appView, enqueuer, new R8ResourceShrinkerState()));
+ }
+ }
+
+ @Override
+ public void done(Enqueuer enqueuer) {
+ appView.setResourceShrinkerState(resourceShrinkerState);
+ EnqueuerFieldAccessAnalysis.super.done(enqueuer);
+ }
+
+ private static boolean enabled(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+ // For now, we only do the resource tracing in the initial round since we don't track inlining
+ // of values yet.
+ return appView.options().androidResourceProvider != null
+ && appView.options().resourceShrinkerConfiguration.isOptimizedShrinking()
+ && enqueuer.getMode().isInitialTreeShaking();
+ }
+
+ @Override
+ public void traceStaticFieldRead(
+ DexField field,
+ SingleFieldResolutionResult<?> resolutionResult,
+ ProgramMethod context,
+ EnqueuerWorklist worklist) {
+ ProgramField resolvedField = resolutionResult.getProgramField();
+ if (resolvedField == null) {
+ return;
+ }
+ if (getMaybeCachedIsRClass(resolvedField.getHolder())) {
+ DexProgramClass holderType = resolvedField.getHolder();
+ if (!fieldToValueMapping.containsKey(holderType)) {
+ populateRClassValues(resolvedField);
+ }
+ assert fieldToValueMapping.containsKey(holderType);
+ RClassFieldToValueStore rClassFieldToValueStore = fieldToValueMapping.get(holderType);
+ IntList integers = rClassFieldToValueStore.valueMapping.get(field);
+ for (Integer integer : integers) {
+ resourceShrinkerState.trace(integer);
+ }
+ }
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private void populateRClassValues(ProgramField field) {
+ // TODO(287398085): Pending discussions with the AAPT2 team, we might need to harden this
+ // to not fail if we wrongly classify an unrelated class as R class in our heuristic..
+ RClassFieldToValueStore.Builder rClassValueBuilder = new RClassFieldToValueStore.Builder();
+ ProgramMethod programClassInitializer = field.getHolder().getProgramClassInitializer();
+ if (programClassInitializer == null) {
+ // No initialization of fields, empty R class.
+ return;
+ }
+ IRCode code = programClassInitializer.buildIR(appView, MethodConversionOptions.nonConverting());
+
+ // We handle two cases:
+ // - Simple integer field assigments.
+ // - Assigments of integer arrays to fields.
+ for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
+ Value value = staticPut.value();
+ if (value.isPhi()) {
+ continue;
+ }
+ IntList values;
+ Instruction definition = staticPut.value().definition;
+ if (definition.isConstNumber()) {
+ values = new IntArrayList(1);
+ values.add(definition.asConstNumber().getIntValue());
+ } else if (definition.isNewArrayEmpty()) {
+ NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
+ values = new IntArrayList();
+ for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) {
+ if (uniqueUser.isArrayPut()) {
+ Value constValue = uniqueUser.asArrayPut().value();
+ if (constValue.isConstNumber()) {
+ values.add(constValue.getDefinition().asConstNumber().getIntValue());
+ }
+ } else {
+ assert uniqueUser == staticPut;
+ }
+ }
+ } else if (definition.isNewArrayFilled()) {
+ values = new IntArrayList();
+ for (Value inValue : definition.asNewArrayFilled().inValues()) {
+ if (value.isPhi()) {
+ continue;
+ }
+ Instruction valueDefinition = inValue.definition;
+ if (valueDefinition.isConstNumber()) {
+ values.add(valueDefinition.asConstNumber().getIntValue());
+ }
+ }
+ } else {
+ continue;
+ }
+ rClassValueBuilder.addMapping(staticPut.getField(), values);
+ }
+
+ fieldToValueMapping.put(field.getHolder(), rClassValueBuilder.build());
+ }
+
+ private final Map<DexProgramClass, Boolean> cachedClassLookups = new IdentityHashMap<>();
+
+ private boolean getMaybeCachedIsRClass(DexProgramClass holder) {
+ Boolean result = cachedClassLookups.get(holder);
+ if (result != null) {
+ return result;
+ }
+ String simpleClassName =
+ DescriptorUtils.getSimpleClassNameFromDescriptor(holder.getType().toDescriptorString());
+ List<String> split = StringUtils.split(simpleClassName, '$');
+
+ if (split.size() < 2) {
+ cachedClassLookups.put(holder, false);
+ return false;
+ }
+ String type = split.get(split.size() - 1);
+ String rClass = split.get(split.size() - 2);
+ // We match on R if:
+ // - The name of the Class is R$type - we allow R to be an inner class.
+ // - The inner type should be with lower case
+ boolean isRClass = Character.isLowerCase(type.charAt(0)) && rClass.equals("R");
+ cachedClassLookups.put(holder, isRClass);
+ return isRClass;
+ }
+
+ private static class RClassFieldToValueStore {
+ private final Map<DexField, IntList> valueMapping;
+
+ private RClassFieldToValueStore(Map<DexField, IntList> valueMapping) {
+ this.valueMapping = valueMapping;
+ }
+
+ public static class Builder {
+ private final Map<DexField, IntList> valueMapping = new IdentityHashMap<>();
+
+ public void addMapping(DexField field, IntList values) {
+ assert !valueMapping.containsKey(field);
+ valueMapping.put(field, values);
+ }
+
+ public RClassFieldToValueStore build() {
+ return new RClassFieldToValueStore(valueMapping);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index ef55a1f..b34bf05 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -462,8 +463,10 @@
}
public <T extends DexReference> boolean assertPinnedNotModified(
- KeepInfoCollection keepInfo, InternalOptions options) {
+ AppView<AppInfoWithLiveness> appView) {
List<DexReference> pinnedItems = new ArrayList<>();
+ KeepInfoCollection keepInfo = appView.getKeepInfo();
+ InternalOptions options = appView.options();
keepInfo.forEachPinnedType(pinnedItems::add, options);
keepInfo.forEachPinnedMethod(pinnedItems::add, options);
keepInfo.forEachPinnedField(pinnedItems::add, options);
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
index cdd27a5..d906b4c 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -117,7 +117,7 @@
}
@Override
- protected DexType getNextClassType(DexType type) {
+ public DexType getNextClassType(DexType type) {
return typeMap.getRepresentativeValueOrDefault(type, type);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index ce123a1..ab20f7d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -86,8 +86,8 @@
public void runIfNecessary(
ExecutorService executorService, Timing timing, RuntimeTypeCheckInfo runtimeTypeCheckInfo)
throws ExecutionException {
- if (options.isEnabled(mode)) {
- timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
+ timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
+ if (shouldRun()) {
IRCodeProvider codeProvider =
appView.hasClassHierarchy()
? IRCodeProvider.create(appView.withClassHierarchy(), this::getConversionOptions)
@@ -99,11 +99,17 @@
// Clear type elements cache after IR building.
appView.dexItemFactory().clearTypeElementsCache();
appView.notifyOptimizationFinishedForTesting();
-
- timing.end();
} else {
appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
}
+ appView.appInfo().notifyHorizontalClassMergerFinished(mode);
+ assert ArtProfileCompletenessChecker.verify(appView);
+ timing.end();
+ }
+
+ private boolean shouldRun() {
+ return options.isEnabled(mode, appView.getWholeProgramOptimizations())
+ && !appView.hasCfByteCodePassThroughMethods();
}
private MutableMethodConversionOptions getConversionOptions() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
index 2e0a307..5416af5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -9,8 +9,8 @@
import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexField;
@@ -19,7 +19,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.horizontalclassmerging.policies.deadlock.SingleCallerInformation;
@@ -39,7 +38,6 @@
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -408,7 +406,7 @@
return true;
}
- class TracerUseRegistry extends UseRegistry<ProgramMethod> {
+ class TracerUseRegistry extends DefaultUseRegistry<ProgramMethod> {
TracerUseRegistry(ProgramMethod context) {
super(appView(), context);
@@ -570,11 +568,6 @@
}
@Override
- public void registerTypeReference(DexType type) {
- // Intentionally empty, new-array etc. does not trigger any class initialization.
- }
-
- @Override
public void registerCallSite(DexCallSite callSite) {
if (isLambdaMetafactoryMethod(callSite, appView().appInfo())) {
// Use of lambda metafactory does not trigger any class initialization.
@@ -582,39 +575,6 @@
fail();
}
}
-
- @Override
- public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
- // Intentionally empty, does not trigger any class initialization.
- }
-
- @Override
- public void registerConstClass(
- DexType type,
- ListIterator<? extends CfOrDexInstruction> iterator,
- boolean ignoreCompatRules) {
- // Intentionally empty, does not trigger any class initialization.
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {
- // Intentionally empty, does not trigger any class initialization.
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- // Intentionally empty, does not trigger any class initialization.
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- // Intentionally empty, does not trigger any class initialization.
- }
-
- @Override
- public void registerExceptionGuard(DexType guard) {
- // Intentionally empty, does not trigger any class initialization.
- }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
index d47b3fb..7e32fd3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
@@ -22,7 +22,7 @@
}
private boolean isSynchronizationClass(DexProgramClass clazz) {
- return appView.appInfo().isLockCandidate(clazz.type) || clazz.hasStaticSynchronizedMethods();
+ return appView.appInfo().isLockCandidate(clazz) || clazz.hasStaticSynchronizedMethods();
}
// TODO(b/270398965): Replace LinkedList.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVerticallyMergedClasses.java
index c7534b3..3259fe7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVerticallyMergedClasses.java
@@ -22,10 +22,10 @@
@Override
public boolean canMerge(DexProgramClass program) {
- if (appView.verticallyMergedClasses() == null) {
+ if (appView.getVerticallyMergedClasses() == null) {
return true;
}
- return !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(program.type);
+ return !appView.getVerticallyMergedClasses().hasBeenMergedIntoSubtype(program.type);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
index 2daa302..cb443c4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -19,7 +19,7 @@
@Override
public boolean canMerge(DexProgramClass clazz) {
- return !appView.appInfo().isNoHorizontalClassMergingOfType(clazz.getType());
+ return !appView.appInfo().isNoHorizontalClassMergingOfType(clazz);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index ded7e1d..12ef6dc 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -16,8 +16,8 @@
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.verticalclassmerging.IllegalAccessDetector;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
index af868d9..9552f5f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/deadlock/SingleCallerInformation.java
@@ -6,12 +6,12 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistry;
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.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.conversion.callgraph.CallGraph;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
@@ -94,7 +94,7 @@
method.registerCodeReferences(new InvokeExtractor(appView, method));
}
- private class InvokeExtractor extends UseRegistry<ProgramMethod> {
+ private class InvokeExtractor extends DefaultUseRegistry<ProgramMethod> {
private final AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierachy;
@@ -190,16 +190,6 @@
}
@Override
- public void registerInstanceFieldRead(DexField field) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- // Intentionally empty.
- }
-
- @Override
public void registerInvokeDirect(DexMethod method) {
DexMethod rewrittenMethod =
appViewWithClassHierachy
@@ -267,11 +257,6 @@
DexField rewrittenField = appViewWithClassHierachy.graphLens().lookupField(field);
triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType());
}
-
- @Override
- public void registerTypeReference(DexType type) {
- // Intentionally empty.
- }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index b3b0924..38b433a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -57,9 +57,13 @@
return true;
}
+ public Map<Value, AbstractValue> analyze(IRCode code) {
+ return new SparseConditionalConstantPropagationOnCode(code).analyze().mapping;
+ }
+
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
- return new SparseConditionalConstantPropagationOnCode(code).run();
+ return new SparseConditionalConstantPropagationOnCode(code).analyze().run();
}
private class SparseConditionalConstantPropagationOnCode {
@@ -86,7 +90,7 @@
visitedBlocks = new BitSet(maxBlockNumber);
}
- protected CodeRewriterResult run() {
+ public SparseConditionalConstantPropagationOnCode analyze() {
BasicBlock firstBlock = code.entryBlock();
visitInstructions(firstBlock);
@@ -113,6 +117,10 @@
}
}
}
+ return this;
+ }
+
+ protected CodeRewriterResult run() {
boolean hasChanged = rewriteConstants();
return CodeRewriterResult.hasChanged(hasChanged);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index e033b0b..916b595 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -14,19 +14,18 @@
import com.android.tools.r8.graph.AbstractAccessContexts;
import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
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.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
@@ -321,7 +320,7 @@
return true;
}
- class TrivialFieldAccessUseRegistry extends UseRegistry<ProgramMethod> {
+ class TrivialFieldAccessUseRegistry extends DefaultUseRegistry<ProgramMethod> {
TrivialFieldAccessUseRegistry(ProgramMethod method) {
super(appView(), method);
@@ -494,32 +493,5 @@
public void registerStaticFieldWrite(DexField field) {
registerFieldAccess(field, true, true, BytecodeInstructionMetadata.none());
}
-
- @Override
- public void registerInitClass(DexType clazz) {}
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {}
-
- @Override
- public void registerInvokeDirect(DexMethod method) {}
-
- @Override
- public void registerInvokeStatic(DexMethod method) {}
-
- @Override
- public void registerInvokeInterface(DexMethod method) {}
-
- @Override
- public void registerInvokeSuper(DexMethod method) {}
-
- @Override
- public void registerNewInstance(DexType type) {}
-
- @Override
- public void registerTypeReference(DexType type) {}
-
- @Override
- public void registerInstanceOf(DexType type) {}
}
}
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 468274c..c1a2b62 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
@@ -284,11 +284,8 @@
if (arrayPut.array() != value) {
return null;
}
- if (!arrayPut.index().isConstNumber()) {
- return null;
- }
- int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
- if (index < 0 || index >= valuesSize) {
+ int index = arrayPut.indexIfConstAndInBounds(valuesSize);
+ if (index < 0) {
return null;
}
if (!updateEnumValueState(valuesState, valuesTypes, index, arrayPut.value())) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index a5fa7b7..e8e15bd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -14,7 +14,9 @@
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
+import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
/**
* Equivalent to the {@link InliningReasonStrategy} in {@link #parent} except for invocations
@@ -38,7 +40,9 @@
ProgramMethod target,
ProgramMethod context,
DefaultInliningOracle oracle,
- MethodProcessor methodProcessor) {
+ InliningIRProvider inliningIRProvider,
+ MethodProcessor methodProcessor,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (references.isAbstractGeneratedMessageLiteBuilder(context.getHolder())
&& invoke.isInvokeSuper()) {
// Aggressively inline invoke-super calls inside the GeneratedMessageLite builders. Such
@@ -48,7 +52,14 @@
}
return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target)
? computeInliningReasonForDynamicMethod(invoke, target, context)
- : parent.computeInliningReason(invoke, target, context, oracle, methodProcessor);
+ : parent.computeInliningReason(
+ invoke,
+ target,
+ context,
+ oracle,
+ inliningIRProvider,
+ methodProcessor,
+ whyAreYouNotInliningReporter);
}
@SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index ac5f054..049920e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -380,10 +380,7 @@
ArrayPut arrayPut = instructionIterator.next().asArrayPut();
// Verify that the index correct.
- Value indexValue = arrayPut.index().getAliasedValue();
- if (indexValue.isPhi()
- || !indexValue.definition.isConstNumber()
- || indexValue.definition.asConstNumber().getIntValue() != expectedNextIndex) {
+ if (arrayPut.indexOrDefault(-1) != expectedNextIndex) {
throw new InvalidRawMessageInfoException();
}
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 27a0112..c71f037 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
@@ -101,7 +101,7 @@
return false;
}
- public SingleBoxedNumberValue asSingleBoxedPrimitive() {
+ public SingleBoxedPrimitiveValue asSingleBoxedPrimitive() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedBooleanValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedBooleanValue.java
index 79b991b..b7520f0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedBooleanValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedBooleanValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.ValueFactory;
import com.android.tools.r8.utils.BooleanUtils;
-public class SingleBoxedBooleanValue extends SingleBoxedNumberValue {
+public class SingleBoxedBooleanValue extends SingleBoxedPrimitiveValue {
private static final SingleBoxedBooleanValue FALSE_INSTANCE = new SingleBoxedBooleanValue(false);
private static final SingleBoxedBooleanValue TRUE_INSTANCE = new SingleBoxedBooleanValue(true);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedByteValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedByteValue.java
index f3f1ded..5c9f48c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedByteValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedByteValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.ValueFactory;
-public class SingleBoxedByteValue extends SingleBoxedNumberValue {
+public class SingleBoxedByteValue extends SingleBoxedPrimitiveValue {
private final int value;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedCharValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedCharValue.java
index cffa634..ee5cab5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedCharValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedCharValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.ValueFactory;
-public class SingleBoxedCharValue extends SingleBoxedNumberValue {
+public class SingleBoxedCharValue extends SingleBoxedPrimitiveValue {
private final int value;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedDoubleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedDoubleValue.java
index 2c03c37..f40ad58 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedDoubleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedDoubleValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.ValueFactory;
-public class SingleBoxedDoubleValue extends SingleBoxedNumberValue {
+public class SingleBoxedDoubleValue extends SingleBoxedPrimitiveValue {
private final long value;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedFloatValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedFloatValue.java
index f2fe64a..509b38d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedFloatValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedFloatValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.ValueFactory;
-public class SingleBoxedFloatValue extends SingleBoxedNumberValue {
+public class SingleBoxedFloatValue extends SingleBoxedPrimitiveValue {
private final int value;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedIntegerValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedIntegerValue.java
index d7811f7..da49211 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedIntegerValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedIntegerValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.ValueFactory;
-public class SingleBoxedIntegerValue extends SingleBoxedNumberValue {
+public class SingleBoxedIntegerValue extends SingleBoxedPrimitiveValue {
private final int value;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedLongValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedLongValue.java
index 51cb06f..86cd49a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedLongValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedLongValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.ValueFactory;
-public class SingleBoxedLongValue extends SingleBoxedNumberValue {
+public class SingleBoxedLongValue extends SingleBoxedPrimitiveValue {
private final long value;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedPrimitiveValue.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedNumberValue.java
rename to src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedPrimitiveValue.java
index ac91612..3e2dea1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedPrimitiveValue.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-public abstract class SingleBoxedNumberValue extends SingleConstValue {
+public abstract class SingleBoxedPrimitiveValue extends SingleConstValue {
@Override
public InstanceFieldInitializationInfo fixupAfterParametersChanged(
@@ -45,7 +45,7 @@
}
@Override
- public SingleBoxedNumberValue asSingleBoxedPrimitive() {
+ public SingleBoxedPrimitiveValue asSingleBoxedPrimitive() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedShortValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedShortValue.java
index e47f751..37cf206 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedShortValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleBoxedShortValue.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
import com.android.tools.r8.ir.code.ValueFactory;
-public class SingleBoxedShortValue extends SingleBoxedNumberValue {
+public class SingleBoxedShortValue extends SingleBoxedPrimitiveValue {
private final int value;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
index 8286eca..9f40455 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
@@ -26,10 +26,27 @@
return inValues.get(INDEX_INDEX);
}
- public int getIndexOrDefault(int defaultValue) {
- return index().isConstant()
- ? index().getConstInstruction().asConstInstruction().asConstNumber().getIntValue()
- : defaultValue;
+ public int indexOrDefault(int defaultValue) {
+ int ret = indexIfConstAndInBounds(Integer.MAX_VALUE);
+ return ret == -1 ? defaultValue : ret;
+ }
+
+ public int indexIfConstAndInBounds(int size) {
+ int ret = index().getConstIntValueIfNonNegative();
+ return ret < size ? ret : -1;
+ }
+
+ public int arraySizeIfConst() {
+ Value arrayRoot = array().getAliasedValue();
+ if (arrayRoot.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmptyOrNewArrayFilled)) {
+ Instruction definition = arrayRoot.getDefinition();
+ if (definition.isNewArrayEmpty()) {
+ Value newArraySizeValue = definition.asNewArrayEmpty().size();
+ return newArraySizeValue.getConstIntValueIfNonNegative();
+ }
+ return definition.asNewArrayFilled().size();
+ }
+ return -1;
}
@Override
@@ -56,31 +73,7 @@
AbstractValueSupplier abstractValueSupplier,
SideEffectAssumption assumption) {
// TODO(b/203731608): Add parameters to the method and use abstract value in R8.
- int arraySize;
- Value arrayRoot = array().getAliasedValue();
- if (arrayRoot.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmptyOrNewArrayFilled)) {
- Instruction definition = arrayRoot.getDefinition();
- if (definition.isNewArrayEmpty()) {
- Value newArraySizeValue = definition.asNewArrayEmpty().size();
- if (newArraySizeValue.isConstant()) {
- arraySize = newArraySizeValue.getConstInstruction().asConstNumber().getIntValue();
- } else {
- return true;
- }
- } else {
- arraySize = definition.asNewArrayFilled().size();
- }
- } else {
- return true;
- }
-
- int index;
- if (index().isConstant()) {
- index = index().getConstInstruction().asConstNumber().getIntValue();
- } else {
- return true;
- }
-
- return arraySize <= 0 || index < 0 || arraySize <= index;
+ int arraySize = arraySizeIfConst();
+ return arraySize < 0 || indexIfConstAndInBounds(arraySize) < 0;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 9aadaff..3570828 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -304,7 +304,7 @@
}
public List<BasicBlock> getSuccessors() {
- return Collections.unmodifiableList(successors);
+ return ListUtils.unmodifiableForTesting(successors);
}
public List<BasicBlock> getMutableSuccessors() {
@@ -368,7 +368,7 @@
}
public List<BasicBlock> getPredecessors() {
- return Collections.unmodifiableList(predecessors);
+ return ListUtils.unmodifiableForTesting(predecessors);
}
public List<BasicBlock> getMutablePredecessors() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 54531af..9fb677b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
import com.android.tools.r8.graph.AppView;
@@ -21,6 +20,7 @@
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
@@ -30,6 +30,7 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
@@ -41,7 +42,7 @@
protected final BasicBlock block;
protected final ListIterator<Instruction> listIterator;
protected Instruction current;
- protected Position position = null;
+ private Position position = null;
private final IRMetadata metadata;
@@ -84,6 +85,16 @@
}
@Override
+ public Instruction peekNext() {
+ // Reset current since listIterator.remove() changes based on whether next() or previous() was
+ // last called.
+ // E.g.: next() -> current=C
+ // peekNext(): next() -> current=D, previous() -> current=D
+ current = null;
+ return IteratorUtils.peekNext(listIterator);
+ }
+
+ @Override
public boolean hasPrevious() {
return listIterator.hasPrevious();
}
@@ -100,10 +111,28 @@
}
@Override
+ public Instruction peekPrevious() {
+ // Reset current since listIterator.remove() changes based on whether next() or previous() was
+ // last called.
+ // E.g.: previous() -> current=B
+ // peekPrevious(): previous() -> current=A, next() -> current=A
+ current = null;
+ return IteratorUtils.peekPrevious(listIterator);
+ }
+
+ @Override
public boolean hasInsertionPosition() {
return position != null;
}
+ public Position getInsertionPosition() {
+ return position;
+ }
+
+ public Position getInsertionPositionOrDefault(Position defaultValue) {
+ return hasInsertionPosition() ? getInsertionPosition() : defaultValue;
+ }
+
@Override
public void setInsertionPosition(Position position) {
this.position = position;
@@ -111,12 +140,12 @@
@Override
public void unsetInsertionPosition() {
- this.position = null;
+ setInsertionPosition(null);
}
/**
- * Adds an instruction to the block. The instruction will be added just before the current cursor
- * position.
+ * Adds an instruction to the block. The instruction will be added just before the instruction
+ * that would be returned by a call to next().
*
* <p>The instruction will be assigned to the block it is added to.
*
@@ -126,65 +155,78 @@
public void add(Instruction instruction) {
instruction.setBlock(block);
assert instruction.getBlock() == block;
- if (position != null && !instruction.hasPosition()) {
- instruction.setPosition(position);
+ if (!instruction.hasPosition() && hasInsertionPosition()) {
+ instruction.setPosition(getInsertionPosition());
}
listIterator.add(instruction);
metadata.record(instruction);
}
+ private boolean hasPriorThrowingInstruction() {
+ Instruction next = peekNext();
+ for (Instruction ins : block.getInstructions()) {
+ if (ins == next) {
+ break;
+ }
+ if (ins.instructionTypeCanThrow()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Instruction[] instructions,
+ Collection<Instruction> instructionsToAdd,
InternalOptions options) {
- InstructionListIterator iterator = this;
- if (!block.hasCatchHandlers()) {
- iterator.addAll(instructions);
- return iterator;
+ // Assert that we are not inserting after the final jump, and also store peekNext() for later.
+ Instruction origNext = null;
+ assert (origNext = peekNext()) != null;
+ InstructionListIterator ret =
+ addPossiblyThrowingInstructionsToPossiblyThrowingBlockImpl(
+ this, code, blockIterator, instructionsToAdd, options);
+ assert ret.peekNext() == origNext;
+ return ret;
+ }
+
+ // Use a static method to ensure dstIterator is used instead of "this".
+ private static InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlockImpl(
+ BasicBlockInstructionListIterator dstIterator,
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ Collection<Instruction> instructionsToAdd,
+ InternalOptions options) {
+ if (!dstIterator.block.hasCatchHandlers() || instructionsToAdd.isEmpty()) {
+ dstIterator.addAll(instructionsToAdd);
+ return dstIterator;
}
- int i = 0;
- if (!block.canThrow()) {
- // Add all non-throwing instructions up until the first throwing instruction.
- for (; i < instructions.length; i++) {
- Instruction materializingInstruction = instructions[i];
- if (!materializingInstruction.instructionTypeCanThrow()) {
- iterator.add(materializingInstruction);
- } else {
- break;
- }
- }
- // Add the first throwing instruction without splitting the block.
- if (i < instructions.length) {
- assert instructions[i].instructionTypeCanThrow();
- iterator.add(instructions[i]);
- i++;
- }
+
+ Iterator<Instruction> srcIterator = instructionsToAdd.iterator();
+
+ // If the throwing instruction is before the cursor, then we must split the block first.
+ // If there is one afterwards, we can add instructions and when we split, the throwing one
+ // will be moved to the split block.
+ boolean splitBeforeAdding = dstIterator.hasPriorThrowingInstruction();
+ if (splitBeforeAdding) {
+ BasicBlock nextBlock =
+ dstIterator.splitCopyCatchHandlers(
+ code, blockIterator, options, UnaryOperator.identity());
+ dstIterator = nextBlock.listIterator(code);
}
- for (; i < instructions.length; i++) {
- BasicBlock splitBlock = iterator.splitCopyCatchHandlers(code, blockIterator, options);
- BasicBlock previousBlock = blockIterator.positionAfterPreviousBlock(splitBlock);
- assert previousBlock == splitBlock;
- iterator = splitBlock.listIterator(code);
- // Add all non-throwing instructions up until the next throwing instruction to the split
- // block.
- for (; i < instructions.length; i++) {
- Instruction materializingInstruction = instructions[i];
- if (!materializingInstruction.instructionTypeCanThrow()) {
- iterator.add(materializingInstruction);
- } else {
- break;
- }
+ do {
+ boolean addedThrowing = dstIterator.addUntilThrowing(srcIterator);
+ if (!addedThrowing || (!srcIterator.hasNext() && splitBeforeAdding)) {
+ break;
}
- // Add the current throwing instruction to the split block.
- if (i < instructions.length) {
- assert instructions[i].instructionTypeCanThrow();
- iterator.add(instructions[i]);
- i++;
- }
- }
- return iterator;
+ BasicBlock nextBlock =
+ dstIterator.splitCopyCatchHandlers(
+ code, blockIterator, options, UnaryOperator.identity());
+ dstIterator = nextBlock.listIterator(code);
+ } while (srcIterator.hasNext());
+
+ return dstIterator;
}
@Override
@@ -317,31 +359,29 @@
current = newInstruction;
}
+ private Position getPreviousPosition() {
+ // Cannot use "current" because it is invalidated by peekNext().
+ Instruction prev = peekPrevious();
+ return prev != null ? prev.getPosition() : block.getPosition();
+ }
+
+ private void addWithPreviousPosition(Instruction instruction, InternalOptions options) {
+ instruction.setPosition(getInsertionPositionOrDefault(getPreviousPosition()), options);
+ add(instruction);
+ }
+
@Override
public Value insertConstNumberInstruction(
IRCode code, InternalOptions options, long value, TypeElement type) {
ConstNumber constNumberInstruction = code.createNumberConstant(value, type);
- // Note that we only keep position info for throwing instructions in release mode.
- if (!hasInsertionPosition()) {
- Position position;
- if (options.debug) {
- position = current != null ? current.getPosition() : block.getPosition();
- } else {
- position = Position.none();
- }
- constNumberInstruction.setPosition(position);
- }
- add(constNumberInstruction);
+ addWithPreviousPosition(constNumberInstruction, options);
return constNumberInstruction.outValue();
}
@Override
public Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value) {
ConstString constStringInstruction = code.createStringConstant(appView, value);
- // Note that we only keep position info for throwing instructions in release mode.
- constStringInstruction.setPosition(
- appView.options().debug ? current.getPosition() : Position.none());
- add(constStringInstruction);
+ addWithPreviousPosition(constStringInstruction, appView.options());
return constStringInstruction.outValue();
}
@@ -437,7 +477,11 @@
@Override
public void replaceCurrentInstructionWithConstClass(
- AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ AppView<?> appView,
+ IRCode code,
+ DexType type,
+ DebugLocalInfo localInfo,
+ AffectedValues affectedValues) {
if (current == null) {
throw new IllegalStateException();
}
@@ -445,7 +489,7 @@
TypeElement typeElement = TypeElement.classClassType(appView, definitelyNotNull());
Value value = code.createValue(typeElement, localInfo);
ConstClass constClass = new ConstClass(value, type);
- replaceCurrentInstruction(constClass);
+ replaceCurrentInstruction(constClass, affectedValues);
}
@Override
@@ -463,14 +507,14 @@
@Override
public void replaceCurrentInstructionWithConstString(
- AppView<?> appView, IRCode code, DexString value) {
+ AppView<?> appView, IRCode code, DexString value, AffectedValues affectedValues) {
if (current == null) {
throw new IllegalStateException();
}
// Replace the instruction by const-string.
ConstString constString = code.createStringConstant(appView, value, current.getLocalInfo());
- replaceCurrentInstruction(constString);
+ replaceCurrentInstruction(constString, affectedValues);
}
@Override
@@ -495,16 +539,10 @@
}
// Replace the instruction by static-get.
- TypeElement newType = TypeElement.fromDexType(field.type, maybeNull(), appView);
- TypeElement oldType = current.getOutType();
+ TypeElement newType = field.getTypeElement(appView);
Value value = code.createValue(newType, current.getLocalInfo());
StaticGet staticGet = new StaticGet(value, field);
- replaceCurrentInstruction(staticGet);
-
- // Update affected values.
- if (value.hasAnyUsers() && !newType.equals(oldType)) {
- affectedValues.addAll(value.affectedValues());
- }
+ replaceCurrentInstruction(staticGet, affectedValues);
}
@Override
@@ -558,16 +596,15 @@
assert !throwBlockInstructionIterator.hasNext();
// Replace the instruction by throw.
- Throw throwInstruction = new Throw(exceptionValue);
- if (hasInsertionPosition()) {
- throwInstruction.setPosition(position);
- } else if (toBeReplaced.getPosition().isSome()) {
- throwInstruction.setPosition(toBeReplaced.getPosition());
- } else {
- assert !toBeReplaced.instructionTypeCanThrow();
- throwInstruction.setPosition(Position.syntheticNone());
- }
- throwBlockInstructionIterator.replaceCurrentInstruction(throwInstruction);
+ throwBlockInstructionIterator.replaceCurrentInstruction(
+ Throw.builder()
+ .setExceptionValue(exceptionValue)
+ .setPosition(
+ getInsertionPositionOrDefault(
+ toBeReplaced.getPosition().isSome()
+ ? toBeReplaced.getPosition()
+ : Position.syntheticNone()))
+ .build());
}
@Override
@@ -617,7 +654,7 @@
throwBlockInstructionIterator = this;
} else {
throwBlockInstructionIterator = throwBlock.listIterator(code);
- throwBlockInstructionIterator.setInsertionPosition(position);
+ throwBlockInstructionIterator.setInsertionPosition(getInsertionPosition());
}
// Insert constant null before the goto instruction.
@@ -629,18 +666,15 @@
assert !throwBlockInstructionIterator.hasNext();
// Replace the instruction by throw.
- Throw throwInstruction = new Throw(nullValue);
- if (hasInsertionPosition()) {
- throwInstruction.setPosition(position);
- } else if (toBeReplaced.getPosition().isSome()) {
- throwInstruction.setPosition(toBeReplaced.getPosition());
- } else {
- // The instruction that is being removed cannot throw, and thus it must be unreachable as we
- // are replacing it by `throw null`, so we can safely use a none-position.
- assert !toBeReplaced.instructionTypeCanThrow();
- throwInstruction.setPosition(Position.syntheticNone());
- }
- throwBlockInstructionIterator.replaceCurrentInstruction(throwInstruction);
+ throwBlockInstructionIterator.replaceCurrentInstruction(
+ Throw.builder()
+ .setExceptionValue(nullValue)
+ .setPosition(
+ getInsertionPositionOrDefault(
+ toBeReplaced.getPosition().isSome()
+ ? toBeReplaced.getPosition()
+ : Position.syntheticNone()))
+ .build());
if (block.hasCatchHandlers()) {
if (block == throwBlock) {
@@ -680,7 +714,7 @@
assert hasNext();
// Get the position at which the block is being split.
- Position position = current != null ? current.getPosition() : block.getPosition();
+ Position position = getPreviousPosition();
// Prepare the new block, placing the exception handlers on the block with the throwing
// instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 41ad960..9beaff7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.lightir.LirBuilder;
+import com.android.tools.r8.utils.ListUtils;
import java.util.List;
import java.util.ListIterator;
@@ -72,7 +73,11 @@
@Override
public String toString() {
- if (getBlock() != null && !getBlock().getSuccessors().isEmpty()) {
+ BasicBlock myBlock = getBlock();
+ // Avoids BasicBlock.exit(), since it will assert when block is invalid.
+ if (myBlock != null
+ && !myBlock.getSuccessors().isEmpty()
+ && ListUtils.last(myBlock.getInstructions()) == this) {
return super.toString() + "block " + getTarget().getNumberAsString();
}
return super.toString() + "block <unknown>";
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 4874229..422824f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Collection;
import java.util.ListIterator;
@@ -23,7 +24,7 @@
public class IRCodeInstructionListIterator implements InstructionListIterator {
- private final ListIterator<BasicBlock> blockIterator;
+ private final BasicBlockIterator blockIterator;
private InstructionListIterator instructionIterator;
private final IRCode code;
@@ -71,8 +72,13 @@
@Override
public void replaceCurrentInstructionWithConstClass(
- AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
- instructionIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+ AppView<?> appView,
+ IRCode code,
+ DexType type,
+ DebugLocalInfo localInfo,
+ AffectedValues affectedValues) {
+ instructionIterator.replaceCurrentInstructionWithConstClass(
+ appView, code, type, localInfo, affectedValues);
}
@Override
@@ -82,8 +88,9 @@
@Override
public void replaceCurrentInstructionWithConstString(
- AppView<?> appView, IRCode code, DexString value) {
- instructionIterator.replaceCurrentInstructionWithConstString(appView, code, value);
+ AppView<?> appView, IRCode code, DexString value, AffectedValues affectedValues) {
+ instructionIterator.replaceCurrentInstructionWithConstString(
+ appView, code, value, affectedValues);
}
@Override
@@ -174,6 +181,16 @@
}
@Override
+ public Instruction peekNext() {
+ // Default impl calls next() / previous(), which affects what remove() does.
+ Instruction next = instructionIterator.peekNext();
+ if (next == null && blockIterator.hasNext()) {
+ next = blockIterator.peekNext().entry();
+ }
+ return next;
+ }
+
+ @Override
public boolean hasPrevious() {
return instructionIterator.hasPrevious() || blockIterator.hasPrevious();
}
@@ -193,6 +210,16 @@
}
@Override
+ public Instruction peekPrevious() {
+ // Default impl calls next() / previous(), which affects what remove() does.
+ Instruction previous = instructionIterator.peekPrevious();
+ if (previous == null && blockIterator.hasPrevious()) {
+ previous = blockIterator.peekPrevious().exit();
+ }
+ return previous;
+ }
+
+ @Override
public int nextIndex() {
throw new UnsupportedOperationException();
}
@@ -211,10 +238,10 @@
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Instruction[] instructions,
+ Collection<Instruction> instructionsToAdd,
InternalOptions options) {
return instructionIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
- code, blockIterator, instructions, options);
+ code, blockIterator, instructionsToAdd, options);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 034ec81..d0426ec 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
@@ -88,7 +89,7 @@
}
public void setPosition(Position position) {
- assert this.position == null;
+ assert !hasPosition();
this.position = position;
}
@@ -1328,6 +1329,10 @@
return false;
}
+ public boolean isInvokeMethod(DexMethod invokedMethod) {
+ return false;
+ }
+
public InvokeMethod asInvokeMethod() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 247079e..3588346 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -13,11 +13,14 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Iterator;
import java.util.ListIterator;
import java.util.Set;
import java.util.function.Consumer;
@@ -33,12 +36,39 @@
}
}
+ default void addAll(Collection<Instruction> instructions) {
+ for (Instruction instruction : instructions) {
+ add(instruction);
+ }
+ }
+
+ default boolean addUntilThrowing(Iterator<Instruction> srcIterator) {
+ while (srcIterator.hasNext()) {
+ // Add all non-throwing instructions up until the first throwing instruction.
+ Instruction instruction = srcIterator.next();
+ add(instruction);
+ if (instruction.instructionTypeCanThrow()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Instruction[] instructions,
+ Collection<Instruction> instructionsToAdd,
InternalOptions options);
+ default InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ Instruction[] instructionsToAdd,
+ InternalOptions options) {
+ return addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ code, blockIterator, Arrays.asList(instructionsToAdd), options);
+ }
+
BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
IRCode code,
ListIterator<BasicBlock> blockIterator,
@@ -166,7 +196,11 @@
}
void replaceCurrentInstructionWithConstClass(
- AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
+ AppView<?> appView,
+ IRCode code,
+ DexType type,
+ DebugLocalInfo localInfo,
+ AffectedValues affectedValues);
default void replaceCurrentInstructionWithConstFalse(IRCode code) {
replaceCurrentInstructionWithConstInt(code, 0);
@@ -174,18 +208,13 @@
void replaceCurrentInstructionWithConstInt(IRCode code, int value);
- void replaceCurrentInstructionWithConstString(AppView<?> appView, IRCode code, DexString value);
+ void replaceCurrentInstructionWithConstString(
+ AppView<?> appView, IRCode code, DexString value, AffectedValues affectedValues);
default void replaceCurrentInstructionWithConstTrue(IRCode code) {
replaceCurrentInstructionWithConstInt(code, 1);
}
- default void replaceCurrentInstructionWithConstString(
- AppView<?> appView, IRCode code, String value) {
- replaceCurrentInstructionWithConstString(
- appView, code, appView.dexItemFactory().createString(value));
- }
-
void replaceCurrentInstructionWithNullCheck(AppView<?> appView, Value object);
void replaceCurrentInstructionWithStaticGet(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 434272d..4a9b217 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -33,7 +33,6 @@
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.library.LibraryOptimizationInfoCollection;
@@ -114,6 +113,11 @@
}
@Override
+ public boolean isInvokeMethod(DexMethod invokedMethod) {
+ return getInvokedMethod().isIdenticalTo(invokedMethod);
+ }
+
+ @Override
public InvokeMethod asInvokeMethod() {
return this;
}
@@ -230,9 +234,8 @@
return result;
}
- public abstract InlineAction computeInlining(
+ public abstract InlineAction.Builder computeInlining(
ProgramMethod singleTarget,
- Reason reason,
DefaultInliningOracle decider,
ClassInitializationAnalysis classInitializationAnalysis,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index edc96f6..58ad2ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -21,7 +21,6 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
@@ -55,14 +54,12 @@
}
@Override
- public final InlineAction computeInlining(
+ public final InlineAction.Builder computeInlining(
ProgramMethod singleTarget,
- Reason reason,
DefaultInliningOracle decider,
ClassInitializationAnalysis classInitializationAnalysis,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- return decider.computeForInvokeWithReceiver(
- this, singleTarget, reason, whyAreYouNotInliningReporter);
+ return decider.computeForInvokeWithReceiver(this, singleTarget, whyAreYouNotInliningReporter);
}
/**
@@ -93,7 +90,7 @@
}
protected boolean isPrivateNestMethodInvoke(DexBuilder builder) {
- if (!builder.getOptions().emitNestAnnotationsInDex) {
+ if (!builder.getOptions().canUseNestBasedAccess()) {
return false;
}
DexProgramClass holder = builder.getProgramMethod().getHolder();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 0061bf3..273a263 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.lightir.LirBuilder;
@@ -147,19 +146,11 @@
}
@Override
- public InlineAction computeInlining(
+ public InlineAction.Builder computeInlining(
ProgramMethod singleTarget,
- Reason reason,
DefaultInliningOracle decider,
ClassInitializationAnalysis classInitializationAnalysis,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- // We never determine a single target for invoke-polymorphic.
- if (singleTarget != null) {
- throw new Unreachable(
- "Unexpected invoke-polymorphic with `"
- + singleTarget.toSourceString()
- + "` as single target");
- }
- throw new Unreachable("Unexpected attempt to inline invoke that does not have a single target");
+ throw new Unreachable();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index f1d4aea..9d5c5bf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -23,7 +23,6 @@
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.lightir.LirBuilder;
@@ -120,14 +119,13 @@
}
@Override
- public InlineAction computeInlining(
+ public InlineAction.Builder computeInlining(
ProgramMethod singleTarget,
- Reason reason,
DefaultInliningOracle decider,
ClassInitializationAnalysis classInitializationAnalysis,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
return decider.computeForInvokeStatic(
- this, singleTarget, reason, classInitializationAnalysis, whyAreYouNotInliningReporter);
+ this, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeType.java b/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
index 9d9fad1..4e015d0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeType.java
@@ -137,7 +137,7 @@
context.getHolder().isInterface()
|| (appView.hasVerticallyMergedClasses()
&& appView
- .verticallyMergedClasses()
+ .getVerticallyMergedClasses()
.hasInterfaceBeenMergedIntoSubtype(originalContext.getHolderType()));
if (originalContextIsInterface) {
// On interfaces invoke-special should be mapped to invoke-super if the invoke-special
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 24e7793..5eb6dfd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.Collection;
@@ -100,8 +101,13 @@
@Override
public void replaceCurrentInstructionWithConstClass(
- AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
- currentBlockIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+ AppView<?> appView,
+ IRCode code,
+ DexType type,
+ DebugLocalInfo localInfo,
+ AffectedValues affectedValues) {
+ currentBlockIterator.replaceCurrentInstructionWithConstClass(
+ appView, code, type, localInfo, affectedValues);
}
@Override
@@ -111,8 +117,9 @@
@Override
public void replaceCurrentInstructionWithConstString(
- AppView<?> appView, IRCode code, DexString value) {
- currentBlockIterator.replaceCurrentInstructionWithConstString(appView, code, value);
+ AppView<?> appView, IRCode code, DexString value, AffectedValues affectedValues) {
+ currentBlockIterator.replaceCurrentInstructionWithConstString(
+ appView, code, value, affectedValues);
}
@Override
@@ -192,10 +199,10 @@
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Instruction[] instructions,
+ Collection<Instruction> instructionsToAdd,
InternalOptions options) {
return currentBlockIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
- code, blockIterator, instructions, options);
+ code, blockIterator, instructionsToAdd, options);
}
@Override
@@ -249,6 +256,27 @@
return currentBlockIterator.next();
}
+ @Override
+ public Instruction peekNext() {
+ // Default impl calls next() / previous(), which will alter this.seen.
+ Instruction current = currentBlockIterator.peekNext();
+ if (!current.isGoto()) {
+ return current;
+ }
+ BasicBlock target = current.asGoto().getTarget();
+ if (!isLinearEdge(currentBlock, target)) {
+ return current;
+ }
+ while (target.isTrivialGoto()) {
+ BasicBlock candidate = target.exit().asGoto().getTarget();
+ if (!isLinearEdge(target, candidate)) {
+ break;
+ }
+ target = candidate;
+ }
+ return target.entry();
+ }
+
private BasicBlock getBeginningOfTrivialLinearGotoChain(BasicBlock block) {
if (block.getPredecessors().size() != 1
|| !isLinearEdge(block.getPredecessors().get(0), block)) {
@@ -290,6 +318,20 @@
}
@Override
+ public Instruction peekPrevious() {
+ // Default impl calls next() / previous(), which will alter this.seen.
+ Instruction ret = currentBlockIterator.peekPrevious();
+ if (ret != null) {
+ return ret;
+ }
+ BasicBlock target = getBeginningOfTrivialLinearGotoChain(currentBlock);
+ if (target == null || target.size() < 2) {
+ return null;
+ }
+ return target.getInstructions().get(target.size() - 2);
+ }
+
+ @Override
public int nextIndex() {
throw new UnsupportedOperationException();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index e873dd8..ab5db40 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -22,6 +22,10 @@
super(exception);
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public int opcode() {
return Opcodes.THROW;
@@ -121,4 +125,24 @@
}
return false;
}
+
+ public static class Builder extends BuilderBase<Builder, Throw> {
+
+ private Value exceptionValue;
+
+ public Builder setExceptionValue(Value exceptionValue) {
+ this.exceptionValue = exceptionValue;
+ return this;
+ }
+
+ @Override
+ public Throw build() {
+ return amend(new Throw(exceptionValue));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index f08a4e2..e35a726 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -38,7 +38,6 @@
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.LongInterval;
import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
@@ -378,25 +377,19 @@
}
public Set<Instruction> aliasedUsers(AliasedValueConfiguration configuration) {
- Set<Instruction> users = SetUtils.newIdentityHashSet(uniqueUsers());
- Set<Instruction> visited = Sets.newIdentityHashSet();
- collectAliasedUsersViaAssume(configuration, visited, uniqueUsers(), users);
+ Set<Instruction> users = Sets.newIdentityHashSet();
+ collectAliasedUsersViaAssume(configuration, this, users);
return users;
}
private static void collectAliasedUsersViaAssume(
- AliasedValueConfiguration configuration,
- Set<Instruction> visited,
- Set<Instruction> usersToTest,
- Set<Instruction> collectedUsers) {
- for (Instruction user : usersToTest) {
- if (!visited.add(user)) {
+ AliasedValueConfiguration configuration, Value value, Set<Instruction> collectedUsers) {
+ for (Instruction user : value.uniqueUsers()) {
+ if (!collectedUsers.add(user)) {
continue;
}
if (configuration.isIntroducingAnAlias(user)) {
- collectedUsers.addAll(user.outValue().uniqueUsers());
- collectAliasedUsersViaAssume(
- configuration, visited, user.outValue().uniqueUsers(), collectedUsers);
+ collectAliasedUsersViaAssume(configuration, user.outValue(), collectedUsers);
}
}
}
@@ -1020,6 +1013,18 @@
&& getConstInstruction().asConstNumber().isZero();
}
+ public int getConstIntValueIfNonNegative() {
+ if (!isConstant()) {
+ return -1;
+ }
+ ConstNumber constNumber = definition.asConstNumber();
+ if (constNumber == null) {
+ return -1;
+ }
+ int ret = constNumber.getIntValue();
+ return ret >= 0 ? ret : -1;
+ }
+
/**
* Overwrites the current type lattice value without any assertions.
*
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index b47b900..0e13017 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -69,6 +69,7 @@
import com.android.tools.r8.ir.optimize.membervaluepropagation.D8MemberValuePropagation;
import com.android.tools.r8.ir.optimize.membervaluepropagation.MemberValuePropagation;
import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
+import com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxer;
import com.android.tools.r8.ir.optimize.outliner.Outliner;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.lightir.IR2LirConverter;
@@ -127,6 +128,7 @@
private final TypeChecker typeChecker;
protected ServiceLoaderRewriter serviceLoaderRewriter;
protected final EnumUnboxer enumUnboxer;
+ protected final NumberUnboxer numberUnboxer;
protected InstanceInitializerOutliner instanceInitializerOutliner;
protected final RemoveVerificationErrorForUnknownReturnedValues
removeVerificationErrorForUnknownReturnedValues;
@@ -214,6 +216,7 @@
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumUnboxer = EnumUnboxer.empty();
+ this.numberUnboxer = NumberUnboxer.empty();
this.assumeInserter = null;
this.instanceInitializerOutliner = null;
this.removeVerificationErrorForUnknownReturnedValues = null;
@@ -255,6 +258,7 @@
? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
: null;
this.enumUnboxer = EnumUnboxer.create(appViewWithLiveness);
+ this.numberUnboxer = NumberUnboxer.create(appViewWithLiveness);
this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
this.outliner = Outliner.create(appViewWithLiveness);
@@ -293,6 +297,7 @@
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
this.enumUnboxer = EnumUnboxer.empty();
+ this.numberUnboxer = NumberUnboxer.empty();
}
this.stringSwitchRemover =
options.isStringSwitchConversionEnabled()
@@ -928,8 +933,13 @@
appView.withArgumentPropagator(
argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor, timing));
+ if (methodProcessor.isComposeMethodProcessor()) {
+ methodProcessor.asComposeMethodProcessor().scan(method, code, timing);
+ }
+
if (methodProcessor.isPrimaryMethodProcessor()) {
enumUnboxer.analyzeEnums(code, methodProcessor);
+ numberUnboxer.analyze(code);
}
if (inliner != null) {
@@ -1142,6 +1152,7 @@
assert method.getHolder().lookupMethod(method.getReference()) == null;
appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.onMethodPruned(method));
enumUnboxer.onMethodPruned(method);
+ numberUnboxer.onMethodPruned(method);
outliner.onMethodPruned(method);
if (inliner != null) {
inliner.onMethodPruned(method);
@@ -1159,6 +1170,7 @@
appView.withArgumentPropagator(
argumentPropagator -> argumentPropagator.onMethodCodePruned(method));
enumUnboxer.onMethodCodePruned(method);
+ numberUnboxer.onMethodCodePruned(method);
outliner.onMethodCodePruned(method);
if (inliner != null) {
inliner.onMethodCodePruned(method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
index 87f9a4b..c5e44d0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -33,7 +33,7 @@
@Override
public DexCode finalizeCode(
IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) {
- if (options.emitNestAnnotationsInDex) {
+ if (options.canUseNestBasedAccess()) {
D8NestBasedAccessDesugaring.checkAndFailOnIncompleteNests(appView);
}
DexEncodedMethod method = code.method();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 07ea7c8..9cb88c3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -50,7 +50,6 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.graph.lens.FieldLookupResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
@@ -115,6 +114,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
+import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -272,7 +272,7 @@
boolean mayHaveUnreachableBlocks = false;
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
- if (block.hasCatchHandlers() && options.enableVerticalClassMerging) {
+ if (block.hasCatchHandlers() && !appView.getVerticallyMergedClasses().isEmpty()) {
boolean anyGuardsRenamed = block.renameGuardsInCatchHandlers(graphLens, codeLens);
if (anyGuardsRenamed) {
mayHaveUnreachableBlocks |= unlinkDeadCatchHandlers(block, graphLens, codeLens);
@@ -1309,7 +1309,7 @@
// A and B although this will lead to invalid code, because this code pattern does generally
// not occur in practice (it leads to a verification error on the JVM, but not on Art).
private void checkInvokeDirect(DexMethod method, InvokeDirect invoke) {
- VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+ VerticallyMergedClasses verticallyMergedClasses = appView.getVerticallyMergedClasses();
if (verticallyMergedClasses == null) {
// No need to check the invocation.
return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index e5a0728..8f4c441 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -54,7 +54,7 @@
void markAsPropagated(DexEncodedMethod method);
- void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo);
+ void setBridgeInfo(ProgramMethod method, BridgeInfo bridgeInfo);
void setClassInlinerMethodConstraint(
ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 15dc3d2..a80055a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -5,9 +5,18 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
+import com.android.tools.r8.optimize.compose.ComposeMethodProcessor;
public abstract class MethodProcessor {
+ public boolean isComposeMethodProcessor() {
+ return false;
+ }
+
+ public ComposeMethodProcessor asComposeMethodProcessor() {
+ return null;
+ }
+
public boolean isPrimaryMethodProcessor() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 8d33d03..d56bdfb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -98,7 +98,6 @@
InternalOptions options = appView.options();
Deque<ProgramMethodSet> waves = new ArrayDeque<>();
Collection<Node> nodes = callGraph.getNodes();
- int waveCount = 1;
while (!nodes.isEmpty()) {
ProgramMethodSet wave = callGraph.extractLeaves();
waves.addLast(wave);
@@ -127,11 +126,11 @@
throws ExecutionException {
TimingMerger merger = timing.beginMerger("primary-processor", executorService);
while (!waves.isEmpty()) {
- processorContext = appView.createProcessorContext();
wave = waves.removeFirst();
assert !wave.isEmpty();
assert waveExtension.isEmpty();
do {
+ processorContext = appView.createProcessorContext();
waveStartAction.notifyWaveStart(wave);
Collection<Timing> timings =
ThreadUtils.processItemsWithResults(
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 1a4a2ec..6b6f2ad 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
@@ -25,6 +25,7 @@
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
+import com.android.tools.r8.optimize.compose.ComposableOptimizationPass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -80,6 +81,7 @@
appView.withArgumentPropagator(
argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing));
enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
+ numberUnboxer.prepareForPrimaryOptimizationPass(timing, executorService);
outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
if (fieldAccessAnalysis != null) {
@@ -142,6 +144,7 @@
// the parameter optimization infos, and rewrite the application.
// TODO(b/199237357): Automatically rewrite state when lens changes.
enumUnboxer.rewriteWithLens();
+ numberUnboxer.rewriteWithLens();
outliner.rewriteWithLens();
appView.withArgumentPropagator(
argumentPropagator ->
@@ -157,11 +160,16 @@
.run(executorService, feedback, timing);
}
+ numberUnboxer.rewriteWithLens();
outliner.rewriteWithLens();
enumUnboxer.unboxEnums(
appView, this, postMethodProcessorBuilder, executorService, feedback, timing);
appView.unboxedEnums().checkEnumsUnboxed(appView);
+ numberUnboxer.rewriteWithLens();
+ outliner.rewriteWithLens();
+ numberUnboxer.unboxNumbers(postMethodProcessorBuilder, timing, executorService);
+
GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
outliner.rewriteWithLens();
@@ -226,6 +234,8 @@
identifierNameStringMarker.decoupleIdentifierNameStringsInFields(executorService);
}
+ ComposableOptimizationPass.run(appView, this, timing);
+
// Assure that no more optimization feedback left after post processing.
assert feedback.noUpdatesLeft();
return appView.appInfo().app();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
index ca4492f..e92ea14 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
@@ -111,6 +111,9 @@
int numberOfCallSites = node.getNumberOfCallSites();
if (numberOfCallSites == 1) {
+ if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) {
+ continue;
+ }
Set<Node> callersWithDeterministicOrder = node.getCallersWithDeterministicOrder();
DexMethod caller = reference;
// We can have recursive methods where the recursive call is the only call site. We do
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
index eb09ac7..809f647 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
@@ -7,16 +7,14 @@
import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
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.DexType;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -25,7 +23,7 @@
import java.util.function.Function;
import java.util.function.Predicate;
-public class InvokeExtractor<N extends NodeBase<N>> extends UseRegistry<ProgramMethod> {
+public class InvokeExtractor<N extends NodeBase<N>> extends DefaultUseRegistry<ProgramMethod> {
protected final AppView<AppInfoWithLiveness> appViewWithLiveness;
protected final N currentMethod;
@@ -200,34 +198,4 @@
public void registerInvokeVirtual(DexMethod method) {
processInvoke(InvokeType.VIRTUAL, method);
}
-
- @Override
- public void registerInitClass(DexType type) {
- // Intentionally empty. This use registry is only tracing method calls.
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {
- // Intentionally empty. This use registry is only tracing method calls.
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- // Intentionally empty. This use registry is only tracing method calls.
- }
-
- @Override
- public void registerStaticFieldRead(DexField field) {
- // Intentionally empty. This use registry is only tracing method calls.
- }
-
- @Override
- public void registerStaticFieldWrite(DexField field) {
- // Intentionally empty. This use registry is only tracing method calls.
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- // Intentionally empty. This use registry is only tracing method calls.
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
index 3af88e1..2a93788 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -247,19 +247,13 @@
if (arrayPut == null || arrayPut.array() != arrayValue) {
return null;
}
- if (!arrayPut.index().isConstNumber()) {
+ int index = arrayPut.indexIfConstAndInBounds(values.length);
+ if (index < 0 || values[index] != null) {
return null;
}
if (arrayPut.instructionInstanceCanThrow(appView, code.context())) {
return null;
}
- int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
- if (index < 0 || index >= values.length) {
- return null;
- }
- if (values[index] != null) {
- return null;
- }
Value value = arrayPut.value();
if (needsTypeCheck && !value.isAlwaysNull(appView)) {
DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
index 740c180..89b6f2d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
@@ -232,10 +232,6 @@
}
private void shortenLiveRanges(IRCode code, ConstantCanonicalizer canonicalizer) {
- if (options.debug) {
- // Shorten live ranges seems to regress code size in debug mode.
- return;
- }
if (options.testing.disableShortenLiveRanges) {
return;
}
@@ -244,11 +240,7 @@
LinkedList<BasicBlock> blocks = code.blocks;
for (BasicBlock block : blocks) {
shortenLiveRangesInsideBlock(
- code,
- block,
- dominatorTreeMemoization,
- addConstantInBlock,
- canonicalizer::isConstantCanonicalizationCandidate);
+ code, block, dominatorTreeMemoization, addConstantInBlock, canonicalizer::isConstant);
}
// Heuristic to decide if constant instructions are shared in dominator block
@@ -446,10 +438,13 @@
DominatorTree dominatorTree = dominatorTreeMemoization.computeIfAbsent();
BasicBlock dominator = dominatorTree.closestDominator(userBlocks);
+ // Do not move the constant if the constant instruction can throw and the dominator or the
+ // original block has catch handlers, or if the code may have monitor instructions, since this
+ // could lead to verification errors.
if (instruction.instructionTypeCanThrow()) {
- if (block.hasCatchHandlers() || dominator.hasCatchHandlers()) {
- // Do not move the constant if the constant instruction can throw
- // and the dominator or the original block has catch handlers.
+ if (block.hasCatchHandlers()
+ || dominator.hasCatchHandlers()
+ || code.metadata().mayHaveMonitorInstruction()) {
continue;
}
}
@@ -489,7 +484,8 @@
addConstantInBlock
.computeIfAbsent(dominator, k -> new LinkedHashMap<>())
.put(copy.outValue(), copy);
- assert iterator.peekPrevious() == instruction;
+ // Using peekPrevious() would disable remove().
+ assert iterator.previous() == instruction && iterator.next() == instruction;
iterator.removeOrReplaceByDebugLocalRead();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index bcace75..a2dd4ef 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
@@ -29,6 +30,10 @@
public static CfInstructionDesugaringCollection create(
AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+ if (appView.options().desugarState.isOff() && appView.options().forceNestDesugaring) {
+ throw new CompilationError(
+ "Cannot combine -Dcom.android.tools.r8.forceNestDesugaring with desugaring turned off");
+ }
if (appView.options().desugarState.isOn()) {
return new NonEmptyCfInstructionDesugaringCollection(appView, apiLevelCompute);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
index cb907d1..929f57a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
@@ -209,7 +209,6 @@
});
}
- @SuppressWarnings("ReferenceEquality")
private static <T extends DexItem> void deduplicateEmulatedInterfaceFlags(
Map<T, HumanEmulatedInterfaceDescriptor> flags,
Map<T, HumanEmulatedInterfaceDescriptor> otherFlags,
@@ -217,7 +216,7 @@
BiConsumer<T, HumanEmulatedInterfaceDescriptor> specific) {
flags.forEach(
(k, v) -> {
- if (otherFlags.get(k) == v) {
+ if (otherFlags.get(k).equals(v)) {
common.accept(k, v);
} else {
specific.accept(k, v);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
index d5dc102..7973e0c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.CONFIGURATION_FORMAT_VERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.AMEND_LIBRARY_FIELD_KEY;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.AMEND_LIBRARY_METHOD_KEY;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.API_GENERIC_TYPES_CONVERSION;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.API_LEVEL_BELOW_OR_EQUAL_KEY;
@@ -172,7 +173,7 @@
toJson.put(AMEND_LIBRARY_METHOD_KEY, amendLibraryToString(flags.getAmendLibraryMethod()));
}
if (!flags.getAmendLibraryField().isEmpty()) {
- toJson.put(AMEND_LIBRARY_METHOD_KEY, amendLibraryToString(flags.getAmendLibraryField()));
+ toJson.put(AMEND_LIBRARY_FIELD_KEY, amendLibraryToString(flags.getAmendLibraryField()));
}
list.add(toJson);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index 14257d8..4fdeb0f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -13,10 +14,8 @@
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.DexType;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.conversion.D8MethodProcessor;
import com.android.tools.r8.profile.rewriting.ProfileRewritingNestBasedAccessDesugaringEventConsumer;
import com.android.tools.r8.utils.ThreadUtils;
@@ -138,7 +137,7 @@
new NestBasedAccessDesugaringUseRegistry(method, eventConsumer)));
}
- private class NestBasedAccessDesugaringUseRegistry extends UseRegistry<ClasspathMethod> {
+ private class NestBasedAccessDesugaringUseRegistry extends DefaultUseRegistry<ClasspathMethod> {
private final NestBasedAccessDesugaringEventConsumer eventConsumer;
@@ -294,25 +293,5 @@
public void registerStaticFieldWrite(DexField field) {
registerFieldAccessFromClasspath(field, false);
}
-
- @Override
- public void registerInitClass(DexType clazz) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- // Intentionally empty.
- }
-
- @Override
- public void registerNewInstance(DexType type) {
- // Intentionally empty.
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- // Intentionally empty.
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index cdd49d1..1b14342 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -403,6 +403,27 @@
}
public boolean isConstantCanonicalizationCandidate(Instruction instruction) {
+ if (!isConstant(instruction)) {
+ return false;
+ }
+ // Do not canonicalize throwing instructions if there are monitor operations in the code.
+ // That could lead to unbalanced locking and could lead to situations where OOM exceptions
+ // could leave a synchronized method without unlocking the monitor.
+ if (instruction.instructionTypeCanThrow() && code.metadata().mayHaveMonitorInstruction()) {
+ return false;
+ }
+ // Constants that are used by invoke range are not canonicalized to be compliant with the
+ // optimization splitRangeInvokeConstant that gives the register allocator more freedom in
+ // assigning register to ranged invokes which can greatly reduce the number of register
+ // needed (and thereby code size as well). Thus no need to do a transformation that should
+ // be removed later by another optimization.
+ if (constantUsedByInvokeRange(instruction)) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isConstant(Instruction instruction) {
// Interested only in instructions types that can be canonicalized, i.e., ConstClass,
// ConstNumber, (DexItemBased)?ConstString, InstanceGet and StaticGet.
switch (instruction.opcode()) {
@@ -460,20 +481,6 @@
if (instruction.outValue().hasLocalInfo()) {
return false;
}
- // Do not canonicalize throwing instructions if there are monitor operations in the code.
- // That could lead to unbalanced locking and could lead to situations where OOM exceptions
- // could leave a synchronized method without unlocking the monitor.
- if (instruction.instructionTypeCanThrow() && code.metadata().mayHaveMonitorInstruction()) {
- return false;
- }
- // Constants that are used by invoke range are not canonicalized to be compliant with the
- // optimization splitRangeInvokeConstant that gives the register allocator more freedom in
- // assigning register to ranged invokes which can greatly reduce the number of register
- // needed (and thereby code size as well). Thus no need to do a transformation that should
- // be removed later by another optimization.
- if (constantUsedByInvokeRange(instruction)) {
- return false;
- }
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 1c9f9fe..3117467 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -7,6 +7,8 @@
import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiSafeForInlining;
+import com.android.tools.r8.dex.code.DexCheckCast;
+import com.android.tools.r8.dex.code.DexInvokeStatic;
import com.android.tools.r8.dex.code.DexMoveResult;
import com.android.tools.r8.dex.code.DexMoveResultObject;
import com.android.tools.r8.dex.code.DexMoveResultWide;
@@ -16,6 +18,8 @@
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.BoxUnboxPrimitiveMethodRoundtrip;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
@@ -23,7 +27,10 @@
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
@@ -36,7 +43,6 @@
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineResult;
-import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.Inliner.RetryAction;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
@@ -52,6 +58,7 @@
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
public final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
@@ -126,23 +133,18 @@
@Override
public boolean passesInliningConstraints(
- InvokeMethod invoke,
SingleResolutionResult<?> resolutionResult,
ProgramMethod singleTarget,
- Reason reason,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
// Do not inline if the inlinee is greater than the api caller level.
// TODO(b/188498051): We should not force inline lower api method calls.
- if (reason != Reason.FORCE
- && !isApiSafeForInlining(method, singleTarget, options, whyAreYouNotInliningReporter)) {
+ if (!isApiSafeForInlining(method, singleTarget, options, whyAreYouNotInliningReporter)) {
return false;
}
// We don't inline into constructors when producing class files since this can mess up
// the stackmap, see b/136250031
- if (method.getDefinition().isInstanceInitializer()
- && options.isGeneratingClassFiles()
- && reason != Reason.FORCE) {
+ if (method.getDefinition().isInstanceInitializer() && options.isGeneratingClassFiles()) {
whyAreYouNotInliningReporter.reportNoInliningIntoConstructorsWhenGeneratingClassFiles();
return false;
}
@@ -164,7 +166,7 @@
// or optimized code. Right now this happens for the class class staticizer, as it just
// processes all relevant methods in parallel with the full optimization pipeline enabled.
// TODO(sgjesse): Add this assert "assert !isProcessedConcurrently.test(candidate);"
- if (reason != Reason.FORCE && methodProcessor.isProcessedConcurrently(singleTarget)) {
+ if (methodProcessor.isProcessedConcurrently(singleTarget)) {
whyAreYouNotInliningReporter.reportProcessedConcurrently();
return false;
}
@@ -174,34 +176,21 @@
return false;
}
- Set<Reason> validInliningReasons = appView.testing().validInliningReasons;
- if (validInliningReasons != null && !validInliningReasons.contains(reason)) {
- whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons);
- return false;
- }
-
// Abort inlining attempt if method -> target access is not right.
if (resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
whyAreYouNotInliningReporter.reportInaccessible();
return false;
}
- if (reason == Reason.SIMPLE && !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
- whyAreYouNotInliningReporter.reportInlineeNotSimple();
- return false;
- }
-
// Don't inline code with references beyond root main dex classes into a root main dex class.
// If we do this it can increase the size of the main dex dependent classes.
- if (reason != Reason.FORCE
- && mainDexInfo.disallowInliningIntoContext(
- appView, method, singleTarget, appView.getSyntheticItems())) {
+ if (mainDexInfo.disallowInliningIntoContext(
+ appView, method, singleTarget, appView.getSyntheticItems())) {
whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
return false;
}
- assert reason != Reason.FORCE
- || !mainDexInfo.disallowInliningIntoContext(
- appView, method, singleTarget, appView.getSyntheticItems());
+ assert !mainDexInfo.disallowInliningIntoContext(
+ appView, method, singleTarget, appView.getSyntheticItems());
return true;
}
@@ -217,16 +206,28 @@
|| method.getDefinition().getCode().hasMonitorInstructions();
}
- public boolean satisfiesRequirementsForSimpleInlining(InvokeMethod invoke, ProgramMethod target) {
+ public boolean satisfiesRequirementsForSimpleInlining(
+ InvokeMethod invoke, ProgramMethod target, Optional<InliningIRProvider> inliningIRProvider) {
// Code size modified by inlining, so only read for non-concurrent methods.
boolean deterministic = !methodProcessor.isProcessedConcurrently(target);
if (deterministic) {
- // If we are looking for a simple method, only inline if actually simple.
+ // Check if the inlinee is sufficiently small to inline.
Code code = target.getDefinition().getCode();
- int instructionLimit =
- inlinerOptions.getSimpleInliningInstructionLimit()
- + getInliningInstructionLimitIncrement(invoke, target);
- if (code.estimatedSizeForInliningAtMost(instructionLimit)) {
+ int instructionLimit = inlinerOptions.getSimpleInliningInstructionLimit();
+ int estimatedMaxIncrement =
+ getEstimatedMaxInliningInstructionLimitIncrement(invoke, target, inliningIRProvider);
+ int estimatedSizeForInlining =
+ code.getEstimatedSizeForInliningIfLessThanOrEquals(
+ instructionLimit + estimatedMaxIncrement);
+ if (estimatedSizeForInlining < 0) {
+ return false;
+ }
+ if (estimatedSizeForInlining <= instructionLimit) {
+ return true;
+ }
+ int actualIncrement =
+ getInliningInstructionLimitIncrement(invoke, target, inliningIRProvider);
+ if (estimatedSizeForInlining <= instructionLimit + actualIncrement) {
return true;
}
}
@@ -237,29 +238,140 @@
return simpleInliningConstraint.isSatisfied(invoke);
}
- private int getInliningInstructionLimitIncrement(InvokeMethod invoke, ProgramMethod candidate) {
+ private int getInliningInstructionLimitIncrement(
+ InvokeMethod invoke, ProgramMethod target, Optional<InliningIRProvider> inliningIRProvider) {
+ if (!options.inlinerOptions().enableSimpleInliningInstructionLimitIncrement) {
+ return 0;
+ }
+ return getInliningInstructionLimitIncrementForNonNullParamOrThrow(invoke, target)
+ + getInliningInstructionLimitIncrementForPrelude(invoke, target, inliningIRProvider)
+ + getInliningInstructionLimitIncrementForReturn(invoke);
+ }
+
+ private int getEstimatedMaxInliningInstructionLimitIncrement(
+ InvokeMethod invoke, ProgramMethod target, Optional<InliningIRProvider> inliningIRProvider) {
+ if (!options.inlinerOptions().enableSimpleInliningInstructionLimitIncrement) {
+ return 0;
+ }
+ return getEstimatedMaxInliningInstructionLimitIncrementForNonNullParamOrThrow(invoke, target)
+ + getEstimatedMaxInliningInstructionLimitIncrementForPrelude(
+ invoke, target, inliningIRProvider)
+ + getEstimatedMaxInliningInstructionLimitIncrementForReturn(invoke);
+ }
+
+ private int getInliningInstructionLimitIncrementForNonNullParamOrThrow(
+ InvokeMethod invoke, ProgramMethod target) {
+ BitSet hints = target.getOptimizationInfo().getNonNullParamOrThrow();
+ if (hints == null) {
+ return 0;
+ }
int instructionLimit = 0;
- BitSet hints = candidate.getDefinition().getOptimizationInfo().getNonNullParamOrThrow();
- if (hints != null) {
- List<Value> arguments = invoke.arguments();
- for (int index = invoke.getFirstNonReceiverArgumentIndex();
- index < arguments.size();
- index++) {
- Value argument = arguments.get(index);
- if ((argument.isArgument()
- || (argument.getType().isReferenceType() && argument.isNeverNull()))
- && hints.get(index)) {
- // 5-4 instructions per parameter check are expected to be removed.
- instructionLimit += 4;
+ for (int index = invoke.getFirstNonReceiverArgumentIndex();
+ index < invoke.arguments().size();
+ index++) {
+ Value argument = invoke.getArgument(index);
+ if (hints.get(index) && argument.getType().isReferenceType() && argument.isNeverNull()) {
+ // 5-4 instructions per parameter check are expected to be removed.
+ instructionLimit += 4;
+ }
+ }
+ return instructionLimit;
+ }
+
+ private int getEstimatedMaxInliningInstructionLimitIncrementForNonNullParamOrThrow(
+ InvokeMethod invoke, ProgramMethod target) {
+ return getInliningInstructionLimitIncrementForNonNullParamOrThrow(invoke, target);
+ }
+
+ private int getInliningInstructionLimitIncrementForPrelude(
+ InvokeMethod invoke, ProgramMethod target, Optional<InliningIRProvider> inliningIRProvider) {
+ if (inliningIRProvider.isEmpty()
+ || invoke.arguments().isEmpty()
+ || !target.getDefinition().getCode().isLirCode()) {
+ return 0;
+ }
+ IRCode code = inliningIRProvider.get().getAndCacheInliningIR(invoke, target);
+ Iterable<Argument> arguments = code::argumentIterator;
+ DexItemFactory factory = appView.dexItemFactory();
+ int increment = 0;
+ for (Argument argument : arguments) {
+ Value argumentValue = argument.outValue();
+ for (Instruction user : argumentValue.uniqueUsers()) {
+ if (user.isCheckCast()) {
+ CheckCast checkCastUser = user.asCheckCast();
+ TypeElement argumentType = invoke.getArgument(argument.getIndex()).getType();
+ TypeElement castType = checkCastUser.getType().toTypeElement(appView);
+ if (argumentType.lessThanOrEqual(castType, appView)) {
+ // We can remove the cast inside the inlinee.
+ increment += DexCheckCast.SIZE;
+ }
+ } else {
+ DexType argumentType = target.getArgumentType(argument.getIndex());
+ BoxUnboxPrimitiveMethodRoundtrip roundtrip =
+ factory.getBoxUnboxPrimitiveMethodRoundtrip(argumentType);
+ if (roundtrip == null) {
+ continue;
+ }
+ Value invokeArgument = invoke.getArgument(argument.getIndex()).getAliasedValue();
+ if (user.isInvokeMethod(roundtrip.getBoxIfPrimitiveElseUnbox())
+ && invokeArgument.isDefinedByInstructionSatisfying(
+ definition ->
+ definition.isInvokeMethod(roundtrip.getUnboxIfPrimitiveElseBox()))) {
+ // We can remove the unbox/box operation inside the inlinee.
+ increment += DexInvokeStatic.SIZE + DexMoveResult.SIZE;
+ if (invokeArgument.numberOfAllUsers() == 1 && argumentValue.numberOfAllUsers() == 1) {
+ // We can remove the box/unbox operation inside the caller.
+ increment += DexInvokeStatic.SIZE + DexMoveResult.SIZE;
+ }
+ }
}
}
}
+ return increment;
+ }
+
+ private int getEstimatedMaxInliningInstructionLimitIncrementForPrelude(
+ InvokeMethod invoke, ProgramMethod target, Optional<InliningIRProvider> inliningIRProvider) {
+ if (inliningIRProvider.isEmpty()
+ || invoke.arguments().isEmpty()
+ || !target.getDefinition().getCode().isLirCode()) {
+ return 0;
+ }
+ int increment = 0;
+ for (int argumentIndex = invoke.getFirstNonReceiverArgumentIndex();
+ argumentIndex < invoke.arguments().size();
+ argumentIndex++) {
+ Value argument = invoke.getArgument(argumentIndex).getAliasedValue();
+ if (argument.getType().isReferenceType()) {
+ // We can maybe remove a cast inside the inlinee.
+ increment += DexCheckCast.SIZE;
+ }
+ DexType argumentType = target.getArgumentType(argumentIndex);
+ BoxUnboxPrimitiveMethodRoundtrip roundtrip =
+ appView.dexItemFactory().getBoxUnboxPrimitiveMethodRoundtrip(argumentType);
+ if (roundtrip != null
+ && argument.isDefinedByInstructionSatisfying(
+ definition -> definition.isInvokeMethod(roundtrip.getUnboxIfPrimitiveElseBox()))) {
+ // We can maybe remove a unbox/box operation inside the inlinee.
+ increment += DexInvokeStatic.SIZE + DexMoveResult.SIZE;
+ // We can maybe remove a box/unbox operation inside the caller.
+ increment += DexInvokeStatic.SIZE + DexMoveResult.SIZE;
+ }
+ }
+ return increment;
+ }
+
+ private int getInliningInstructionLimitIncrementForReturn(InvokeMethod invoke) {
if (options.isGeneratingDex() && invoke.hasOutValue() && invoke.outValue().hasNonDebugUsers()) {
assert DexMoveResult.SIZE == DexMoveResultObject.SIZE;
assert DexMoveResult.SIZE == DexMoveResultWide.SIZE;
- instructionLimit += DexMoveResult.SIZE;
+ return DexMoveResult.SIZE;
}
- return instructionLimit;
+ return 0;
+ }
+
+ private int getEstimatedMaxInliningInstructionLimitIncrementForReturn(InvokeMethod invoke) {
+ return getInliningInstructionLimitIncrementForReturn(invoke);
}
@Override
@@ -294,12 +406,19 @@
}
Reason reason =
- reasonStrategy.computeInliningReason(invoke, singleTarget, context, this, methodProcessor);
+ reasonStrategy.computeInliningReason(
+ invoke,
+ singleTarget,
+ context,
+ this,
+ inliningIRProvider,
+ methodProcessor,
+ whyAreYouNotInliningReporter);
if (reason == Reason.NEVER) {
return null;
}
- if (reason == Reason.SIMPLE
+ if (methodProcessor.getCallSiteInformation().hasSingleCallSite(singleTarget, context)
&& !singleTarget.getDefinition().isProcessed()
&& methodProcessor.isPrimaryMethodProcessor()) {
// The single target has this method as single caller, but the single target is not yet
@@ -310,12 +429,14 @@
if (!singleTarget
.getDefinition()
- .isInliningCandidate(method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
+ .isInliningCandidate(appView, method, whyAreYouNotInliningReporter)) {
return null;
}
if (!passesInliningConstraints(
- invoke, resolutionResult, singleTarget, reason, whyAreYouNotInliningReporter)) {
+ resolutionResult,
+ singleTarget,
+ whyAreYouNotInliningReporter)) {
return null;
}
@@ -329,14 +450,14 @@
}
}
- InlineAction action =
+ InlineAction.Builder actionBuilder =
invoke.computeInlining(
- singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter);
- if (action == null) {
+ singleTarget, this, classInitializationAnalysis, whyAreYouNotInliningReporter);
+ if (actionBuilder == null) {
return null;
}
- if (!setDowncastTypeIfNeeded(appView, action, invoke, singleTarget, context)) {
+ if (!setDowncastTypeIfNeeded(appView, actionBuilder, invoke, singleTarget, context)) {
return null;
}
@@ -351,7 +472,24 @@
return null;
}
- return action;
+ if (reason == Reason.MULTI_CALLER_CANDIDATE) {
+ assert methodProcessor.isPrimaryMethodProcessor();
+ actionBuilder.setReason(
+ satisfiesRequirementsForSimpleInlining(
+ invoke, singleTarget, Optional.of(inliningIRProvider))
+ ? Reason.SIMPLE
+ : reason);
+ } else {
+ if (reason == Reason.SIMPLE
+ && !satisfiesRequirementsForSimpleInlining(
+ invoke, singleTarget, Optional.of(inliningIRProvider))) {
+ whyAreYouNotInliningReporter.reportInlineeNotSimple();
+ return null;
+ }
+ actionBuilder.setReason(reason);
+ }
+
+ return actionBuilder.build();
}
private boolean neverInline(
@@ -380,10 +518,9 @@
return false;
}
- public InlineAction computeForInvokeWithReceiver(
+ public InlineAction.Builder computeForInvokeWithReceiver(
InvokeMethodWithReceiver invoke,
ProgramMethod singleTarget,
- Reason reason,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
Value receiver = invoke.getReceiver();
if (receiver.getType().isDefinitelyNull()) {
@@ -391,8 +528,6 @@
whyAreYouNotInliningReporter.reportReceiverDefinitelyNull();
return null;
}
-
- InlineAction action = new InlineAction(singleTarget, invoke, reason);
if (receiver.getType().isNullable()) {
assert !receiver.getType().isDefinitelyNull();
// When inlining an instance method call, we need to preserve the null check for the
@@ -404,23 +539,22 @@
return null;
}
}
- return action;
+ return InlineAction.builder().setInvoke(invoke).setTarget(singleTarget);
}
- public InlineAction computeForInvokeStatic(
+ public InlineAction.Builder computeForInvokeStatic(
InvokeStatic invoke,
ProgramMethod singleTarget,
- Reason reason,
ClassInitializationAnalysis classInitializationAnalysis,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- InlineAction action = new InlineAction(singleTarget, invoke, reason);
+ InlineAction.Builder actionBuilder =
+ InlineAction.builder().setInvoke(invoke).setTarget(singleTarget);
if (isTargetClassInitialized(invoke, method, singleTarget, classInitializationAnalysis)) {
- return action;
+ return actionBuilder;
}
if (appView.canUseInitClass()
&& inlinerOptions.enableInliningOfInvokesWithClassInitializationSideEffects) {
- action.setShouldEnsureStaticInitialization();
- return action;
+ return actionBuilder.setShouldEnsureStaticInitialization();
}
whyAreYouNotInliningReporter.reportMustTriggerClassInitialization();
return null;
@@ -610,7 +744,7 @@
@Override
public boolean stillHasBudget(
InlineAction action, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- if (action.reason.mustBeInlined()) {
+ if (action.mustBeInlined()) {
return true;
}
boolean stillHasBudget = instructionAllowance > 0;
@@ -622,12 +756,13 @@
@Override
public boolean willExceedBudget(
+ InlineAction action,
IRCode code,
+ IRCode inlinee,
InvokeMethod invoke,
- InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- if (inlinee.reason.mustBeInlined()) {
+ if (action.mustBeInlined()) {
return false;
}
return willExceedInstructionBudget(inlinee, whyAreYouNotInliningReporter)
@@ -637,9 +772,9 @@
}
private boolean willExceedInstructionBudget(
- InlineeWithReason inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- int numberOfInstructions = Inliner.numberOfInstructions(inlinee.code);
- if (instructionAllowance < Inliner.numberOfInstructions(inlinee.code)) {
+ IRCode inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ int numberOfInstructions = Inliner.numberOfInstructions(inlinee);
+ if (instructionAllowance < Inliner.numberOfInstructions(inlinee)) {
whyAreYouNotInliningReporter.reportWillExceedInstructionBudget(
numberOfInstructions, instructionAllowance);
return true;
@@ -658,13 +793,13 @@
private boolean willExceedMonitorEnterValuesBudget(
IRCode code,
InvokeMethod invoke,
- InlineeWithReason inlinee,
+ IRCode inlinee,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (!code.metadata().mayHaveMonitorInstruction()) {
return false;
}
- if (!inlinee.code.metadata().mayHaveMonitorInstruction()) {
+ if (!inlinee.metadata().mayHaveMonitorInstruction()) {
return false;
}
@@ -675,7 +810,7 @@
return false;
}
- for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) {
+ for (Monitor monitor : inlinee.<Monitor>instructions(Instruction::isMonitorEnter)) {
Value monitorEnterValue = monitor.object().getAliasedValue();
if (monitorEnterValue.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
monitorEnterValue =
@@ -722,14 +857,12 @@
* exception-edge. We therefore abort inlining if the number of exception-edges explode.
*/
private boolean willExceedControlFlowResolutionBlocksBudget(
- InlineeWithReason inlinee,
- BasicBlock block,
- WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ IRCode inlinee, BasicBlock block, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (!block.hasCatchHandlers()) {
return false;
}
int numberOfThrowingInstructionsInInlinee = 0;
- for (BasicBlock inlineeBlock : inlinee.code.blocks) {
+ for (BasicBlock inlineeBlock : inlinee.blocks) {
numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions();
}
// Estimate the number of "control flow resolution blocks", where we will insert a
@@ -749,9 +882,9 @@
}
@Override
- public void markInlined(InlineeWithReason inlinee) {
+ public void markInlined(IRCode inlinee) {
// TODO(118734615): All inlining use from the budget - should that only be SIMPLE?
- instructionAllowance -= Inliner.numberOfInstructions(inlinee.code);
+ instructionAllowance -= Inliner.numberOfInstructions(inlinee);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index a73733e..9cb641e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -15,8 +15,6 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineResult;
-import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -25,15 +23,12 @@
final class ForcedInliningOracle implements InliningOracle, InliningStrategy {
private final AppView<AppInfoWithLiveness> appView;
- private final ProgramMethod method;
private final Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline;
ForcedInliningOracle(
AppView<AppInfoWithLiveness> appView,
- ProgramMethod method,
Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline) {
this.appView = appView;
- this.method = method;
this.invokesToInline = invokesToInline;
}
@@ -49,10 +44,8 @@
@Override
public boolean passesInliningConstraints(
- InvokeMethod invoke,
SingleResolutionResult<?> resolutionResult,
ProgramMethod candidate,
- Reason reason,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
return true;
}
@@ -76,29 +69,16 @@
ClassInitializationAnalysis classInitializationAnalysis,
InliningIRProvider inliningIRProvider,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- InlineAction action = computeForInvoke(invoke, resolutionResult, whyAreYouNotInliningReporter);
- if (action == null) {
- return null;
- }
- if (!setDowncastTypeIfNeeded(appView, action, invoke, singleTarget, context)) {
- return null;
- }
- return action;
- }
-
- @SuppressWarnings("ReferenceEquality")
- private InlineAction computeForInvoke(
- InvokeMethod invoke,
- SingleResolutionResult<?> resolutionResult,
- WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
Inliner.InliningInfo info = invokesToInline.get(invoke);
if (info == null) {
return null;
}
- assert method.getDefinition() != info.target.getDefinition();
- assert passesInliningConstraints(
- invoke, resolutionResult, info.target, Reason.FORCE, whyAreYouNotInliningReporter);
- return new InlineAction(info.target, invoke, Reason.FORCE);
+ InlineAction.Builder actionBuilder =
+ InlineAction.builder().setInvoke(invoke).setTarget(info.target);
+ if (!setDowncastTypeIfNeeded(appView, actionBuilder, invoke, singleTarget, context)) {
+ return null;
+ }
+ return actionBuilder.build();
}
@Override
@@ -119,16 +99,17 @@
@Override
public boolean willExceedBudget(
+ InlineAction action,
IRCode code,
+ IRCode inlinee,
InvokeMethod invoke,
- InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
return false; // Unlimited allowance.
}
@Override
- public void markInlined(InlineeWithReason inlinee) {}
+ public void markInlined(IRCode inlinee) {}
@Override
public ClassTypeElement getReceiverTypeOrDefault(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 1004186..fd3ec8c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -70,6 +70,7 @@
import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -122,6 +123,10 @@
: null;
}
+ public LensCodeRewriter getLensCodeRewriter() {
+ return lensCodeRewriter;
+ }
+
@SuppressWarnings("ReferenceEquality")
private ConstraintWithTarget instructionAllowedForInlining(
Instruction instruction, InliningConstraints inliningConstraints, ProgramMethod context) {
@@ -227,6 +232,10 @@
return otherConstraint;
}
+ public boolean isNever() {
+ return this == NEVER;
+ }
+
boolean isSet(int value) {
return (this.value & value) != 0;
}
@@ -263,12 +272,17 @@
}
ConstraintWithTarget(Constraint constraint, DexType targetHolder) {
- assert constraint != Constraint.NEVER && constraint != Constraint.ALWAYS;
+ assert constraint != Constraint.NEVER;
+ assert constraint != Constraint.ALWAYS;
assert targetHolder != null;
this.constraint = constraint;
this.targetHolder = targetHolder;
}
+ public boolean isNever() {
+ return constraint.isNever();
+ }
+
@Override
public int hashCode() {
if (targetHolder == null) {
@@ -487,7 +501,6 @@
* that will inline a method irrespective of visibility and instruction checks.
*/
public enum Reason {
- FORCE, // Inlinee is marked for forced inlining (bridge method or renamed constructor).
ALWAYS, // Inlinee is marked for inlining due to alwaysinline directive.
SINGLE_CALLER, // Inlinee has precisely one caller.
// Inlinee has multiple callers and should not be inlined. Only used during the primary
@@ -495,11 +508,6 @@
MULTI_CALLER_CANDIDATE,
SIMPLE, // Inlinee has simple code suitable for inlining.
NEVER; // Inlinee must not be inlined.
-
- public boolean mustBeInlined() {
- // TODO(118734615): Include SINGLE_CALLER here as well?
- return this == FORCE || this == ALWAYS;
- }
}
public abstract static class InlineResult {
@@ -529,6 +537,10 @@
this.reason = reason;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
InlineAction asInlineAction() {
return this;
@@ -546,12 +558,15 @@
shouldEnsureStaticInitialization = true;
}
- InlineeWithReason buildInliningIR(
+ boolean mustBeInlined() {
+ return reason == Reason.ALWAYS;
+ }
+
+ IRCode buildInliningIR(
AppView<AppInfoWithLiveness> appView,
InvokeMethod invoke,
ProgramMethod context,
- InliningIRProvider inliningIRProvider,
- LensCodeRewriter lensCodeRewriter) {
+ InliningIRProvider inliningIRProvider) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
@@ -699,17 +714,12 @@
}
}
}
-
- if (inliningIRProvider.shouldApplyCodeRewritings(target)) {
- assert lensCodeRewriter != null;
- lensCodeRewriter.rewrite(code, target, inliningIRProvider.getMethodProcessor());
- }
if (options.testing.inlineeIrModifier != null) {
options.testing.inlineeIrModifier.accept(code);
}
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
- return new InlineeWithReason(code, reason);
+ return code;
}
private void handleSimpleEffectAnalysisResult(
@@ -778,6 +788,51 @@
instruction.forceOverwritePosition(
position.replacePosition(outermostCaller, removeInnerFrame));
}
+
+ public static class Builder {
+
+ private DexProgramClass downcastClass;
+ private InvokeMethod invoke;
+ private Reason reason;
+ private boolean shouldEnsureStaticInitialization;
+ private ProgramMethod target;
+
+ Builder setDowncastClass(DexProgramClass downcastClass) {
+ this.downcastClass = downcastClass;
+ return this;
+ }
+
+ Builder setInvoke(InvokeMethod invoke) {
+ this.invoke = invoke;
+ return this;
+ }
+
+ Builder setReason(Reason reason) {
+ this.reason = reason;
+ return this;
+ }
+
+ Builder setShouldEnsureStaticInitialization() {
+ this.shouldEnsureStaticInitialization = true;
+ return this;
+ }
+
+ Builder setTarget(ProgramMethod target) {
+ this.target = target;
+ return this;
+ }
+
+ InlineAction build() {
+ InlineAction action = new InlineAction(target, invoke, reason);
+ if (downcastClass != null) {
+ action.setDowncastClass(downcastClass);
+ }
+ if (shouldEnsureStaticInitialization) {
+ action.setShouldEnsureStaticInitialization();
+ }
+ return action;
+ }
+ }
}
public static class RetryAction extends InlineResult {
@@ -788,17 +843,6 @@
}
}
- static class InlineeWithReason {
-
- final Reason reason;
- final IRCode code;
-
- InlineeWithReason(IRCode code, Reason reason) {
- this.code = code;
- this.reason = reason;
- }
- }
-
static int numberOfInstructions(IRCode code) {
int numberOfInstructions = 0;
for (BasicBlock block : code.blocks) {
@@ -839,6 +883,10 @@
public final ProgramMethod target;
public final DexProgramClass receiverClass; // null, if unknown
+ public InliningInfo(ProgramMethod target) {
+ this(target, null);
+ }
+
public InliningInfo(ProgramMethod target, DexProgramClass receiverClass) {
this.target = target;
this.receiverClass = receiverClass;
@@ -852,7 +900,7 @@
InliningIRProvider inliningIRProvider,
MethodProcessor methodProcessor,
Timing timing) {
- ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
+ ForcedInliningOracle oracle = new ForcedInliningOracle(appView, invokesToInline);
performInliningImpl(
oracle,
oracle,
@@ -894,7 +942,7 @@
options.inliningInstructionAllowance - numberOfInstructions(code),
inliningReasonStrategy);
InliningIRProvider inliningIRProvider =
- new InliningIRProvider(appView, method, code, methodProcessor);
+ new InliningIRProvider(appView, method, code, lensCodeRewriter, methodProcessor);
assert inliningIRProvider.verifyIRCacheIsEmpty();
performInliningImpl(
oracle, oracle, method, code, feedback, inliningIRProvider, methodProcessor, timing);
@@ -986,13 +1034,14 @@
continue;
}
+ InliningOracle singleTargetOracle = getSingleTargetOracle(invoke, singleTarget, oracle);
DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
- oracle.isForcedInliningOracle()
+ singleTargetOracle.isForcedInliningOracle()
? NopWhyAreYouNotInliningReporter.getInstance()
: WhyAreYouNotInliningReporter.createFor(singleTarget, appView, context);
InlineResult inlineResult =
- oracle.computeInlining(
+ singleTargetOracle.computeInlining(
code,
invoke,
resolutionResult,
@@ -1022,11 +1071,9 @@
continue;
}
- InlineeWithReason inlinee =
- action.buildInliningIR(
- appView, invoke, context, inliningIRProvider, lensCodeRewriter);
+ IRCode inlinee = action.buildInliningIR(appView, invoke, context, inliningIRProvider);
if (strategy.willExceedBudget(
- code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
+ action, code, inlinee, invoke, block, whyAreYouNotInliningReporter)) {
assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
@@ -1034,21 +1081,16 @@
// Verify this code went through the full pipeline.
assert singleTarget.getDefinition().isProcessed();
- boolean inlineeMayHaveInvokeMethod = inlinee.code.metadata().mayHaveInvokeMethod();
+ boolean inlineeMayHaveInvokeMethod = inlinee.metadata().mayHaveInvokeMethod();
// Inline the inlinee code in place of the invoke instruction
// Back up before the invoke instruction.
iterator.previous();
strategy.markInlined(inlinee);
iterator.inlineInvoke(
- appView,
- code,
- inlinee.code,
- blockIterator,
- blocksToRemove,
- action.getDowncastClass());
+ appView, code, inlinee, blockIterator, blocksToRemove, action.getDowncastClass());
- if (inlinee.reason == Reason.SINGLE_CALLER) {
+ if (methodProcessor.getCallSiteInformation().hasSingleCallSite(singleTarget, context)) {
assert converter.isInWave();
feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
if (singleCallerInlinedMethodsInWave.isEmpty()) {
@@ -1065,12 +1107,12 @@
code, blockIterator, block, affectedValues, blocksToRemove, timing);
// The synthetic and bridge flags are maintained only if the inlinee has also these flags.
- if (context.getDefinition().isBridge() && !inlinee.code.method().isBridge()) {
- context.getDefinition().accessFlags.demoteFromBridge();
+ if (context.getAccessFlags().isBridge() && !singleTarget.getAccessFlags().isBridge()) {
+ context.getAccessFlags().demoteFromBridge();
}
- if (context.getDefinition().accessFlags.isSynthetic()
- && !inlinee.code.method().accessFlags.isSynthetic()) {
- context.getDefinition().accessFlags.demoteFromSynthetic();
+ if (context.getAccessFlags().isSynthetic()
+ && !singleTarget.getAccessFlags().isSynthetic()) {
+ context.getAccessFlags().demoteFromSynthetic();
}
context.getDefinition().copyMetadata(appView, singleTargetMethod);
@@ -1098,6 +1140,14 @@
assert code.isConsistentSSA(appView);
}
+ private InliningOracle getSingleTargetOracle(
+ InvokeMethod invoke, ProgramMethod singleTarget, InliningOracle oracle) {
+ return oracle.isForcedInliningOracle() || !singleTarget.getOptimizationInfo().forceInline()
+ ? oracle
+ : new ForcedInliningOracle(
+ appView, ImmutableMap.of(invoke, new InliningInfo(singleTarget)));
+ }
+
private boolean tryInlineMethodWithoutSideEffects(
IRCode code,
InstructionListIterator iterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 46dd4a0..8d5d425 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -24,8 +24,8 @@
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.VerticalClassMerger.SingleTypeMapperGraphLens;
import com.android.tools.r8.utils.TriFunction;
+import com.android.tools.r8.verticalclassmerging.SingleTypeMapperGraphLens;
// Computes the inlining constraint for a given instruction.
public class InliningConstraints {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index b484dda..b7f06c8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.Inliner.InlineResult;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
@@ -25,10 +24,8 @@
ProgramMethod lookupSingleTarget(InvokeMethod invoke, ProgramMethod context);
boolean passesInliningConstraints(
- InvokeMethod invoke,
SingleResolutionResult<?> resolutionResult,
ProgramMethod candidate,
- Reason reason,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
InlineResult computeInlining(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index b76544b..a7bb682 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -41,18 +40,19 @@
* <p>Return true if the strategy will *not* allow inlining.
*/
boolean willExceedBudget(
+ InlineAction action,
IRCode code,
+ IRCode inlinee,
InvokeMethod invoke,
- InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
/** Inform the strategy that the inlinee has been inlined. */
- void markInlined(InlineeWithReason inlinee);
+ void markInlined(IRCode inlinee);
default boolean setDowncastTypeIfNeeded(
AppView<AppInfoWithLiveness> appView,
- InlineAction action,
+ InlineAction.Builder actionBuilder,
InvokeMethod invoke,
ProgramMethod singleTarget,
ProgramMethod context) {
@@ -61,7 +61,7 @@
if (AccessControl.isClassAccessible(downcastClass, context, appView).isPossiblyFalse()) {
return false;
}
- action.setDowncastClass(downcastClass);
+ actionBuilder.setDowncastClass(downcastClass);
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
index 4b5f0c9..baa1fc5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
-import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineResult;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
@@ -75,7 +74,7 @@
int inliningInstructionAllowance = Integer.MAX_VALUE;
return new DefaultInliningOracle(
appView,
- new FixedInliningReasonStrategy(Reason.MULTI_CALLER_CANDIDATE),
+ new FixedInliningReasonStrategy(Reason.ALWAYS),
method,
methodProcessor,
inliningInstructionAllowance);
@@ -120,9 +119,6 @@
stopTrackingCallSitesForMethod(singleTarget);
continue;
}
-
- InlineAction action = inlineResult.asInlineAction();
- assert action.reason == Reason.MULTI_CALLER_CANDIDATE;
recordCallEdgeForMultiCallerInlining(method, singleTarget, methodProcessor);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index bd85948..898bd66 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
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.SingleFieldValue;
@@ -48,6 +47,7 @@
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -507,7 +507,7 @@
}
private boolean verifyWasInstanceInitializer() {
- VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+ VerticallyMergedClasses verticallyMergedClasses = appView.getVerticallyMergedClasses();
assert verticallyMergedClasses != null;
assert verticallyMergedClasses.isMergeTarget(method.getHolderType())
|| appView.horizontallyMergedClasses().isMergeTarget(method.getHolderType());
@@ -659,7 +659,7 @@
}
private void handleArrayPut(ArrayPut arrayPut) {
- int index = arrayPut.getIndexOrDefault(-1);
+ int index = arrayPut.indexOrDefault(-1);
MemberType memberType = arrayPut.getMemberType();
// An array-put instruction can potentially write the given array slot on all arrays because
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 0aa891a..360d241 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -27,7 +27,6 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.Set;
import java.util.function.BiConsumer;
public class ReflectionOptimizer {
@@ -80,7 +79,7 @@
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
- Set<Value> affectedValues) {
+ AffectedValues affectedValues) {
return (type, baseClass) -> {
InitClass initClass = null;
if (invoke.getInvokedMethod().match(appView.dexItemFactory().classMethods.forName)) {
@@ -124,9 +123,8 @@
// Otherwise insert a const-class instruction.
BasicBlock block = invoke.getBlock();
- affectedValues.addAll(invoke.outValue().affectedValues());
instructionIterator.replaceCurrentInstructionWithConstClass(
- appView, code, type, invoke.getLocalInfo());
+ appView, code, type, invoke.getLocalInfo(), affectedValues);
if (initClass != null) {
if (block.hasCatchHandlers()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 3c4eac4..020390f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -180,7 +180,8 @@
// Is inlining allowed.
InliningIRProvider inliningIRProvider =
- new InliningIRProvider(appView, method, code, methodProcessor);
+ new InliningIRProvider(
+ appView, method, code, inliner.getLensCodeRewriter(), methodProcessor);
ClassInlinerCostAnalysis costAnalysis =
new ClassInlinerCostAnalysis(appView, inliningIRProvider, processor.getReceivers());
if (costAnalysis.willExceedInstructionBudget(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 595c8dc..afa1947 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -56,7 +56,6 @@
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.EligibilityStatus;
import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
@@ -421,34 +420,32 @@
continue;
}
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod == dexItemFactory.objectMembers.constructor) {
+ SingleResolutionResult<?> resolutionResult =
+ invoke.resolveMethod(appView, code.context()).asSingleResolution();
+ if (resolutionResult == null) {
+ throw new IllegalClassInlinerStateException();
+ }
+
+ DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
+ if (resolvedMethod
+ .getReference()
+ .isIdenticalTo(dexItemFactory.objectMembers.constructor)) {
continue;
}
- if (!dexItemFactory.isConstructor(invokedMethod)) {
+ if (!resolvedMethod.getDefinition().isInstanceInitializer()) {
throw new IllegalClassInlinerStateException();
}
- DexProgramClass holder =
- asProgramClassOrNull(appView.definitionForHolder(invokedMethod, method));
- if (holder == null) {
+ if (!resolvedMethod
+ .getDefinition()
+ .isInliningCandidate(
+ appView, method, NopWhyAreYouNotInliningReporter.getInstance())) {
throw new IllegalClassInlinerStateException();
}
- ProgramMethod singleTarget = holder.lookupProgramMethod(invokedMethod);
- if (singleTarget == null
- || !singleTarget
- .getDefinition()
- .isInliningCandidate(
- method,
- Reason.ALWAYS,
- appView.appInfo(),
- NopWhyAreYouNotInliningReporter.getInstance())) {
- throw new IllegalClassInlinerStateException();
- }
-
- directMethodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass));
+ directMethodCalls.put(
+ invoke, new InliningInfo(resolvedMethod.asProgramMethod(), eligibleClass));
break;
}
}
@@ -482,11 +479,6 @@
if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod == dexItemFactory.objectMembers.constructor) {
- continue;
- }
-
Value receiver = invoke.getReceiver().getAliasedValue(aliasesThroughAssumeAndCheckCasts);
if (receiver != eligibleInstance) {
continue;
@@ -498,6 +490,14 @@
throw new IllegalClassInlinerStateException();
}
+ DexMethod objectConstructor = dexItemFactory.objectMembers.constructor;
+ if (resolutionResult
+ .getResolvedMethod()
+ .getReference()
+ .isIdenticalTo(objectConstructor)) {
+ continue;
+ }
+
DispatchTargetLookupResult dispatchTargetLookupResult;
if (invoke.isInvokeDirect() || invoke.isInvokeSuper()) {
dispatchTargetLookupResult =
@@ -978,10 +978,7 @@
}
DexEncodedMethod encodedParentMethod = encodedParent.getDefinition();
if (!encodedParentMethod.isInliningCandidate(
- method,
- Reason.ALWAYS,
- appView.appInfo(),
- NopWhyAreYouNotInliningReporter.getInstance())) {
+ appView, method, NopWhyAreYouNotInliningReporter.getInstance())) {
return null;
}
// Check the api level is allowed to be inlined.
@@ -1163,10 +1160,8 @@
// Check if the method is inline-able by standard inliner.
InliningOracle oracle = defaultOracle.computeIfAbsent();
if (!oracle.passesInliningConstraints(
- invoke,
resolutionResult,
singleTarget,
- Reason.ALWAYS,
NopWhyAreYouNotInliningReporter.getInstance())) {
return false;
}
@@ -1303,11 +1298,7 @@
}
if (!singleTarget
.getDefinition()
- .isInliningCandidate(
- method,
- Reason.ALWAYS,
- appView.appInfo(),
- NopWhyAreYouNotInliningReporter.getInstance())) {
+ .isInliningCandidate(appView, method, NopWhyAreYouNotInliningReporter.getInstance())) {
// If `singleTarget` is not an inlining candidate, we won't be able to inline it here.
//
// Note that there may be some false negatives here since the method may
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 73e2edd..47f2e03 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -140,7 +140,7 @@
MethodProcessor methodProcessor,
Timing timing) {
DexEncodedMethod definition = method.getDefinition();
- identifyBridgeInfo(definition, code, feedback, timing);
+ identifyBridgeInfo(method, code, feedback, timing);
analyzeReturns(code, feedback, methodProcessor, timing);
if (options.enableClassInlining) {
computeClassInlinerMethodConstraint(method, code, feedback, timing);
@@ -162,9 +162,9 @@
}
private void identifyBridgeInfo(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+ ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
timing.begin("Identify bridge info");
- feedback.setBridgeInfo(method, BridgeAnalyzer.analyzeMethod(method, code));
+ feedback.setBridgeInfo(method, BridgeAnalyzer.analyzeMethod(method.getDefinition(), code));
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
index 509c709..0dd4db5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
@@ -168,7 +168,9 @@
if (!keepInfo.isShrinkingAllowed(appView.options())) {
// Method is kept and could be overridden outside app (e.g., in tests). Verify we don't
// have any optimization info recorded for non-abstract methods.
- assert method.isAbstract() || method.getOptimizationInfo().isDefault();
+ assert method.isAbstract()
+ || method.getOptimizationInfo().isDefault()
+ || method.getOptimizationInfo().returnValueHasBeenPropagated();
newState.joinMethodOptimizationInfo(
appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance());
} else if (!method.isAbstract()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoReprocessingEnqueuer.java
index 2470444..be20d5b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoReprocessingEnqueuer.java
@@ -5,13 +5,11 @@
package com.android.tools.r8.ir.optimize.info;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
-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.UseRegistryWithResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -72,7 +70,8 @@
postMethodProcessorBuilder.addAll(methodsToReprocessInClass, currentGraphLens));
}
- static class AffectedMethodUseRegistry extends UseRegistryWithResult<Boolean, ProgramMethod> {
+ static class AffectedMethodUseRegistry
+ extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
private final AppView<AppInfoWithLiveness> appViewWithLiveness;
@@ -143,23 +142,5 @@
markAffected();
}
}
-
- @Override
- public void registerInstanceFieldRead(DexField field) {}
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {}
-
- @Override
- public void registerStaticFieldRead(DexField field) {}
-
- @Override
- public void registerStaticFieldWrite(DexField field) {}
-
- @Override
- public void registerInitClass(DexType type) {}
-
- @Override
- public void registerTypeReference(DexType type) {}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 8a071b8..f4de742 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -228,7 +228,7 @@
}
@Override
- public synchronized void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {
+ public synchronized void setBridgeInfo(ProgramMethod method, BridgeInfo bridgeInfo) {
getMethodOptimizationInfoForUpdating(method).setBridgeInfo(bridgeInfo);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index c3ff56a..05a1cdc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -94,7 +94,7 @@
public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {}
@Override
- public void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {}
+ public void setBridgeInfo(ProgramMethod method, BridgeInfo bridgeInfo) {}
@Override
public void setClassInlinerMethodConstraint(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index a440900..339d9eb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -151,8 +151,8 @@
}
@Override
- public void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {
- method.getMutableOptimizationInfo().setBridgeInfo(bridgeInfo);
+ public void setBridgeInfo(ProgramMethod method, BridgeInfo bridgeInfo) {
+ method.getDefinition().getMutableOptimizationInfo().setBridgeInfo(bridgeInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index 02d13dd..259a701 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import java.util.Set;
public class DefaultInliningReasonStrategy implements InliningReasonStrategy {
@@ -35,57 +36,42 @@
ProgramMethod target,
ProgramMethod context,
DefaultInliningOracle oracle,
- MethodProcessor methodProcessor) {
+ InliningIRProvider inliningIRProvider,
+ MethodProcessor methodProcessor,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
DexEncodedMethod targetMethod = target.getDefinition();
DexMethod targetReference = target.getReference();
- if (targetMethod.getOptimizationInfo().forceInline()) {
- assert appView.getKeepInfo(target).isInliningAllowed(appView.options());
- return Reason.FORCE;
- }
+ Reason reason;
if (appView.appInfo().hasLiveness()
&& appView.withLiveness().appInfo().isAlwaysInlineMethod(targetReference)) {
- return Reason.ALWAYS;
- }
- if (options.disableInliningOfLibraryMethodOverrides
+ reason = Reason.ALWAYS;
+ } else if (options.disableInliningOfLibraryMethodOverrides
&& targetMethod.isLibraryMethodOverride().isTrue()) {
// This method will always have an implicit call site from the library, so we won't be able to
// remove it after inlining even if we have single or dual call site information from the
// program.
- return Reason.SIMPLE;
+ reason = Reason.SIMPLE;
+ } else if (callSiteInformation.hasSingleCallSite(target, context)) {
+ reason = Reason.SINGLE_CALLER;
+ } else if (isMultiCallerInlineCandidate(target, methodProcessor)) {
+ reason =
+ methodProcessor.isPrimaryMethodProcessor()
+ ? Reason.MULTI_CALLER_CANDIDATE
+ : Reason.ALWAYS;
+ } else {
+ reason = Reason.SIMPLE;
}
- if (isSingleCallerInliningTarget(target, context)) {
- return Reason.SINGLE_CALLER;
+ Set<Reason> validInliningReasons = appView.testing().validInliningReasons;
+ if (validInliningReasons != null && !validInliningReasons.contains(reason)) {
+ reason = Reason.NEVER;
+ whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons);
}
- if (isMultiCallerInlineCandidate(invoke, target, oracle, methodProcessor)) {
- return methodProcessor.isPrimaryMethodProcessor()
- ? Reason.MULTI_CALLER_CANDIDATE
- : Reason.ALWAYS;
- }
- return Reason.SIMPLE;
- }
-
- private boolean isSingleCallerInliningTarget(ProgramMethod method, ProgramMethod context) {
- if (!callSiteInformation.hasSingleCallSite(method, context)) {
- return false;
- }
- if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) {
- return false;
- }
- if (appView.testing().validInliningReasons != null
- && !appView.testing().validInliningReasons.contains(Reason.SINGLE_CALLER)) {
- return false;
- }
- return true;
+ return reason;
}
private boolean isMultiCallerInlineCandidate(
- InvokeMethod invoke,
ProgramMethod singleTarget,
- DefaultInliningOracle oracle,
MethodProcessor methodProcessor) {
- if (oracle.satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
- return false;
- }
if (methodProcessor.isPrimaryMethodProcessor()) {
return callSiteInformation.isMultiCallerInlineCandidate(singleTarget);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
index ec998a7..1c4c78a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
@@ -24,7 +24,9 @@
ProgramMethod target,
ProgramMethod context,
DefaultInliningOracle oracle,
- MethodProcessor methodProcessor) {
+ InliningIRProvider inliningIRProvider,
+ MethodProcessor methodProcessor,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
return reason;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index b1e76f3..c62a014 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.origin.Origin;
import java.util.IdentityHashMap;
@@ -20,6 +21,7 @@
private final AppView<?> appView;
private final ProgramMethod context;
+ private final LensCodeRewriter lensCodeRewriter;
private final NumberGenerator valueNumberGenerator;
private final MethodProcessor methodProcessor;
@@ -28,14 +30,20 @@
private InliningIRProvider() {
this.appView = null;
this.context = null;
+ this.lensCodeRewriter = null;
this.valueNumberGenerator = null;
this.methodProcessor = null;
}
public InliningIRProvider(
- AppView<?> appView, ProgramMethod context, IRCode code, MethodProcessor methodProcessor) {
+ AppView<?> appView,
+ ProgramMethod context,
+ IRCode code,
+ LensCodeRewriter lensCodeRewriter,
+ MethodProcessor methodProcessor) {
this.appView = appView;
this.context = context;
+ this.lensCodeRewriter = lensCodeRewriter;
this.valueNumberGenerator = code.valueNumberGenerator;
this.methodProcessor = methodProcessor;
}
@@ -66,11 +74,6 @@
public boolean verifyIRCacheIsEmpty() {
throw new Unreachable();
}
-
- @Override
- public boolean shouldApplyCodeRewritings(ProgramMethod method) {
- throw new Unreachable();
- }
};
}
@@ -80,13 +83,18 @@
return cached;
}
Origin origin = method.getOrigin();
- return method.buildInliningIR(
- context,
- appView,
- valueNumberGenerator,
- Position.getPositionForInlining(invoke, context),
- origin,
- methodProcessor);
+ IRCode code =
+ method.buildInliningIR(
+ context,
+ appView,
+ valueNumberGenerator,
+ Position.getPositionForInlining(invoke, context),
+ origin,
+ methodProcessor);
+ if (lensCodeRewriter != null && methodProcessor.shouldApplyCodeRewritings(method)) {
+ lensCodeRewriter.rewrite(code, method, methodProcessor);
+ }
+ return code;
}
public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
@@ -108,8 +116,4 @@
assert cache.isEmpty();
return true;
}
-
- public boolean shouldApplyCodeRewritings(ProgramMethod method) {
- return methodProcessor.shouldApplyCodeRewritings(method);
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
index c749be9..17161be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
@@ -17,5 +17,7 @@
ProgramMethod target,
ProgramMethod context,
DefaultInliningOracle oracle,
- MethodProcessor methodProcessor);
+ InliningIRProvider inliningIRProvider,
+ MethodProcessor methodProcessor,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
deleted file mode 100644
index 792195a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright (c) 2019, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedBooleanValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.ConstString;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.StringUtils;
-import java.util.Set;
-
-public class BooleanMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- BooleanMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedBooleanType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- DexMethod singleTargetReference = singleTarget.getReference();
- if (singleTargetReference.isIdenticalTo(dexItemFactory.booleanMembers.booleanValue)) {
- optimizeBooleanValue(code, instructionIterator, invoke);
- } else if (singleTargetReference.isIdenticalTo(dexItemFactory.booleanMembers.parseBoolean)) {
- optimizeParseBoolean(code, instructionIterator, invoke);
- } else if (singleTargetReference.isIdenticalTo(dexItemFactory.booleanMembers.valueOf)) {
- optimizeValueOf(code, instructionIterator, invoke, affectedValues);
- }
- }
-
- private void optimizeBooleanValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod booleanValueInvoke) {
- // Optimize Boolean.valueOf(b).booleanValue() into b.
- AbstractValue abstractValue =
- booleanValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedBoolean()) {
- if (booleanValueInvoke.hasOutValue()) {
- SingleBoxedBooleanValue singleBoxedBoolean = abstractValue.asSingleBoxedBoolean();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedBoolean
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, booleanValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-
- private void optimizeParseBoolean(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
- Value argument = invoke.getFirstArgument().getAliasedValue();
- if (argument.isDefinedByInstructionSatisfying(Instruction::isConstString)) {
- ConstString constString = argument.getDefinition().asConstString();
- if (!constString.instructionInstanceCanThrow(appView, code.context())) {
- String value = StringUtils.toLowerCase(constString.getValue().toString());
- if (value.equals("true")) {
- instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
- } else if (value.equals("false")) {
- instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
- }
- }
- }
- }
-
- private void optimizeValueOf(
- IRCode code,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- Set<Value> affectedValues) {
- // Optimize Boolean.valueOf(b) into Boolean.FALSE or Boolean.TRUE.
- Value argument = invoke.getFirstOperand();
- AbstractValue abstractValue = argument.getAbstractValue(appView, code.context());
- if (abstractValue.isSingleNumberValue()) {
- instructionIterator.replaceCurrentInstructionWithStaticGet(
- appView,
- code,
- abstractValue.asSingleNumberValue().getBooleanValue()
- ? dexItemFactory.booleanMembers.TRUE
- : dexItemFactory.booleanMembers.FALSE,
- affectedValues);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
deleted file mode 100644
index 00669f0..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2019, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedByteValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
-
-public class ByteMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- ByteMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedByteType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- if (singleTarget.getReference().isIdenticalTo(dexItemFactory.byteMembers.byteValue)) {
- optimizeByteValue(code, instructionIterator, invoke);
- }
- }
-
- private void optimizeByteValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod byteValueInvoke) {
- // Optimize Byte.valueOf(b).byteValue() into b.
- AbstractValue abstractValue =
- byteValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedByte()) {
- if (byteValueInvoke.hasOutValue()) {
- SingleBoxedByteValue singleBoxedByte = abstractValue.asSingleBoxedByte();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedByte
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, byteValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/CharacterMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/CharacterMethodOptimizer.java
deleted file mode 100644
index 5378e9a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/CharacterMethodOptimizer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2023, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedCharValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
-
-public class CharacterMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- CharacterMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedCharType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- if (singleTarget.getReference().isIdenticalTo(dexItemFactory.charMembers.charValue)) {
- optimizeCharValue(code, instructionIterator, invoke);
- }
- }
-
- private void optimizeCharValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod charValueInvoke) {
- // Optimize Char.valueOf(c).charValue() into c.
- AbstractValue abstractValue =
- charValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedChar()) {
- if (charValueInvoke.hasOutValue()) {
- SingleBoxedCharValue singleBoxedChar = abstractValue.asSingleBoxedChar();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedChar
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, charValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/DoubleMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/DoubleMethodOptimizer.java
deleted file mode 100644
index 9912bc0..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/DoubleMethodOptimizer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2023, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedDoubleValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
-
-public class DoubleMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- DoubleMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedDoubleType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- if (singleTarget.getReference().isIdenticalTo(dexItemFactory.doubleMembers.doubleValue)) {
- optimizeDoubleValue(code, instructionIterator, invoke);
- }
- }
-
- private void optimizeDoubleValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod doubleValueInvoke) {
- // Optimize Double.valueOf(d).doubleValue() into d.
- AbstractValue abstractValue =
- doubleValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedDouble()) {
- if (doubleValueInvoke.hasOutValue()) {
- SingleBoxedDoubleValue singleBoxedDouble = abstractValue.asSingleBoxedDouble();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedDouble
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, doubleValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index a3eba42..f5cc0a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Set;
@@ -37,13 +38,13 @@
@Override
@SuppressWarnings("ReferenceEquality")
- public void optimize(
+ public InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove) {
if (appView.hasLiveness()
&& singleTarget.getReference() == appView.dexItemFactory().enumMembers.valueOf
@@ -51,6 +52,7 @@
insertAssumeDynamicType(
appView.withLiveness(), code, instructionIterator, invoke, affectedValues);
}
+ return instructionIterator;
}
@SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/FloatMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/FloatMethodOptimizer.java
deleted file mode 100644
index 8cd19ea..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/FloatMethodOptimizer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2023, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedFloatValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
-
-public class FloatMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- FloatMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedFloatType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- if (singleTarget.getReference().isIdenticalTo(dexItemFactory.floatMembers.floatValue)) {
- optimizeFloatValue(code, instructionIterator, invoke);
- }
- }
-
- private void optimizeFloatValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod floatValueInvoke) {
- // Optimize Float.valueOf(f).floatValue() into f.
- AbstractValue abstractValue =
- floatValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedFloat()) {
- if (floatValueInvoke.hasOutValue()) {
- SingleBoxedFloatValue singleBoxedFloat = abstractValue.asSingleBoxedFloat();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedFloat
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, floatValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/IntegerMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/IntegerMethodOptimizer.java
deleted file mode 100644
index d34ce58..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/IntegerMethodOptimizer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2023, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedIntegerValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
-
-public class IntegerMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- IntegerMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedIntType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- if (singleTarget.getReference().isIdenticalTo(dexItemFactory.integerMembers.intValue)) {
- optimizeIntegerValue(code, instructionIterator, invoke);
- }
- }
-
- private void optimizeIntegerValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod intValueInvoke) {
- // Optimize Integer.valueOf(i).intValue() into i.
- AbstractValue abstractValue =
- intValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedInteger()) {
- if (intValueInvoke.hasOutValue()) {
- SingleBoxedIntegerValue singleBoxedInteger = abstractValue.asSingleBoxedInteger();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedInteger
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, intValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index ca3de34..f47e771 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.library.primitive.PrimitiveMethodOptimizer;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
@@ -44,16 +45,9 @@
public LibraryMemberOptimizer(AppView<?> appView, Timing timing) {
this.appView = appView;
timing.begin("Register optimizers");
- register(new BooleanMethodOptimizer(appView));
- register(new ByteMethodOptimizer(appView));
- register(new CharacterMethodOptimizer(appView));
- register(new DoubleMethodOptimizer(appView));
- register(new FloatMethodOptimizer(appView));
- register(new IntegerMethodOptimizer(appView));
- register(new LongMethodOptimizer(appView));
+ PrimitiveMethodOptimizer.forEachPrimitiveOptimizer(appView, this::register);
register(new ObjectMethodOptimizer(appView));
register(new ObjectsMethodOptimizer(appView));
- register(new ShortMethodOptimizer(appView));
register(new StringBuilderMethodOptimizer(appView));
register(new StringMethodOptimizer(appView));
if (appView.enableWholeProgramOptimizations()) {
@@ -160,17 +154,18 @@
LibraryMethodModelCollection.State optimizationState =
optimizationStates.computeIfAbsent(
optimizer, LibraryMethodModelCollection::createInitialState);
- optimizer.optimize(
- code,
- blockIterator,
- instructionIterator,
- invoke,
- singleTarget,
- affectedValues,
- blocksToRemove,
- optimizationState,
- methodProcessor,
- methodProcessingContext);
+ instructionIterator =
+ optimizer.optimize(
+ code,
+ blockIterator,
+ instructionIterator,
+ invoke,
+ singleTarget,
+ affectedValues,
+ blocksToRemove,
+ optimizationState,
+ methodProcessor,
+ methodProcessingContext);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
index fef1563..ab2ea9c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -12,9 +12,10 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.library.LibraryMethodModelCollection.State;
+import com.android.tools.r8.ir.optimize.library.primitive.BooleanMethodOptimizer;
import java.util.Set;
/** Used to model the behavior of library methods for optimization purposes. */
@@ -34,31 +35,31 @@
* Invoked for instructions in {@param code} that invoke a method on the class returned by {@link
* #getType()}. The given {@param singleTarget} is guaranteed to be non-null.
*/
- void optimize(
+ InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove,
T state,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext);
@SuppressWarnings("unchecked")
- default void optimize(
+ default InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove,
Object state,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
- optimize(
+ return optimize(
code,
blockIterator,
instructionIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
index 3e3b850..f4de53a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.shaking.MaximumRemovedAndroidLogLevelRule;
import com.android.tools.r8.shaking.ProguardConfiguration;
import java.util.Set;
@@ -107,13 +108,13 @@
}
@Override
- public void optimize(
+ public InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove) {
// Replace Android logging statements like Log.w(...) and Log.IsLoggable(..., WARNING) at or
// below a certain logging level by false.
@@ -122,6 +123,7 @@
if (VERBOSE <= logLevel && logLevel <= maxRemovedAndroidLogLevel) {
instructionIterator.replaceCurrentInstructionWithConstFalse(code);
}
+ return instructionIterator;
}
private int getMaxRemovedAndroidLogLevel(ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LongMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LongMethodOptimizer.java
deleted file mode 100644
index 51dfd00..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LongMethodOptimizer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2023, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedLongValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
-
-public class LongMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- LongMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedLongType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- if (singleTarget.getReference().isIdenticalTo(dexItemFactory.longMembers.longValue)) {
- optimizeLongValue(code, instructionIterator, invoke);
- }
- }
-
- private void optimizeLongValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod longValueInvoke) {
- // Optimize Long.valueOf(l).longValue() into l.
- AbstractValue abstractValue =
- longValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedLong()) {
- if (longValueInvoke.hasOutValue()) {
- SingleBoxedLongValue singleBoxedLong = abstractValue.asSingleBoxedLong();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedLong
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, longValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
index e545164..64d1715 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import java.util.Set;
public class NopLibraryMethodModelCollection extends StatelessLibraryMethodModelCollection {
@@ -32,12 +32,14 @@
}
@Override
- public void optimize(
+ public InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {}
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ return instructionIterator;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
index 72c4a3e..90ad902 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import java.util.Set;
public class ObjectMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -31,17 +31,18 @@
@Override
@SuppressWarnings("ReferenceEquality")
- public void optimize(
+ public InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove) {
if (singleTarget.getReference() == dexItemFactory.objectMembers.getClass) {
optimizeGetClass(instructionIterator, invoke);
}
+ return instructionIterator;
}
private void optimizeGetClass(InstructionListIterator instructionIterator, InvokeMethod invoke) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index 9890bca..8fe29c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexItemFactory.ObjectsMethods;
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.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -19,6 +20,7 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.Set;
@@ -45,13 +47,13 @@
@Override
@SuppressWarnings("ReferenceEquality")
- public void optimize(
+ public InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove) {
DexMethod singleTargetReference = singleTarget.getReference();
switch (singleTargetReference.getName().byteAt(0)) {
@@ -97,6 +99,7 @@
// Intentionally empty.
break;
}
+ return instructionIterator;
}
private void optimizeEquals(
@@ -219,7 +222,7 @@
IRCode code,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
DexClassAndMethod singleTarget) {
Value object = invoke.getFirstArgument();
TypeElement type = object.getType();
@@ -227,10 +230,9 @@
// Optimize Objects.toString(null) into "null".
if (type.isDefinitelyNull()) {
if (singleTarget.getReference() == objectsMethods.toStringWithObject) {
- if (invoke.hasOutValue()) {
- affectedValues.addAll(invoke.outValue().affectedValues());
- }
- instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
+ DexString nullString = dexItemFactory.createString("null");
+ instructionIterator.replaceCurrentInstructionWithConstString(
+ appView, code, nullString, affectedValues);
} else {
assert singleTarget.getReference() == objectsMethods.toStringWithObjectAndNullDefault;
if (invoke.hasOutValue()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ShortMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ShortMethodOptimizer.java
deleted file mode 100644
index 399ada9..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ShortMethodOptimizer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2023, 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.library;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleBoxedShortValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
-
-public class ShortMethodOptimizer extends StatelessLibraryMethodModelCollection {
-
- private final AppView<?> appView;
- private final DexItemFactory dexItemFactory;
-
- ShortMethodOptimizer(AppView<?> appView) {
- this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- @Override
- public DexType getType() {
- return dexItemFactory.boxedShortType;
- }
-
- @Override
- public void optimize(
- IRCode code,
- BasicBlockIterator blockIterator,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove) {
- if (singleTarget.getReference().isIdenticalTo(dexItemFactory.shortMembers.shortValue)) {
- optimizeShortValue(code, instructionIterator, invoke);
- }
- }
-
- private void optimizeShortValue(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod shortValueInvoke) {
- // Optimize Short.valueOf(s).shortValue() into s.
- AbstractValue abstractValue =
- shortValueInvoke.getFirstArgument().getAbstractValue(appView, code.context());
- if (abstractValue.isSingleBoxedShort()) {
- if (shortValueInvoke.hasOutValue()) {
- SingleBoxedShortValue singleBoxedShort = abstractValue.asSingleBoxedShort();
- instructionIterator.replaceCurrentInstruction(
- singleBoxedShort
- .toPrimitive(appView.abstractValueFactory())
- .createMaterializingInstruction(appView, code, shortValueInvoke));
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
index 9cfdcbd..4a73b82 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
@@ -11,8 +11,8 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.library.StatelessLibraryMethodModelCollection.State;
import java.util.Set;
@@ -24,29 +24,29 @@
return null;
}
- public abstract void optimize(
+ public abstract InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove);
@Override
- public final void optimize(
+ public final InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove,
State state,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
assert state == null;
- optimize(
+ return optimize(
code,
blockIterator,
instructionIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index f5e8538..5f1d875 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
import com.android.tools.r8.ir.optimize.library.StringBuilderMethodOptimizer.State;
@@ -69,13 +70,13 @@
@Override
@SuppressWarnings("ReferenceEquality")
- public void optimize(
+ public InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove,
State state,
MethodProcessor methodProcessor,
@@ -95,6 +96,7 @@
optimizeToString(instructionIterator, invokeWithReceiver);
}
}
+ return instructionIterator;
}
private void optimizeAppend(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index f49beb1..a43cbe6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -5,33 +5,79 @@
package com.android.tools.r8.ir.optimize.library;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
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.DexItemFactory.JavaUtilLocaleMembers;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.ValueUtils;
+import com.android.tools.r8.utils.ValueUtils.ArrayValues;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Set;
public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection {
+ private static boolean DEBUG =
+ System.getProperty("com.android.tools.r8.debug.StringMethodOptimizer") != null;
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
+ private final boolean enableStringFormatOptimizations;
+ private final ImmutableMap<DexMethod, DexMethod> valueOfToStringAppend;
StringMethodOptimizer(AppView<?> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
+ this.enableStringFormatOptimizations = appView.options().enableStringFormatOptimization;
+ this.valueOfToStringAppend =
+ ImmutableMap.<DexMethod, DexMethod>builder()
+ .put(
+ dexItemFactory.integerMembers.valueOf,
+ dexItemFactory.stringBuilderMethods.appendInt)
+ .put(dexItemFactory.byteMembers.valueOf, dexItemFactory.stringBuilderMethods.appendInt)
+ .put(dexItemFactory.shortMembers.valueOf, dexItemFactory.stringBuilderMethods.appendInt)
+ .put(dexItemFactory.longMembers.valueOf, dexItemFactory.stringBuilderMethods.appendLong)
+ .put(dexItemFactory.charMembers.valueOf, dexItemFactory.stringBuilderMethods.appendChar)
+ .put(
+ dexItemFactory.booleanMembers.valueOf,
+ dexItemFactory.stringBuilderMethods.appendBoolean)
+ .put(
+ dexItemFactory.floatMembers.valueOf,
+ dexItemFactory.stringBuilderMethods.appendFloat)
+ .put(
+ dexItemFactory.doubleMembers.valueOf,
+ dexItemFactory.stringBuilderMethods.appendDouble)
+ .build();
+ }
+
+ private static void debugLog(IRCode code, String message) {
+ System.err.println(message + " method=" + code.context().getReference());
}
@Override
@@ -41,20 +87,28 @@
@Override
@SuppressWarnings("ReferenceEquality")
- public void optimize(
+ public InstructionListIterator optimize(
IRCode code,
BasicBlockIterator blockIterator,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
+ AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove) {
DexMethod singleTargetReference = singleTarget.getReference();
- if (singleTargetReference == dexItemFactory.stringMembers.equals) {
+ var stringMembers = dexItemFactory.stringMembers;
+ if (singleTargetReference == stringMembers.equals) {
optimizeEquals(code, instructionIterator, invoke.asInvokeMethodWithReceiver());
- } else if (singleTargetReference == dexItemFactory.stringMembers.valueOf) {
+ } else if (singleTargetReference == stringMembers.valueOf) {
optimizeValueOf(code, instructionIterator, invoke.asInvokeStatic(), affectedValues);
+ } else if (enableStringFormatOptimizations
+ && (singleTargetReference == stringMembers.format
+ || singleTargetReference == stringMembers.formatWithLocale)) {
+ instructionIterator =
+ optimizeFormat(
+ code, instructionIterator, blockIterator, invoke.asInvokeStatic(), affectedValues);
}
+ return instructionIterator;
}
private void optimizeEquals(
@@ -70,20 +124,338 @@
}
}
+ private static class SimpleStringFormatSpec {
+ private static class Part {
+ final String value;
+ final int placeholderIdx;
+ private final char formatChar;
+
+ Part(String value) {
+ this.value = value;
+ this.placeholderIdx = -1;
+ this.formatChar = '\0';
+ }
+
+ Part(int placeholderIdx, char formatChar) {
+ this.value = null;
+ this.placeholderIdx = placeholderIdx;
+ this.formatChar = formatChar;
+ }
+
+ boolean isPlaceholder() {
+ return value == null;
+ }
+
+ public boolean isLiteral() {
+ return value != null;
+ }
+ }
+
+ final List<Part> parts;
+ final int placeholderCount;
+
+ SimpleStringFormatSpec(List<Part> parts) {
+ this.parts = parts;
+ placeholderCount = (int) parts.stream().filter(Part::isPlaceholder).count();
+ assert placeholderCount >= 1 || parts.size() <= 1;
+ }
+
+ static SimpleStringFormatSpec parse(boolean allowNumbers, String spec) {
+ ArrayList<Part> parts = new ArrayList<>();
+ int startIdx = 0;
+ int curPlaceholderIdx = 0;
+ int specLen = spec.length();
+ String curPartValue = "";
+ while (true) {
+ int percentIdx = spec.indexOf('%', startIdx);
+ if (percentIdx == -1) {
+ if (startIdx < specLen) {
+ curPartValue = curPartValue.concat(spec.substring(startIdx));
+ }
+ if (!curPartValue.isEmpty() || parts.isEmpty()) {
+ parts.add(new Part(curPartValue));
+ }
+ return new SimpleStringFormatSpec(parts);
+ }
+ // Trailing % is invalid.
+ if (percentIdx + 1 == specLen) {
+ return null;
+ }
+ curPartValue = curPartValue.concat(spec.substring(startIdx, percentIdx));
+ char formatChar = spec.charAt(percentIdx + 1);
+ switch (formatChar) {
+ case 'd':
+ if (!allowNumbers) {
+ return null;
+ }
+ // Intentional fall-through.
+ case 'b':
+ case 's':
+ if (!curPartValue.isEmpty()) {
+ parts.add(new Part(curPartValue));
+ curPartValue = "";
+ }
+ parts.add(new Part(curPlaceholderIdx, formatChar));
+ curPlaceholderIdx += 1;
+ break;
+ case '%':
+ curPartValue = curPartValue.concat("%");
+ break;
+ default:
+ // Do not handle modifiers or other types, because only simple %s result
+ // in smaller code to change to StringBuilder (and are sufficiently common).
+ return null;
+ }
+ startIdx = percentIdx + 2;
+ }
+ }
+ }
+
+ private boolean isDefinitelyNotFormattable(TypeElement type) {
+ ClassTypeElement classType = type.asClassType();
+ if (classType == null) {
+ return false;
+ }
+ DexClass clazz = appView.definitionFor(classType.getClassType());
+ if (clazz == null || !clazz.isFinal()) {
+ // TODO(b/244238384): Extend to non-final classes.
+ return false;
+ }
+ TypeElement formattableType = dexItemFactory.javaUtilFormattableType.toTypeElement(appView);
+ return !type.lessThanOrEqualUpToNullability(formattableType, appView);
+ }
+
+ private boolean isSupportedFormatType(char formatChar, TypeElement type) {
+ switch (formatChar) {
+ case 'b':
+ // String.format() converts null to "false" and non-Boolean objects to "true", which we
+ // cannot replicate without inserting extra logic.
+ return type.isDefinitelyNotNull() && type.isClassType(dexItemFactory.boxedBooleanType);
+ case 'd':
+ // %d requires Byte, Short, Integer, or Long, and prints null as "null".
+ // TODO(b/244238384): Extend to BigInteger.
+ return type.isClassType(dexItemFactory.boxedIntType)
+ || type.isClassType(dexItemFactory.boxedLongType)
+ || type.isClassType(dexItemFactory.boxedByteType)
+ || type.isClassType(dexItemFactory.boxedShortType);
+ default:
+ assert formatChar == 's';
+ // Check for string as an optimization since it's the common case.
+ return type.isStringType(dexItemFactory) || isDefinitelyNotFormattable(type);
+ }
+ }
+
+ private boolean localeIsNullOrRootOrEnglish(Value value) {
+ if (value.isAlwaysNull(appView)) {
+ return true;
+ }
+ if (!value.isDefinedByInstructionSatisfying(Instruction::isStaticGet)) {
+ return false;
+ }
+ StaticGet staticGet = value.definition.asStaticGet();
+ DexField field = staticGet.getField();
+ JavaUtilLocaleMembers localeMembers = dexItemFactory.javaUtilLocaleMembers;
+ return field.isIdenticalTo(localeMembers.ENGLISH)
+ || field.isIdenticalTo(localeMembers.ROOT)
+ || field.isIdenticalTo(localeMembers.US);
+ }
+
+ private InstructionListIterator optimizeFormat(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ BasicBlockIterator blockIterator,
+ InvokeStatic formatInvoke,
+ AffectedValues affectedValues) {
+ boolean hasLocale =
+ formatInvoke
+ .getInvokedMethod()
+ .isIdenticalTo(dexItemFactory.stringMembers.formatWithLocale);
+ int specParamIdx = hasLocale ? 1 : 0;
+ Value specValue = formatInvoke.getArgument(specParamIdx).getAliasedValue();
+ if (!specValue.isConstString()) {
+ if (DEBUG) {
+ debugLog(code, "optimizeFormat: Non-Const Spec");
+ }
+ return instructionIterator;
+ }
+ Instruction specInstruction = specValue.getDefinition();
+ String specString = specInstruction.asConstString().getValue().toString();
+ boolean allowNumbers =
+ hasLocale && localeIsNullOrRootOrEnglish(formatInvoke.getFirstArgument().getAliasedValue());
+ SimpleStringFormatSpec parsedSpec = SimpleStringFormatSpec.parse(allowNumbers, specString);
+ if (parsedSpec == null) {
+ if (DEBUG) {
+ debugLog(code, "optimizeFormat: Unsupported format with allowNumbers=" + allowNumbers);
+ }
+ return instructionIterator;
+ }
+
+ Value paramsValue = formatInvoke.getArgument(specParamIdx + 1);
+ List<Value> elementValues;
+ if (paramsValue.isAlwaysNull(appView)) {
+ elementValues = Collections.emptyList();
+ } else {
+ ArrayValues arrayValues =
+ ValueUtils.computeSingleUseArrayValues(paramsValue, formatInvoke, code);
+ if (arrayValues == null) {
+ return instructionIterator;
+ }
+ elementValues = arrayValues.getElementValues();
+ }
+
+ // Extra args are ignored, while too few throw.
+ if (elementValues.size() < parsedSpec.placeholderCount) {
+ // TODO(b/244238384): Raise IllegalFormatException.
+ return instructionIterator;
+ }
+
+ // Optimize no placeholders.
+ if (parsedSpec.placeholderCount == 0) {
+ instructionIterator.replaceCurrentInstructionWithConstString(
+ appView,
+ code,
+ dexItemFactory.createString(parsedSpec.parts.get(0).value),
+ affectedValues);
+ if (DEBUG) {
+ debugLog(code, "String.format(): Optimized no placeholders");
+ }
+ return instructionIterator;
+ }
+
+ for (SimpleStringFormatSpec.Part part : parsedSpec.parts) {
+ if (part.isPlaceholder()) {
+ Value paramValue = elementValues.get(part.placeholderIdx);
+ if (paramValue == null || paramValue.isAlwaysNull(appView)) {
+ // Save having to call isAlwaysNull() again.
+ elementValues.set(part.placeholderIdx, null);
+ continue;
+ }
+ if (!isSupportedFormatType(part.formatChar, paramValue.getType())) {
+ if (DEBUG) {
+ debugLog(
+ code,
+ String.format(
+ "String.format(): Unsupported param %s type %%%s: %s",
+ part.placeholderIdx, part.formatChar, paramValue.getType()));
+ }
+ return instructionIterator;
+ }
+ }
+ }
+
+ ArrayList<Instruction> newInstructions = new ArrayList<>();
+
+ // Rely on StringBuilder optimizations to convert this to using the string constructor (plus
+ // other StringBuilder / valueOf optimizations that may apply).
+ NewInstance newInstance =
+ NewInstance.builder()
+ .setType(dexItemFactory.stringBuilderType)
+ .setPosition(formatInvoke)
+ .setFreshOutValue(
+ code,
+ dexItemFactory.stringBuilderType.toTypeElement(
+ appView, Nullability.definitelyNotNull()))
+ .build();
+ Value stringBuilderValue = newInstance.outValue();
+ newInstructions.add(newInstance);
+
+ newInstructions.add(
+ InvokeDirect.builder()
+ .setMethod(dexItemFactory.stringBuilderMethods.defaultConstructor)
+ .setSingleArgument(stringBuilderValue)
+ .setPosition(formatInvoke)
+ .build());
+
+ for (SimpleStringFormatSpec.Part part : parsedSpec.parts) {
+ Value paramValue;
+ DexMethod appendMethod = null;
+ if (part.isLiteral()) {
+ // Create strings for non-placeholder parts of the spec string.
+ ConstString constString =
+ ConstString.builder()
+ .setValue(dexItemFactory.createString(part.value))
+ .setPosition(specInstruction)
+ .setFreshOutValue(
+ code, TypeElement.stringClassType(appView, Nullability.definitelyNotNull()))
+ .build();
+ newInstructions.add(constString);
+ paramValue = constString.outValue();
+ appendMethod = dexItemFactory.stringBuilderMethods.appendString;
+ } else {
+ paramValue = elementValues.get(part.placeholderIdx);
+ if (paramValue == null) {
+ ConstString constString =
+ ConstString.builder()
+ .setValue(dexItemFactory.createString(part.formatChar == 'b' ? "false" : "null"))
+ .setPosition(specInstruction)
+ .setFreshOutValue(
+ code, TypeElement.stringClassType(appView, Nullability.definitelyNotNull()))
+ .build();
+ newInstructions.add(constString);
+ paramValue = constString.outValue();
+ appendMethod = dexItemFactory.stringBuilderMethods.appendString;
+ } else {
+ Value paramValueRoot = paramValue.getAliasedValue();
+ InvokeStatic paramInvoke =
+ paramValueRoot.isPhi() ? null : paramValueRoot.definition.asInvokeStatic();
+ // See if the parameter is a call to Integer.valueOf, Boolean.valueOf, etc.
+ if (paramInvoke != null) {
+ DexMethod invokedMethod = paramInvoke.getInvokedMethod();
+ appendMethod = valueOfToStringAppend.get(invokedMethod);
+ if (appendMethod != null) {
+ paramValue = paramInvoke.getFirstArgument();
+ }
+ }
+ if (appendMethod == null) {
+ appendMethod =
+ paramValue.getType().isStringType(dexItemFactory)
+ ? dexItemFactory.stringBuilderMethods.appendString
+ : dexItemFactory.stringBuilderMethods.appendObject;
+ }
+ }
+ }
+ InvokeVirtual appendInvoke =
+ InvokeVirtual.builder()
+ .setMethod(appendMethod)
+ .setPosition(formatInvoke)
+ .setArguments(stringBuilderValue, paramValue)
+ .build();
+ newInstructions.add(appendInvoke);
+ }
+ InvokeVirtual toStringInvoke =
+ InvokeVirtual.builder()
+ .setMethod(dexItemFactory.stringBuilderMethods.toString)
+ .setPosition(formatInvoke)
+ .setSingleArgument(stringBuilderValue)
+ .setFreshOutValue(code, dexItemFactory.stringType.toTypeElement(appView))
+ .build();
+
+ // Replace the String.format(), but for simplicity, leave all other array and valueOf() invokes
+ // to be removed by dead code elimination.
+ instructionIterator.replaceCurrentInstruction(toStringInvoke, affectedValues);
+ instructionIterator.previous();
+ instructionIterator =
+ instructionIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ code, blockIterator, newInstructions, appView.options());
+ if (DEBUG) {
+ debugLog(code, "String.format(): Optimized.");
+ }
+ return instructionIterator;
+ }
+
private void optimizeValueOf(
IRCode code,
InstructionListIterator instructionIterator,
InvokeStatic invoke,
- Set<Value> affectedValues) {
+ AffectedValues affectedValues) {
Value object = invoke.getFirstArgument();
TypeElement type = object.getType();
// Optimize String.valueOf(null) into "null".
if (type.isDefinitelyNull()) {
- instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
- if (invoke.hasOutValue()) {
- affectedValues.addAll(invoke.outValue().affectedValues());
- }
+ DexString nullString = dexItemFactory.createString("null");
+ instructionIterator.replaceCurrentInstructionWithConstString(
+ appView, code, nullString, affectedValues);
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/BooleanMethodOptimizer.java
new file mode 100644
index 0000000..395386d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/BooleanMethodOptimizer.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2023, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Set;
+
+public class BooleanMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ BooleanMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.booleanMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.booleanMembers.booleanValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedBoolean();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedBooleanType;
+ }
+
+ @Override
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ if (singleTarget.getReference().isIdenticalTo(dexItemFactory.booleanMembers.parseBoolean)) {
+ optimizeParseBoolean(code, instructionIterator, invoke);
+ } else {
+ optimizeBoxingMethods(code, instructionIterator, invoke, singleTarget, affectedValues);
+ }
+ return instructionIterator;
+ }
+
+ private void optimizeParseBoolean(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+ Value argument = invoke.getFirstArgument().getAliasedValue();
+ if (argument.isDefinedByInstructionSatisfying(Instruction::isConstString)) {
+ ConstString constString = argument.getDefinition().asConstString();
+ if (!constString.instructionInstanceCanThrow(appView, code.context())) {
+ String value = StringUtils.toLowerCase(constString.getValue().toString());
+ if (value.equals("true")) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
+ } else if (value.equals("false")) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
+ }
+ }
+ }
+ }
+
+ @Override
+ void optimizeBoxMethod(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod boxInvoke,
+ AffectedValues affectedValues) {
+ // Optimize Boolean.valueOf(b) into Boolean.FALSE or Boolean.TRUE.
+ Value argument = boxInvoke.getFirstOperand();
+ AbstractValue abstractValue = argument.getAbstractValue(appView, code.context());
+ if (abstractValue.isSingleNumberValue()) {
+ instructionIterator.replaceCurrentInstructionWithStaticGet(
+ appView,
+ code,
+ abstractValue.asSingleNumberValue().getBooleanValue()
+ ? dexItemFactory.booleanMembers.TRUE
+ : dexItemFactory.booleanMembers.FALSE,
+ affectedValues);
+ return;
+ }
+ super.optimizeBoxMethod(code, instructionIterator, boxInvoke, affectedValues);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/ByteMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/ByteMethodOptimizer.java
new file mode 100644
index 0000000..8f97ed0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/ByteMethodOptimizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class ByteMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ ByteMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.byteMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.byteMembers.byteValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedByte();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedByteType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/CharacterMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/CharacterMethodOptimizer.java
new file mode 100644
index 0000000..689155d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/CharacterMethodOptimizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class CharacterMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ CharacterMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.charMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.charMembers.charValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedChar();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedCharType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/DoubleMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/DoubleMethodOptimizer.java
new file mode 100644
index 0000000..780cc81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/DoubleMethodOptimizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class DoubleMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ DoubleMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.doubleMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.doubleMembers.doubleValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedDouble();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedDoubleType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/FloatMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/FloatMethodOptimizer.java
new file mode 100644
index 0000000..c3206b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/FloatMethodOptimizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class FloatMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ FloatMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.floatMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.floatMembers.floatValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedFloat();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedFloatType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/IntegerMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/IntegerMethodOptimizer.java
new file mode 100644
index 0000000..dba75d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/IntegerMethodOptimizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class IntegerMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ IntegerMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.integerMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.integerMembers.intValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedInteger();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedIntType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/LongMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/LongMethodOptimizer.java
new file mode 100644
index 0000000..960baaa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/LongMethodOptimizer.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class LongMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ LongMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.longMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.longMembers.longValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedLong();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedLongType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/PrimitiveMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/PrimitiveMethodOptimizer.java
new file mode 100644
index 0000000..96d23ce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/PrimitiveMethodOptimizer.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2023, 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.library.primitive;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleBoxedPrimitiveValue;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.ir.optimize.library.StatelessLibraryMethodModelCollection;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public abstract class PrimitiveMethodOptimizer extends StatelessLibraryMethodModelCollection {
+
+ final AppView<?> appView;
+ final DexItemFactory dexItemFactory;
+
+ PrimitiveMethodOptimizer(AppView<?> appView) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ public static void forEachPrimitiveOptimizer(
+ AppView<?> appView, Consumer<PrimitiveMethodOptimizer> register) {
+ register.accept(new BooleanMethodOptimizer(appView));
+ register.accept(new ByteMethodOptimizer(appView));
+ register.accept(new CharacterMethodOptimizer(appView));
+ register.accept(new DoubleMethodOptimizer(appView));
+ register.accept(new FloatMethodOptimizer(appView));
+ register.accept(new IntegerMethodOptimizer(appView));
+ register.accept(new LongMethodOptimizer(appView));
+ register.accept(new ShortMethodOptimizer(appView));
+ }
+
+ abstract DexMethod getBoxMethod();
+
+ abstract DexMethod getUnboxMethod();
+
+ abstract boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue);
+
+ @Override
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ optimizeBoxingMethods(code, instructionIterator, invoke, singleTarget, affectedValues);
+ return instructionIterator;
+ }
+
+ void optimizeBoxingMethods(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues) {
+ if (singleTarget.getReference().isIdenticalTo(getUnboxMethod())) {
+ optimizeUnboxMethod(code, instructionIterator, invoke);
+ } else if (singleTarget.getReference().isIdenticalTo(getBoxMethod())) {
+ optimizeBoxMethod(code, instructionIterator, invoke, affectedValues);
+ }
+ }
+
+ void optimizeBoxMethod(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod boxInvoke,
+ AffectedValues affectedValues) {
+ Value firstArg = boxInvoke.getFirstArgument();
+ if (firstArg
+ .getAliasedValue()
+ .isDefinedByInstructionSatisfying(i -> i.isInvokeMethod(getUnboxMethod()))) {
+ // Optimize Primitive.box(boxed.unbox()) into boxed.
+ InvokeMethod unboxInvoke = firstArg.getAliasedValue().getDefinition().asInvokeMethod();
+ assert unboxInvoke.isInvokeVirtual();
+ Value src = boxInvoke.outValue();
+ Value replacement = unboxInvoke.getFirstArgument();
+ // We need to update affected values if the nullability is different.
+ src.replaceUsers(replacement, affectedValues);
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ }
+
+ void optimizeUnboxMethod(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod unboxInvoke) {
+ Value firstArg = unboxInvoke.getFirstArgument();
+ AbstractValue abstractValue = firstArg.getAbstractValue(appView, code.context());
+ if (isMatchingSingleBoxedPrimitive(abstractValue)) {
+ // Optimize Primitive.box(cst).unbox() into cst, possibly inter-procedurally.
+ if (unboxInvoke.hasOutValue()) {
+ SingleBoxedPrimitiveValue singleBoxedNumber = abstractValue.asSingleBoxedPrimitive();
+ instructionIterator.replaceCurrentInstruction(
+ singleBoxedNumber
+ .toPrimitive(appView.abstractValueFactory())
+ .createMaterializingInstruction(appView, code, unboxInvoke));
+ } else {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ }
+ return;
+ }
+ if (firstArg
+ .getAliasedValue()
+ .isDefinedByInstructionSatisfying(i -> i.isInvokeMethod(getBoxMethod()))) {
+ // Optimize Primitive.box(unboxed).unbox() into unboxed.
+ InvokeMethod boxInvoke = firstArg.getAliasedValue().getDefinition().asInvokeMethod();
+ assert boxInvoke.isInvokeStatic();
+ unboxInvoke.outValue().replaceUsers(boxInvoke.getFirstArgument());
+ instructionIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/ShortMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/ShortMethodOptimizer.java
new file mode 100644
index 0000000..8bae65e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/primitive/ShortMethodOptimizer.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, 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.library.primitive;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class ShortMethodOptimizer extends PrimitiveMethodOptimizer {
+
+ ShortMethodOptimizer(AppView<?> appView) {
+ super(appView);
+ }
+
+ @Override
+ DexMethod getBoxMethod() {
+ return dexItemFactory.shortMembers.valueOf;
+ }
+
+ @Override
+ DexMethod getUnboxMethod() {
+ return dexItemFactory.shortMembers.shortValue;
+ }
+
+ @Override
+ boolean isMatchingSingleBoxedPrimitive(AbstractValue abstractValue) {
+ return abstractValue.isSingleBoxedShort();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.boxedShortType;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/MethodBoxingStatus.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/MethodBoxingStatus.java
new file mode 100644
index 0000000..75d2340
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/MethodBoxingStatus.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2023, 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.numberunboxer;
+
+import com.android.tools.r8.utils.ArrayUtils;
+
+public class MethodBoxingStatus {
+
+ public static final MethodBoxingStatus NONE_UNBOXABLE = new MethodBoxingStatus(null, null);
+
+ private final ValueBoxingStatus returnStatus;
+ private final ValueBoxingStatus[] argStatuses;
+
+ public static MethodBoxingStatus create(
+ ValueBoxingStatus returnStatus, ValueBoxingStatus[] argStatuses) {
+ if (returnStatus.isNotUnboxable()
+ && ArrayUtils.all(argStatuses, ValueBoxingStatus.NOT_UNBOXABLE)) {
+ return NONE_UNBOXABLE;
+ }
+ return new MethodBoxingStatus(returnStatus, argStatuses);
+ }
+
+ private MethodBoxingStatus(ValueBoxingStatus returnStatus, ValueBoxingStatus[] argStatuses) {
+ this.returnStatus = returnStatus;
+ this.argStatuses = argStatuses;
+ }
+
+ public MethodBoxingStatus merge(MethodBoxingStatus other) {
+ if (isNoneUnboxable() || other.isNoneUnboxable()) {
+ return NONE_UNBOXABLE;
+ }
+ assert argStatuses.length == other.argStatuses.length;
+ ValueBoxingStatus[] newArgStatuses = new ValueBoxingStatus[argStatuses.length];
+ for (int i = 0; i < other.argStatuses.length; i++) {
+ newArgStatuses[i] = other.argStatuses[i].merge(argStatuses[i]);
+ }
+ return create(returnStatus.merge(other.returnStatus), newArgStatuses);
+ }
+
+ public boolean isNoneUnboxable() {
+ return this == NONE_UNBOXABLE;
+ }
+
+ public ValueBoxingStatus getReturnStatus() {
+ assert !isNoneUnboxable();
+ return returnStatus;
+ }
+
+ public ValueBoxingStatus getArgStatus(int i) {
+ assert !isNoneUnboxable();
+ return argStatuses[i];
+ }
+
+ public ValueBoxingStatus[] getArgStatuses() {
+ assert !isNoneUnboxable();
+ return argStatuses;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MethodBoxingStatus[");
+ if (this == NONE_UNBOXABLE) {
+ sb.append("NONE_UNBOXABLE");
+ } else {
+ for (int i = 0; i < argStatuses.length; i++) {
+ if (argStatuses[i].mayBeUnboxable()) {
+ sb.append(i).append(":").append(argStatuses[i]).append(";");
+ }
+ }
+ if (returnStatus.mayBeUnboxable()) {
+ sb.append("ret").append(":").append(returnStatus).append(";");
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
new file mode 100644
index 0000000..dbfb37a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxer.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2023, 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.numberunboxer;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public abstract class NumberUnboxer {
+
+ public static NumberUnboxer create(AppView<AppInfoWithLiveness> appView) {
+ if (appView.testing().enableNumberUnboxer) {
+ return new NumberUnboxerImpl(appView);
+ }
+ return empty();
+ }
+
+ public static NumberUnboxer empty() {
+ return new Empty();
+ }
+
+ public abstract void prepareForPrimaryOptimizationPass(
+ Timing timing, ExecutorService executorService) throws ExecutionException;
+
+ public abstract void analyze(IRCode code);
+
+ public abstract void unboxNumbers(
+ PostMethodProcessor.Builder postMethodProcessorBuilder,
+ Timing timing,
+ ExecutorService executorService);
+
+ public abstract void onMethodPruned(ProgramMethod method);
+
+ public abstract void onMethodCodePruned(ProgramMethod method);
+
+ public abstract void rewriteWithLens();
+
+ static class Empty extends NumberUnboxer {
+
+ @Override
+ public void prepareForPrimaryOptimizationPass(Timing timing, ExecutorService executorService)
+ throws ExecutionException {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void analyze(IRCode code) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void unboxNumbers(
+ PostMethodProcessor.Builder postMethodProcessorBuilder,
+ Timing timing,
+ ExecutorService executorService) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void onMethodPruned(ProgramMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void onMethodCodePruned(ProgramMethod method) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void rewriteWithLens() {
+ // Intentionally empty.
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
new file mode 100644
index 0000000..2f0a128
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerBoxingStatusResolution.java
@@ -0,0 +1,196 @@
+// Copyright (c) 2023, 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.numberunboxer;
+
+import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.NO_UNBOX;
+import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.TO_PROCESS;
+import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.UNBOX;
+import static com.android.tools.r8.utils.ListUtils.*;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult;
+import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodArg;
+import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodRet;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.WorkList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NumberUnboxerBoxingStatusResolution {
+
+ // TODO(b/307872552): Add threshold to NumberUnboxing options.
+ private static final int UNBOX_DELTA_THRESHOLD = 0;
+ private final Map<DexMethod, MethodBoxingStatusResult> boxingStatusResultMap =
+ new IdentityHashMap<>();
+
+ static class MethodBoxingStatusResult {
+
+ public static MethodBoxingStatusResult createNonUnboxable(DexMethod method) {
+ // Replace by singleton.
+ return new MethodBoxingStatusResult(method, NO_UNBOX);
+ }
+
+ public static MethodBoxingStatusResult create(DexMethod method) {
+ return new MethodBoxingStatusResult(method, TO_PROCESS);
+ }
+
+ MethodBoxingStatusResult(DexMethod method, BoxingStatusResult init) {
+ this.ret = init;
+ this.args = new BoxingStatusResult[method.getArity()];
+ Arrays.fill(args, init);
+ }
+
+ enum BoxingStatusResult {
+ TO_PROCESS,
+ UNBOX,
+ NO_UNBOX
+ }
+
+ BoxingStatusResult ret;
+ BoxingStatusResult[] args;
+
+ public void setRet(BoxingStatusResult ret) {
+ this.ret = ret;
+ }
+
+ public BoxingStatusResult getRet() {
+ return ret;
+ }
+
+ public void setArg(BoxingStatusResult arg, int i) {
+ this.args[i] = arg;
+ }
+
+ public BoxingStatusResult getArg(int i) {
+ return args[i];
+ }
+
+ public BoxingStatusResult[] getArgs() {
+ return args;
+ }
+ }
+
+ void markNoneUnboxable(DexMethod method) {
+ boxingStatusResultMap.put(method, MethodBoxingStatusResult.createNonUnboxable(method));
+ }
+
+ private MethodBoxingStatusResult getMethodBoxingStatusResult(DexMethod method) {
+ return boxingStatusResultMap.computeIfAbsent(method, MethodBoxingStatusResult::create);
+ }
+
+ BoxingStatusResult get(TransitiveDependency transitiveDependency) {
+ assert transitiveDependency.isMethodDependency();
+ MethodBoxingStatusResult methodBoxingStatusResult =
+ getMethodBoxingStatusResult(transitiveDependency.asMethodDependency().getMethod());
+ if (transitiveDependency.isMethodRet()) {
+ return methodBoxingStatusResult.getRet();
+ }
+ assert transitiveDependency.isMethodArg();
+ return methodBoxingStatusResult.getArg(transitiveDependency.asMethodArg().getParameterIndex());
+ }
+
+ void register(TransitiveDependency transitiveDependency, BoxingStatusResult boxingStatusResult) {
+ assert transitiveDependency.isMethodDependency();
+ MethodBoxingStatusResult methodBoxingStatusResult =
+ getMethodBoxingStatusResult(transitiveDependency.asMethodDependency().getMethod());
+ if (transitiveDependency.isMethodRet()) {
+ methodBoxingStatusResult.setRet(boxingStatusResult);
+ return;
+ }
+ assert transitiveDependency.isMethodArg();
+ methodBoxingStatusResult.setArg(
+ boxingStatusResult, transitiveDependency.asMethodArg().getParameterIndex());
+ }
+
+ public Map<DexMethod, MethodBoxingStatusResult> resolve(
+ Map<DexMethod, MethodBoxingStatus> methodBoxingStatus) {
+ List<DexMethod> methods = ListUtils.sort(methodBoxingStatus.keySet(), DexMethod::compareTo);
+ for (DexMethod method : methods) {
+ MethodBoxingStatus status = methodBoxingStatus.get(method);
+ if (status.isNoneUnboxable()) {
+ markNoneUnboxable(method);
+ continue;
+ }
+ MethodBoxingStatusResult methodBoxingStatusResult = getMethodBoxingStatusResult(method);
+ if (status.getReturnStatus().isNotUnboxable()) {
+ methodBoxingStatusResult.setRet(NO_UNBOX);
+ } else {
+ if (methodBoxingStatusResult.getRet() == TO_PROCESS) {
+ resolve(methodBoxingStatus, new MethodRet(method));
+ }
+ }
+ for (int i = 0; i < status.getArgStatuses().length; i++) {
+ ValueBoxingStatus argStatus = status.getArgStatus(i);
+ if (argStatus.isNotUnboxable()) {
+ methodBoxingStatusResult.setArg(NO_UNBOX, i);
+ } else {
+ if (methodBoxingStatusResult.getArg(i) == TO_PROCESS) {
+ resolve(methodBoxingStatus, new MethodArg(i, method));
+ }
+ }
+ }
+ }
+ assert allProcessed();
+ return boxingStatusResultMap;
+ }
+
+ private boolean allProcessed() {
+ boxingStatusResultMap.forEach(
+ (k, v) -> {
+ assert v.getRet() != TO_PROCESS;
+ for (BoxingStatusResult arg : v.getArgs()) {
+ assert arg != TO_PROCESS;
+ }
+ });
+ return true;
+ }
+
+ private ValueBoxingStatus getValueBoxingStatus(
+ TransitiveDependency dep, Map<DexMethod, MethodBoxingStatus> methodBoxingStatus) {
+ // Later we will implement field dependencies.
+ assert dep.isMethodDependency();
+ MethodBoxingStatus status = methodBoxingStatus.get(dep.asMethodDependency().getMethod());
+ if (dep.isMethodRet()) {
+ return status.getReturnStatus();
+ }
+ assert dep.isMethodArg();
+ return status.getArgStatus(dep.asMethodArg().getParameterIndex());
+ }
+
+ private void resolve(
+ Map<DexMethod, MethodBoxingStatus> methodBoxingStatus, TransitiveDependency dep) {
+ WorkList<TransitiveDependency> workList = WorkList.newIdentityWorkList(dep);
+ int delta = 0;
+ while (workList.hasNext()) {
+ TransitiveDependency next = workList.next();
+ BoxingStatusResult boxingStatusResult = get(next);
+ if (boxingStatusResult == UNBOX) {
+ delta++;
+ continue;
+ }
+ ValueBoxingStatus valueBoxingStatus = getValueBoxingStatus(next, methodBoxingStatus);
+ if (boxingStatusResult == NO_UNBOX || valueBoxingStatus.isNotUnboxable()) {
+ // TODO(b/307872552): Unbox when a non unboxable non null dependency is present.
+ // If a dependency is not unboxable, we need to prove it's non-null, else we cannot unbox.
+ // In this first version we bail out by setting a negative delta.
+ delta = -1;
+ break;
+ }
+ assert boxingStatusResult == TO_PROCESS;
+ workList.addIfNotSeen(valueBoxingStatus.getTransitiveDependencies());
+ // Each dependency has been pessimistically marked as requiring extra boxing operation.
+ // TODO(b/307872552): Test and re-evaluate the delta computation.
+ delta += valueBoxingStatus.getTransitiveDependencies().size();
+ delta += valueBoxingStatus.getBoxingDelta();
+ }
+ BoxingStatusResult boxingStatusResult = delta > UNBOX_DELTA_THRESHOLD ? UNBOX : NO_UNBOX;
+ for (TransitiveDependency transitiveDependency : workList.getSeenSet()) {
+ assert transitiveDependency.isMethodDependency();
+ register(transitiveDependency.asMethodDependency(), boxingStatusResult);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
new file mode 100644
index 0000000..cf8f2ec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
@@ -0,0 +1,351 @@
+// Copyright (c) 2023, 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.numberunboxer;
+
+import static com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult.BoxingStatusResult.UNBOX;
+import static com.android.tools.r8.ir.optimize.numberunboxer.ValueBoxingStatus.NOT_UNBOXABLE;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+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.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.numberunboxer.NumberUnboxerBoxingStatusResolution.MethodBoxingStatusResult;
+import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodArg;
+import com.android.tools.r8.ir.optimize.numberunboxer.TransitiveDependency.MethodRet;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class NumberUnboxerImpl extends NumberUnboxer {
+
+ private AppView<AppInfoWithLiveness> appView;
+ private DexItemFactory factory;
+ private Set<DexType> boxedTypes;
+
+ // Temporarily keep the information here, and not in the MethodOptimizationInfo as the
+ // optimization is developed and unfinished.
+ private Map<DexMethod, MethodBoxingStatus> methodBoxingStatus = new ConcurrentHashMap<>();
+ private Map<DexMethod, DexMethod> virtualMethodsRepresentative;
+
+ public NumberUnboxerImpl(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ this.boxedTypes = factory.primitiveToBoxed.values();
+ }
+
+ /**
+ * The preparation agglomerate targets or virtual calls into a deterministic method amongst them.
+ * This allows R8 to compute the boxing status once for all targets of the same call.
+ */
+ @Override
+ public void prepareForPrimaryOptimizationPass(Timing timing, ExecutorService executorService)
+ throws ExecutionException {
+ timing.begin("Prepare number unboxer tree fixer");
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+ ImmediateProgramSubtypingInfo.create(appView);
+ List<Set<DexProgramClass>> connectedComponents =
+ new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
+ .computeStronglyConnectedComponents();
+ Set<Map<DexMethod, DexMethod>> virtualMethodsRepresentativeToMerge =
+ ConcurrentHashMap.newKeySet();
+ ThreadUtils.processItems(
+ connectedComponents,
+ component ->
+ virtualMethodsRepresentativeToMerge.add(computeVirtualMethodRepresentative(component)),
+ appView.options().getThreadingModule(),
+ executorService);
+ virtualMethodsRepresentative =
+ MapUtils.newImmutableMap(
+ builder -> virtualMethodsRepresentativeToMerge.forEach(builder::putAll));
+ timing.end();
+ }
+
+ // TODO(b/307872552): Do not store irrelevant representative.
+ private Map<DexMethod, DexMethod> computeVirtualMethodRepresentative(
+ Set<DexProgramClass> component) {
+ DexMethodSignatureMap<List<DexMethod>> componentVirtualMethods = DexMethodSignatureMap.create();
+ for (DexProgramClass clazz : component) {
+ for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
+ DexMethod reference = virtualProgramMethod.getReference();
+ List<DexMethod> set =
+ componentVirtualMethods.computeIfAbsent(virtualProgramMethod, k -> new ArrayList<>());
+ set.add(reference);
+ }
+ }
+ Map<DexMethod, DexMethod> vMethodRepresentative = new IdentityHashMap<>();
+ for (List<DexMethod> vMethods : componentVirtualMethods.values()) {
+ if (vMethods.size() > 1) {
+ vMethods.sort(Comparator.naturalOrder());
+ DexMethod representative = vMethods.get(0);
+ for (int i = 1; i < vMethods.size(); i++) {
+ vMethodRepresentative.put(vMethods.get(i), representative);
+ }
+ }
+ }
+ return vMethodRepresentative;
+ }
+
+ private void registerMethodUnboxingStatusIfNeeded(
+ ProgramMethod method, ValueBoxingStatus returnStatus, ValueBoxingStatus[] args) {
+ if (args == null && returnStatus == null) {
+ // We don't register anything if nothing unboxable was found.
+ return;
+ }
+ ValueBoxingStatus nonNullReturnStatus = returnStatus == null ? NOT_UNBOXABLE : returnStatus;
+ ValueBoxingStatus[] nonNullArgs =
+ args == null ? ValueBoxingStatus.notUnboxableArray(method.getReference().getArity()) : args;
+ MethodBoxingStatus unboxingStatus = MethodBoxingStatus.create(nonNullReturnStatus, nonNullArgs);
+ assert !unboxingStatus.isNoneUnboxable();
+ DexMethod representative =
+ virtualMethodsRepresentative.getOrDefault(method.getReference(), method.getReference());
+ methodBoxingStatus.compute(
+ representative,
+ (m, old) -> {
+ if (old == null) {
+ return unboxingStatus;
+ }
+ return old.merge(unboxingStatus);
+ });
+ }
+
+ /**
+ * Analysis phase: Figures out in each method if parameters, invoke, field accesses and return
+ * values are used in boxing operations.
+ */
+ @Override
+ public void analyze(IRCode code) {
+ DexMethod contextReference = code.context().getReference();
+ ValueBoxingStatus[] args = null;
+ ValueBoxingStatus returnStatus = null;
+ for (Instruction next : code.instructions()) {
+ if (next.isArgument()) {
+ ValueBoxingStatus unboxingStatus = analyzeOutput(next.outValue());
+ if (unboxingStatus.mayBeUnboxable()) {
+ if (args == null) {
+ args = new ValueBoxingStatus[contextReference.getArity()];
+ }
+ args[next.asArgument().getIndex()] = unboxingStatus;
+ }
+ } else if (next.isReturn()) {
+ Return ret = next.asReturn();
+ if (ret.hasReturnValue() && (returnStatus == null || returnStatus.mayBeUnboxable())) {
+ ValueBoxingStatus unboxingStatus = analyzeInput(ret.returnValue(), code.context());
+ if (unboxingStatus.mayBeUnboxable()) {
+ returnStatus =
+ returnStatus == null ? unboxingStatus : returnStatus.merge(unboxingStatus);
+ } else {
+ returnStatus = NOT_UNBOXABLE;
+ }
+ }
+ } else if (next.isInvokeMethod()) {
+ analyzeInvoke(next.asInvokeMethod(), code.context());
+ } else if (next.isInvokeCustom()) {
+ throw new Unimplemented();
+ }
+ }
+ // TODO(b/307872552): Analyse field access to unbox fields.
+ registerMethodUnboxingStatusIfNeeded(code.context(), returnStatus, args);
+ }
+
+ private void analyzeInvoke(InvokeMethod invoke, ProgramMethod context) {
+ ProgramMethod resolvedMethod =
+ appView
+ .appInfo()
+ .resolveMethodLegacy(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+ .getResolvedProgramMethod();
+ if (resolvedMethod == null) {
+ return;
+ }
+ ValueBoxingStatus[] args = null;
+ int shift = invoke.getFirstNonReceiverArgumentIndex();
+ for (int i = shift; i < invoke.inValues().size(); i++) {
+ ValueBoxingStatus unboxingStatus = analyzeInput(invoke.getArgument(i), context);
+ if (unboxingStatus.mayBeUnboxable()) {
+ if (args == null) {
+ args = new ValueBoxingStatus[invoke.getInvokedMethod().getArity()];
+ Arrays.fill(args, NOT_UNBOXABLE);
+ }
+ args[i - shift] = unboxingStatus;
+ }
+ }
+ ValueBoxingStatus returnVal = null;
+ if (invoke.hasOutValue()) {
+ ValueBoxingStatus unboxingStatus = analyzeOutput(invoke.outValue());
+ if (unboxingStatus.mayBeUnboxable()) {
+ returnVal = unboxingStatus;
+ }
+ }
+ registerMethodUnboxingStatusIfNeeded(resolvedMethod, returnVal, args);
+ }
+
+ private boolean shouldConsiderForUnboxing(Value value) {
+ // TODO(b/307872552): So far we consider only boxed type value to unbox them into their
+ // corresponding primitive type, for example, Integer -> int. It would be nice to support
+ // the pattern checkCast(BoxType) followed by a boxing operation, so that for example when
+ // we have MyClass<T> and T is proven to be an Integer, we can unbox into int.
+ return value.getType().isClassType()
+ && boxedTypes.contains(value.getType().asClassType().getClassType());
+ }
+
+ // Inputs are values flowing into a method return, an invoke argument or a field write.
+ private ValueBoxingStatus analyzeInput(Value inValue, ProgramMethod context) {
+ if (!shouldConsiderForUnboxing(inValue)) {
+ return NOT_UNBOXABLE;
+ }
+ DexType boxedType = inValue.getType().asClassType().getClassType();
+ DexType primitiveType = factory.primitiveToBoxed.inverse().get(boxedType);
+ DexMethod boxPrimitiveMethod = factory.getBoxPrimitiveMethod(primitiveType);
+ if (!inValue.isPhi()) {
+ Instruction definition = inValue.getAliasedValue().getDefinition();
+ if (definition.isArgument()) {
+ return ValueBoxingStatus.with(
+ new MethodArg(definition.asArgument().getIndex(), context.getReference()));
+ }
+ if (definition.isInvokeMethod()) {
+ if (boxPrimitiveMethod.isIdenticalTo(definition.asInvokeMethod().getInvokedMethod())) {
+ // The result of a boxing operation is non nullable.
+ if (!inValue.hasPhiUsers() && inValue.hasSingleUniqueUser()) {
+ // Unboxing would remove a boxing operation.
+ return ValueBoxingStatus.with(1);
+ }
+ // Unboxing would add and remove a boxing operation.
+ return ValueBoxingStatus.with(0);
+ }
+ InvokeMethod invoke = definition.asInvokeMethod();
+ ProgramMethod resolvedMethod =
+ appView
+ .appInfo()
+ .resolveMethodLegacy(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+ .getResolvedProgramMethod();
+ if (resolvedMethod != null) {
+ return ValueBoxingStatus.with(new MethodRet(invoke.getInvokedMethod()));
+ }
+ }
+ }
+ // TODO(b/307872552) We should support field reads as transitive dependencies.
+ if (inValue.getType().isNullable()) {
+ return NOT_UNBOXABLE;
+ }
+ // TODO(b/307872552): We could analyze simple phis, for example,
+ // Integer i = bool ? integer.valueOf(1) : Integer.valueOf(2);
+ // removes 2 operations and does not add 1.
+ // Since we cannot interpret the definition, unboxing adds a boxing operation.
+ return ValueBoxingStatus.with(-1);
+ }
+
+ // Outputs are method arguments, invoke return values and field reads.
+ private ValueBoxingStatus analyzeOutput(Value outValue) {
+ if (!shouldConsiderForUnboxing(outValue)) {
+ return NOT_UNBOXABLE;
+ }
+ DexType boxedType = outValue.getType().asClassType().getClassType();
+ DexMethod unboxPrimitiveMethod = factory.getUnboxPrimitiveMethod(boxedType);
+ boolean metUnboxingOperation = false;
+ boolean metOtherOperation = outValue.hasPhiUsers();
+ for (Instruction uniqueUser : outValue.aliasedUsers()) {
+ if (uniqueUser.isAssumeWithNonNullAssumption()) {
+ // Nothing to do, the assume will be removed by unboxing.
+ } else if (uniqueUser.isInvokeMethod()
+ && unboxPrimitiveMethod.isIdenticalTo(uniqueUser.asInvokeMethod().getInvokedMethod())) {
+ metUnboxingOperation = true;
+ } else {
+ metOtherOperation = true;
+ }
+ }
+ return ValueBoxingStatus.with(computeBoxingDelta(metUnboxingOperation, metOtherOperation));
+ }
+
+ private int computeBoxingDelta(boolean metUnboxingOperation, boolean metOtherOperation) {
+ if (metUnboxingOperation) {
+ if (metOtherOperation) {
+ // Unboxing would add and remove a boxing operation.
+ return 0;
+ }
+ // Unboxing would remove a boxing operation.
+ return 1;
+ }
+ if (metOtherOperation) {
+ // Unboxing would add a boxing operation.
+ return -1;
+ }
+ // Unused, unboxing won't change the number of boxing operations.
+ return 0;
+ }
+
+ @Override
+ public void unboxNumbers(
+ PostMethodProcessor.Builder postMethodProcessorBuilder,
+ Timing timing,
+ ExecutorService executorService) {
+
+ Map<DexMethod, MethodBoxingStatusResult> result =
+ new NumberUnboxerBoxingStatusResolution().resolve(methodBoxingStatus);
+
+ // TODO(b/307872552): The result encodes for each method which return value and parameter of
+ // each method should be unboxed. We need here to implement the treefixer using it, and set up
+ // correctly the reprocessing with a code rewriter similar to the enum unboxing code rewriter.
+ // We should implement the optimization, so far, we just print out the result.
+ StringBuilder stringBuilder = new StringBuilder();
+ result.forEach(
+ (k, v) -> {
+ if (v.getRet() == UNBOX) {
+ stringBuilder
+ .append("Unboxing of return value of ")
+ .append(k)
+ .append(System.lineSeparator());
+ }
+ for (int i = 0; i < v.getArgs().length; i++) {
+ if (v.getArg(i) == UNBOX) {
+ stringBuilder
+ .append("Unboxing of arg ")
+ .append(i)
+ .append(" of ")
+ .append(k)
+ .append(System.lineSeparator());
+ }
+ }
+ });
+ appView.reporter().warning(stringBuilder.toString());
+ }
+
+ @Override
+ public void onMethodPruned(ProgramMethod method) {
+ // TODO(b/307872552): Should we do something about this? We might need to change the
+ // representative.
+ }
+
+ @Override
+ public void onMethodCodePruned(ProgramMethod method) {
+ // TODO(b/307872552): I don't think we should do anything here.
+ }
+
+ @Override
+ public void rewriteWithLens() {
+ // TODO(b/307872552): This needs to rewrite the methodBoxingStatus.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/TransitiveDependency.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/TransitiveDependency.java
new file mode 100644
index 0000000..f368e52
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/TransitiveDependency.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2023, 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.numberunboxer;
+
+import com.android.tools.r8.graph.DexMethod;
+
+// Transitive dependencies are computed only one way, the other way is always pessimistic.
+// This means that invoke arguments, method return value and field write have information such as
+// "A value flowing into it is a method argument, a field read or an invoke return value".
+// However, method argument, field reads and invoke return value are analyzed pessimistically,
+// i.e., if they are used as an invoke argument, a method return value or a field write, then
+// the delta is pessimistically increased.
+public interface TransitiveDependency {
+
+ default boolean isMethodDependency() {
+ return false;
+ }
+
+ default MethodDependency asMethodDependency() {
+ return null;
+ }
+
+ default boolean isMethodArg() {
+ return false;
+ }
+
+ default MethodArg asMethodArg() {
+ return null;
+ }
+
+ default boolean isMethodRet() {
+ return false;
+ }
+
+ default MethodRet asMethodRet() {
+ return null;
+ }
+
+ @Override
+ int hashCode();
+
+ @Override
+ boolean equals(Object o);
+
+ abstract class MethodDependency implements TransitiveDependency {
+
+ private final DexMethod method;
+
+ public MethodDependency(DexMethod method) {
+ this.method = method;
+ }
+
+ @Override
+ public boolean isMethodDependency() {
+ return true;
+ }
+
+ @Override
+ public MethodDependency asMethodDependency() {
+ return this;
+ }
+
+ public DexMethod getMethod() {
+ return method;
+ }
+ }
+
+ class MethodRet extends MethodDependency {
+
+ public MethodRet(DexMethod method) {
+ super(method);
+ }
+
+ @Override
+ public boolean isMethodRet() {
+ return true;
+ }
+
+ @Override
+ public MethodRet asMethodRet() {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return getMethod().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MethodRet)) {
+ return false;
+ }
+ return getMethod().isIdenticalTo(((MethodRet) obj).getMethod());
+ }
+
+ @Override
+ public String toString() {
+ return "MethodRet("
+ + getMethod().getHolderType().getSimpleName()
+ + "#"
+ + getMethod().name
+ + ')';
+ }
+ }
+
+ class MethodArg extends MethodDependency {
+
+ private final int parameterIndex;
+
+ public MethodArg(int parameterIndex, DexMethod method) {
+ super(method);
+ assert parameterIndex >= 0;
+ this.parameterIndex = parameterIndex;
+ }
+
+ public int getParameterIndex() {
+ return parameterIndex;
+ }
+
+ @Override
+ public boolean isMethodArg() {
+ return true;
+ }
+
+ @Override
+ public MethodArg asMethodArg() {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Integer.hashCode(parameterIndex) + 7 * getMethod().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MethodArg)) {
+ return false;
+ }
+ MethodArg other = (MethodArg) obj;
+ return getMethod().isIdenticalTo(other.getMethod())
+ && getParameterIndex() == other.getParameterIndex();
+ }
+
+ @Override
+ public String toString() {
+ return "MethodArg("
+ + getMethod().getHolderType().getSimpleName()
+ + "#"
+ + getMethod().name
+ + "#"
+ + parameterIndex
+ + ')';
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/ValueBoxingStatus.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/ValueBoxingStatus.java
new file mode 100644
index 0000000..53a7867
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/ValueBoxingStatus.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2023, 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.numberunboxer;
+
+import com.google.common.collect.ImmutableMultiset;
+import java.util.Arrays;
+
+/**
+ * The value boxing status represents the result of the number unboxer analysis on a given value,
+ * such as a method argument, return value or a field. It contains a boxingDelta which encodes the
+ * number of boxing operation that would be introduced or removed if the value is unboxed and
+ * optionally a list of transitive dependency. To use the value boxing status, the number unboxer
+ * needs to decide for each of the transitive dependency if they're going to be unboxed or not,
+ * compute the concrete boxing delta and unbox if relevant.
+ */
+public class ValueBoxingStatus {
+
+ // TODO(b/307872552): Add threshold to NumberUnboxing options.
+ private static final int MAX_TRANSITIVE_DEPENDENCIES = 7;
+ public static final ValueBoxingStatus NOT_UNBOXABLE =
+ new ValueBoxingStatus(0, ImmutableMultiset.of());
+ private final int boxingDelta;
+ private final ImmutableMultiset<TransitiveDependency> transitiveDependencies;
+
+ public static ValueBoxingStatus[] notUnboxableArray(int size) {
+ ValueBoxingStatus[] valueBoxingStatuses = new ValueBoxingStatus[size];
+ Arrays.fill(valueBoxingStatuses, ValueBoxingStatus.NOT_UNBOXABLE);
+ return valueBoxingStatuses;
+ }
+
+ public static ValueBoxingStatus with(int boxingDelta) {
+ return with(boxingDelta, ImmutableMultiset.of());
+ }
+
+ public static ValueBoxingStatus with(TransitiveDependency transitiveDependency) {
+ return with(0, ImmutableMultiset.of(transitiveDependency));
+ }
+
+ public static ValueBoxingStatus with(
+ int boxingDelta, ImmutableMultiset<TransitiveDependency> transitiveDependencies) {
+ if (transitiveDependencies.size() > MAX_TRANSITIVE_DEPENDENCIES) {
+ return NOT_UNBOXABLE;
+ }
+ return new ValueBoxingStatus(boxingDelta, transitiveDependencies);
+ }
+
+ private ValueBoxingStatus(
+ int boxingDelta, ImmutableMultiset<TransitiveDependency> transitiveDependencies) {
+ this.boxingDelta = boxingDelta;
+ this.transitiveDependencies = transitiveDependencies;
+ }
+
+ public boolean mayBeUnboxable() {
+ return !isNotUnboxable();
+ }
+
+ public boolean isNotUnboxable() {
+ return this == NOT_UNBOXABLE;
+ }
+
+ public int getBoxingDelta() {
+ assert mayBeUnboxable();
+ return boxingDelta;
+ }
+
+ public ImmutableMultiset<TransitiveDependency> getTransitiveDependencies() {
+ return transitiveDependencies;
+ }
+
+ public ValueBoxingStatus merge(ValueBoxingStatus unboxingStatus) {
+ if (isNotUnboxable() || unboxingStatus.isNotUnboxable()) {
+ return NOT_UNBOXABLE;
+ }
+ int newDelta = boxingDelta + unboxingStatus.getBoxingDelta();
+ if (unboxingStatus.getTransitiveDependencies().isEmpty()) {
+ if (newDelta == boxingDelta) {
+ return this;
+ }
+ return with(newDelta, transitiveDependencies);
+ }
+ if (transitiveDependencies.isEmpty()) {
+ if (newDelta == unboxingStatus.getBoxingDelta()) {
+ return unboxingStatus;
+ }
+ return with(newDelta, unboxingStatus.getTransitiveDependencies());
+ }
+ ImmutableMultiset<TransitiveDependency> newDeps =
+ ImmutableMultiset.<TransitiveDependency>builder()
+ .addAll(transitiveDependencies)
+ .addAll(unboxingStatus.getTransitiveDependencies())
+ .build();
+ return with(newDelta, newDeps);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ValueBoxingStatus[");
+ if (isNotUnboxable()) {
+ sb.append("NOT_UNBOXABLE");
+ } else {
+ sb.append(boxingDelta);
+ for (TransitiveDependency transitiveDependency : transitiveDependencies) {
+ sb.append(";");
+ sb.append(transitiveDependency.toString());
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
index b4ed86d..9297330 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
@@ -1842,12 +1842,6 @@
}
@Override
- public int estimatedSizeForInlining() {
- // We just onlined this, so do not inline it again.
- return Integer.MAX_VALUE;
- }
-
- @Override
public int estimatedDexCodeSizeUpperBoundInBytes() {
return Integer.MAX_VALUE;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAction.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAction.java
index 1b36879..8696c95 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAction.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
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.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -87,10 +88,8 @@
AffectedValues affectedValues,
StringBuilderOracle oracle) {
assert oracle.isToString(instruction, instruction.getFirstOperand());
- if (instruction.hasOutValue()) {
- instruction.outValue().addAffectedValuesTo(affectedValues);
- }
- iterator.replaceCurrentInstructionWithConstString(appView, code, replacement);
+ DexString string = appView.dexItemFactory().createString(replacement);
+ iterator.replaceCurrentInstructionWithConstString(appView, code, string, affectedValues);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index dbbfe16..f60a3fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -439,9 +439,8 @@
Value out = invoke.outValue();
TypeElement inType = in.getType();
if (out != null && in.isAlwaysNull(appView)) {
- affectedValues.addAll(out.affectedValues());
it.replaceCurrentInstructionWithConstString(
- appView, code, dexItemFactory.createString("null"));
+ appView, code, dexItemFactory.createString("null"), affectedValues);
} else if (inType.nullability().isDefinitelyNotNull()
&& inType.isClassType()
&& inType.asClassType().getClassType().equals(dexItemFactory.stringType)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index d89c044..a6b6c18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.PrimaryMethodProcessor;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -61,7 +62,7 @@
if (isCandidateForInstanceOfOptimization(method, abstractReturnValue)) {
synchronized (this) {
if (candidatesForInstanceOfOptimization.isEmpty()) {
- converter.addWaveDoneAction(() -> execute(methodProcessor));
+ converter.addWaveDoneAction(() -> execute(methodProcessor.asPrimaryMethodProcessor()));
}
candidatesForInstanceOfOptimization.add(method);
}
@@ -74,7 +75,7 @@
&& abstractReturnValue.isSingleBoolean();
}
- public void execute(MethodProcessor methodProcessor) {
+ public void execute(PrimaryMethodProcessor methodProcessor) {
assert !candidatesForInstanceOfOptimization.isEmpty();
ProgramMethodSet processed = ProgramMethodSet.create();
for (ProgramMethod method : candidatesForInstanceOfOptimization) {
@@ -86,7 +87,7 @@
@SuppressWarnings("ReferenceEquality")
private void processCandidateForInstanceOfOptimization(
- ProgramMethod method, MethodProcessor methodProcessor) {
+ ProgramMethod method, PrimaryMethodProcessor methodProcessor) {
DexEncodedMethod definition = method.getDefinition();
if (!definition.isNonPrivateVirtualMethod()) {
return;
@@ -135,8 +136,8 @@
// The parent method is already guaranteed to return the same value.
} else if (isClassAccessible(method.getHolder(), parentMethod, appView).isTrue()) {
parentMethod.setCode(
- parentMethodDefinition.buildInstanceOfCode(
- method.getHolderType(), abstractParentReturnValue.isTrue(), appView.options()),
+ parentMethodDefinition.buildInstanceOfCfCode(
+ method.getHolderType(), abstractParentReturnValue.isTrue()),
appView);
// Rebuild inlining constraints.
IRCode code =
@@ -146,6 +147,7 @@
feedback.fixupUnusedArguments(parentMethod, unusedArguments -> unusedArguments.clear(0));
feedback.unsetAbstractReturnValue(parentMethod);
feedback.unsetClassInlinerMethodConstraint(parentMethod);
+ methodProcessor.scheduleDesugaredMethodForProcessing(parentMethod);
} else {
return;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 615cd59..e583aea 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -113,7 +113,9 @@
m -> keepByteCodeFunctions.add(m.getReference()));
}
}
- appView.setCfByteCodePassThrough(keepByteCodeFunctions);
+ if (appView.options().enableCfByteCodePassThrough) {
+ appView.setCfByteCodePassThrough(keepByteCodeFunctions);
+ }
} else {
assert enqueuer.getMode().isFinalTreeShaking();
enqueuer.forAllLiveClasses(
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
index 8270d8a..54a9c8c 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -613,11 +613,14 @@
}
@Override
- public int estimatedSizeForInlining() {
+ public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) {
if (useDexEstimationStrategy) {
LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this);
for (LirInstructionView view : this) {
estimation.onInstructionView(view);
+ if (estimation.getSizeEstimate() > threshold) {
+ return -1;
+ }
}
return estimation.getSizeEstimate();
} else {
@@ -625,23 +628,11 @@
// (even switches!) and ignores stack instructions, thus loads to arguments are not included.
// The result is a much smaller estimate than for DEX. Once LIR is in place we should use the
// same estimate for both.
- return instructionCount;
- }
- }
-
- @Override
- public boolean estimatedSizeForInliningAtMost(int threshold) {
- if (useDexEstimationStrategy) {
- LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this);
- for (LirInstructionView view : this) {
- estimation.onInstructionView(view);
- if (estimation.getSizeEstimate() > threshold) {
- return false;
- }
+ int estimatedSizedForInlining = instructionCount;
+ if (estimatedSizedForInlining <= threshold) {
+ return estimatedSizedForInlining;
}
- return true;
- } else {
- return estimatedSizeForInlining() <= threshold;
+ return -1;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 9700c94..9074bdb 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -476,11 +476,8 @@
continue;
}
ArrayPut arrayPut = instruction.asArrayPut();
- if (!arrayPut.index().isConstNumber()) {
- return null;
- }
- int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
- if (index < 0 || index >= values.length) {
+ int index = arrayPut.indexIfConstAndInBounds(values.length);
+ if (index < 0) {
return null;
}
DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayPut.value(), factory);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 74c76d1..96275ab 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -41,7 +41,7 @@
this.appView = appView;
}
- public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
assert appView.options().isMinifying();
SubtypingInfo subtypingInfo = MinifierUtils.createSubtypingInfo(appView);
timing.begin("ComputeInterfaces");
@@ -90,8 +90,9 @@
new RecordInvokeDynamicInvokeCustomRewriter(appView, lens).run(executorService);
timing.end();
+ appView.testing().namingLensConsumer.accept(appView.dexItemFactory(), lens);
appView.notifyOptimizationFinishedForTesting();
- return lens;
+ appView.setNamingLens(lens);
}
abstract static class BaseMinificationNamingStrategy {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 8868840..71ff9e8 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -820,7 +820,15 @@
skipWhitespace();
int to = parseNumber();
if (from > to) {
- throw new ParseException("From is larger than to in range: " + from + ":" + to);
+ // Past versions of R8 would incorrectly put 0 as the "to" range in some instances
+ // and fail to order the range. For 0-values assume the range is a singleton position.
+ if (to == 0) {
+ to = from;
+ } else {
+ int tmp = to;
+ to = from;
+ from = tmp;
+ }
}
return nonCardinalRangeCache.get(from, to);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
new file mode 100644
index 0000000..f06f226
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
@@ -0,0 +1,338 @@
+// Copyright (c) 2023, 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;
+
+import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
+import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
+import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
+import com.android.tools.r8.profile.rewriting.ConcreteProfileCollectionAdditions;
+import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
+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.ListUtils;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
+
+public class BridgeHoistingToSharedSyntheticSuperClass {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ BridgeHoistingToSharedSyntheticSuperClass(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public static void run(
+ AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ InternalOptions options = appView.options();
+ if (!options.isOptimizing() || !options.isShrinking()) {
+ return;
+ }
+ if (!appView.options().canHaveNonReboundConstructorInvoke()) {
+ // TODO(b/309575527): Extend to all runtimes.
+ return;
+ }
+ TestingOptions testingOptions = options.getTestingOptions();
+ if (!testingOptions.enableBridgeHoistingToSharedSyntheticSuperclass) {
+ return;
+ }
+ timing.time(
+ "BridgeHoistingToSharedSyntheticSuperClass",
+ () ->
+ new BridgeHoistingToSharedSyntheticSuperClass(appView)
+ .internalRun(executorService, timing));
+ }
+
+ private void internalRun(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ Collection<Group> groups = createInitialGroups(appView);
+ groups = refineGroups(groups);
+ if (!groups.isEmpty()) {
+ rewriteApplication(groups);
+ commitPendingSyntheticClasses();
+ updateArtProfiles(groups);
+ new BridgeHoisting(appView).run(executorService, timing);
+ }
+ }
+
+ /** Returns the set of (non-singleton) groups that have the same superclass. */
+ private Collection<Group> createInitialGroups(AppView<AppInfoWithLiveness> appView) {
+ Map<DexClass, Group> groups = new LinkedHashMap<>();
+ for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+ if (!clazz.hasSuperType()) {
+ continue;
+ }
+ DexClass superclass = appView.definitionFor(clazz.getSuperType());
+ if (superclass != null) {
+ groups.computeIfAbsent(superclass, ignoreKey(Group::new)).addClass(clazz);
+ }
+ }
+ groups.values().removeIf(Group::isSingleton);
+ return groups.values();
+ }
+
+ private Collection<Group> refineGroups(Collection<Group> groups) {
+ Collection<Group> newGroups = new ArrayList<>();
+ for (Group group : groups) {
+ Iterables.addAll(newGroups, refineGroup(group));
+ }
+ return newGroups;
+ }
+
+ /**
+ * Splits the group into a collection of smaller groups that should receive a shared superclass.
+ *
+ * <p>For each class, this creates a specification of the bridges (a mapping from bridge method
+ * signatures to their bridge implementation). Two classes are selected for getting a shared
+ * synthetic super class if the bridge specification of one is a subset of the other (i.e., a
+ * subset of the bridges can be shared and there are no bridges with the same signature that have
+ * different behavior).
+ */
+ private Iterable<Group> refineGroup(Group group) {
+ List<Group> newGroups = new ArrayList<>();
+ for (DexProgramClass clazz : group) {
+ BridgeSpecification bridgeSpecification = getBridgeSpecification(clazz);
+ if (bridgeSpecification.isEmpty()) {
+ continue;
+ }
+ Group targetGroup = getGroupForClass(newGroups, clazz, bridgeSpecification);
+ if (targetGroup == null) {
+ newGroups.add(new Group(clazz, bridgeSpecification));
+ }
+ }
+ // Only introduce a shared super class for non-singleton groups that do not already have a
+ // shared superclass in the first place.
+ return Iterables.filter(
+ newGroups, newGroup -> !newGroup.isSingleton() && newGroup.size() < group.size());
+ }
+
+ // TODO(b/309575527): Avoid building IR for all methods.
+ private BridgeSpecification getBridgeSpecification(DexProgramClass clazz) {
+ BridgeSpecification bridgeSpecification = new BridgeSpecification();
+ clazz.forEachProgramVirtualMethodMatching(
+ DexEncodedMethod::hasCode,
+ method -> {
+ IRCode code = method.buildIR(appView, MethodConversionOptions.nonConverting());
+ BridgeInfo bridgeInfo = BridgeAnalyzer.analyzeMethod(method.getDefinition(), code);
+ if (bridgeInfo != null) {
+ getSimpleFeedback().setBridgeInfo(method, bridgeInfo);
+ if (bridgeInfo.isVirtualBridgeInfo()) {
+ bridgeSpecification.addBridge(
+ method.getMethodSignature(), bridgeInfo.asVirtualBridgeInfo());
+ }
+ }
+ });
+ return bridgeSpecification;
+ }
+
+ private Group getGroupForClass(
+ Collection<Group> groups, DexProgramClass clazz, BridgeSpecification bridgeSpecification) {
+ for (Group group : groups) {
+ if (bridgeSpecification.lessThanOrEquals(group.getBridgeSpecification())) {
+ group.addClass(clazz);
+ return group;
+ } else if (group.getBridgeSpecification().lessThanOrEquals(bridgeSpecification)) {
+ group.addClass(clazz);
+ group.setBridgeSpecification(bridgeSpecification);
+ return group;
+ }
+ }
+ return null;
+ }
+
+ private void rewriteApplication(Collection<Group> groups) {
+ MainThreadContext mainThreadContext =
+ appView.createProcessorContext().createMainThreadContext();
+ for (Group group : groups) {
+ DexProgramClass representative = ListUtils.first(group.getClasses());
+ Set<DexType> interfaces = SetUtils.newIdentityHashSet(representative.getInterfaces());
+ for (DexProgramClass clazz : Iterables.skip(group.getClasses(), 1)) {
+ interfaces.removeIf(type -> !clazz.getInterfaces().contains(type));
+ }
+ DexProgramClass syntheticSuperclass =
+ appView
+ .getSyntheticItems()
+ .createClass(
+ kinds -> kinds.SHARED_SUPER_CLASS,
+ mainThreadContext.createUniqueContext(representative),
+ appView,
+ classBuilder -> {
+ classBuilder
+ .setAbstract()
+ .setSuperType(representative.getSuperType())
+ .setInterfaces(ListUtils.sort(interfaces, Comparator.naturalOrder()));
+ group
+ .getBridgeSpecification()
+ .forEach(
+ (bridge, target) ->
+ classBuilder.addMethod(
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(
+ MethodAccessFlags.builder()
+ .setAbstract()
+ .setPublic()
+ .build())
+ // TODO(b/309575527): Set correct api level.
+ .setApiLevelForDefinition(appView.computedMinApiLevel())
+ // TODO(b/309575527): Set correct library override info.
+ .setIsLibraryMethodOverride(OptionalBool.FALSE)
+ .setName(target.getName())
+ .setProto(target.getProto())));
+ });
+ for (DexProgramClass clazz : group) {
+ clazz.setSuperType(syntheticSuperclass.getType());
+ }
+ }
+ }
+
+ private void commitPendingSyntheticClasses() {
+ assert appView.getSyntheticItems().hasPendingSyntheticClasses();
+ appView.setAppInfo(
+ appView.appInfo().rebuildWithLiveness(appView.getSyntheticItems().commit(appView.app())));
+ }
+
+ private void updateArtProfiles(Collection<Group> groups) {
+ ConcreteProfileCollectionAdditions profileCollectionAdditions =
+ ProfileCollectionAdditions.create(appView).asConcrete();
+ if (profileCollectionAdditions == null) {
+ return;
+ }
+ for (Group group : groups) {
+ for (DexProgramClass clazz : group) {
+ profileCollectionAdditions.applyIfContextIsInProfile(
+ clazz, additionsBuilder -> additionsBuilder.addClassRule(clazz.getSuperType()));
+ group
+ .getBridgeSpecification()
+ .forEach(
+ (bridge, target) -> {
+ DexEncodedMethod targetMethod = clazz.getMethodCollection().getMethod(target);
+ if (targetMethod != null) {
+ profileCollectionAdditions.applyIfContextIsInProfile(
+ targetMethod.getReference(),
+ additionsBuilder ->
+ additionsBuilder.addMethodRule(
+ target.withHolder(clazz.getSuperType(), appView.dexItemFactory())));
+ }
+ });
+ }
+ }
+ profileCollectionAdditions.commit(appView);
+ }
+
+ private static class Group implements Iterable<DexProgramClass> {
+
+ private final List<DexProgramClass> classes;
+ private BridgeSpecification bridgeSpecification;
+
+ public Group() {
+ this.classes = new ArrayList<>();
+ this.bridgeSpecification = null;
+ }
+
+ public Group(DexProgramClass clazz, BridgeSpecification bridgeSpecification) {
+ this.classes = ListUtils.newArrayList(clazz);
+ this.bridgeSpecification = bridgeSpecification;
+ }
+
+ void addClass(DexProgramClass clazz) {
+ classes.add(clazz);
+ }
+
+ BridgeSpecification getBridgeSpecification() {
+ return bridgeSpecification;
+ }
+
+ List<DexProgramClass> getClasses() {
+ return classes;
+ }
+
+ void setBridgeSpecification(BridgeSpecification bridgeSpecification) {
+ this.bridgeSpecification = bridgeSpecification;
+ }
+
+ boolean isSingleton() {
+ return size() == 1;
+ }
+
+ @Override
+ public Iterator<DexProgramClass> iterator() {
+ return classes.iterator();
+ }
+
+ public int size() {
+ return classes.size();
+ }
+ }
+
+ private static class BridgeSpecification {
+
+ private final DexMethodSignatureMap<DexMethodSignature> bridges =
+ DexMethodSignatureMap.create();
+
+ void addBridge(DexMethodSignature method, VirtualBridgeInfo bridgeInfo) {
+ bridges.put(method, bridgeInfo.getInvokedMethod().getSignature());
+ }
+
+ boolean containsBridgeWithTarget(DexMethodSignature method, DexMethodSignature target) {
+ return target.equals(bridges.get(method));
+ }
+
+ void forEach(BiConsumer<? super DexMethodSignature, ? super DexMethodSignature> consumer) {
+ bridges.forEach(consumer);
+ }
+
+ boolean isEmpty() {
+ return bridges.isEmpty();
+ }
+
+ boolean lessThanOrEquals(BridgeSpecification bridgeSpecification) {
+ if (size() > bridgeSpecification.size()) {
+ return false;
+ }
+ for (Entry<DexMethodSignature, DexMethodSignature> entry : bridges.entrySet()) {
+ DexMethodSignature method = entry.getKey();
+ DexMethodSignature target = entry.getValue();
+ if (!bridgeSpecification.containsBridgeWithTarget(method, target)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ int size() {
+ return bridges.size();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java b/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java
deleted file mode 100644
index c51b13c..0000000
--- a/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright (c) 2016, 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;
-
-import static com.android.tools.r8.dex.Constants.ACC_PRIVATE;
-import static com.android.tools.r8.dex.Constants.ACC_PROTECTED;
-import static com.android.tools.r8.dex.Constants.ACC_PUBLIC;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-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.FieldAccessFlags;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
-import com.android.tools.r8.ir.optimize.MethodPoolCollection;
-import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder;
-import com.android.tools.r8.optimize.accessmodification.AccessModifierOptions;
-import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.base.Equivalence.Wrapper;
-import java.util.LinkedHashSet;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-public final class LegacyAccessModifier {
-
- private final AppView<AppInfoWithLiveness> appView;
- private final SubtypingInfo subtypingInfo;
- private final MethodPoolCollection methodPoolCollection;
-
- private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder();
-
- private LegacyAccessModifier(AppView<AppInfoWithLiveness> appView) {
- this.appView = appView;
- this.subtypingInfo = appView.appInfo().computeSubtypingInfo();
- this.methodPoolCollection =
- // We will add private instance methods when we promote them.
- new MethodPoolCollection(
- appView, subtypingInfo, MethodPoolCollection::excludesPrivateInstanceMethod);
- }
-
- /**
- * Marks all package private and protected methods and fields as public. Makes all private static
- * methods public. Makes private instance methods public final instance methods, if possible.
- *
- * <p>This will destructively update the DexApplication passed in as argument.
- */
- public static void run(
- AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
- throws ExecutionException {
- AccessModifierOptions accessModifierOptions = appView.options().getAccessModifierOptions();
- if (accessModifierOptions.isAccessModificationEnabled()
- && accessModifierOptions.isLegacyAccessModifierEnabled()) {
- timing.begin("Access modification");
- new LegacyAccessModifier(appView).internalRun(executorService, timing);
- timing.end();
- if (appView.graphLens().isPublicizerLens()) {
- // We can now remove redundant bridges. Note that we do not need to update the
- // invoke-targets here, as the existing invokes will simply dispatch to the now
- // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
- new RedundantBridgeRemover(appView.withLiveness()).run(executorService, timing);
- }
- }
- }
-
- private void internalRun(ExecutorService executorService, Timing timing)
- throws ExecutionException {
- // Phase 1: Collect methods to check if private instance methods don't have conflicts.
- methodPoolCollection.buildAll(executorService, timing);
-
- // Phase 2: Visit classes and promote class/member to public if possible.
- timing.begin("Phase 2: promoteToPublic");
- appView.appInfo().forEachReachableInterface(clazz -> processType(clazz.getType()));
- processType(appView.dexItemFactory().objectType);
- timing.end();
-
- PublicizerLens publicizerLens = lensBuilder.build(appView);
- if (publicizerLens != null) {
- appView.setGraphLens(publicizerLens);
- }
-
- appView.notifyOptimizationFinishedForTesting();
- }
-
- private void doPublicize(ProgramDefinition definition) {
- definition.getAccessFlags().promoteToPublic();
- }
-
- private void processType(DexType type) {
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
- if (clazz != null) {
- processClass(clazz);
- }
- subtypingInfo.forAllImmediateExtendsSubtypes(type, this::processType);
- }
-
- private void processClass(DexProgramClass clazz) {
- if (appView.appInfo().isAccessModificationAllowed(clazz)) {
- doPublicize(clazz);
- }
-
- // Publicize fields.
- clazz.forEachProgramField(this::processField);
-
- // Publicize methods.
- Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>();
- clazz.forEachProgramMethod(
- method -> {
- if (publicizeMethod(method)) {
- privateInstanceMethods.add(method.getDefinition());
- }
- });
- if (!privateInstanceMethods.isEmpty()) {
- clazz.virtualizeMethods(privateInstanceMethods);
- }
-
- // Publicize inner class attribute.
- InnerClassAttribute attr = clazz.getInnerClassAttributeForThisClass();
- if (attr != null) {
- int accessFlags = ((attr.getAccess() | ACC_PUBLIC) & ~ACC_PRIVATE) & ~ACC_PROTECTED;
- clazz.replaceInnerClassAttributeForThisClass(
- new InnerClassAttribute(
- accessFlags, attr.getInner(), attr.getOuter(), attr.getInnerName()));
- }
- }
-
- private void processField(ProgramField field) {
- if (appView.appInfo().isAccessModificationAllowed(field)) {
- publicizeField(field);
- }
- }
-
- private void publicizeField(ProgramField field) {
- FieldAccessFlags flags = field.getAccessFlags();
- if (!flags.isPublic()) {
- flags.promoteToPublic();
- }
- }
-
- private boolean publicizeMethod(ProgramMethod method) {
- MethodAccessFlags accessFlags = method.getAccessFlags();
- if (accessFlags.isPublic()) {
- return false;
- }
- // If this method is mentioned in keep rules, do not transform (rule applications changed).
- DexEncodedMethod definition = method.getDefinition();
- if (!appView.appInfo().isAccessModificationAllowed(method)) {
- // TODO(b/131130038): Also do not publicize package-private and protected methods that are
- // kept.
- if (definition.isPrivate()) {
- return false;
- }
- }
-
- if (method.getDefinition().isInstanceInitializer() || accessFlags.isProtected()) {
- doPublicize(method);
- return false;
- }
-
- if (accessFlags.isPackagePrivate()) {
- // If we publicize a package private method we have to ensure there is no overrides of it. We
- // could potentially publicize a method if it only has package-private overrides.
- // TODO(b/182136236): See if we can break the hierarchy for clusters.
- MemberPool<DexMethod> memberPool = methodPoolCollection.get(method.getHolder());
- Wrapper<DexMethod> methodKey = MethodSignatureEquivalence.get().wrap(method.getReference());
- if (memberPool.below(
- methodKey,
- false,
- true,
- (clazz, ignored) ->
- !method.getContextType().getPackageName().equals(clazz.getType().getPackageName()))) {
- return false;
- }
- doPublicize(method);
- return false;
- }
-
- assert accessFlags.isPrivate();
-
- if (accessFlags.isStatic()) {
- // For private static methods we can just relax the access to public, since
- // even though JLS prevents from declaring static method in derived class if
- // an instance method with same signature exists in superclass, JVM actually
- // does not take into account access of the static methods.
- doPublicize(method);
- return false;
- }
-
- // We can't publicize private instance methods in interfaces or methods that are copied from
- // interfaces to lambda-desugared classes because this will be added as a new default method.
- // TODO(b/111118390): It might be possible to transform it into static methods, though.
- if (method.getHolder().isInterface() || accessFlags.isSynthetic()) {
- return false;
- }
-
- boolean wasSeen = methodPoolCollection.markIfNotSeen(method.getHolder(), method.getReference());
- if (wasSeen) {
- // We can't do anything further because even renaming is not allowed due to the keep rule.
- if (!appView.appInfo().isMinificationAllowed(method)) {
- return false;
- }
- // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
- return false;
- }
- lensBuilder.add(method.getReference());
- accessFlags.promoteToFinal();
- doPublicize(method);
- // The method just became public and is therefore not a library override.
- definition.setLibraryMethodOverride(OptionalBool.FALSE);
- return true;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index d8514a0..388638c 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -390,8 +390,12 @@
appView.computedMinApiLevel()));
}
builder.setIsLibraryMethodOverrideIf(
- target.isLibraryMethod(), OptionalBool.TRUE);
+ // Treat classpath override as library override.
+ target.isLibraryMethod() || target.isClasspathMethod(),
+ OptionalBool.TRUE);
});
+ assert !bridgeMethodDefinition.belongsToVirtualPool()
+ || !bridgeMethodDefinition.isLibraryMethodOverride().isUnknown();
bridgeHolder.addMethod(bridgeMethodDefinition);
eventConsumer.acceptMemberRebindingBridgeMethod(
bridgeMethodDefinition.asProgramMethod(bridgeHolder), target);
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index dfd16e6..331d29e 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -7,17 +7,16 @@
import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
import com.android.tools.r8.graph.MethodAccessInfoCollection;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.ThreadUtils;
@@ -101,7 +100,8 @@
executorService);
}
- private static class NonReboundMemberReferencesRegistry extends UseRegistry<ProgramMethod> {
+ private static class NonReboundMemberReferencesRegistry
+ extends DefaultUseRegistry<ProgramMethod> {
private final AppInfoWithClassHierarchy appInfo;
private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
@@ -232,25 +232,5 @@
// simply use the empty set.
invokes.put(method, ProgramMethodSet.empty());
}
-
- @Override
- public void registerInitClass(DexType type) {
- // Intentionally empty.
- }
-
- @Override
- public void registerNewInstance(DexType type) {
- // Intentionally empty.
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- // Intentionally empty.
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java
index 64570f5..086b81f 100644
--- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java
+++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java
@@ -62,8 +62,7 @@
throws ExecutionException {
timing.begin("Access modification");
AccessModifierOptions accessModifierOptions = appView.options().getAccessModifierOptions();
- if (accessModifierOptions.isAccessModificationEnabled()
- && !accessModifierOptions.isLegacyAccessModifierEnabled()) {
+ if (accessModifierOptions.isAccessModificationEnabled()) {
new AccessModifier(appView)
.processStronglyConnectedComponents(executorService)
.installLens(executorService, timing);
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java
index 81c1f03..6891352 100644
--- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java
+++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java
@@ -5,18 +5,13 @@
package com.android.tools.r8.optimize.accessmodification;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.SystemPropertyUtils;
public class AccessModifierOptions {
- private boolean enableLegacyAccessModifier =
- SystemPropertyUtils.parseSystemPropertyOrDefault(
- "com.android.tools.r8.accessmodification.legacy", false);
-
// TODO(b/131130038): Do not allow accessmodification when kept.
private boolean forceModifyPackagePrivateAndProtectedMethods = true;
- private InternalOptions options;
+ private final InternalOptions options;
public AccessModifierOptions(InternalOptions options) {
this.options = options;
@@ -30,9 +25,6 @@
if (isAccessModificationRulePresent()) {
return true;
}
- if (isLegacyAccessModifierEnabled()) {
- return false;
- }
// TODO(b/288062771): Enable access modification by default for L8.
return options.synthesizedClassPrefix.isEmpty()
&& !options.forceProguardCompatibility
@@ -53,8 +45,4 @@
this.forceModifyPackagePrivateAndProtectedMethods =
forceModifyPackagePrivateAndProtectedMethods;
}
-
- public boolean isLegacyAccessModifierEnabled() {
- return enableLegacyAccessModifier;
- }
}
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 2f90ff5..97ead9d 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
@@ -9,6 +9,7 @@
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.ir.code.AbstractValueSupplier;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -112,7 +113,9 @@
ProgramMethod method, IRCode code, MethodProcessor methodProcessor, Timing timing) {
if (codeScanner != null) {
assert methodProcessor.isPrimaryMethodProcessor();
- codeScanner.scan(method, code, timing);
+ AbstractValueSupplier abstractValueSupplier =
+ value -> value.getAbstractValue(appView, method);
+ codeScanner.scan(method, code, abstractValueSupplier, timing);
assert effectivelyUnusedArgumentsAnalysis != null;
effectivelyUnusedArgumentsAnalysis.scan(method, code);
@@ -226,14 +229,16 @@
postMethodProcessorBuilder.rewrittenWithLens(appView);
timing.begin("Compute optimization info");
- new ArgumentPropagatorOptimizationInfoPopulator(
+ new ArgumentPropagatorOptimizationInfoPropagator(
appView,
converter,
immediateSubtypingInfo,
codeScannerResult,
- postMethodProcessorBuilder,
stronglyConnectedProgramComponents,
interfaceDispatchOutsideProgram)
+ .propagateOptimizationInfo(executorService, timing);
+ new ArgumentPropagatorOptimizationInfoPopulator(
+ appView, converter, codeScannerResult, 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 49d6400..0dc55d3 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
@@ -17,6 +17,7 @@
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.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.IRCode;
@@ -89,6 +90,10 @@
private final MethodStateCollectionByReference methodStates =
MethodStateCollectionByReference.createConcurrent();
+ public ArgumentPropagatorCodeScanner(AppView<AppInfoWithLiveness> appView) {
+ this(appView, new ArgumentPropagatorReprocessingCriteriaCollection(appView));
+ }
+
ArgumentPropagatorCodeScanner(
AppView<AppInfoWithLiveness> appView,
ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection) {
@@ -105,7 +110,7 @@
virtualRootMethods.putAll(extension);
}
- MethodStateCollectionByReference getMethodStates() {
+ public MethodStateCollectionByReference getMethodStates() {
return methodStates;
}
@@ -113,7 +118,8 @@
return virtualRootMethods.get(method.getReference());
}
- boolean isMethodParameterAlreadyUnknown(MethodParameter methodParameter, ProgramMethod method) {
+ protected boolean isMethodParameterAlreadyUnknown(
+ MethodParameter methodParameter, ProgramMethod method) {
MethodState methodState =
methodStates.get(
method.getDefinition().belongsToDirectPool() || isMonomorphicVirtualMethod(method)
@@ -141,19 +147,27 @@
return monomorphicVirtualMethods.contains(method);
}
- void scan(ProgramMethod method, IRCode code, Timing timing) {
+ public void scan(
+ ProgramMethod method,
+ IRCode code,
+ AbstractValueSupplier abstractValueSupplier,
+ Timing timing) {
timing.begin("Argument propagation scanner");
for (Invoke invoke : code.<Invoke>instructions(Instruction::isInvoke)) {
if (invoke.isInvokeMethod()) {
- scan(invoke.asInvokeMethod(), method, timing);
+ scan(invoke.asInvokeMethod(), abstractValueSupplier, method, timing);
} else if (invoke.isInvokeCustom()) {
- scan(invoke.asInvokeCustom(), method);
+ scan(invoke.asInvokeCustom());
}
}
timing.end();
}
- private void scan(InvokeMethod invoke, ProgramMethod context, Timing timing) {
+ private void scan(
+ InvokeMethod invoke,
+ AbstractValueSupplier abstractValueSupplier,
+ ProgramMethod context,
+ Timing timing) {
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invokedMethod.getHolderType().isArrayType()) {
// Nothing to propagate; the targeted method is not a program method.
@@ -231,13 +245,27 @@
// possible dispatch targets and propagate the information to these methods (this is expensive).
// Instead we record the information in one place and then later propagate the information to
// all dispatch targets.
- ProgramMethod finalResolvedMethod = resolvedMethod;
+ addTemporaryMethodState(invoke, resolvedMethod, abstractValueSupplier, context, timing);
+ }
+
+ protected void addTemporaryMethodState(
+ InvokeMethod invoke,
+ ProgramMethod resolvedMethod,
+ AbstractValueSupplier abstractValueSupplier,
+ ProgramMethod context,
+ Timing timing) {
timing.begin("Add method state");
methodStates.addTemporaryMethodState(
appView,
getRepresentative(invoke, resolvedMethod),
existingMethodState ->
- computeMethodState(invoke, finalResolvedMethod, context, existingMethodState, timing),
+ computeMethodState(
+ invoke,
+ resolvedMethod,
+ abstractValueSupplier,
+ context,
+ existingMethodState,
+ timing),
timing);
timing.end();
}
@@ -245,6 +273,7 @@
private MethodState computeMethodState(
InvokeMethod invoke,
ProgramMethod resolvedMethod,
+ AbstractValueSupplier abstractValueSupplier,
ProgramMethod context,
MethodState existingMethodState,
Timing timing) {
@@ -262,6 +291,7 @@
computePolymorphicMethodState(
invoke.asInvokeMethodWithReceiver(),
resolvedMethod,
+ abstractValueSupplier,
context,
existingMethodState.asPolymorphicOrBottom());
} else {
@@ -271,6 +301,7 @@
invoke,
resolvedMethod,
invoke.lookupSingleProgramTarget(appView, context),
+ abstractValueSupplier,
context,
existingMethodState.asMonomorphicOrBottom());
}
@@ -285,6 +316,7 @@
private MethodState computePolymorphicMethodState(
InvokeMethodWithReceiver invoke,
ProgramMethod resolvedMethod,
+ AbstractValueSupplier abstractValueSupplier,
ProgramMethod context,
ConcretePolymorphicMethodStateOrBottom existingMethodState) {
DynamicTypeWithUpperBound dynamicReceiverType = invoke.getReceiver().getDynamicType(appView);
@@ -320,6 +352,7 @@
invoke,
resolvedMethod,
singleTarget,
+ abstractValueSupplier,
context,
existingMethodStateForBounds.asMonomorphicOrBottom(),
dynamicReceiverType);
@@ -363,12 +396,14 @@
InvokeMethod invoke,
ProgramMethod resolvedMethod,
ProgramMethod singleTarget,
+ AbstractValueSupplier abstractValueSupplier,
ProgramMethod context,
ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
return computeMonomorphicMethodState(
invoke,
resolvedMethod,
singleTarget,
+ abstractValueSupplier,
context,
existingMethodState,
invoke.isInvokeMethodWithReceiver()
@@ -381,6 +416,7 @@
InvokeMethod invoke,
ProgramMethod resolvedMethod,
ProgramMethod singleTarget,
+ AbstractValueSupplier abstractValueSupplier,
ProgramMethod context,
ConcreteMonomorphicMethodStateOrBottom existingMethodState,
DynamicType dynamicReceiverType) {
@@ -410,6 +446,7 @@
singleTarget,
argumentIndex,
invoke.getArgument(argumentIndex),
+ abstractValueSupplier,
context,
existingMethodState));
}
@@ -454,11 +491,12 @@
ProgramMethod singleTarget,
int argumentIndex,
Value argument,
+ AbstractValueSupplier abstractValueSupplier,
ProgramMethod context,
ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
ParameterState modeledState =
modeling.modelParameterStateForArgumentToFunction(
- invoke, singleTarget, argumentIndex, argument);
+ invoke, singleTarget, argumentIndex, argument, context);
if (modeledState != null) {
return modeledState;
}
@@ -504,7 +542,7 @@
: new ConcreteArrayTypeParameterState(nullability);
}
- AbstractValue abstractValue = argument.getAbstractValue(appView, context);
+ 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.
@@ -555,8 +593,7 @@
&& !isMonomorphicVirtualMethod(getRepresentative(invoke, resolvedMethod));
}
- @SuppressWarnings("UnusedVariable")
- private void scan(InvokeCustom invoke, ProgramMethod context) {
+ private void scan(InvokeCustom invoke) {
// If the bootstrap method is program declared it will be called. The call is with runtime
// provided arguments so ensure that the argument information is unknown.
DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
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 7ab506e..7931180 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
@@ -23,10 +23,14 @@
}
ParameterState modelParameterStateForArgumentToFunction(
- InvokeMethod invoke, ProgramMethod singleTarget, int argumentIndex, Value argument) {
+ InvokeMethod invoke,
+ ProgramMethod singleTarget,
+ int argumentIndex,
+ Value argument,
+ ProgramMethod context) {
if (composeModeling != null) {
return composeModeling.modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
- invoke, singleTarget, argumentIndex, argument);
+ invoke, singleTarget, argumentIndex, argument, context);
}
return 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 9b0f212..b857d7d 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,16 +5,15 @@
package com.android.tools.r8.optimize.argumentpropagation;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexEncodedMethod;
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.DexType;
import com.android.tools.r8.graph.FieldResolutionResult;
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.UseRegistryWithResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
@@ -145,7 +144,8 @@
postMethodProcessorBuilder.addAll(methodsToReprocessInClass, currentGraphLens));
}
- static class AffectedMethodUseRegistry extends UseRegistryWithResult<Boolean, ProgramMethod> {
+ static class AffectedMethodUseRegistry
+ extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
private final AppView<AppInfoWithLiveness> appViewWithLiveness;
private final ArgumentPropagatorGraphLens graphLens;
@@ -242,11 +242,5 @@
markAffected();
}
}
-
- @Override
- public void registerInitClass(DexType type) {}
-
- @Override
- public void registerTypeReference(DexType type) {}
}
}
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 c626347..ea9d69a 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
@@ -7,10 +7,8 @@
import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
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;
@@ -28,9 +26,6 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.StateCloner;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.InParameterFlowPropagator;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.VirtualDispatchMethodArgumentPropagator;
import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -40,10 +35,8 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
/**
* Propagates the argument flow information collected by the {@link ArgumentPropagatorCodeScanner}.
@@ -58,28 +51,16 @@
private final InternalOptions options;
private final PostMethodProcessor.Builder postMethodProcessorBuilder;
- private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
- private final List<Set<DexProgramClass>> stronglyConnectedProgramComponents;
-
- private final BiConsumer<Set<DexProgramClass>, DexMethodSignature>
- interfaceDispatchOutsideProgram;
-
- ArgumentPropagatorOptimizationInfoPopulator(
+ public ArgumentPropagatorOptimizationInfoPopulator(
AppView<AppInfoWithLiveness> appView,
PrimaryR8IRConverter converter,
- ImmediateProgramSubtypingInfo immediateSubtypingInfo,
MethodStateCollectionByReference methodStates,
- PostMethodProcessor.Builder postMethodProcessorBuilder,
- List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
- BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram) {
+ PostMethodProcessor.Builder postMethodProcessorBuilder) {
this.appView = appView;
this.converter = converter;
- this.immediateSubtypingInfo = immediateSubtypingInfo;
this.methodStates = methodStates;
this.options = appView.options();
this.postMethodProcessorBuilder = postMethodProcessorBuilder;
- this.stronglyConnectedProgramComponents = stronglyConnectedProgramComponents;
- this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
}
/**
@@ -88,24 +69,6 @@
*/
void populateOptimizationInfo(ExecutorService executorService, Timing timing)
throws ExecutionException {
- // TODO(b/190154391): Propagate argument information to handle virtual dispatch.
- // TODO(b/190154391): To deal with arguments that are themselves passed as arguments to invoke
- // instructions, build a flow graph where nodes are parameters and there is an edge from a
- // parameter p1 to p2 if the value of p2 is at least the value of p1. Then propagate the
- // collected argument information throughout the flow graph.
- timing.begin("Propagate argument information for virtual methods");
- ThreadUtils.processItems(
- stronglyConnectedProgramComponents,
- this::processStronglyConnectedComponent,
- appView.options().getThreadingModule(),
- executorService);
- timing.end();
-
- // Solve the parameter flow constraints.
- timing.begin("Solve flow constraints");
- new InParameterFlowPropagator(appView, converter, methodStates).run(executorService);
- timing.end();
-
// The information stored on each method is now sound, and can be used as optimization info.
timing.begin("Set optimization info");
setOptimizationInfo(executorService);
@@ -114,41 +77,6 @@
assert methodStates.isEmpty();
}
- private void processStronglyConnectedComponent(Set<DexProgramClass> stronglyConnectedComponent) {
- // Invoke instructions that target interface methods may dispatch to methods that are not
- // defined on a subclass of the interface method holder.
- //
- // Example: Calling I.m() will dispatch to A.m(), but A is not a subtype of I.
- //
- // class A { public void m() {} }
- // interface I { void m(); }
- // class B extends A implements I {}
- //
- // To handle this we first propagate any argument information stored for I.m() to A.m() by doing
- // a top-down traversal over the interfaces in the strongly connected component.
- new InterfaceMethodArgumentPropagator(
- appView,
- immediateSubtypingInfo,
- methodStates,
- signature ->
- interfaceDispatchOutsideProgram.accept(stronglyConnectedComponent, signature))
- .run(stronglyConnectedComponent);
-
- // Now all the argument information for a given method is guaranteed to be stored on a supertype
- // of the method's holder. All that remains is to propagate the information downwards in the
- // class hierarchy to propagate the argument information for a non-private virtual method to its
- // overrides.
- // TODO(b/190154391): Before running the top-down traversal, consider lowering the argument
- // information for non-private virtual methods. If we have some argument information with upper
- // bound=B, which is stored on a method on class A, we could move this argument information
- // from class A to B. This way we could potentially get rid of the "inactive argument
- // information" during the depth-first class hierarchy traversal, since the argument
- // information would be active by construction when it is first seen during the top-down class
- // hierarchy traversal.
- new VirtualDispatchMethodArgumentPropagator(appView, immediateSubtypingInfo, methodStates)
- .run(stronglyConnectedComponent);
- }
-
private void setOptimizationInfo(ExecutorService executorService) throws ExecutionException {
ProgramMethodSet prunedMethods = ProgramMethodSet.createConcurrent();
ThreadUtils.processItems(
@@ -170,8 +98,12 @@
return prunedMethods;
}
- private void setOptimizationInfo(ProgramMethod method, ProgramMethodSet prunedMethods) {
- MethodState methodState = methodStates.remove(method);
+ public void setOptimizationInfo(ProgramMethod method, ProgramMethodSet prunedMethods) {
+ setOptimizationInfo(method, prunedMethods, methodStates.remove(method));
+ }
+
+ public void setOptimizationInfo(
+ ProgramMethod method, ProgramMethodSet prunedMethods, MethodState methodState) {
if (methodState.isBottom()) {
if (method.getDefinition().isClassInitializer()) {
return;
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
new file mode 100644
index 0000000..2c8f5e4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2023, 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.DexMethodSignature;
+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.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.InParameterFlowPropagator;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.VirtualDispatchMethodArgumentPropagator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
+
+/**
+ * Propagates the argument flow information collected by the {@link ArgumentPropagatorCodeScanner}.
+ * This is needed to propagate argument information from call sites to all possible dispatch
+ * targets.
+ */
+public class ArgumentPropagatorOptimizationInfoPropagator {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final PrimaryR8IRConverter converter;
+ private final MethodStateCollectionByReference methodStates;
+
+ private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+ private final List<Set<DexProgramClass>> stronglyConnectedProgramComponents;
+
+ private final BiConsumer<Set<DexProgramClass>, DexMethodSignature>
+ interfaceDispatchOutsideProgram;
+
+ ArgumentPropagatorOptimizationInfoPropagator(
+ AppView<AppInfoWithLiveness> appView,
+ PrimaryR8IRConverter converter,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+ MethodStateCollectionByReference methodStates,
+ List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
+ BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram) {
+ this.appView = appView;
+ this.converter = converter;
+ this.immediateSubtypingInfo = immediateSubtypingInfo;
+ this.methodStates = methodStates;
+ this.stronglyConnectedProgramComponents = stronglyConnectedProgramComponents;
+ this.interfaceDispatchOutsideProgram = interfaceDispatchOutsideProgram;
+ }
+
+ /** Computes an over-approximation of each parameter's value and type. */
+ void propagateOptimizationInfo(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ timing.begin("Propagate argument information for virtual methods");
+ ThreadUtils.processItems(
+ stronglyConnectedProgramComponents,
+ this::processStronglyConnectedComponent,
+ appView.options().getThreadingModule(),
+ executorService);
+ timing.end();
+
+ // Solve the parameter flow constraints.
+ timing.begin("Solve flow constraints");
+ new InParameterFlowPropagator(appView, converter, methodStates).run(executorService);
+ timing.end();
+ }
+
+ private void processStronglyConnectedComponent(Set<DexProgramClass> stronglyConnectedComponent) {
+ // Invoke instructions that target interface methods may dispatch to methods that are not
+ // defined on a subclass of the interface method holder.
+ //
+ // Example: Calling I.m() will dispatch to A.m(), but A is not a subtype of I.
+ //
+ // class A { public void m() {} }
+ // interface I { void m(); }
+ // class B extends A implements I {}
+ //
+ // To handle this we first propagate any argument information stored for I.m() to A.m() by doing
+ // a top-down traversal over the interfaces in the strongly connected component.
+ new InterfaceMethodArgumentPropagator(
+ appView,
+ immediateSubtypingInfo,
+ methodStates,
+ signature ->
+ interfaceDispatchOutsideProgram.accept(stronglyConnectedComponent, signature))
+ .run(stronglyConnectedComponent);
+
+ // Now all the argument information for a given method is guaranteed to be stored on a supertype
+ // of the method's holder. All that remains is to propagate the information downwards in the
+ // class hierarchy to propagate the argument information for a non-private virtual method to its
+ // overrides.
+ // TODO(b/190154391): Before running the top-down traversal, consider lowering the argument
+ // information for non-private virtual methods. If we have some argument information with upper
+ // bound=B, which is stored on a method on class A, we could move this argument information
+ // from class A to B. This way we could potentially get rid of the "inactive argument
+ // information" during the depth-first class hierarchy traversal, since the argument
+ // information would be active by construction when it is first seen during the top-down class
+ // hierarchy traversal.
+ new VirtualDispatchMethodArgumentPropagator(appView, immediateSubtypingInfo, methodStates)
+ .run(stronglyConnectedComponent);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index 387d67d..5a8e0de 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -99,17 +99,22 @@
// Record the invokes from the newly synthesized bridge methods in the method access info
// collection.
- MethodAccessInfoCollection.Modifier methodAccessInfoCollectionModifier =
- appView.appInfo().getMethodAccessInfoCollection().modifier();
- result.forEachHoistedBridge(
- (bridge, bridgeInfo) -> {
- if (bridgeInfo.isVirtualBridgeInfo()) {
- DexMethod reference = bridgeInfo.asVirtualBridgeInfo().getInvokedMethod();
- methodAccessInfoCollectionModifier.registerInvokeVirtualInContext(reference, bridge);
- } else {
- assert false;
- }
- });
+ MethodAccessInfoCollection methodAccessInfoCollection =
+ appView.appInfo().getMethodAccessInfoCollection();
+ if (!methodAccessInfoCollection.isVirtualInvokesDestroyed()) {
+ MethodAccessInfoCollection.Modifier methodAccessInfoCollectionModifier =
+ methodAccessInfoCollection.modifier();
+ result.forEachHoistedBridge(
+ (bridge, bridgeInfo) -> {
+ if (bridgeInfo.isVirtualBridgeInfo()) {
+ DexMethod reference = bridgeInfo.asVirtualBridgeInfo().getInvokedMethod();
+ methodAccessInfoCollectionModifier.registerInvokeVirtualInContext(
+ reference, bridge);
+ } else {
+ assert false;
+ }
+ });
+ }
}
appView.notifyOptimizationFinishedForTesting();
@@ -257,7 +262,7 @@
// Now update the code of the bridge method chosen as representative.
representative
.setCode(createCodeForVirtualBridge(representative, methodToInvoke), appView);
- feedback.setBridgeInfo(representative.getDefinition(), new VirtualBridgeInfo(methodToInvoke));
+ feedback.setBridgeInfo(representative, new VirtualBridgeInfo(methodToInvoke));
// Move the bridge method to the super class, and record this in the graph lens.
DexMethod newMethodReference =
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
new file mode 100644
index 0000000..5d0b693
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2023, 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.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.AbstractValueSupplier;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorCodeScanner;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
+
+public class ArgumentPropagatorCodeScannerForComposableFunctions
+ extends ArgumentPropagatorCodeScanner {
+
+ private final ComposableCallGraph callGraph;
+
+ public ArgumentPropagatorCodeScannerForComposableFunctions(
+ AppView<AppInfoWithLiveness> appView, ComposableCallGraph callGraph) {
+ super(appView);
+ this.callGraph = callGraph;
+ }
+
+ @Override
+ protected void addTemporaryMethodState(
+ InvokeMethod invoke,
+ ProgramMethod resolvedMethod,
+ AbstractValueSupplier abstractValueSupplier,
+ ProgramMethod context,
+ Timing timing) {
+ ComposableCallGraphNode node = callGraph.getNodes().get(resolvedMethod);
+ if (node != null && node.isComposable()) {
+ super.addTemporaryMethodState(invoke, resolvedMethod, abstractValueSupplier, context, timing);
+ }
+ }
+
+ @Override
+ protected boolean isMethodParameterAlreadyUnknown(
+ MethodParameter methodParameter, ProgramMethod method) {
+ // We haven't defined the virtual root mapping, so we can't tell.
+ return false;
+ }
+}
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 49cd7d5..6ffeb6d 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,8 +4,10 @@
package com.android.tools.r8.optimize.compose;
import com.android.tools.r8.graph.AppView;
+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.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.code.InstanceGet;
@@ -19,16 +21,31 @@
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;
public class ArgumentPropagatorComposeModeling {
private final AppView<AppInfoWithLiveness> appView;
- private final ComposeReferences composeReferences;
+ private final ComposeReferences rewrittenComposeReferences;
+
+ private final DexType rewrittenFunction2Type;
+ private final DexString invokeName;
public ArgumentPropagatorComposeModeling(AppView<AppInfoWithLiveness> appView) {
assert appView.testing().modelUnknownChangedAndDefaultArgumentsToComposableFunctions;
this.appView = appView;
- this.composeReferences = appView.getComposeReferences();
+ this.rewrittenComposeReferences =
+ appView
+ .getComposeReferences()
+ .rewrittenWithLens(appView.graphLens(), GraphLens.getIdentityLens());
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.rewrittenFunction2Type =
+ appView
+ .graphLens()
+ .lookupType(
+ dexItemFactory.createType("Lkotlin/jvm/functions/Function2;"),
+ GraphLens.getIdentityLens());
+ this.invokeName = dexItemFactory.createString("invoke");
}
/**
@@ -68,13 +85,28 @@
* </pre>
*/
public ParameterState modelParameterStateForChangedOrDefaultArgumentToComposableFunction(
- InvokeMethod invoke, ProgramMethod singleTarget, int argumentIndex, Value argument) {
+ InvokeMethod invoke,
+ ProgramMethod singleTarget,
+ int argumentIndex,
+ Value argument,
+ ProgramMethod context) {
+ // TODO(b/302483644): Add some robust way of detecting restart lambda contexts.
+ if (!context.getHolder().getInterfaces().contains(rewrittenFunction2Type)
+ || !invoke.getPosition().getOutermostCaller().getMethod().getName().isEqualTo(invokeName)
+ || Iterables.isEmpty(
+ context
+ .getHolder()
+ .instanceFields(
+ f -> f.getName().isIdenticalTo(rewrittenComposeReferences.changedFieldName)))) {
+ return null;
+ }
+
// First check if this is an invoke to a @Composable function.
if (singleTarget == null
|| !singleTarget
.getDefinition()
.annotations()
- .hasAnnotation(composeReferences.composableType)) {
+ .hasAnnotation(rewrittenComposeReferences.composableType)) {
return null;
}
@@ -103,7 +135,7 @@
invokedMethod.getArity() - 2 - BooleanUtils.intValue(hasDefaultParameter);
if (!invokedMethod
.getParameter(composerParameterIndex)
- .isIdenticalTo(composeReferences.composerType)) {
+ .isIdenticalTo(rewrittenComposeReferences.composerType)) {
return null;
}
@@ -122,13 +154,9 @@
// 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 =
- appView
- .graphLens()
- .getOriginalMethodSignature(
- invokeStatic.getInvokedMethod(), GraphLens.getIdentityLens());
+ DexMethod maybeUpdateChangedFlagsMethod = invokeStatic.getInvokedMethod();
if (!maybeUpdateChangedFlagsMethod.isIdenticalTo(
- composeReferences.updatedChangedFlagsMethod)) {
+ rewrittenComposeReferences.updatedChangedFlagsMethod)) {
return null;
}
// Assume the call does not impact the $$changed capture and strip the call.
@@ -154,10 +182,10 @@
.createDefiniteBitsNumberValue(
BitUtils.ALL_BITS_SET_MASK, BitUtils.ALL_BITS_SET_MASK << 1));
}
- expectedFieldName = composeReferences.changedFieldName;
+ expectedFieldName = rewrittenComposeReferences.changedFieldName;
} else {
// We are looking at an argument to the $$default parameter of the @Composable function.
- expectedFieldName = composeReferences.defaultFieldName;
+ expectedFieldName = rewrittenComposeReferences.defaultFieldName;
}
// At this point we expect that the restart lambda is reading either this.$$changed or
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java
new file mode 100644
index 0000000..0cbf138
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java
@@ -0,0 +1,170 @@
+// Copyright (c) 2023, 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.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import java.util.function.Consumer;
+
+/**
+ * A partial call graph that stores call edges to @Composable functions. By processing all the call
+ * sites of a given @Composable function we can reapply arguent propagation for the @Composable
+ * function.
+ */
+public class ComposableCallGraph {
+
+ private final ProgramMethodMap<ComposableCallGraphNode> nodes;
+
+ public ComposableCallGraph(ProgramMethodMap<ComposableCallGraphNode> nodes) {
+ this.nodes = nodes;
+ }
+
+ public static Builder builder(AppView<AppInfoWithLiveness> appView) {
+ return new Builder(appView);
+ }
+
+ public static ComposableCallGraph empty() {
+ return new ComposableCallGraph(ProgramMethodMap.empty());
+ }
+
+ public void forEachNode(Consumer<ComposableCallGraphNode> consumer) {
+ nodes.forEachValue(consumer);
+ }
+
+ public ProgramMethodMap<ComposableCallGraphNode> getNodes() {
+ return nodes;
+ }
+
+ public boolean isEmpty() {
+ return nodes.isEmpty();
+ }
+
+ public static class Builder {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final ProgramMethodMap<ComposableCallGraphNode> nodes = ProgramMethodMap.create();
+
+ Builder(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public ComposableCallGraph build() {
+ createCallGraphNodesForComposableFunctions();
+ if (!nodes.isEmpty()) {
+ addCallEdgesToComposableFunctions();
+ }
+ return new ComposableCallGraph(nodes);
+ }
+
+ private void createCallGraphNodesForComposableFunctions() {
+ ComposeReferences rewrittenComposeReferences =
+ appView
+ .getComposeReferences()
+ .rewrittenWithLens(appView.graphLens(), GraphLens.getIdentityLens());
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.forEachProgramDirectMethodMatching(
+ method -> method.annotations().hasAnnotation(rewrittenComposeReferences.composableType),
+ method -> {
+ // TODO(b/302483644): Don't include kept @Composable functions, since we can't
+ // optimize them anyway.
+ assert method.getAccessFlags().isStatic();
+ nodes.put(method, new ComposableCallGraphNode(method, true));
+ });
+ }
+ }
+
+ // TODO(b/302483644): Parallelize identification of @Composable call sites.
+ private void addCallEdgesToComposableFunctions() {
+ // Code is fully rewritten so no need to lens rewrite in registry.
+ assert appView.codeLens() == appView.graphLens();
+
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.forEachProgramMethodMatching(
+ DexEncodedMethod::hasCode,
+ method -> {
+ Code code = method.getDefinition().getCode();
+
+ // TODO(b/302483644): Leverage LIR code constant pool for efficient checking.
+ // TODO(b/302483644): Maybe remove the possibility of CF/DEX at this point.
+ assert code.isLirCode()
+ || code.isCfCode()
+ || code.isDexCode()
+ || code.isDefaultInstanceInitializerCode()
+ || code.isThrowNullCode();
+
+ code.registerCodeReferences(
+ method,
+ new UseRegistry<>(appView, method) {
+
+ private final AppView<AppInfoWithLiveness> appViewWithLiveness =
+ appView.withLiveness();
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ ProgramMethod resolvedMethod =
+ appViewWithLiveness
+ .appInfo()
+ .unsafeResolveMethodDueToDexFormat(method)
+ .getResolvedProgramMethod();
+ if (resolvedMethod == null) {
+ return;
+ }
+
+ ComposableCallGraphNode callee = nodes.get(resolvedMethod);
+ if (callee == null || !callee.isComposable()) {
+ // Only record calls to Composable functions.
+ return;
+ }
+
+ ComposableCallGraphNode caller =
+ nodes.computeIfAbsent(
+ getContext(), context -> new ComposableCallGraphNode(context, false));
+ callee.addCaller(caller);
+ }
+
+ @Override
+ public void registerInitClass(DexType type) {}
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {}
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {}
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {}
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {}
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {}
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {}
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {}
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {}
+
+ @Override
+ public void registerTypeReference(DexType type) {}
+ });
+ });
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraphNode.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraphNode.java
new file mode 100644
index 0000000..13ec14db
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraphNode.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2023, 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.graph.ProgramMethod;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class ComposableCallGraphNode {
+
+ private final ProgramMethod method;
+ private final boolean isComposable;
+
+ private final Set<ComposableCallGraphNode> callers = SetUtils.newIdentityHashSet();
+ private final Set<ComposableCallGraphNode> callees = SetUtils.newIdentityHashSet();
+
+ ComposableCallGraphNode(ProgramMethod method, boolean isComposable) {
+ this.method = method;
+ this.isComposable = isComposable;
+ }
+
+ public void addCaller(ComposableCallGraphNode caller) {
+ callers.add(caller);
+ caller.callees.add(this);
+ }
+
+ public void forEachComposableCallee(Consumer<ComposableCallGraphNode> consumer) {
+ for (ComposableCallGraphNode callee : callees) {
+ if (callee.isComposable()) {
+ consumer.accept(callee);
+ }
+ }
+ }
+
+ public Set<ComposableCallGraphNode> getCallers() {
+ return callers;
+ }
+
+ public ProgramMethod getMethod() {
+ return method;
+ }
+
+ public boolean isComposable() {
+ return isComposable;
+ }
+
+ @Override
+ public String toString() {
+ return method.toString();
+ }
+}
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
new file mode 100644
index 0000000..244dbb4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposableOptimizationPass.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2023, 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.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;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ComposableOptimizationPass {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final PrimaryR8IRConverter converter;
+
+ private ComposableOptimizationPass(
+ AppView<AppInfoWithLiveness> appView, PrimaryR8IRConverter converter) {
+ this.appView = appView;
+ this.converter = converter;
+ }
+
+ public static void run(
+ AppView<AppInfoWithLiveness> appView, PrimaryR8IRConverter converter, Timing timing) {
+ InternalOptions options = appView.options();
+ if (!options.isOptimizing() || !options.isShrinking()) {
+ return;
+ }
+ TestingOptions testingOptions = options.getTestingOptions();
+ if (!testingOptions.enableComposableOptimizationPass
+ || !testingOptions.modelUnknownChangedAndDefaultArgumentsToComposableFunctions) {
+ return;
+ }
+ timing.time(
+ "ComposableOptimizationPass",
+ () -> new ComposableOptimizationPass(appView, converter).processWaves());
+ }
+
+ void processWaves() {
+ ComposableCallGraph callGraph = ComposableCallGraph.builder(appView).build();
+ ComposeMethodProcessor methodProcessor =
+ new ComposeMethodProcessor(appView, callGraph, converter);
+ Set<ComposableCallGraphNode> wave = createInitialWave(callGraph);
+ while (!wave.isEmpty()) {
+ Set<ComposableCallGraphNode> optimizedComposableFunctions = methodProcessor.processWave(wave);
+ wave = createNextWave(methodProcessor, optimizedComposableFunctions);
+ }
+ }
+
+ // TODO(b/302483644): Should we skip root @Composable functions that don't have any nested
+ // @Composable functions (?).
+ private Set<ComposableCallGraphNode> computeComposableRoots(ComposableCallGraph callGraph) {
+ Set<ComposableCallGraphNode> composableRoots = Sets.newIdentityHashSet();
+ callGraph.forEachNode(
+ node -> {
+ if (!node.isComposable()
+ || Iterables.any(node.getCallers(), ComposableCallGraphNode::isComposable)) {
+ // This is not a @Composable root.
+ return;
+ }
+ if (node.getCallers().isEmpty()) {
+ // Don't include root @Composable functions that are never called. These are either kept
+ // or will be removed in tree shaking.
+ return;
+ }
+ composableRoots.add(node);
+ });
+ return composableRoots;
+ }
+
+ private Set<ComposableCallGraphNode> createInitialWave(ComposableCallGraph callGraph) {
+ Set<ComposableCallGraphNode> wave = Sets.newIdentityHashSet();
+ Set<ComposableCallGraphNode> composableRoots = computeComposableRoots(callGraph);
+ composableRoots.forEach(composableRoot -> wave.addAll(composableRoot.getCallers()));
+ return wave;
+ }
+
+ // TODO(b/302483644): Consider repeatedly extracting the roots from the graph similar to the way
+ // we extract leaves in the primary optimization pass.
+ private static Set<ComposableCallGraphNode> createNextWave(
+ ComposeMethodProcessor methodProcessor,
+ Set<ComposableCallGraphNode> optimizedComposableFunctions) {
+ Set<ComposableCallGraphNode> nextWave =
+ SetUtils.newIdentityHashSet(optimizedComposableFunctions);
+
+ // If the new wave contains two @Composable functions where one calls the other, then defer the
+ // processing of the callee to a later wave, to ensure that we have seen all of its callers
+ // before processing the callee.
+ List<ComposableCallGraphNode> deferredComposableFunctions = new ArrayList<>();
+ nextWave.forEach(
+ node -> {
+ if (SetUtils.containsAnyOf(nextWave, node.getCallers())) {
+ deferredComposableFunctions.add(node);
+ }
+ });
+ deferredComposableFunctions.forEach(nextWave::remove);
+
+ // To optimize the @Composable functions that are called from the @Composable functions of the
+ // next wave in the wave after that, we need to include their callers in the next wave as well.
+ Set<ComposableCallGraphNode> callersOfCalledComposableFunctions = Sets.newIdentityHashSet();
+ nextWave.forEach(
+ node ->
+ node.forEachComposableCallee(
+ callee -> callersOfCalledComposableFunctions.addAll(callee.getCallers())));
+ nextWave.addAll(callersOfCalledComposableFunctions);
+ nextWave.removeIf(methodProcessor::isProcessed);
+ return nextWave;
+ }
+}
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
new file mode 100644
index 0000000..8ee6237
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2023, 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.contexts.CompilationContext.ProcessorContext;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+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;
+import com.android.tools.r8.ir.code.AbstractValueSupplier;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
+import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
+import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
+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.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.LazyBox;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+
+public class ComposeMethodProcessor extends MethodProcessor {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final ArgumentPropagatorCodeScanner codeScanner;
+ private final PrimaryR8IRConverter converter;
+
+ private final Set<ComposableCallGraphNode> processed = Sets.newIdentityHashSet();
+
+ public ComposeMethodProcessor(
+ AppView<AppInfoWithLiveness> appView,
+ ComposableCallGraph callGraph,
+ PrimaryR8IRConverter converter) {
+ this.appView = appView;
+ this.codeScanner = new ArgumentPropagatorCodeScannerForComposableFunctions(appView, callGraph);
+ this.converter = converter;
+ }
+
+ // TODO(b/302483644): Process wave concurrently.
+ public Set<ComposableCallGraphNode> processWave(Set<ComposableCallGraphNode> wave) {
+ ProcessorContext processorContext = appView.createProcessorContext();
+ wave.forEach(
+ node -> {
+ assert !processed.contains(node);
+ converter.processDesugaredMethod(
+ node.getMethod(),
+ OptimizationFeedback.getIgnoreFeedback(),
+ this,
+ processorContext.createMethodProcessingContext(node.getMethod()),
+ MethodConversionOptions.forLirPhase(appView));
+ });
+ processed.addAll(wave);
+ return optimizeComposableFunctionsCalledFromWave(wave);
+ }
+
+ private Set<ComposableCallGraphNode> optimizeComposableFunctionsCalledFromWave(
+ Set<ComposableCallGraphNode> wave) {
+ ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
+ new ArgumentPropagatorOptimizationInfoPopulator(appView, null, null, null);
+ Set<ComposableCallGraphNode> optimizedComposableFunctions = Sets.newIdentityHashSet();
+ wave.forEach(
+ node ->
+ node.forEachComposableCallee(
+ callee -> {
+ if (Iterables.all(callee.getCallers(), this::isProcessed)) {
+ optimizationInfoPopulator.setOptimizationInfo(
+ callee.getMethod(), ProgramMethodSet.empty(), getMethodState(callee));
+ // TODO(b/302483644): Only enqueue this callee if its optimization info changed.
+ optimizedComposableFunctions.add(callee);
+ }
+ }));
+ return optimizedComposableFunctions;
+ }
+
+ private MethodState getMethodState(ComposableCallGraphNode node) {
+ assert processed.containsAll(node.getCallers());
+ MethodState methodState = codeScanner.getMethodStates().get(node.getMethod());
+ return widenMethodState(methodState);
+ }
+
+ /**
+ * 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()) {
+ ConcreteParameterState concreteParameterState =
+ monomorphicMethodState.getParameterState(i).asConcrete();
+ if (concreteParameterState.hasInParameters()) {
+ monomorphicMethodState.setParameterState(i, ParameterState.unknown());
+ }
+ }
+ }
+ } else {
+ assert methodState.isUnknown();
+ }
+ return methodState;
+ }
+
+ public void scan(ProgramMethod method, IRCode code, Timing timing) {
+ LazyBox<Map<Value, AbstractValue>> abstractValues =
+ new LazyBox<>(() -> new SparseConditionalConstantPropagation(appView).analyze(code));
+ AbstractValueSupplier abstractValueSupplier =
+ value -> {
+ AbstractValue abstractValue = abstractValues.computeIfAbsent().get(value);
+ assert abstractValue != null;
+ return abstractValue;
+ };
+ codeScanner.scan(method, code, abstractValueSupplier, timing);
+ }
+
+ public boolean isProcessed(ComposableCallGraphNode node) {
+ return processed.contains(node);
+ }
+
+ @Override
+ public CallSiteInformation getCallSiteInformation() {
+ return CallSiteInformation.empty();
+ }
+
+ @Override
+ public MethodProcessorEventConsumer getEventConsumer() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isComposeMethodProcessor() {
+ return true;
+ }
+
+ @Override
+ public ComposeMethodProcessor asComposeMethodProcessor() {
+ return this;
+ }
+
+ @Override
+ public boolean isProcessedConcurrently(ProgramMethod method) {
+ return false;
+ }
+
+ @Override
+ public void scheduleDesugaredMethodForProcessing(ProgramMethod method) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean shouldApplyCodeRewritings(ProgramMethod method) {
+ return false;
+ }
+}
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 7a489a7..e441188 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
@@ -7,6 +7,7 @@
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.lens.GraphLens;
public class ComposeReferences {
@@ -31,4 +32,26 @@
factory.createProto(factory.intType, factory.intType),
"updateChangedFlags");
}
+
+ 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;
+ }
+
+ 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/profile/art/ArtProfileCompletenessChecker.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
index d7fde7d..78235d8 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
@@ -59,7 +59,7 @@
for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
if (appView.horizontallyMergedClasses().hasBeenMergedIntoDifferentType(clazz.getType())
|| (appView.hasVerticallyMergedClasses()
- && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(clazz.getType()))
+ && appView.getVerticallyMergedClasses().hasBeenMergedIntoSubtype(clazz.getType()))
|| appView.unboxedEnums().isUnboxedEnum(clazz)) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
index 9d133de..7fa618c 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
@@ -90,7 +90,7 @@
}
}
- void applyIfContextIsInProfile(
+ public void applyIfContextIsInProfile(
DexProgramClass context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
accept(additions -> additions.applyIfContextIsInProfile(context.getType(), builderConsumer));
}
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 55501d8..f7836d3 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -77,6 +77,7 @@
RepackagingLens lens = repackageClasses(appBuilder, executorService);
if (lens != null) {
appView.rewriteWithLensAndApplication(lens, appBuilder.build(), executorService, timing);
+ appView.testing().repackagingLensConsumer.accept(appView.dexItemFactory(), lens);
}
appView.notifyOptimizationFinishedForTesting();
timing.end();
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 43ce750..794ef20 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -473,21 +473,17 @@
@Override
public void notifyHorizontalClassMergerFinished(
HorizontalClassMerger.Mode horizontalClassMergerMode) {
- if (horizontalClassMergerMode.isInitial()
- && !options().getAccessModifierOptions().isLegacyAccessModifierEnabled()) {
+ if (horizontalClassMergerMode.isInitial()) {
getMethodAccessInfoCollection().destroy();
}
}
public void notifyMemberRebindingFinished(AppView<AppInfoWithLiveness> appView) {
getFieldAccessInfoCollection().restrictToProgram(appView);
- if (!options().getAccessModifierOptions().isLegacyAccessModifierEnabled()) {
- getMethodAccessInfoCollection().destroyNonDirectNonSuperInvokes();
- }
}
public void notifyRedundantBridgeRemoverFinished(boolean initial) {
- if (initial && !options().getAccessModifierOptions().isLegacyAccessModifierEnabled()) {
+ if (initial) {
getMethodAccessInfoCollection().destroySuperInvokes();
}
}
@@ -495,9 +491,7 @@
@Override
public void notifyMinifierFinished() {
liveMethods = ThrowingSet.get();
- if (!options().getAccessModifierOptions().isLegacyAccessModifierEnabled()) {
- getMethodAccessInfoCollection().destroy();
- }
+ getMethodAccessInfoCollection().destroy();
}
public void notifyTreePrunerFinished(Enqueuer.Mode mode) {
@@ -743,10 +737,6 @@
return alwaysInline.contains(method);
}
- public boolean hasNoAlwaysInlineMethods() {
- return alwaysInline.isEmpty();
- }
-
public boolean isNeverInlineDueToSingleCallerMethod(ProgramMethod method) {
return neverInlineDueToSingleCaller.contains(method.getReference());
}
@@ -854,8 +844,8 @@
* can potentially cause incorrect behavior when merging classes. A conservative choice is to not
* merge any const-class classes. More info at b/142438687.
*/
- public boolean isLockCandidate(DexType type) {
- return lockCandidates.contains(type);
+ public boolean isLockCandidate(DexProgramClass clazz) {
+ return lockCandidates.contains(clazz.getType());
}
public Set<DexType> getDeadProtoTypes() {
@@ -1332,8 +1322,8 @@
.isDefinitelyInstanceOfStaticType(appView, () -> dynamicReceiverType, staticReceiverType)) {
return null;
}
- DexClass initialResolutionHolder = definitionFor(method.holder);
- if (initialResolutionHolder == null || initialResolutionHolder.isInterface() != isInterface) {
+ DexClass initialResolutionHolder = resolutionResult.getInitialResolutionHolder();
+ if (initialResolutionHolder.isInterface() != isInterface) {
return null;
}
DexType refinedReceiverType =
@@ -1343,27 +1333,24 @@
// The refined receiver is not defined in the program and we cannot determine the target.
return null;
}
- if (!dynamicReceiverType.hasDynamicLowerBoundType()) {
- if (singleTargetLookupCache.hasPositiveCacheHit(refinedReceiverType, method)) {
- return singleTargetLookupCache.getPositiveCacheHit(refinedReceiverType, method);
- }
- if (singleTargetLookupCache.hasNegativeCacheHit(refinedReceiverType, method)) {
- return null;
- }
+ if (singleTargetLookupCache.hasPositiveCacheHit(refinedReceiverType, method)) {
+ return singleTargetLookupCache.getPositiveCacheHit(refinedReceiverType, method);
+ }
+ if (!dynamicReceiverType.hasDynamicLowerBoundType()
+ && singleTargetLookupCache.hasNegativeCacheHit(refinedReceiverType, method)) {
+ return null;
}
if (resolutionResult
.isAccessibleForVirtualDispatchFrom(context.getHolder(), appView)
.isFalse()) {
return null;
}
- // If the method is modeled, return the resolution.
+ // If the resolved method is final, return the resolution.
DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
- if (modeledPredicate.isModeled(resolutionResult.getResolvedHolder().getType())) {
- if (resolutionResult.getResolvedHolder().isFinal()
- || (resolvedMethod.getAccessFlags().isFinal()
- && resolvedMethod.getAccessFlags().isPublic())) {
- singleTargetLookupCache.addToCache(refinedReceiverType, method, resolvedMethod);
- return resolvedMethod;
+ if (resolvedMethod.getHolder().isFinal() || resolvedMethod.getAccessFlags().isFinal()) {
+ if (!resolvedMethod.isLibraryMethod()
+ || modeledPredicate.isModeled(resolvedMethod.getHolderType())) {
+ return singleTargetLookupCache.addToCache(refinedReceiverType, method, resolvedMethod);
}
}
DispatchTargetLookupResult exactTarget =
@@ -1527,13 +1514,15 @@
}
/** Predicate on types that *must* never be merged horizontally. */
- public boolean isNoHorizontalClassMergingOfType(DexType type) {
- return noClassMerging.contains(type) || noHorizontalClassMerging.contains(type);
+ public boolean isNoHorizontalClassMergingOfType(DexProgramClass clazz) {
+ return noClassMerging.contains(clazz.getType())
+ || noHorizontalClassMerging.contains(clazz.getType());
}
/** Predicate on types that *must* never be merged vertically. */
- public boolean isNoVerticalClassMergingOfType(DexType type) {
- return noClassMerging.contains(type) || noVerticalClassMerging.contains(type);
+ public boolean isNoVerticalClassMergingOfType(DexProgramClass clazz) {
+ return noClassMerging.contains(clazz.getType())
+ || noVerticalClassMerging.contains(clazz.getType());
}
public boolean verifyNoIteratingOverPrunedClasses() {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 6bcbfc7..c8d9806 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -95,6 +95,7 @@
import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
import com.android.tools.r8.graph.analysis.GetArrayOfMissingTypeVerifyErrorWorkaround;
import com.android.tools.r8.graph.analysis.InvokeVirtualToInterfaceVerifyErrorWorkaround;
+import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis;
import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -506,6 +507,7 @@
}
appView.withGeneratedMessageLiteBuilderShrinker(
shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
+ ResourceAccessAnalysis.register(appView, this);
}
targetedMethods = new LiveMethodsSet(graphReporter::registerMethod);
@@ -3677,6 +3679,7 @@
timing.end();
timing.begin("Finish analysis");
analyses.forEach(analyses -> analyses.done(this));
+ fieldAccessAnalyses.forEach(fieldAccessAnalyses -> fieldAccessAnalyses.done(this));
timing.end();
assert verifyKeptGraph();
timing.begin("Finish compat building");
@@ -5144,7 +5147,7 @@
initializer = clazz.getProgramDefaultInitializer();
} else {
DexType[] parameterTypes = new DexType[parametersSize];
- int missingIndices = parametersSize;
+ int missingIndices;
if (newArrayEmpty != null) {
missingIndices = parametersSize;
@@ -5168,12 +5171,8 @@
return;
}
- Value indexValue = arrayPutInstruction.index();
- if (indexValue.isPhi() || !indexValue.definition.isConstNumber()) {
- return;
- }
- int index = indexValue.definition.asConstNumber().getIntValue();
- if (index >= parametersSize) {
+ int index = arrayPutInstruction.indexIfConstAndInBounds(parametersSize);
+ if (index < 0) {
return;
}
@@ -5407,7 +5406,7 @@
}
Set<T> getItems() {
- return Collections.unmodifiableSet(items);
+ return SetUtils.unmodifiableForTesting(items);
}
}
@@ -5463,7 +5462,7 @@
}
Set<DexEncodedMethod> getItems() {
- return Collections.unmodifiableSet(items);
+ return SetUtils.unmodifiableForTesting(items);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 16b3d76..1410247 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -107,9 +107,9 @@
}
// Check if one of the types that have been merged into `clazz` satisfies the if-rule.
- if (appView.verticallyMergedClasses() != null) {
+ if (appView.getVerticallyMergedClasses() != null) {
Iterable<DexType> sources =
- appView.verticallyMergedClasses().getSourcesFor(clazz.type);
+ appView.getVerticallyMergedClasses().getSourcesFor(clazz.type);
for (DexType sourceType : sources) {
// Note that, although `sourceType` has been merged into `type`, the dex class for
// `sourceType` is still available until the second round of tree shaking. This
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index a0af134..6099834 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -159,9 +159,9 @@
}
if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
DexType type = getInheritanceClassName().getSpecificType();
- if (appView.verticallyMergedClasses() != null
- && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
- DexType target = appView.verticallyMergedClasses().getTargetFor(type);
+ if (appView.getVerticallyMergedClasses() != null
+ && appView.getVerticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
+ DexType target = appView.getVerticallyMergedClasses().getTargetFor(type);
DexClass clazz = appView.definitionFor(target);
assert clazz != null && clazz.isProgramClass();
return Iterables.concat(
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index 71057cd..a8233bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -49,8 +49,9 @@
if (matches(type)) {
return true;
}
- if (appView.verticallyMergedClasses() != null) {
- return appView.verticallyMergedClasses().getSourcesFor(type).stream().anyMatch(this::matches);
+ if (appView.getVerticallyMergedClasses() != null) {
+ return appView.getVerticallyMergedClasses().getSourcesFor(type).stream()
+ .anyMatch(this::matches);
}
return false;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 1e8f56d..200fbc6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -931,8 +931,8 @@
DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
// TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
// the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
- return appView.verticallyMergedClasses() != null
- && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
+ return appView.getVerticallyMergedClasses() != null
+ && appView.getVerticallyMergedClasses().getSourcesFor(clazz.type).stream()
.filter(
sourceType ->
appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index cb0f347..5b6511d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
import java.util.Set;
+import java.util.function.Function;
public class RuntimeTypeCheckInfo {
@@ -31,6 +32,31 @@
this.exceptionGuardTypes = exceptionGuardTypes;
}
+ public boolean isCheckCastType(DexProgramClass clazz) {
+ return checkCastTypes.contains(clazz.type);
+ }
+
+ public boolean isInstanceOfType(DexProgramClass clazz) {
+ return instanceOfTypes.contains(clazz.type);
+ }
+
+ public boolean isExceptionGuardType(DexProgramClass clazz) {
+ return exceptionGuardTypes.contains(clazz.type);
+ }
+
+ public boolean isRuntimeCheckType(DexProgramClass clazz) {
+ return isInstanceOfType(clazz) || isCheckCastType(clazz) || isExceptionGuardType(clazz);
+ }
+
+ public RuntimeTypeCheckInfo rewriteWithLens(
+ NonIdentityGraphLens graphLens, GraphLens appliedGraphLens) {
+ Function<DexType, DexType> typeRewriter = type -> graphLens.lookupType(type, appliedGraphLens);
+ return new RuntimeTypeCheckInfo(
+ SetUtils.mapIdentityHashSet(instanceOfTypes, typeRewriter),
+ SetUtils.mapIdentityHashSet(checkCastTypes, typeRewriter),
+ SetUtils.mapIdentityHashSet(exceptionGuardTypes, typeRewriter));
+ }
+
public static class Builder
implements EnqueuerInstanceOfAnalysis,
EnqueuerCheckCastAnalysis,
@@ -52,7 +78,7 @@
RuntimeTypeCheckInfo runtimeTypeCheckInfo =
new RuntimeTypeCheckInfo(instanceOfTypes, checkCastTypes, exceptionGuardTypes);
return graphLens.isNonIdentityLens() && graphLens != appliedGraphLens
- ? runtimeTypeCheckInfo.rewriteWithLens(graphLens.asNonIdentityLens())
+ ? runtimeTypeCheckInfo.rewriteWithLens(graphLens.asNonIdentityLens(), appliedGraphLens)
: runtimeTypeCheckInfo;
}
@@ -90,27 +116,4 @@
.registerExceptionGuardAnalysis(this);
}
}
-
- public boolean isCheckCastType(DexProgramClass clazz) {
- return checkCastTypes.contains(clazz.type);
- }
-
- public boolean isInstanceOfType(DexProgramClass clazz) {
- return instanceOfTypes.contains(clazz.type);
- }
-
- public boolean isExceptionGuardType(DexProgramClass clazz) {
- return exceptionGuardTypes.contains(clazz.type);
- }
-
- public boolean isRuntimeCheckType(DexProgramClass clazz) {
- return isInstanceOfType(clazz) || isCheckCastType(clazz) || isExceptionGuardType(clazz);
- }
-
- public RuntimeTypeCheckInfo rewriteWithLens(NonIdentityGraphLens graphLens) {
- return new RuntimeTypeCheckInfo(
- SetUtils.mapIdentityHashSet(instanceOfTypes, graphLens::lookupType),
- SetUtils.mapIdentityHashSet(checkCastTypes, graphLens::lookupType),
- SetUtils.mapIdentityHashSet(exceptionGuardTypes, graphLens::lookupType));
- }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
index 0c576e0..fbe53c8 100644
--- a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
+++ b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
@@ -31,10 +31,11 @@
.add(method);
}
- public void addToCache(DexType refinedReceiverType, DexMethod method, DexClassAndMethod target) {
+ public DexClassAndMethod addToCache(
+ DexType refinedReceiverType, DexMethod method, DexClassAndMethod target) {
if (target == null) {
addNoSingleTargetToCache(refinedReceiverType, method);
- return;
+ return null;
}
assert !ObjectUtils.identical(target.getDefinition(), DexEncodedMethod.SENTINEL);
assert !hasNegativeCacheHit(refinedReceiverType, method);
@@ -43,6 +44,7 @@
positiveCache
.computeIfAbsent(refinedReceiverType, ignoreKey(ConcurrentHashMap::new))
.put(method, target);
+ return target;
}
public void removeInstantiatedType(DexType instantiatedType, AppInfoWithLiveness appInfo) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
deleted file mode 100644
index f96b242..0000000
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ /dev/null
@@ -1,2317 +0,0 @@
-// Copyright (c) 2017, 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.shaking;
-
-import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
-import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.code.InvokeType.DIRECT;
-import static com.android.tools.r8.ir.code.InvokeType.STATIC;
-import static com.android.tools.r8.ir.code.InvokeType.VIRTUAL;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
-
-import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
-import com.android.tools.r8.androidapi.ComputedApiLevel;
-import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
-import com.android.tools.r8.graph.AccessControl;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndField;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMember;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature.ClassSignatureBuilder;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.GenericSignatureContextBuilder;
-import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
-import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
-import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
-import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.graph.UseRegistryWithResult;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
-import com.android.tools.r8.graph.fixup.TreeFixerBase;
-import com.android.tools.r8.graph.lens.FieldLookupResult;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.lens.MethodLookupResult;
-import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
-import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
-import com.android.tools.r8.ir.code.InvokeType;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
-import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
-import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.CollectionUtils;
-import com.android.tools.r8.utils.FieldSignatureEquivalence;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
-import it.unimi.dsi.fastutil.ints.Int2IntMap;
-import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
-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;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
-
-/**
- * Merges Supertypes with a single implementation into their single subtype.
- *
- * <p>A common use-case for this is to merge an interface into its single implementation.
- *
- * <p>The class merger only fixes the structure of the graph but leaves the actual instructions
- * untouched. Fixup of instructions is deferred via a {@link GraphLens} to the IR building phase.
- */
-public class VerticalClassMerger {
-
- private enum AbortReason {
- ALREADY_MERGED,
- ALWAYS_INLINE,
- CONFLICT,
- ILLEGAL_ACCESS,
- MAIN_DEX_ROOT_OUTSIDE_REFERENCE,
- MERGE_ACROSS_NESTS,
- NATIVE_METHOD,
- NO_SIDE_EFFECTS,
- PINNED_SOURCE,
- RESOLUTION_FOR_FIELDS_MAY_CHANGE,
- RESOLUTION_FOR_METHODS_MAY_CHANGE,
- SERVICE_LOADER,
- SOURCE_AND_TARGET_LOCK_CANDIDATES,
- STATIC_INITIALIZERS,
- UNHANDLED_INVOKE_DIRECT,
- UNHANDLED_INVOKE_SUPER,
- UNSAFE_INLINING,
- UNSUPPORTED_ATTRIBUTES,
- API_REFERENCE_LEVEL
- }
-
- private enum Rename {
- ALWAYS,
- IF_NEEDED,
- NEVER
- }
-
- private final DexApplication application;
- private final AppInfoWithLiveness appInfo;
- private final AppView<AppInfoWithLiveness> appView;
- private final InternalOptions options;
- private final SubtypingInfo subtypingInfo;
- private final ExecutorService executorService;
- private final Timing timing;
- private Collection<DexMethod> invokes;
- private final AndroidApiLevelCompute apiLevelCompute;
-
- private final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
-
- // Set of merge candidates. Note that this must have a deterministic iteration order.
- private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
-
- // Map from source class to target class.
- private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses =
- BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
-
- private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedInterfaces =
- BidirectionalManyToOneHashMap.newIdentityHashMap();
-
- // Set of types that must not be merged into their subtype.
- private final Set<DexType> pinnedTypes = Sets.newIdentityHashSet();
-
- // The resulting graph lens that should be used after class merging.
- private final VerticalClassMergerGraphLens.Builder lensBuilder;
-
- // All the bridge methods that have been synthesized during vertical class merging.
- private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
-
- private final MainDexInfo mainDexInfo;
-
- public VerticalClassMerger(
- DexApplication application,
- AppView<AppInfoWithLiveness> appView,
- ExecutorService executorService,
- Timing timing) {
- this.application = application;
- this.appInfo = appView.appInfo();
- this.appView = appView;
- this.options = appView.options();
- this.mainDexInfo = appInfo.getMainDexInfo();
- this.subtypingInfo = appInfo.computeSubtypingInfo();
- this.executorService = executorService;
- this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory());
- this.apiLevelCompute = appView.apiLevelCompute();
- this.timing = timing;
-
- Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
- initializePinnedTypes(classes); // Must be initialized prior to mergeCandidates.
- initializeMergeCandidates(classes);
- }
-
- private void initializeMergeCandidates(Iterable<DexProgramClass> classes) {
- for (DexProgramClass sourceClass : classes) {
- DexType singleSubtype = subtypingInfo.getSingleDirectSubtype(sourceClass.type);
- if (singleSubtype == null) {
- continue;
- }
- DexProgramClass targetClass = asProgramClassOrNull(appView.definitionFor(singleSubtype));
- if (targetClass == null) {
- continue;
- }
- if (!isMergeCandidate(sourceClass, targetClass, pinnedTypes)) {
- continue;
- }
- if (!isStillMergeCandidate(sourceClass, targetClass)) {
- continue;
- }
- if (mergeMayLeadToIllegalAccesses(sourceClass, targetClass)) {
- continue;
- }
- mergeCandidates.add(sourceClass);
- }
- }
-
- // Returns a set of types that must not be merged into other types.
- private void initializePinnedTypes(Iterable<DexProgramClass> classes) {
- // For all pinned fields, also pin the type of the field (because changing the type of the field
- // implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
- // the return type and the parameter types of the method.
- // TODO(b/156715504): Compute referenced-by-pinned in the keep info objects.
- List<DexReference> pinnedItems = new ArrayList<>();
- appInfo.getKeepInfo().forEachPinnedType(pinnedItems::add, options);
- appInfo.getKeepInfo().forEachPinnedMethod(pinnedItems::add, options);
- appInfo.getKeepInfo().forEachPinnedField(pinnedItems::add, options);
- extractPinnedItems(pinnedItems, AbortReason.PINNED_SOURCE);
-
- for (DexProgramClass clazz : classes) {
- for (DexEncodedMethod method : clazz.methods()) {
- if (method.accessFlags.isNative()) {
- markTypeAsPinned(clazz.type, AbortReason.NATIVE_METHOD);
- }
- }
- }
-
- // It is valid to have an invoke-direct instruction in a default interface method that targets
- // another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
- // SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
- // Therefore, we disallow merging such interfaces into their subtypes.
- for (DexMethod signature : appInfo.getVirtualMethodsTargetedByInvokeDirect()) {
- markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_DIRECT);
- }
-
- // The set of targets that must remain for proper resolution error cases should not be merged.
- // TODO(b/192821424): Can be removed if handled.
- extractPinnedItems(
- appInfo.getFailedMethodResolutionTargets(), AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
- }
-
- private <T extends DexReference> void extractPinnedItems(Iterable<T> items, AbortReason reason) {
- for (DexReference item : items) {
- if (item.isDexType()) {
- markTypeAsPinned(item.asDexType(), reason);
- } else if (item.isDexField()) {
- // Pin the holder and the type of the field.
- DexField field = item.asDexField();
- markTypeAsPinned(field.holder, reason);
- markTypeAsPinned(field.type, reason);
- } else {
- assert item.isDexMethod();
- // Pin the holder, the return type and the parameter types of the method. If we were to
- // merge any of these types into their sub classes, then we would implicitly change the
- // signature of this method.
- DexMethod method = item.asDexMethod();
- markTypeAsPinned(method.holder, reason);
- markTypeAsPinned(method.proto.returnType, reason);
- for (DexType parameterType : method.proto.parameters.values) {
- markTypeAsPinned(parameterType, reason);
- }
- }
- }
- }
-
- @SuppressWarnings("UnusedVariable")
- private void markTypeAsPinned(DexType type, AbortReason reason) {
- DexType baseType = type.toBaseType(appView.dexItemFactory());
- if (!baseType.isClassType() || appInfo.isPinnedWithDefinitionLookup(baseType)) {
- // We check for the case where the type is pinned according to appInfo.isPinned,
- // so we only need to add it here if it is not the case.
- return;
- }
-
- DexClass clazz = appInfo.definitionFor(baseType);
- if (clazz != null && clazz.isProgramClass()) {
- pinnedTypes.add(baseType);
- }
- }
-
- // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
- // method do not change in response to any class merges.
- @SuppressWarnings("ReferenceEquality")
- private boolean isMergeCandidate(
- DexProgramClass sourceClass, DexProgramClass targetClass, Set<DexType> pinnedTypes) {
- assert targetClass != null;
- ObjectAllocationInfoCollection allocationInfo = appInfo.getObjectAllocationInfoCollection();
- if (allocationInfo.isInstantiatedDirectly(sourceClass)
- || allocationInfo.isInterfaceWithUnknownSubtypeHierarchy(sourceClass)
- || allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass)
- || appInfo.isPinned(sourceClass)
- || pinnedTypes.contains(sourceClass.type)
- || appInfo.isNoVerticalClassMergingOfType(sourceClass.type)) {
- return false;
- }
-
- assert Streams.stream(Iterables.concat(sourceClass.fields(), sourceClass.methods()))
- .noneMatch(appInfo::isPinned);
-
- if (!FeatureSplitBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
- sourceClass, targetClass, appView)) {
- return false;
- }
- if (appView.appServices().allServiceTypes().contains(sourceClass.type)
- && appInfo.isPinned(targetClass)) {
- return false;
- }
- if (sourceClass.isAnnotation()) {
- return false;
- }
- if (!sourceClass.isInterface()
- && targetClass.isSerializable(appView)
- && !appInfo.isSerializable(sourceClass.type)) {
- // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
- // 1.10 The Serializable Interface
- // ...
- // A Serializable class must do the following:
- // ...
- // * Have access to the no-arg constructor of its first non-serializable superclass
- return false;
- }
-
- // If there is a constructor in the target, make sure that all source constructors can be
- // inlined.
- if (!Iterables.isEmpty(targetClass.programInstanceInitializers())) {
- TraversalContinuation<?, ?> result =
- sourceClass.traverseProgramInstanceInitializers(
- method -> {
- AbortReason reason = disallowInlining(method, targetClass);
- if (reason != null) {
- // Cannot guarantee that markForceInline() will work.
- return TraversalContinuation.doBreak();
- }
- return TraversalContinuation.doContinue();
- });
- if (result.shouldBreak()) {
- return false;
- }
- }
- if (sourceClass.getEnclosingMethodAttribute() != null
- || !sourceClass.getInnerClasses().isEmpty()) {
- // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
- return false;
- }
- // We abort class merging when merging across nests or from a nest to non-nest.
- // Without nest this checks null == null.
- if (targetClass.getNestHost() != sourceClass.getNestHost()) {
- return false;
- }
-
- // If there is an invoke-special to a default interface method and we are not merging into an
- // interface, then abort, since invoke-special to a virtual class method requires desugaring.
- if (sourceClass.isInterface() && !targetClass.isInterface()) {
- TraversalContinuation<?, ?> result =
- sourceClass.traverseProgramMethods(
- method -> {
- boolean foundInvokeSpecialToDefaultLibraryMethod =
- method.registerCodeReferencesWithResult(
- new InvokeSpecialToDefaultLibraryMethodUseRegistry(appView, method));
- return TraversalContinuation.breakIf(foundInvokeSpecialToDefaultLibraryMethod);
- });
- if (result.shouldBreak()) {
- return false;
- }
- }
- return true;
- }
-
- // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
- // method may change in response to class merges. Therefore, this method should always be called
- // before merging [clazz] into its subtype.
- @SuppressWarnings("ReferenceEquality")
- private boolean isStillMergeCandidate(DexProgramClass sourceClass, DexProgramClass targetClass) {
- assert isMergeCandidate(sourceClass, targetClass, pinnedTypes);
- assert !mergedClasses.containsValue(sourceClass.getType());
- // For interface types, this is more complicated, see:
- // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5
- // We basically can't move the clinit, since it is not called when implementing classes have
- // their clinit called - except when the interface has a default method.
- if ((sourceClass.hasClassInitializer() && targetClass.hasClassInitializer())
- || targetClass.classInitializationMayHaveSideEffects(
- appView, type -> type == sourceClass.type)
- || (sourceClass.isInterface()
- && sourceClass.classInitializationMayHaveSideEffects(appView))) {
- // TODO(herhut): Handle class initializers.
- return false;
- }
- boolean sourceCanBeSynchronizedOn =
- appView.appInfo().isLockCandidate(sourceClass.type)
- || sourceClass.hasStaticSynchronizedMethods();
- boolean targetCanBeSynchronizedOn =
- appView.appInfo().isLockCandidate(targetClass.type)
- || targetClass.hasStaticSynchronizedMethods();
- if (sourceCanBeSynchronizedOn && targetCanBeSynchronizedOn) {
- return false;
- }
- if (targetClass.getEnclosingMethodAttribute() != null
- || !targetClass.getInnerClasses().isEmpty()) {
- // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
- return false;
- }
- if (methodResolutionMayChange(sourceClass, targetClass)) {
- return false;
- }
- // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
- // to the super class.
- if (fieldResolutionMayChange(sourceClass, targetClass)) {
- return false;
- }
- // Only merge if api reference level of source class is equal to target class. The check is
- // somewhat expensive.
- if (appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()) {
- ComputedApiLevel sourceApiLevel =
- getApiReferenceLevelForMerging(apiLevelCompute, sourceClass);
- ComputedApiLevel targetApiLevel =
- getApiReferenceLevelForMerging(apiLevelCompute, targetClass);
- if (!sourceApiLevel.equals(targetApiLevel)) {
- return false;
- }
- }
- return true;
- }
-
- private boolean mergeMayLeadToIllegalAccesses(DexProgramClass source, DexProgramClass target) {
- if (source.isSamePackage(target)) {
- // When merging two classes from the same package, we only need to make sure that [source]
- // does not get less visible, since that could make a valid access to [source] from another
- // package illegal after [source] has been merged into [target].
- assert source.getAccessFlags().isPackagePrivateOrPublic();
- assert target.getAccessFlags().isPackagePrivateOrPublic();
- // TODO(b/287891322): Allow merging if `source` is only accessed from inside its own package.
- return source.getAccessFlags().isPublic() && target.getAccessFlags().isPackagePrivate();
- }
-
- // Check that all accesses to [source] and its members from inside the current package of
- // [source] will continue to work. This is guaranteed if [target] is public and all members of
- // [source] are either private or public.
- //
- // (Deliberately not checking all accesses to [source] since that would be expensive.)
- if (!target.isPublic()) {
- return true;
- }
- for (DexEncodedField field : source.fields()) {
- if (!(field.isPublic() || field.isPrivate())) {
- return true;
- }
- }
- for (DexEncodedMethod method : source.methods()) {
- if (!(method.isPublic() || method.isPrivate())) {
- return true;
- }
- // Check if the target is overriding and narrowing the access.
- if (method.isPublic()) {
- DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.getReference());
- if (targetOverride != null && !targetOverride.isPublic()) {
- return true;
- }
- }
- }
- // Check that all accesses from [source] to classes or members from the current package of
- // [source] will continue to work. This is guaranteed if the methods of [source] do not access
- // any private or protected classes or members from the current package of [source].
- TraversalContinuation<?, ?> result =
- source.traverseProgramMethods(
- method -> {
- boolean foundIllegalAccess =
- method.registerCodeReferencesWithResult(
- new IllegalAccessDetector(appView, method));
- if (foundIllegalAccess) {
- return TraversalContinuation.doBreak();
- }
- return TraversalContinuation.doContinue();
- });
- return result.shouldBreak();
- }
-
- private Collection<DexMethod> getInvokes() {
- if (invokes == null) {
- invokes = new OverloadedMethodSignaturesRetriever().get();
- }
- return invokes;
- }
-
- // Collects all potentially overloaded method signatures that reference at least one type that
- // may be the source or target of a merge operation.
- private class OverloadedMethodSignaturesRetriever {
- private final Reference2BooleanOpenHashMap<DexProto> cache =
- new Reference2BooleanOpenHashMap<>();
- private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
- private final Set<DexType> mergeeCandidates = new HashSet<>();
-
- public OverloadedMethodSignaturesRetriever() {
- for (DexProgramClass mergeCandidate : mergeCandidates) {
- DexType singleSubtype = subtypingInfo.getSingleDirectSubtype(mergeCandidate.type);
- mergeeCandidates.add(singleSubtype);
- }
- }
-
- @SuppressWarnings("ReferenceEquality")
- public Collection<DexMethod> get() {
- Map<DexString, DexProto> overloadingInfo = new HashMap<>();
-
- // Find all signatures that may reference a type that could be the source or target of a
- // merge operation.
- Set<Wrapper<DexMethod>> filteredSignatures = new HashSet<>();
- for (DexProgramClass clazz : appInfo.classes()) {
- for (DexEncodedMethod encodedMethod : clazz.methods()) {
- DexMethod method = encodedMethod.getReference();
- DexClass definition = appInfo.definitionFor(method.holder);
- if (definition != null
- && definition.isProgramClass()
- && protoMayReferenceMergedSourceOrTarget(method.proto)) {
- filteredSignatures.add(equivalence.wrap(method));
-
- // Record that we have seen a method named [signature.name] with the proto
- // [signature.proto]. If at some point, we find a method with the same name, but a
- // different proto, it could be the case that a method with the given name is
- // overloaded.
- DexProto existing = overloadingInfo.computeIfAbsent(method.name, key -> method.proto);
- if (existing != DexProto.SENTINEL && !existing.equals(method.proto)) {
- // Mark that this signature is overloaded by mapping it to SENTINEL.
- overloadingInfo.put(method.name, DexProto.SENTINEL);
- }
- }
- }
- }
-
- List<DexMethod> result = new ArrayList<>();
- for (Wrapper<DexMethod> wrappedSignature : filteredSignatures) {
- DexMethod signature = wrappedSignature.get();
-
- // Ignore those method names that are definitely not overloaded since they cannot lead to
- // any collisions.
- if (overloadingInfo.get(signature.name) == DexProto.SENTINEL) {
- result.add(signature);
- }
- }
- return result;
- }
-
- private boolean protoMayReferenceMergedSourceOrTarget(DexProto proto) {
- boolean result;
- if (cache.containsKey(proto)) {
- result = cache.getBoolean(proto);
- } else {
- result = false;
- if (typeMayReferenceMergedSourceOrTarget(proto.returnType)) {
- result = true;
- } else {
- for (DexType type : proto.parameters.values) {
- if (typeMayReferenceMergedSourceOrTarget(type)) {
- result = true;
- break;
- }
- }
- }
- cache.put(proto, result);
- }
- return result;
- }
-
- private boolean typeMayReferenceMergedSourceOrTarget(DexType type) {
- type = type.toBaseType(appView.dexItemFactory());
- if (type.isClassType()) {
- if (mergeeCandidates.contains(type)) {
- return true;
- }
- DexClass clazz = appInfo.definitionFor(type);
- if (clazz != null && clazz.isProgramClass()) {
- return mergeCandidates.contains(clazz.asProgramClass());
- }
- }
- return false;
- }
- }
-
- public VerticalClassMergerGraphLens run() throws ExecutionException {
- timing.begin("merge");
- // Visit the program classes in a top-down order according to the class hierarchy.
- TopDownClassHierarchyTraversal.forProgramClasses(appView)
- .visit(mergeCandidates, this::mergeClassIfPossible);
- timing.end();
-
- VerticallyMergedClasses verticallyMergedClasses =
- new VerticallyMergedClasses(mergedClasses, mergedInterfaces);
- appView.setVerticallyMergedClasses(verticallyMergedClasses);
- if (verticallyMergedClasses.isEmpty()) {
- return null;
- }
-
- timing.begin("fixup");
- VerticalClassMergerGraphLens lens =
- new VerticalClassMergerTreeFixer(
- appView, lensBuilder, verticallyMergedClasses, synthesizedBridges)
- .fixupTypeReferences();
- KeepInfoCollection keepInfo = appView.getKeepInfo();
- keepInfo.mutate(
- mutator ->
- mutator.removeKeepInfoForMergedClasses(
- PrunedItems.builder().setRemovedClasses(mergedClasses.keySet()).build()));
- timing.end();
-
- assert lens != null;
- assert verifyGraphLens(lens);
-
- // Include bridges in art profiles.
- ProfileCollectionAdditions profileCollectionAdditions =
- ProfileCollectionAdditions.create(appView);
- if (!profileCollectionAdditions.isNop()) {
- for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
- profileCollectionAdditions.applyIfContextIsInProfile(
- lens.getPreviousMethodSignature(synthesizedBridge.method),
- additionsBuilder -> additionsBuilder.addRule(synthesizedBridge.method));
- }
- }
- profileCollectionAdditions.commit(appView);
-
- // Rewrite collections using the lens.
- appView.rewriteWithLens(lens, executorService, timing);
-
- // Copy keep info to newly synthesized methods.
- keepInfo.mutate(
- mutator -> {
- for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
- ProgramMethod bridge =
- asProgramMethodOrNull(appView.definitionFor(synthesizedBridge.method));
- ProgramMethod target =
- asProgramMethodOrNull(appView.definitionFor(synthesizedBridge.invocationTarget));
- if (bridge != null && target != null) {
- mutator.joinMethod(bridge, info -> info.merge(appView.getKeepInfo(target).joiner()));
- continue;
- }
- assert false;
- }
- });
-
- appView.notifyOptimizationFinishedForTesting();
- return lens;
- }
-
- @SuppressWarnings("ReferenceEquality")
- private boolean verifyGraphLens(VerticalClassMergerGraphLens graphLens) {
- // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
- // getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
- // for this check to succeed, since it is not guaranteed that calling lookupMethod() with a
- // pinned method will return the method itself.
- //
- // Consider the following example.
- //
- // class A {
- // public void method() {}
- // }
- // class B extends A {
- // @Override
- // public void method() {}
- // }
- // class C extends B {
- // @Override
- // public void method() {}
- // }
- //
- // If A.method() is pinned, then A cannot be merged into B, but B can still be merged into C.
- // Now, if there is an invoke-super instruction in C that hits B.method(), then this needs to
- // be rewritten into an invoke-direct instruction. In particular, there could be an instruction
- // `invoke-super A.method` in C. This would hit B.method(). Therefore, the graph lens records
- // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
- // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
- // pinned, because this rewriting does not affect A.method() in any way.
- assert graphLens.assertPinnedNotModified(appInfo.getKeepInfo(), options);
-
- for (DexProgramClass clazz : appInfo.classes()) {
- for (DexEncodedMethod encodedMethod : clazz.methods()) {
- DexMethod method = encodedMethod.getReference();
- DexMethod originalMethod = graphLens.getOriginalMethodSignature(method);
- DexMethod renamedMethod = graphLens.getRenamedMethodSignature(originalMethod);
-
- // Must be able to map back and forth.
- if (encodedMethod.hasCode() && encodedMethod.getCode() instanceof SynthesizedBridgeCode) {
- // For virtual methods, the vertical class merger creates two methods in the sub class
- // in order to deal with invoke-super instructions (one that is private and one that is
- // virtual). Therefore, it is not possible to go back and forth. Instead, we check that
- // the two methods map back to the same original method, and that the original method
- // can be mapped to the implementation method.
- DexMethod implementationMethod =
- ((SynthesizedBridgeCode) encodedMethod.getCode()).invocationTarget;
- DexMethod originalImplementationMethod =
- graphLens.getOriginalMethodSignature(implementationMethod);
- assert originalMethod == originalImplementationMethod;
- assert implementationMethod == renamedMethod;
- } else {
- assert method == renamedMethod;
- }
-
- // Verify that all types are up-to-date. After vertical class merging, there should be no
- // more references to types that have been merged into another type.
- assert !mergedClasses.containsKey(method.proto.returnType);
- assert Arrays.stream(method.proto.parameters.values).noneMatch(mergedClasses::containsKey);
- }
- }
- return true;
- }
-
- @SuppressWarnings("ReferenceEquality")
- private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
- for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
- DexEncodedMethod directTargetMethod =
- target.lookupDirectMethod(virtualSourceMethod.getReference());
- if (directTargetMethod != null) {
- // A private method shadows a virtual method. This situation is rare, since it is not
- // allowed by javac. Therefore, we just give up in this case. (In principle, it would be
- // possible to rename the private method in the subclass, and then move the virtual method
- // to the subclass without changing its name.)
- return true;
- }
- }
-
- // When merging an interface into a class, all instructions on the form "invoke-interface
- // [source].m" are changed into "invoke-virtual [target].m". We need to abort the merge if this
- // transformation could hide IncompatibleClassChangeErrors.
- if (source.isInterface() && !target.isInterface()) {
- List<DexEncodedMethod> defaultMethods = new ArrayList<>();
- for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
- if (!virtualMethod.accessFlags.isAbstract()) {
- defaultMethods.add(virtualMethod);
- }
- }
-
- // For each of the default methods, the subclass [target] could inherit another default method
- // with the same signature from another interface (i.e., there is a conflict). In such cases,
- // instructions on the form "invoke-interface [source].foo()" will fail with an Incompatible-
- // ClassChangeError.
- //
- // Example:
- // interface I1 { default void m() {} }
- // interface I2 { default void m() {} }
- // class C implements I1, I2 {
- // ... invoke-interface I1.m ... <- IncompatibleClassChangeError
- // }
- for (DexEncodedMethod method : defaultMethods) {
- // Conservatively find all possible targets for this method.
- LookupResultSuccess lookupResult =
- appInfo
- .resolveMethodOnInterfaceLegacy(method.getHolderType(), method.getReference())
- .lookupVirtualDispatchTargets(target, appView)
- .asLookupResultSuccess();
- assert lookupResult != null;
- if (lookupResult == null) {
- return true;
- }
- if (lookupResult.contains(method)) {
- Box<Boolean> found = new Box<>(false);
- lookupResult.forEach(
- interfaceTarget -> {
- if (interfaceTarget.getDefinition() == method) {
- return;
- }
- DexClass enclosingClass = interfaceTarget.getHolder();
- if (enclosingClass != null && enclosingClass.isInterface()) {
- // Found a default method that is different from the one in [source], aborting.
- found.set(true);
- }
- },
- lambdaTarget -> {
- // The merger should already have excluded lambda implemented interfaces.
- assert false;
- });
- if (found.get()) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- private void mergeClassIfPossible(DexProgramClass clazz) {
- if (!mergeCandidates.contains(clazz)) {
- return;
- }
-
- DexType singleSubtype = subtypingInfo.getSingleDirectSubtype(clazz.type);
- DexProgramClass targetClass = appView.definitionFor(singleSubtype).asProgramClass();
- assert !mergedClasses.containsKey(targetClass.type);
- if (mergedClasses.containsValue(clazz.type)) {
- return;
- }
- assert isMergeCandidate(clazz, targetClass, pinnedTypes);
- if (mergedClasses.containsValue(targetClass.type)) {
- if (!isStillMergeCandidate(clazz, targetClass)) {
- return;
- }
- } else {
- assert isStillMergeCandidate(clazz, targetClass);
- }
-
- // Guard against the case where we have two methods that may get the same signature
- // if we replace types. This is rare, so we approximate and err on the safe side here.
- if (new CollisionDetector(clazz.type, targetClass.type).mayCollide()) {
- return;
- }
-
- // Check with main dex classes to see if we are allowed to merge.
- if (!mainDexInfo.canMerge(clazz, targetClass, appView.getSyntheticItems())) {
- return;
- }
-
- ClassMerger merger = new ClassMerger(clazz, targetClass);
- boolean merged;
- try {
- merged = merger.merge();
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- if (merged) {
- // Commit the changes to the graph lens.
- lensBuilder.merge(merger.getRenamings());
- synthesizedBridges.addAll(merger.getSynthesizedBridges());
- }
- }
-
- @SuppressWarnings("ReferenceEquality")
- private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
- if (source.type == target.superType) {
- // If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class
- // Target implements an interface that declares a static final field f, this should yield an
- // IncompatibleClassChangeError.
- // TODO(christofferqa): In the following we only check if a static field from an interface
- // shadows an instance field from [source]. We could actually check if there is an iget/iput
- // instruction whose resolution would be affected by the merge. The situation where a static
- // field shadows an instance field is probably not widespread in practice, though.
- FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
- Set<Wrapper<DexField>> staticFieldsInInterfacesOfTarget = new HashSet<>();
- for (DexType interfaceType : target.interfaces.values) {
- DexClass clazz = appInfo.definitionFor(interfaceType);
- for (DexEncodedField staticField : clazz.staticFields()) {
- staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.getReference()));
- }
- }
- for (DexEncodedField instanceField : source.instanceFields()) {
- if (staticFieldsInInterfacesOfTarget.contains(
- equivalence.wrap(instanceField.getReference()))) {
- // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an
- // interface would now hit an instance field from [source], so that an IncompatibleClass-
- // ChangeError would no longer be thrown. Abort merge.
- return true;
- }
- }
- }
- return false;
- }
-
- private class ClassMerger {
-
- private final DexProgramClass source;
- private final DexProgramClass target;
- private final VerticalClassMergerGraphLens.Builder deferredRenamings =
- new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory());
- private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
-
- private boolean abortMerge = false;
-
- private ClassMerger(DexProgramClass source, DexProgramClass target) {
- this.source = source;
- this.target = target;
- }
-
- public boolean merge() throws ExecutionException {
- // Merge the class [clazz] into [targetClass] by adding all methods to
- // targetClass that are not currently contained.
- // Step 1: Merge methods
- Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
- addAll(existingMethods, target.methods(), MethodSignatureEquivalence.get());
-
- Map<Wrapper<DexMethod>, DexEncodedMethod> directMethods = new HashMap<>();
- Map<Wrapper<DexMethod>, DexEncodedMethod> virtualMethods = new HashMap<>();
-
- Predicate<DexMethod> availableMethodSignatures =
- (method) -> {
- Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method);
- return !existingMethods.contains(wrapped)
- && !directMethods.containsKey(wrapped)
- && !virtualMethods.containsKey(wrapped);
- };
-
- source.forEachProgramDirectMethod(
- directMethod -> {
- DexEncodedMethod definition = directMethod.getDefinition();
- if (definition.isInstanceInitializer()) {
- DexEncodedMethod resultingConstructor =
- renameConstructor(
- definition,
- candidate ->
- availableMethodSignatures.test(candidate)
- && source.lookupVirtualMethod(candidate) == null);
- add(directMethods, resultingConstructor, MethodSignatureEquivalence.get());
- blockRedirectionOfSuperCalls(resultingConstructor.getReference());
- } else {
- DexEncodedMethod resultingDirectMethod =
- renameMethod(
- definition,
- availableMethodSignatures,
- definition.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
- add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
- deferredRenamings.map(
- directMethod.getReference(), resultingDirectMethod.getReference());
- deferredRenamings.recordMove(
- directMethod.getReference(), resultingDirectMethod.getReference());
- blockRedirectionOfSuperCalls(resultingDirectMethod.getReference());
-
- // Private methods in the parent class may be targeted with invoke-super if the two
- // classes are in the same nest. Ensure such calls are mapped to invoke-direct.
- if (definition.isInstance()
- && definition.isPrivate()
- && AccessControl.isMemberAccessible(directMethod, source, target, appView)
- .isTrue()) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- directMethod.getReference(),
- prototypeChanges ->
- new MethodLookupResult(
- resultingDirectMethod.getReference(), null, DIRECT, prototypeChanges),
- target.getType());
- }
- }
- });
-
- for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
- DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod);
- if (shadowedBy != null) {
- if (virtualMethod.isAbstract()) {
- // Remove abstract/interface methods that are shadowed. The identity mapping below is
- // needed to ensure we correctly fixup the mapping in case the signature refers to
- // merged classes.
- deferredRenamings
- .map(virtualMethod.getReference(), shadowedBy.getReference())
- .map(shadowedBy.getReference(), shadowedBy.getReference())
- .recordMerge(virtualMethod.getReference(), shadowedBy.getReference());
-
- // The override now corresponds to the method in the parent, so unset its synthetic flag
- // if the method in the parent is not synthetic.
- if (!virtualMethod.isSyntheticMethod() && shadowedBy.isSyntheticMethod()) {
- shadowedBy.accessFlags.demoteFromSynthetic();
- }
- continue;
- }
- } else {
- if (abortMerge) {
- // If [virtualMethod] does not resolve to a single method in [target], abort.
- assert restoreDebuggingState(
- Streams.concat(directMethods.values().stream(), virtualMethods.values().stream()));
- return false;
- }
-
- // The method is not shadowed. If it is abstract, we can simply move it to the subclass.
- // Non-abstract methods are handled below (they cannot simply be moved to the subclass as
- // a virtual method, because they might be the target of an invoke-super instruction).
- if (virtualMethod.isAbstract()) {
- // Abort if target is non-abstract and does not override the abstract method.
- if (!target.isAbstract()) {
- assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
- abortMerge = true;
- return false;
- }
- // Update the holder of [virtualMethod] using renameMethod().
- DexEncodedMethod resultingVirtualMethod =
- renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
- resultingVirtualMethod.setLibraryMethodOverride(
- virtualMethod.isLibraryMethodOverride());
- deferredRenamings.map(
- virtualMethod.getReference(), resultingVirtualMethod.getReference());
- deferredRenamings.recordMove(
- virtualMethod.getReference(), resultingVirtualMethod.getReference());
- add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
- continue;
- }
- }
-
- DexEncodedMethod resultingMethod;
- if (source.accessFlags.isInterface()) {
- // Moving a default interface method into its subtype. This method could be hit directly
- // via an invoke-super instruction from any of the transitive subtypes of this interface,
- // due to the way invoke-super works on default interface methods. In order to be able
- // to hit this method directly after the merge, we need to make it public, and find a
- // method name that does not collide with one in the hierarchy of this class.
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- String resultingMethodBaseName =
- virtualMethod.getName().toString() + '$' + source.getTypeName().replace('.', '$');
- DexMethod resultingMethodReference =
- dexItemFactory.createMethod(
- target.getType(),
- virtualMethod.getProto().prependParameter(source.getType(), dexItemFactory),
- dexItemFactory.createGloballyFreshMemberString(resultingMethodBaseName));
- assert availableMethodSignatures.test(resultingMethodReference);
- resultingMethod =
- virtualMethod.toTypeSubstitutedMethodAsInlining(
- resultingMethodReference, dexItemFactory);
- makeStatic(resultingMethod);
- } else {
- // This virtual method could be called directly from a sub class via an invoke-super in-
- // struction. Therefore, we translate this virtual method into an instance method with a
- // unique name, such that relevant invoke-super instructions can be rewritten to target
- // this method directly.
- resultingMethod = renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
- if (appView.options().getProguardConfiguration().isAccessModificationAllowed()) {
- makePublic(resultingMethod);
- } else {
- makePrivate(resultingMethod);
- }
- }
-
- add(
- resultingMethod.belongsToDirectPool() ? directMethods : virtualMethods,
- resultingMethod,
- MethodSignatureEquivalence.get());
-
- // Record that invoke-super instructions in the target class should be redirected to the
- // newly created direct method.
- redirectSuperCallsInTarget(virtualMethod, resultingMethod);
- blockRedirectionOfSuperCalls(resultingMethod.getReference());
-
- if (shadowedBy == null) {
- // In addition to the newly added direct method, create a virtual method such that we do
- // not accidentally remove the method from the interface of this class.
- // Note that this method is added independently of whether it will actually be used. If
- // it turns out that the method is never used, it will be removed by the final round
- // of tree shaking.
- shadowedBy = buildBridgeMethod(virtualMethod, resultingMethod);
- deferredRenamings.recordCreationOfBridgeMethod(
- virtualMethod.getReference(), shadowedBy.getReference());
- add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
- }
-
- // Copy over any keep info from the original virtual method.
- ProgramMethod programMethod = new ProgramMethod(target, shadowedBy);
- appView
- .getKeepInfo()
- .mutate(
- mutableKeepInfoCollection ->
- mutableKeepInfoCollection.joinMethod(
- programMethod,
- info ->
- info.merge(
- mutableKeepInfoCollection
- .getMethodInfo(virtualMethod, source)
- .joiner())));
-
- deferredRenamings.map(virtualMethod.getReference(), shadowedBy.getReference());
- deferredRenamings.recordMove(
- virtualMethod.getReference(),
- resultingMethod.getReference(),
- resultingMethod.isStatic());
- }
-
- if (abortMerge) {
- assert restoreDebuggingState(
- Streams.concat(directMethods.values().stream(), virtualMethods.values().stream()));
- return false;
- }
-
- // Rewrite generic signatures before we merge a base with a generic signature.
- rewriteGenericSignatures(target, source, directMethods.values(), virtualMethods.values());
-
- // Convert out of DefaultInstanceInitializerCode, since this piece of code will require lens
- // code rewriting.
- target.forEachProgramInstanceInitializerMatching(
- method -> method.getCode().isDefaultInstanceInitializerCode(),
- method -> DefaultInstanceInitializerCode.uncanonicalizeCode(appView, method));
-
- // Step 2: Merge fields
- Set<DexString> existingFieldNames = new HashSet<>();
- for (DexEncodedField field : target.fields()) {
- existingFieldNames.add(field.getReference().name);
- }
-
- // In principle, we could allow multiple fields with the same name, and then only rename the
- // field in the end when we are done merging all the classes, if it it turns out that the two
- // fields ended up having the same type. This would not be too expensive, since we visit the
- // entire program using VerticalClassMerger.TreeFixer anyway.
- //
- // For now, we conservatively report that a signature is already taken if there is a field
- // with the same name. If minification is used with -overloadaggressively, this is solved
- // later anyway.
- Predicate<DexField> availableFieldSignatures =
- field -> !existingFieldNames.contains(field.name);
-
- DexEncodedField[] mergedInstanceFields =
- mergeFields(
- source.instanceFields(),
- target.instanceFields(),
- availableFieldSignatures,
- existingFieldNames);
-
- DexEncodedField[] mergedStaticFields =
- mergeFields(
- source.staticFields(),
- target.staticFields(),
- availableFieldSignatures,
- existingFieldNames);
-
- // Step 3: Merge interfaces
- Set<DexType> interfaces = mergeArrays(target.interfaces.values, source.interfaces.values);
- // Now destructively update the class.
- // Step 1: Update supertype or fix interfaces.
- if (source.isInterface()) {
- interfaces.remove(source.type);
- } else {
- assert !target.isInterface();
- target.superType = source.superType;
- }
- target.interfaces =
- interfaces.isEmpty()
- ? DexTypeList.empty()
- : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
- // Step 2: ensure -if rules cannot target the members that were merged into the target class.
- directMethods.values().forEach(feedback::markMethodCannotBeKept);
- virtualMethods.values().forEach(feedback::markMethodCannotBeKept);
- for (int i = 0; i < source.instanceFields().size(); i++) {
- feedback.markFieldCannotBeKept(mergedInstanceFields[i]);
- }
- for (int i = 0; i < source.staticFields().size(); i++) {
- feedback.markFieldCannotBeKept(mergedStaticFields[i]);
- }
- // Step 3: replace fields and methods.
- target.addDirectMethods(directMethods.values());
- target.addVirtualMethods(virtualMethods.values());
- target.setInstanceFields(mergedInstanceFields);
- target.setStaticFields(mergedStaticFields);
- // Step 4: Clear the members of the source class since they have now been moved to the target.
- source.getMethodCollection().clearDirectMethods();
- source.getMethodCollection().clearVirtualMethods();
- source.clearInstanceFields();
- source.clearStaticFields();
- // Step 5: Record merging.
- mergedClasses.put(source.type, target.type);
- if (source.isInterface()) {
- mergedInterfaces.put(source.type, target.type);
- }
- assert !abortMerge;
- assert GenericSignatureCorrectnessHelper.createForVerification(
- appView, GenericSignatureContextBuilder.createForSingleClass(appView, target))
- .evaluateSignaturesForClass(target)
- .isValid();
- return true;
- }
-
- /**
- * The rewriting of generic signatures is pretty simple, but require some bookkeeping. We take
- * the arguments to the base type:
- *
- * <pre>
- * class Sub<X> extends Base<X, String>
- * </pre>
- *
- * for
- *
- * <pre>
- * class Base<T,R> extends OtherBase<T> implements I<R> {
- * T t() { ... };
- * }
- * </pre>
- *
- * and substitute T -> X and R -> String
- */
- private void rewriteGenericSignatures(
- DexProgramClass target,
- DexProgramClass source,
- Collection<DexEncodedMethod> directMethods,
- Collection<DexEncodedMethod> virtualMethods) {
- ClassSignature targetSignature = target.getClassSignature();
- if (targetSignature.hasNoSignature()) {
- // Null out all source signatures that is moved, but do not clear out the class since this
- // could be referred to by other generic signatures.
- // TODO(b/147504070): If merging classes with enclosing/innerclasses, this needs to be
- // reconsidered.
- directMethods.forEach(DexEncodedMethod::clearGenericSignature);
- virtualMethods.forEach(DexEncodedMethod::clearGenericSignature);
- source.fields().forEach(DexEncodedMember::clearGenericSignature);
- return;
- }
- GenericSignaturePartialTypeArgumentApplier classApplier =
- getGenericSignatureArgumentApplier(target, source);
- if (classApplier == null) {
- target.clearClassSignature();
- target.members().forEach(DexEncodedMember::clearGenericSignature);
- return;
- }
- // We could generate a substitution map.
- ClassSignature rewrittenSource = classApplier.visitClassSignature(source.getClassSignature());
- // The variables in the class signature is now rewritten to use the targets argument.
- ClassSignatureBuilder builder = ClassSignature.builder();
- builder.addFormalTypeParameters(targetSignature.getFormalTypeParameters());
- if (!source.isInterface()) {
- if (rewrittenSource.hasSignature()) {
- builder.setSuperClassSignature(rewrittenSource.getSuperClassSignatureOrNull());
- } else {
- builder.setSuperClassSignature(new ClassTypeSignature(source.superType));
- }
- } else {
- builder.setSuperClassSignature(targetSignature.getSuperClassSignatureOrNull());
- }
- // Compute the seen set for interfaces to add. This is similar to the merging of interfaces
- // but allow us to maintain the type arguments.
- Set<DexType> seenInterfaces = new HashSet<>();
- if (source.isInterface()) {
- seenInterfaces.add(source.type);
- }
- for (ClassTypeSignature iFace : targetSignature.getSuperInterfaceSignatures()) {
- if (seenInterfaces.add(iFace.type())) {
- builder.addSuperInterfaceSignature(iFace);
- }
- }
- if (rewrittenSource.hasSignature()) {
- for (ClassTypeSignature iFace : rewrittenSource.getSuperInterfaceSignatures()) {
- if (!seenInterfaces.contains(iFace.type())) {
- builder.addSuperInterfaceSignature(iFace);
- }
- }
- } else {
- // Synthesize raw uses of interfaces to align with the actual class
- for (DexType iFace : source.interfaces) {
- if (!seenInterfaces.contains(iFace)) {
- builder.addSuperInterfaceSignature(new ClassTypeSignature(iFace));
- }
- }
- }
- target.setClassSignature(builder.build(appView.dexItemFactory()));
-
- // Go through all type-variable references for members and update them.
- CollectionUtils.forEach(
- method -> {
- MethodTypeSignature methodSignature = method.getGenericSignature();
- if (methodSignature.hasNoSignature()) {
- return;
- }
- method.setGenericSignature(
- classApplier
- .buildForMethod(methodSignature.getFormalTypeParameters())
- .visitMethodSignature(methodSignature));
- },
- directMethods,
- virtualMethods);
-
- source.forEachField(
- field -> {
- if (field.getGenericSignature().hasNoSignature()) {
- return;
- }
- field.setGenericSignature(
- classApplier.visitFieldTypeSignature(field.getGenericSignature()));
- });
- }
-
- private GenericSignaturePartialTypeArgumentApplier getGenericSignatureArgumentApplier(
- DexProgramClass target, DexProgramClass source) {
- assert target.getClassSignature().hasSignature();
- // We can assert proper structure below because the generic signature validator has run
- // before and pruned invalid signatures.
- List<FieldTypeSignature> genericArgumentsToSuperType =
- target
- .getClassSignature()
- .getGenericArgumentsToSuperType(source.type, appView.dexItemFactory());
- if (genericArgumentsToSuperType == null) {
- assert false : "Type should be present in generic signature";
- return null;
- }
- Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
- List<FormalTypeParameter> formals = source.getClassSignature().getFormalTypeParameters();
- if (genericArgumentsToSuperType.size() != formals.size()) {
- if (!genericArgumentsToSuperType.isEmpty()) {
- assert false : "Invalid argument count to formals";
- return null;
- }
- } else {
- for (int i = 0; i < formals.size(); i++) {
- // It is OK to override a generic type variable so we just use put.
- substitutionMap.put(formals.get(i).getName(), genericArgumentsToSuperType.get(i));
- }
- }
- return GenericSignaturePartialTypeArgumentApplier.build(
- appView,
- TypeParameterContext.empty().addPrunedSubstitutions(substitutionMap),
- (type1, type2) -> true,
- type -> true);
- }
-
- private boolean restoreDebuggingState(Stream<DexEncodedMethod> toBeDiscarded) {
- toBeDiscarded.forEach(
- method -> {
- assert !method.isObsolete();
- method.setObsolete();
- });
- source.forEachMethod(
- method -> {
- if (method.isObsolete()) {
- method.unsetObsolete();
- }
- });
- assert Streams.concat(Streams.stream(source.methods()), Streams.stream(target.methods()))
- .allMatch(method -> !method.isObsolete());
- return true;
- }
-
- public VerticalClassMergerGraphLens.Builder getRenamings() {
- return deferredRenamings;
- }
-
- public List<SynthesizedBridgeCode> getSynthesizedBridges() {
- return synthesizedBridges;
- }
-
- private void redirectSuperCallsInTarget(
- DexEncodedMethod oldTarget, DexEncodedMethod newTarget) {
- DexMethod oldTargetReference = oldTarget.getReference();
- DexMethod newTargetReference = newTarget.getReference();
- InvokeType newTargetType = newTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT;
- if (source.accessFlags.isInterface()) {
- // If we merge a default interface method from interface I to its subtype C, then we need
- // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()".
- //
- // Unlike when we merge a class into its subclass (the else-branch below), we should *not*
- // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
- // if I has a supertype J. This is due to the fact that invoke-super instructions that
- // resolve to a method on an interface never hit an implementation below that interface.
- deferredRenamings.mapVirtualMethodToDirectInType(
- oldTargetReference,
- prototypeChanges ->
- new MethodLookupResult(newTargetReference, null, STATIC, prototypeChanges),
- target.type);
- } else {
- // If we merge class B into class C, and class C contains an invocation super.m(), then it
- // is insufficient to rewrite "invoke-super B.m()" to "invoke-{direct,virtual} C.m$B()" (the
- // method C.m$B denotes the direct/virtual method that has been created in C for B.m). In
- // particular, there might be an instruction "invoke-super A.m()" in C that resolves to B.m
- // at runtime (A is a superclass of B), which also needs to be rewritten to
- // "invoke-{direct,virtual} C.m$B()".
- //
- // We handle this by adding a mapping for [target] and all of its supertypes.
- DexProgramClass holder = target;
- while (holder != null && holder.isProgramClass()) {
- DexMethod signatureInHolder =
- oldTargetReference.withHolder(holder, appView.dexItemFactory());
- // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
- boolean resolutionSucceeds =
- holder.lookupVirtualMethod(signatureInHolder) != null
- || appInfo.lookupSuperTarget(signatureInHolder, holder, appView) != null;
- if (resolutionSucceeds) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInHolder,
- prototypeChanges ->
- new MethodLookupResult(
- newTargetReference, null, newTargetType, prototypeChanges),
- target.type);
- } else {
- break;
- }
-
- // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
- // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
- // the code above. However, instructions on the form "invoke-super A.m()" should also be
- // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
- // that have been merged into [holder].
- Set<DexType> mergedTypes = mergedClasses.getKeys(holder.type);
- for (DexType type : mergedTypes) {
- DexMethod signatureInType =
- oldTargetReference.withHolder(type, appView.dexItemFactory());
- // Resolution would have succeeded if the method used to be in [type], or if one of
- // its super classes declared the method.
- boolean resolutionSucceededBeforeMerge =
- lensBuilder.hasMappingForSignatureInContext(holder, signatureInType)
- || appInfo.lookupSuperTarget(signatureInHolder, holder, appView) != null;
- if (resolutionSucceededBeforeMerge) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInType,
- prototypeChanges ->
- new MethodLookupResult(
- newTargetReference, null, newTargetType, prototypeChanges),
- target.type);
- }
- }
- holder =
- holder.superType != null
- ? asProgramClassOrNull(appInfo.definitionFor(holder.superType))
- : null;
- }
- }
- }
-
- private void blockRedirectionOfSuperCalls(DexMethod method) {
- // We are merging a class B into C. The methods from B are being moved into C, and then we
- // subsequently rewrite the invoke-super instructions in C that hit a method in B, such that
- // they use an invoke-direct instruction instead. In this process, we need to avoid rewriting
- // the invoke-super instructions that originally was in the superclass B.
- //
- // Example:
- // class A {
- // public void m() {}
- // }
- // class B extends A {
- // public void m() { super.m(); } <- invoke must not be rewritten to invoke-direct
- // (this would lead to an infinite loop)
- // }
- // class C extends B {
- // public void m() { super.m(); } <- invoke needs to be rewritten to invoke-direct
- // }
- deferredRenamings.markMethodAsMerged(method);
- }
-
- private DexEncodedMethod buildBridgeMethod(
- DexEncodedMethod method, DexEncodedMethod invocationTarget) {
- DexType holder = target.type;
- DexProto proto = method.getReference().proto;
- DexString name = method.getReference().name;
- DexMethod newMethod = application.dexItemFactory.createMethod(holder, proto, name);
- MethodAccessFlags accessFlags = method.accessFlags.copy();
- accessFlags.setBridge();
- accessFlags.setSynthetic();
- accessFlags.unsetAbstract();
-
- assert invocationTarget.isStatic()
- || invocationTarget.isNonPrivateVirtualMethod()
- || invocationTarget.isNonStaticPrivateMethod();
- SynthesizedBridgeCode code =
- new SynthesizedBridgeCode(
- newMethod,
- invocationTarget.getReference(),
- invocationTarget.isStatic()
- ? STATIC
- : (invocationTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT),
- target.isInterface());
-
- // Add the bridge to the list of synthesized bridges such that the method signatures will
- // be updated by the end of vertical class merging.
- synthesizedBridges.add(code);
-
- CfVersion classFileVersion =
- method.hasClassFileVersion() ? method.getClassFileVersion() : null;
- DexEncodedMethod bridge =
- DexEncodedMethod.syntheticBuilder()
- .setMethod(newMethod)
- .setAccessFlags(accessFlags)
- .setCode(code)
- .setClassFileVersion(classFileVersion)
- .setApiLevelForDefinition(method.getApiLevelForDefinition())
- .setApiLevelForCode(method.getApiLevelForDefinition())
- .setIsLibraryMethodOverride(method.isLibraryMethodOverride())
- .setGenericSignature(method.getGenericSignature())
- .build();
- if (method.accessFlags.isPromotedToPublic()) {
- // The bridge is now the public method serving the role of the original method, and should
- // reflect that this method was publicized.
- assert bridge.accessFlags.isPromotedToPublic();
- }
- return bridge;
- }
-
- @SuppressWarnings("ReferenceEquality")
- // Returns the method that shadows the given method, or null if method is not shadowed.
- private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
- MethodResolutionResult resolutionResult =
- appInfo.resolveMethodOnLegacy(target, method.getReference());
- if (!resolutionResult.isSingleResolution()) {
- // May happen in case of missing classes, or if multiple implementations were found.
- abortMerge = true;
- return null;
- }
- DexEncodedMethod actual = resolutionResult.getSingleTarget();
- if (actual != method) {
- assert actual.isVirtualMethod() == method.isVirtualMethod();
- return actual;
- }
- // The method is not actually overridden. This means that we will move `method` to the
- // subtype. If `method` is abstract, then so should the subtype be.
- return null;
- }
-
- private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void add(
- Map<Wrapper<R>, D> map, D item, Equivalence<R> equivalence) {
- map.put(equivalence.wrap(item.getReference()), item);
- }
-
- private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void addAll(
- Collection<Wrapper<R>> collection, Iterable<D> items, Equivalence<R> equivalence) {
- for (D item : items) {
- collection.add(equivalence.wrap(item.getReference()));
- }
- }
-
- private <T> Set<T> mergeArrays(T[] one, T[] other) {
- Set<T> merged = new LinkedHashSet<>();
- Collections.addAll(merged, one);
- Collections.addAll(merged, other);
- return merged;
- }
-
- private DexEncodedField[] mergeFields(
- Collection<DexEncodedField> sourceFields,
- Collection<DexEncodedField> targetFields,
- Predicate<DexField> availableFieldSignatures,
- Set<DexString> existingFieldNames) {
- DexEncodedField[] result = new DexEncodedField[sourceFields.size() + targetFields.size()];
- // Add fields from source
- int i = 0;
- for (DexEncodedField field : sourceFields) {
- DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
- existingFieldNames.add(resultingField.getReference().name);
- deferredRenamings.map(field.getReference(), resultingField.getReference());
- result[i] = resultingField;
- i++;
- }
- // Add fields from target.
- for (DexEncodedField field : targetFields) {
- result[i] = field;
- i++;
- }
- return result;
- }
-
- // Note that names returned by this function are not necessarily unique. Clients should
- // repeatedly try to generate a fresh name until it is unique.
- private DexString getFreshName(String nameString, int index, DexType holder) {
- String freshName = nameString + "$" + holder.toSourceString().replace('.', '$');
- if (index > 1) {
- freshName += index;
- }
- return application.dexItemFactory.createString(freshName);
- }
-
- private DexEncodedMethod renameConstructor(
- DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
- assert method.isInstanceInitializer();
- DexType oldHolder = method.getHolderType();
-
- DexMethod newSignature;
- int count = 1;
- do {
- DexString newName = getFreshName(TEMPORARY_INSTANCE_INITIALIZER_PREFIX, count, oldHolder);
- newSignature =
- application.dexItemFactory.createMethod(
- target.type, method.getReference().proto, newName);
- count++;
- } while (!availableMethodSignatures.test(newSignature));
-
- DexEncodedMethod result =
- method.toTypeSubstitutedMethodAsInlining(newSignature, appView.dexItemFactory());
- result.getMutableOptimizationInfo().markForceInline();
- deferredRenamings.map(method.getReference(), result.getReference());
- deferredRenamings.recordMove(method.getReference(), result.getReference());
- // Renamed constructors turn into ordinary private functions. They can be private, as
- // they are only references from their direct subclass, which they were merged into.
- result.accessFlags.unsetConstructor();
- makePrivate(result);
- return result;
- }
-
- private DexEncodedMethod renameMethod(
- DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures, Rename strategy) {
- return renameMethod(method, availableMethodSignatures, strategy, method.getReference().proto);
- }
-
- private DexEncodedMethod renameMethod(
- DexEncodedMethod method,
- Predicate<DexMethod> availableMethodSignatures,
- Rename strategy,
- DexProto newProto) {
- // We cannot handle renaming static initializers yet and constructors should have been
- // renamed already.
- assert !method.accessFlags.isConstructor() || strategy == Rename.NEVER;
- DexString oldName = method.getReference().name;
- DexType oldHolder = method.getHolderType();
-
- DexMethod newSignature;
- switch (strategy) {
- case IF_NEEDED:
- newSignature = application.dexItemFactory.createMethod(target.type, newProto, oldName);
- if (availableMethodSignatures.test(newSignature)) {
- break;
- }
- // Fall-through to ALWAYS so that we assign a new name.
-
- case ALWAYS:
- int count = 1;
- do {
- DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
- newSignature = application.dexItemFactory.createMethod(target.type, newProto, newName);
- count++;
- } while (!availableMethodSignatures.test(newSignature));
- break;
-
- case NEVER:
- newSignature = application.dexItemFactory.createMethod(target.type, newProto, oldName);
- assert availableMethodSignatures.test(newSignature);
- break;
-
- default:
- throw new Unreachable();
- }
-
- return method.toTypeSubstitutedMethodAsInlining(newSignature, appView.dexItemFactory());
- }
-
- private DexEncodedField renameFieldIfNeeded(
- DexEncodedField field, Predicate<DexField> availableFieldSignatures) {
- DexString oldName = field.getReference().name;
- DexType oldHolder = field.getHolderType();
-
- DexField newSignature =
- application.dexItemFactory.createField(target.type, field.getReference().type, oldName);
- if (!availableFieldSignatures.test(newSignature)) {
- int count = 1;
- do {
- DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
- newSignature =
- application.dexItemFactory.createField(
- target.type, field.getReference().type, newName);
- count++;
- } while (!availableFieldSignatures.test(newSignature));
- }
-
- return field.toTypeSubstitutedField(appView, newSignature);
- }
-
- private void makeStatic(DexEncodedMethod method) {
- method.accessFlags.setStatic();
- if (!method.getCode().isCfCode()) {
- // Due to member rebinding we may have inserted bridge methods with synthesized code.
- // Currently, there is no easy way to make such code static.
- abortMerge = true;
- }
- }
- }
-
- private static void makePrivate(DexEncodedMethod method) {
- assert !method.accessFlags.isAbstract();
- method.accessFlags.unsetPublic();
- method.accessFlags.unsetProtected();
- method.accessFlags.setPrivate();
- }
-
- private static void makePublic(DexEncodedMethod method) {
- MethodAccessFlags accessFlags = method.getAccessFlags();
- assert !accessFlags.isAbstract();
- accessFlags.unsetPrivate();
- accessFlags.unsetProtected();
- accessFlags.setPublic();
- }
-
- private static class VerticalClassMergerTreeFixer extends TreeFixerBase {
-
- private final AppView<AppInfoWithLiveness> appView;
- private final VerticalClassMergerGraphLens.Builder lensBuilder;
- private final VerticallyMergedClasses mergedClasses;
- private final List<SynthesizedBridgeCode> synthesizedBridges;
-
- VerticalClassMergerTreeFixer(
- AppView<AppInfoWithLiveness> appView,
- VerticalClassMergerGraphLens.Builder lensBuilder,
- VerticallyMergedClasses mergedClasses,
- List<SynthesizedBridgeCode> synthesizedBridges) {
- super(appView);
- this.appView = appView;
- this.lensBuilder =
- VerticalClassMergerGraphLens.Builder.createBuilderForFixup(lensBuilder, mergedClasses);
- this.mergedClasses = mergedClasses;
- this.synthesizedBridges = synthesizedBridges;
- }
-
- private VerticalClassMergerGraphLens fixupTypeReferences() {
- // Globally substitute merged class types in protos and holders.
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- clazz.getMethodCollection().replaceMethods(this::fixupMethod);
- clazz.setStaticFields(fixupFields(clazz.staticFields()));
- clazz.setInstanceFields(fixupFields(clazz.instanceFields()));
- clazz.setPermittedSubclassAttributes(
- fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()));
- }
- for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
- synthesizedBridge.updateMethodSignatures(this::fixupMethodReference);
- }
- VerticalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
- if (lens != null) {
- new AnnotationFixer(lens, appView.graphLens()).run(appView.appInfo().classes());
- }
- return lens;
- }
-
- @Override
- public DexType mapClassType(DexType type) {
- while (mergedClasses.hasBeenMergedIntoSubtype(type)) {
- type = mergedClasses.getTargetFor(type);
- }
- return type;
- }
-
- @Override
- public void recordClassChange(DexType from, DexType to) {
- // Fixup of classes is not used so no class type should change.
- throw new Unreachable();
- }
-
- @Override
- public void recordFieldChange(DexField from, DexField to) {
- if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
- lensBuilder.map(from, to);
- }
- }
-
- @Override
- public void recordMethodChange(DexMethod from, DexMethod to) {
- if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
- lensBuilder.map(from, to).recordMove(from, to);
- }
- }
-
- @Override
- public DexEncodedMethod recordMethodChange(
- DexEncodedMethod method, DexEncodedMethod newMethod) {
- recordMethodChange(method.getReference(), newMethod.getReference());
- if (newMethod.isNonPrivateVirtualMethod()) {
- // Since we changed the return type or one of the parameters, this method cannot be a
- // classpath or library method override, since we only class merge program classes.
- assert !method.isLibraryMethodOverride().isTrue();
- newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
- }
- return newMethod;
- }
- }
-
- private class CollisionDetector {
-
- private static final int NOT_FOUND = Integer.MIN_VALUE;
-
- // TODO(herhut): Maybe cache seenPositions for target classes.
- private final Map<DexString, Int2IntMap> seenPositions = new IdentityHashMap<>();
- private final Reference2IntMap<DexProto> targetProtoCache;
- private final Reference2IntMap<DexProto> sourceProtoCache;
- private final DexType source, target;
- private final Collection<DexMethod> invokes = getInvokes();
-
- private CollisionDetector(DexType source, DexType target) {
- this.source = source;
- this.target = target;
- this.targetProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
- this.targetProtoCache.defaultReturnValue(NOT_FOUND);
- this.sourceProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
- this.sourceProtoCache.defaultReturnValue(NOT_FOUND);
- }
-
- boolean mayCollide() {
- timing.begin("collision detection");
- fillSeenPositions();
- boolean result = false;
- // If the type is not used in methods at all, there cannot be any conflict.
- if (!seenPositions.isEmpty()) {
- for (DexMethod method : invokes) {
- Int2IntMap positionsMap = seenPositions.get(method.name);
- if (positionsMap != null) {
- int arity = method.getArity();
- int previous = positionsMap.get(arity);
- if (previous != NOT_FOUND) {
- assert previous != 0;
- int positions = computePositionsFor(method.proto, source, sourceProtoCache);
- if ((positions & previous) != 0) {
- result = true;
- break;
- }
- }
- }
- }
- }
- timing.end();
- return result;
- }
-
- private void fillSeenPositions() {
- for (DexMethod method : invokes) {
- DexType[] parameters = method.proto.parameters.values;
- int arity = parameters.length;
- int positions = computePositionsFor(method.proto, target, targetProtoCache);
- if (positions != 0) {
- Int2IntMap positionsMap =
- seenPositions.computeIfAbsent(method.name, k -> {
- Int2IntMap result = new Int2IntOpenHashMap();
- result.defaultReturnValue(NOT_FOUND);
- return result;
- });
- int value = 0;
- int previous = positionsMap.get(arity);
- if (previous != NOT_FOUND) {
- value = previous;
- }
- value |= positions;
- positionsMap.put(arity, value);
- }
- }
-
- }
-
- @SuppressWarnings("ReferenceEquality")
- // Given a method signature and a type, this method computes a bit vector that denotes the
- // positions at which the given type is used in the method signature.
- private int computePositionsFor(
- DexProto proto, DexType type, Reference2IntMap<DexProto> cache) {
- int result = cache.getInt(proto);
- if (result != NOT_FOUND) {
- return result;
- }
- result = 0;
- int bitsUsed = 0;
- int accumulator = 0;
- for (DexType parameterType : proto.parameters.values) {
- DexType parameterBaseType = parameterType.toBaseType(appView.dexItemFactory());
- // Substitute the type with the already merged class to estimate what it will look like.
- DexType mappedType = mergedClasses.getOrDefault(parameterBaseType, parameterBaseType);
- accumulator <<= 1;
- bitsUsed++;
- if (mappedType == type) {
- accumulator |= 1;
- }
- // Handle overflow on 31 bit boundary.
- if (bitsUsed == Integer.SIZE - 1) {
- result |= accumulator;
- accumulator = 0;
- bitsUsed = 0;
- }
- }
- // We also take the return type into account for potential conflicts.
- DexType returnBaseType = proto.returnType.toBaseType(appView.dexItemFactory());
- DexType mappedReturnType = mergedClasses.getOrDefault(returnBaseType, returnBaseType);
- accumulator <<= 1;
- if (mappedReturnType == type) {
- accumulator |= 1;
- }
- result |= accumulator;
- cache.put(proto, result);
- return result;
- }
- }
-
- @SuppressWarnings("ReferenceEquality")
- private AbortReason disallowInlining(ProgramMethod method, DexProgramClass context) {
- if (appView.options().inlinerOptions().enableInlining) {
- Code code = method.getDefinition().getCode();
- if (code.isCfCode()) {
- CfCode cfCode = code.asCfCode();
- ConstraintWithTarget constraint =
- cfCode.computeInliningConstraint(
- method,
- appView,
- new SingleTypeMapperGraphLens(method.getHolderType(), context),
- context.programInstanceInitializers().iterator().next());
- if (constraint == ConstraintWithTarget.NEVER) {
- return AbortReason.UNSAFE_INLINING;
- }
- // Constructors can have references beyond the root main dex classes. This can increase the
- // size of the main dex dependent classes and we should bail out.
- if (mainDexInfo.disallowInliningIntoContext(
- appView, context, method, appView.getSyntheticItems())) {
- return AbortReason.MAIN_DEX_ROOT_OUTSIDE_REFERENCE;
- }
- return null;
- } else if (code.isDefaultInstanceInitializerCode()) {
- return null;
- }
- // For non-jar/cf code we currently cannot guarantee that markForceInline() will succeed.
- }
- return AbortReason.UNSAFE_INLINING;
- }
-
- public class SingleTypeMapperGraphLens extends NonIdentityGraphLens {
-
- private final DexType source;
- private final DexProgramClass target;
-
- public SingleTypeMapperGraphLens(DexType source, DexProgramClass target) {
- super(appView.dexItemFactory(), GraphLens.getIdentityLens());
- this.source = source;
- this.target = target;
- }
-
- @Override
- public Iterable<DexType> getOriginalTypes(DexType type) {
- throw new Unreachable();
- }
-
- @Override
- public DexType getPreviousClassType(DexType type) {
- throw new Unreachable();
- }
-
- @Override
- @SuppressWarnings("ReferenceEquality")
- public final DexType getNextClassType(DexType type) {
- return type == source ? target.type : mergedClasses.getOrDefault(type, type);
- }
-
- @Override
- public DexField getPreviousFieldSignature(DexField field) {
- throw new Unreachable();
- }
-
- @Override
- public DexField getNextFieldSignature(DexField field) {
- throw new Unreachable();
- }
-
- @Override
- public DexMethod getPreviousMethodSignature(DexMethod method) {
- throw new Unreachable();
- }
-
- @Override
- public DexMethod getNextMethodSignature(DexMethod method) {
- throw new Unreachable();
- }
-
- @Override
- public MethodLookupResult lookupMethod(
- DexMethod method, DexMethod context, InvokeType type, GraphLens codeLens) {
- // First look up the method using the existing graph lens (for example, the type will have
- // changed if the method was publicized by ClassAndMemberPublicizer).
- MethodLookupResult lookup = appView.graphLens().lookupMethod(method, context, type, codeLens);
- // Then check if there is a renaming due to the vertical class merger.
- DexMethod newMethod = lensBuilder.methodMap.get(lookup.getReference());
- if (newMethod == null) {
- return lookup;
- }
- MethodLookupResult.Builder methodLookupResultBuilder =
- MethodLookupResult.builder(this)
- .setReference(newMethod)
- .setPrototypeChanges(lookup.getPrototypeChanges())
- .setType(lookup.getType());
- if (lookup.getType() == InvokeType.INTERFACE) {
- // If an interface has been merged into a class, invoke-interface needs to be translated
- // to invoke-virtual.
- DexClass clazz = appInfo.definitionFor(newMethod.holder);
- if (clazz != null && !clazz.accessFlags.isInterface()) {
- assert appInfo.definitionFor(method.holder).accessFlags.isInterface();
- methodLookupResultBuilder.setType(VIRTUAL);
- }
- }
- return methodLookupResultBuilder.build();
- }
-
- @Override
- protected MethodLookupResult internalDescribeLookupMethod(
- MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
- // This is unreachable since we override the implementation of lookupMethod() above.
- throw new Unreachable();
- }
-
- @Override
- public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
- DexMethod method, GraphLens codeLens) {
- throw new Unreachable();
- }
-
- @Override
- public DexField lookupField(DexField field, GraphLens codeLens) {
- return lensBuilder.fieldMap.getOrDefault(field, field);
- }
-
- @Override
- protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
- // This is unreachable since we override the implementation of lookupField() above.
- throw new Unreachable();
- }
-
- @Override
- @SuppressWarnings("HidingField")
- public boolean isContextFreeForMethods(GraphLens codeLens) {
- return true;
- }
- }
-
- // Searches for a reference to a non-private, non-public class, field or method declared in the
- // same package as [source].
- public static class IllegalAccessDetector extends UseRegistryWithResult<Boolean, ProgramMethod> {
-
- private final AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy;
-
- public IllegalAccessDetector(
- AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy,
- ProgramMethod context) {
- super(appViewWithClassHierarchy, context, false);
- this.appViewWithClassHierarchy = appViewWithClassHierarchy;
- }
-
- protected boolean checkFoundPackagePrivateAccess() {
- assert getResult();
- return true;
- }
-
- protected boolean setFoundPackagePrivateAccess() {
- setResult(true);
- return true;
- }
-
- protected static boolean continueSearchForPackagePrivateAccess() {
- return false;
- }
-
- private boolean checkFieldReference(DexField field) {
- return checkRewrittenFieldReference(appViewWithClassHierarchy.graphLens().lookupField(field));
- }
-
- private boolean checkRewrittenFieldReference(DexField field) {
- assert field.getHolderType().isClassType();
- DexType fieldHolder = field.getHolderType();
- if (fieldHolder.isSamePackage(getContext().getHolderType())) {
- if (checkRewrittenTypeReference(fieldHolder)) {
- return checkFoundPackagePrivateAccess();
- }
- DexClassAndField resolvedField =
- appViewWithClassHierarchy.appInfo().resolveField(field).getResolutionPair();
- if (resolvedField == null) {
- return setFoundPackagePrivateAccess();
- }
- if (resolvedField.getHolder() != getContext().getHolder()
- && !resolvedField.getAccessFlags().isPublic()) {
- return setFoundPackagePrivateAccess();
- }
- if (checkRewrittenFieldType(resolvedField)) {
- return checkFoundPackagePrivateAccess();
- }
- }
- return continueSearchForPackagePrivateAccess();
- }
-
- protected boolean checkRewrittenFieldType(DexClassAndField field) {
- return continueSearchForPackagePrivateAccess();
- }
-
- private boolean checkRewrittenMethodReference(
- DexMethod rewrittenMethod, OptionalBool isInterface) {
- DexType baseType =
- rewrittenMethod.getHolderType().toBaseType(appViewWithClassHierarchy.dexItemFactory());
- if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
- if (checkTypeReference(rewrittenMethod.getHolderType())) {
- return checkFoundPackagePrivateAccess();
- }
- MethodResolutionResult resolutionResult =
- isInterface.isUnknown()
- ? appViewWithClassHierarchy
- .appInfo()
- .unsafeResolveMethodDueToDexFormat(rewrittenMethod)
- : appViewWithClassHierarchy
- .appInfo()
- .resolveMethod(rewrittenMethod, isInterface.isTrue());
- if (!resolutionResult.isSingleResolution()) {
- return setFoundPackagePrivateAccess();
- }
- DexClassAndMethod resolvedMethod =
- resolutionResult.asSingleResolution().getResolutionPair();
- if (resolvedMethod.getHolder() != getContext().getHolder()
- && !resolvedMethod.getAccessFlags().isPublic()) {
- return setFoundPackagePrivateAccess();
- }
- }
- return continueSearchForPackagePrivateAccess();
- }
-
- private boolean checkTypeReference(DexType type) {
- return internalCheckTypeReference(type, appViewWithClassHierarchy.graphLens());
- }
-
- private boolean checkRewrittenTypeReference(DexType type) {
- return internalCheckTypeReference(type, GraphLens.getIdentityLens());
- }
-
- private boolean internalCheckTypeReference(DexType type, GraphLens graphLens) {
- DexType baseType =
- graphLens.lookupType(type.toBaseType(appViewWithClassHierarchy.dexItemFactory()));
- if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
- DexClass clazz = appViewWithClassHierarchy.definitionFor(baseType);
- if (clazz == null || !clazz.isPublic()) {
- return setFoundPackagePrivateAccess();
- }
- }
- return continueSearchForPackagePrivateAccess();
- }
-
- @Override
- public void registerInitClass(DexType clazz) {
- if (appViewWithClassHierarchy.initClassLens().isFinal()) {
- // The InitClass lens is always rewritten up until the most recent graph lens, so first map
- // the class type to the most recent graph lens.
- DexType rewrittenType = appViewWithClassHierarchy.graphLens().lookupType(clazz);
- DexField initClassField =
- appViewWithClassHierarchy.initClassLens().getInitClassField(rewrittenType);
- checkRewrittenFieldReference(initClassField);
- } else {
- checkTypeReference(clazz);
- }
- }
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {
- MethodLookupResult lookup =
- appViewWithClassHierarchy.graphLens().lookupInvokeVirtual(method, getContext());
- checkRewrittenMethodReference(lookup.getReference(), OptionalBool.FALSE);
- }
-
- @Override
- public void registerInvokeDirect(DexMethod method) {
- MethodLookupResult lookup =
- appViewWithClassHierarchy.graphLens().lookupInvokeDirect(method, getContext());
- checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
- }
-
- @Override
- public void registerInvokeStatic(DexMethod method) {
- MethodLookupResult lookup =
- appViewWithClassHierarchy.graphLens().lookupInvokeStatic(method, getContext());
- checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
- }
-
- @Override
- public void registerInvokeInterface(DexMethod method) {
- MethodLookupResult lookup =
- appViewWithClassHierarchy.graphLens().lookupInvokeInterface(method, getContext());
- checkRewrittenMethodReference(lookup.getReference(), OptionalBool.TRUE);
- }
-
- @Override
- public void registerInvokeSuper(DexMethod method) {
- MethodLookupResult lookup =
- appViewWithClassHierarchy.graphLens().lookupInvokeSuper(method, getContext());
- checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- checkFieldReference(field);
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {
- checkFieldReference(field);
- }
-
- @Override
- public void registerNewInstance(DexType type) {
- checkTypeReference(type);
- }
-
- @Override
- public void registerStaticFieldRead(DexField field) {
- checkFieldReference(field);
- }
-
- @Override
- public void registerStaticFieldWrite(DexField field) {
- checkFieldReference(field);
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- checkTypeReference(type);
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- checkTypeReference(type);
- }
- }
-
- public static class InvokeSpecialToDefaultLibraryMethodUseRegistry
- extends UseRegistryWithResult<Boolean, ProgramMethod> {
-
- InvokeSpecialToDefaultLibraryMethodUseRegistry(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
- super(appView, context, false);
- assert context.getHolder().isInterface();
- }
-
- @Override
- @SuppressWarnings("ReferenceEquality")
- public void registerInvokeSpecial(DexMethod method) {
- ProgramMethod context = getContext();
- if (method.getHolderType() != context.getHolderType()) {
- return;
- }
-
- DexEncodedMethod definition = context.getHolder().lookupMethod(method);
- if (definition != null && definition.belongsToVirtualPool()) {
- setResult(true);
- }
- }
-
- @Override
- public void registerInitClass(DexType type) {}
-
- @Override
- public void registerInvokeDirect(DexMethod method) {}
-
- @Override
- public void registerInvokeInterface(DexMethod method) {}
-
- @Override
- public void registerInvokeStatic(DexMethod method) {}
-
- @Override
- public void registerInvokeSuper(DexMethod method) {}
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {}
-
- @Override
- public void registerInstanceFieldRead(DexField field) {}
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {}
-
- @Override
- public void registerStaticFieldRead(DexField field) {}
-
- @Override
- public void registerStaticFieldWrite(DexField field) {}
-
- @Override
- public void registerTypeReference(DexType type) {}
- }
-
- public static class SynthesizedBridgeCode extends AbstractSynthesizedCode {
-
- private DexMethod method;
- private DexMethod invocationTarget;
- private InvokeType type;
- private final boolean isInterface;
-
- public SynthesizedBridgeCode(
- DexMethod method,
- DexMethod invocationTarget,
- InvokeType type,
- boolean isInterface) {
- this.method = method;
- this.invocationTarget = invocationTarget;
- this.type = type;
- this.isInterface = isInterface;
- }
-
- // By the time the synthesized code object is created, vertical class merging still has not
- // finished. Therefore it is possible that the method signatures `method` and `invocationTarget`
- // will change as a result of additional class merging operations. To deal with this, the
- // vertical class merger explicitly invokes this method to update `method` and `invocation-
- // Target` when vertical class merging has finished.
- //
- // Note that, without this step, these method signatures might refer to intermediate signatures
- // that are only present in the middle of vertical class merging, which means that the graph
- // lens will not work properly (since the graph lens generated by vertical class merging only
- // expects to be applied to method signatures from *before* vertical class merging or *after*
- // vertical class merging).
- public void updateMethodSignatures(Function<DexMethod, DexMethod> transformer) {
- method = transformer.apply(method);
- invocationTarget = transformer.apply(invocationTarget);
- }
-
- @Override
- public SourceCodeProvider getSourceCodeProvider() {
- ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
- ForwardMethodSourceCode.builder(method);
- forwardSourceCodeBuilder
- .setReceiver(method.holder)
- .setTargetReceiver(type.isStatic() ? null : method.holder)
- .setTarget(invocationTarget)
- .setInvokeType(type)
- .setIsInterface(isInterface);
- return forwardSourceCodeBuilder::build;
- }
-
- @Override
- public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
- return registry -> {
- assert registry.getTraversalContinuation().shouldContinue();
- switch (type) {
- case DIRECT:
- registry.registerInvokeDirect(invocationTarget);
- break;
- case STATIC:
- registry.registerInvokeStatic(invocationTarget);
- break;
- case VIRTUAL:
- registry.registerInvokeVirtual(invocationTarget);
- break;
- default:
- throw new Unreachable("Unexpected invocation type: " + type);
- }
- };
- }
- }
-
- public Collection<DexType> getRemovedClasses() {
- return Collections.unmodifiableCollection(mergedClasses.keySet());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index da83f1e..068a1a7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -178,7 +178,7 @@
EnclosingMethodAttribute enclosingMembers = null;
List<InnerClassAttribute> innerClasses = Collections.emptyList();
for (SyntheticMethodBuilder builder : methods) {
- DexEncodedMethod method = builder.build();
+ DexEncodedMethod method = builder.build(getClassKind());
if (method.isNonPrivateVirtualMethod()) {
virtualMethods.add(method);
} else {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 781be07..41c19f5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -1002,7 +1002,7 @@
builder.setName(methodReference.getName());
builder.setProto(methodReference.getProto());
buildMethodCallback.accept(builder);
- methodDefinition = builder.build();
+ methodDefinition = builder.build(clazz.getKind());
methodCollection.addMethod(methodDefinition);
newMethodCallback.accept((T) DexClassAndMethod.create(clazz, methodDefinition));
return methodDefinition;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 64746c9..b758859 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -19,6 +20,7 @@
import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.OptionalBool;
public class SyntheticMethodBuilder {
@@ -40,6 +42,7 @@
private ComputedApiLevel apiLevelForDefinition = ComputedApiLevel.notSet();
private ComputedApiLevel apiLevelForCode = ComputedApiLevel.notSet();
private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
+ private OptionalBool isLibraryMethodOverride = OptionalBool.UNKNOWN;
private boolean checkAndroidApiLevels = true;
@@ -55,8 +58,9 @@
this.syntheticKind = syntheticKind;
}
- public boolean hasName() {
- return name != null;
+ public SyntheticMethodBuilder setIsLibraryMethodOverride(OptionalBool isLibraryMethodOverride) {
+ this.isLibraryMethodOverride = isLibraryMethodOverride;
+ return this;
}
public SyntheticMethodBuilder setName(String name) {
@@ -127,7 +131,7 @@
return this;
}
- DexEncodedMethod build() {
+ DexEncodedMethod build(ClassKind<?> classKind) {
assert name != null;
DexMethod methodSignature = getMethodSignature();
MethodAccessFlags accessFlags = getAccessFlags();
@@ -145,6 +149,11 @@
.setApiLevelForCode(apiLevelForCode)
.setOptimizationInfo(optimizationInfo)
.applyIf(!checkAndroidApiLevels, DexEncodedMethod.Builder::disableAndroidApiLevelCheck)
+ .applyIf(
+ classKind == ClassKind.PROGRAM
+ && accessFlags.belongsToVirtualPool()
+ && !isLibraryMethodOverride.isUnknown(),
+ builder -> builder.setIsLibraryMethodOverride(isLibraryMethodOverride))
.build();
assert !syntheticKind.isSingleSyntheticMethod()
|| isValidSingleSyntheticMethod(method, syntheticKind);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 4707606..a1795a7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -61,6 +61,10 @@
public final SyntheticKind LAMBDA = generator.forInstanceClass("Lambda");
public final SyntheticKind THREAD_LOCAL = generator.forInstanceClass("ThreadLocal");
+ // Merging not permitted since this could defeat the purpose of the synthetic class.
+ public final SyntheticKind SHARED_SUPER_CLASS =
+ generator.forNonSharableInstanceClass("SharedSuper");
+
// TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT =
generator.forNonSharableInstanceClass("$IA");
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 7762a9c..27d7516 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -151,6 +151,15 @@
return array;
}
+ public static <T> boolean all(T[] elements, T elementToLookFor) {
+ for (Object element : elements) {
+ if (!Objects.equals(element, elementToLookFor)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static <T> boolean contains(T[] elements, T elementToLookFor) {
for (Object element : elements) {
if (Objects.equals(element, elementToLookFor)) {
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
index 4b20c06..47df9b4 100644
--- a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -47,6 +47,10 @@
return (s, t) -> {};
}
+ public static <S, T, U> TriConsumer<S, T, U> emptyTriConsumer() {
+ return (s, t, u) -> {};
+ }
+
public static <T> ThrowingConsumer<T, RuntimeException> emptyThrowingConsumer() {
return ignore -> {};
}
diff --git a/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java b/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
index 368974b..da6b74e 100644
--- a/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
+++ b/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
@@ -46,6 +46,11 @@
public boolean shouldFailCompilation() {
throw new Unreachable();
}
+
+ @Override
+ public boolean shouldLogDumpInfoMessage() {
+ throw new Unreachable();
+ }
};
}
@@ -85,6 +90,8 @@
public abstract boolean shouldFailCompilation();
+ public abstract boolean shouldLogDumpInfoMessage();
+
abstract static class DumpInputToFileOrDirectoryFlags extends DumpInputFlags {
@Override
@@ -99,5 +106,10 @@
}
return true;
}
+
+ @Override
+ public boolean shouldLogDumpInfoMessage() {
+ return true;
+ }
}
}
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 174afcc..de093c4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.GlobalSyntheticsConsumer;
import com.android.tools.r8.MapIdProvider;
import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.ResourceShrinkerConfiguration;
import com.android.tools.r8.SourceFileProvider;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.SyntheticInfoConsumer;
@@ -46,6 +47,7 @@
import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.AppView.WholeProgramOptimizations;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClasspathClass;
@@ -59,8 +61,8 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.Policy;
@@ -75,7 +77,6 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.nest.Nest;
import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
import com.android.tools.r8.lightir.IR2LirConverter;
import com.android.tools.r8.lightir.LirCode;
@@ -83,6 +84,7 @@
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MapConsumer;
import com.android.tools.r8.naming.MapVersion;
+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.redundantbridgeremoval.RedundantBridgeRemovalOptions;
@@ -97,6 +99,7 @@
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration;
import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration;
+import com.android.tools.r8.repackaging.RepackagingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.GlobalKeepInfoConfiguration;
@@ -107,6 +110,8 @@
import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.structural.Ordered;
+import com.android.tools.r8.verticalclassmerging.VerticalClassMergerOptions;
+import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Predicates;
@@ -195,6 +200,9 @@
public CancelCompilationChecker cancelCompilationChecker = null;
public AndroidResourceProvider androidResourceProvider = null;
public AndroidResourceConsumer androidResourceConsumer = null;
+ public ResourceShrinkerConfiguration resourceShrinkerConfiguration =
+ ResourceShrinkerConfiguration.DEFAULT_CONFIGURATION;
+ public ResourceAccessAnalysis resourceAccessAnalysis = null;
public boolean checkIfCancelled() {
if (cancelCompilationChecker == null) {
@@ -272,6 +280,10 @@
itemFactory = proguardConfiguration.getDexItemFactory();
enableTreeShaking = proguardConfiguration.isShrinking();
enableMinification = proguardConfiguration.isObfuscating();
+ // TODO(b/244238384): Enable.
+ enableStringFormatOptimization =
+ System.getProperty("com.android.tools.r8.optimizeStringFormat") != null;
+
if (!proguardConfiguration.isOptimizing()) {
// TODO(b/171457102): Avoid the need for this.
// -dontoptimize disables optimizations by flipping related flags.
@@ -316,13 +328,13 @@
disableGlobalOptimizations();
enableNameReflectionOptimization = false;
enableStringConcatenationOptimization = false;
+ enableStringFormatOptimization = false;
}
public void disableGlobalOptimizations() {
inlinerOptions.enableInlining = false;
enableClassInlining = false;
enableDevirtualization = false;
- enableVerticalClassMerging = false;
enableEnumUnboxing = false;
outline.enabled = false;
enableEnumValueOptimization = false;
@@ -331,6 +343,7 @@
enableInitializedClassesAnalysis = false;
callSiteOptimizationOptions.disableOptimization();
horizontalClassMergerOptions.setRestrictToSynthetics();
+ verticalClassMergerOptions.disable();
}
// Configure options according to platform build assumptions.
@@ -386,7 +399,6 @@
// Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
public boolean enableFieldBitAccessAnalysis =
System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
- public boolean enableVerticalClassMerging = true;
public boolean enableUnusedInterfaceRemoval = true;
public boolean enableDevirtualization = true;
public boolean enableEnumUnboxing = true;
@@ -400,6 +412,8 @@
public boolean enableServiceLoaderRewriting = true;
public boolean enableNameReflectionOptimization = true;
public boolean enableStringConcatenationOptimization = true;
+ // Enabled only for R8 (not D8).
+ public boolean enableStringFormatOptimization;
public boolean enableTreeShakingOfLibraryMethodOverrides = false;
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
@@ -474,7 +488,7 @@
public boolean createSingletonsForStatelessLambdas =
System.getProperty("com.android.tools.r8.createSingletonsForStatelessLambdas") != null;
- // TODO(b/293591931): Remove this flag.
+ // TODO(b/293591931): Remove this flag when records are stable in Platform
// Flag to allow record annotations in DEX. See b/231930852 for context.
private final boolean emitRecordAnnotationsInDex =
System.getProperty("com.android.tools.r8.emitRecordAnnotationsInDex") != null;
@@ -482,6 +496,9 @@
// Flag to allow nest annotations in DEX. See b/231930852 for context.
public boolean emitNestAnnotationsInDex =
System.getProperty("com.android.tools.r8.emitNestAnnotationsInDex") != null;
+ // Flag to allow force nest desugaring, even if natively supported on the chosen API level.
+ public boolean forceNestDesugaring =
+ System.getProperty("com.android.tools.r8.forceNestDesugaring") != null;
// TODO(b/293591931): Remove this flag.
// Flag to allow permitted subclasses annotations in DEX. See b/231930852 for context.
@@ -644,10 +661,12 @@
if (featureSplitConfiguration != null) {
for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
ProgramConsumer programConsumer = featureSplit.getProgramConsumer();
- programConsumer.finished(reporter);
- DataResourceConsumer dataResourceConsumer = programConsumer.getDataResourceConsumer();
- if (dataResourceConsumer != null) {
- dataResourceConsumer.finished(reporter);
+ if (programConsumer != null) {
+ programConsumer.finished(reporter);
+ DataResourceConsumer dataResourceConsumer = programConsumer.getDataResourceConsumer();
+ if (dataResourceConsumer != null) {
+ dataResourceConsumer.finished(reporter);
+ }
}
}
}
@@ -852,13 +871,15 @@
* and check cast instructions needs to be collected.
*/
public boolean isClassMergingExtensionRequired(Enqueuer.Mode mode) {
+ WholeProgramOptimizations wholeProgramOptimizations = WholeProgramOptimizations.ON;
if (mode.isInitialTreeShaking()) {
- return (horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL)
- && !horizontalClassMergerOptions.isRestrictedToSynthetics())
- || enableVerticalClassMerging;
+ return horizontalClassMergerOptions.isEnabled(
+ HorizontalClassMerger.Mode.INITIAL, wholeProgramOptimizations)
+ && !horizontalClassMergerOptions.isRestrictedToSynthetics();
}
if (mode.isFinalTreeShaking()) {
- return horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL)
+ return horizontalClassMergerOptions.isEnabled(
+ HorizontalClassMerger.Mode.FINAL, wholeProgramOptimizations)
&& !horizontalClassMergerOptions.isRestrictedToSynthetics();
}
assert false;
@@ -910,6 +931,8 @@
private final InlinerOptions inlinerOptions = new InlinerOptions(this);
private final HorizontalClassMergerOptions horizontalClassMergerOptions =
new HorizontalClassMergerOptions();
+ private final VerticalClassMergerOptions verticalClassMergerOptions =
+ new VerticalClassMergerOptions(this);
private final OpenClosedInterfacesOptions openClosedInterfacesOptions =
new OpenClosedInterfacesOptions();
private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
@@ -959,6 +982,10 @@
return horizontalClassMergerOptions;
}
+ public VerticalClassMergerOptions getVerticalClassMergerOptions() {
+ return verticalClassMergerOptions;
+ }
+
public ProtoShrinkingOptions protoShrinking() {
return protoShrinking;
}
@@ -1714,6 +1741,8 @@
parseSystemPropertyForDevelopmentOrDefault(
"com.android.tools.r8.inliningInstructionLimit", -1);
+ public boolean enableSimpleInliningInstructionLimitIncrement = true;
+
public int[] multiCallerInliningInstructionLimits =
new int[] {Integer.MAX_VALUE, 28, 16, 12, 10};
@@ -1746,7 +1775,7 @@
}
public static void setOnlyForceInlining(InternalOptions options) {
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.testing.validInliningReasons = ImmutableSet.of();
}
public int getSimpleInliningInstructionLimit() {
@@ -1839,12 +1868,18 @@
return enableClassInitializerDeadlockDetection;
}
- public boolean isEnabled(HorizontalClassMerger.Mode mode) {
+ public boolean isEnabled(
+ HorizontalClassMerger.Mode mode, WholeProgramOptimizations wholeProgramOptimizations) {
if (!enable || debug || intermediate) {
return false;
}
+ if (wholeProgramOptimizations.isOn()) {
+ if (!isOptimizing() || !isShrinking()) {
+ return false;
+ }
+ }
if (mode.isInitial()) {
- return enableInitial && inlinerOptions.enableInlining && isShrinking();
+ return enableInitial && inlinerOptions.enableInlining;
}
assert mode.isFinal();
return true;
@@ -2142,6 +2177,7 @@
public static class TestingOptions {
+ public boolean enableNumberUnboxer = false;
public boolean roundtripThroughLir = false;
public boolean canUseLir(AppView<?> appView) {
@@ -2306,13 +2342,19 @@
public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration>
repackagingConfigurationFactory = DefaultRepackagingConfiguration::new;
- public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
- ConsumerUtils.emptyBiConsumer();
+ public TriConsumer<DexItemFactory, HorizontallyMergedClasses, HorizontalClassMerger.Mode>
+ horizontallyMergedClassesConsumer = ConsumerUtils.emptyTriConsumer();
public Function<List<Policy>, List<Policy>> horizontalClassMergingPolicyRewriter =
Function.identity();
public TriFunction<AppView<?>, Iterable<DexProgramClass>, DexProgramClass, DexProgramClass>
horizontalClassMergingTarget = (appView, candidates, target) -> target;
+ public BiConsumer<DexItemFactory, NamingLens> namingLensConsumer =
+ ConsumerUtils.emptyBiConsumer();
+
+ public BiConsumer<DexItemFactory, RepackagingLens> repackagingLensConsumer =
+ ConsumerUtils.emptyBiConsumer();
+
public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer =
ConsumerUtils.emptyBiConsumer();
@@ -2351,6 +2393,7 @@
public boolean allowUnusedDontWarnRules = true;
public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
public boolean alwaysUsePessimisticRegisterAllocation = false;
+ public boolean enableBridgeHoistingToSharedSyntheticSuperclass = false;
public boolean enableCheckCastAndInstanceOfRemoval = true;
public boolean enableDeadSwitchCaseElimination = true;
public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
@@ -2360,6 +2403,7 @@
public boolean enableEnumUnboxingDebugLogs =
System.getProperty("com.android.tools.r8.enableEnumUnboxingDebugLogs") != null;
public boolean enableEnumWithSubtypesUnboxing = true;
+ public boolean enableVerticalClassMergerLensAssertion = false;
public boolean forceRedundantConstNumberRemoval = false;
public boolean enableExperimentalDesugaredLibraryKeepRuleGenerator = false;
public boolean invertConditionals = false;
@@ -2404,6 +2448,9 @@
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",
@@ -2640,11 +2687,11 @@
}
public boolean canUseNestBasedAccess() {
- return hasFeaturePresentFrom(null) || emitNestAnnotationsInDex;
+ return (hasFeaturePresentFrom(null) || emitNestAnnotationsInDex) && !forceNestDesugaring;
}
public boolean canUseRecords() {
- return hasFeaturePresentFrom(AndroidApiLevel.U) || emitRecordAnnotationsInDex;
+ return hasFeaturePresentFrom(null) || emitRecordAnnotationsInDex;
}
public boolean canUseSealedClasses() {
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 506d1e3b..6730a23 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -88,13 +88,24 @@
return null;
}
+ /**
+ * Returns the previous element or null if !hasPrevious(). A subsequent call to iterator.remove()
+ * will remove the peeked element.
+ */
public static <T> T peekPrevious(ListIterator<T> iterator) {
- T previous = iterator.previous();
- T next = iterator.next();
- assert previous == next;
- return previous;
+ if (iterator.hasPrevious()) {
+ T previous = iterator.previous();
+ T next = iterator.next();
+ assert previous == next;
+ return previous;
+ }
+ return null;
}
+ /**
+ * Returns the next element or null if !hasNext(). A subsequent call to iterator.remove() will
+ * remove the peeked element.
+ */
public static <T> T peekNext(ListIterator<T> iterator) {
if (iterator.hasNext()) {
T next = iterator.next();
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index d09475d..4be2a4e 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -8,6 +8,7 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
@@ -354,4 +355,8 @@
ts.addAll(other);
return ts;
}
+
+ public static <T> List<T> unmodifiableForTesting(List<T> list) {
+ return InternalOptions.assertionsEnabled() ? Collections.unmodifiableList(list) : list;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ObjectUtils.java b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
index 9dfd1e0..1ceaf95 100644
--- a/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
@@ -21,6 +21,10 @@
return a == b;
}
+ public static boolean notIdentical(Object a, Object b) {
+ return !identical(a, b);
+ }
+
public static <S, T> T mapNotNull(S object, Function<? super S, ? extends T> fn) {
if (object != null) {
return fn.apply(object);
diff --git a/src/main/java/com/android/tools/r8/utils/ValueUtils.java b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
index 60b2e4e..dcb72ad 100644
--- a/src/main/java/com/android/tools/r8/utils/ValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
@@ -6,12 +6,29 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewArrayFilled;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
public class ValueUtils {
+ // We allocate an array of this size, so guard against it getting too big.
+ private static int MAX_ARRAY_SIZE = 100000;
+ private static boolean DEBUG =
+ System.getProperty("com.android.tools.r8.debug.computeSingleUseArrayValues") != null;
@SuppressWarnings("ReferenceEquality")
public static boolean isStringBuilder(Value value, DexItemFactory dexItemFactory) {
@@ -45,4 +62,306 @@
return false;
}
}
+
+ public static final class ArrayValues {
+ private List<Value> elementValues;
+ private ArrayPut[] arrayPutsByIndex;
+
+ private ArrayValues(List<Value> elementValues) {
+ this.elementValues = elementValues;
+ }
+
+ private ArrayValues(ArrayPut[] arrayPutsByIndex) {
+ this.arrayPutsByIndex = arrayPutsByIndex;
+ }
+
+ /** May contain null entries when array has null entries. */
+ public List<Value> getElementValues() {
+ if (elementValues == null) {
+ ArrayPut[] puts = arrayPutsByIndex;
+ Value[] elementValuesArr = new Value[puts.length];
+ for (int i = 0; i < puts.length; ++i) {
+ ArrayPut arrayPut = puts[i];
+ elementValuesArr[i] = arrayPut == null ? null : arrayPut.value();
+ }
+ elementValues = Arrays.asList(elementValuesArr);
+ }
+ return elementValues;
+ }
+
+ public int size() {
+ return elementValues != null ? elementValues.size() : arrayPutsByIndex.length;
+ }
+ }
+
+ private static BasicBlock findNextUnseen(
+ int startIdx, List<BasicBlock> predecessors, Set<BasicBlock> seen) {
+ int size = predecessors.size();
+ for (int i = startIdx; i < size; ++i) {
+ BasicBlock next = predecessors.get(i);
+ if (!seen.contains(next)) {
+ return next;
+ }
+ }
+ return null;
+ }
+
+ private static void debugLog(IRCode code, String message) {
+ System.err.println(message + " method=" + code.context().getReference());
+ }
+
+ /**
+ * Returns all dominator blocks of destBlock between (and including) it and sourceBlock. Returns
+ * null if sourceBlock is not a dominator of destBlock. Returns null if the algorithm is taking
+ * too long.
+ */
+ private static Set<BasicBlock> computeSimpleCaseDominatorBlocks(
+ BasicBlock sourceBlock, BasicBlock destBlock, IRCode code) {
+ // Fast-path: blocks are the same.
+ // As of Nov 2023 in Chrome for String.format() optimization, this is hit 77% of the time .
+ if (destBlock == sourceBlock) {
+ if (DEBUG) {
+ debugLog(code, "computeSimpleCaseDominatorBlocks: SAME BLOCK");
+ }
+ return Collections.singleton(sourceBlock);
+ }
+
+ // Fast-path: Linear path from source -> dest.
+ // As of Nov 2023 in Chrome for String.format() optimization, this is hit 14% of the time .
+ BasicBlock curBlock = destBlock;
+ List<BasicBlock> curPredecessors;
+ Set<BasicBlock> ret = Sets.newIdentityHashSet();
+ while (true) {
+ curPredecessors = curBlock.getPredecessors();
+ ret.add(curBlock);
+ if (curBlock == sourceBlock) {
+ if (DEBUG) {
+ debugLog(code, "computeSimpleCaseDominatorBlocks: LINEAR PATH");
+ }
+ return ret;
+ }
+
+ BasicBlock nextBlock = findNextUnseen(0, curPredecessors, ret);
+ if (nextBlock == null) {
+ debugLog(code, "computeSimpleCaseDominatorBlocks: Not a dominator.");
+ return null;
+ }
+ if (findNextUnseen(curPredecessors.indexOf(nextBlock) + 1, curPredecessors, ret) != null) {
+ // Multiple predecessors.
+ break;
+ }
+
+ curBlock = nextBlock;
+ }
+
+ // Algorithm: Traverse all predecessor paths between curBlock and sourceBlock, tracking the
+ // number of times each block is visited.
+ // Returns blocks with a "visit count" equal to the number of paths.
+ // This algorithm has worst case exponential complexity (for a fully connected graph).
+ // Rather than falling back to a DominatorTree for complex cases, this currently just returns
+ // an empty set when the number of paths is not small. Thus, it should not be used when false
+ // negatives are not acceptable.
+ //
+ // As of Nov 2023 in Chrome for String.format() optimization, the totalPathCounts were:
+ // 2 * 12, 3 * 6, 4 * 2, 5 * 1, 12 * 1, 22 * 1, 24 * 1, 35 * 4,
+ // MAX_PATH_COUNT * 2 (even with MAX_PATH_COUNT=3200)
+ final int MAX_PATH_COUNT = 36;
+
+ // TODO(agrieve): Lower MAX_PATH_COUNT and use DominatorTree for complicated cases. Or...
+ // Algorithm vNext:
+ // Track the fraction of paths that reach each node. Doing so would be ~linear for graphs
+ // without cycles. E.g.:
+ // DestNodeValue=1
+ // NodeValue=SUM(block.NodeValue / block.numSuccessors for block in successors)
+ // Dominators=(b for b in blocks if b.NodeValue == 1)
+ // To choose which block to visit next, choose any where all predecessors have already been
+ // visited. If no block has all predecessors visited, then an unvisited block is from a
+ // cycle (they cannot be from outside of the sourceBlock->destBlock subgraph so long as
+ // sourceBlock dominates destBlock).
+ // To deal with cycles (clearly not linear time now):
+ // * Find all blocks that participate in a cycle by doing a full traversal starting from
+ // each candidate until one is found. Store the set of edges that do not reach sourceBlock
+ // (except through the cycle).
+ // * When computing the value of a block whose successors participate in a cycle:
+ // NodeValue=SUM(block.NodeValue / effectiveNumSuccessors for block in successors)
+ // where effectiveNumSuccessors=SUM(1 for s in successors if (b->s) not in cycleOnlyEdges)
+
+ Multiset<BasicBlock> blockCounts = HashMultiset.create();
+ int totalPathCount = 0;
+
+ // Should never need to re-visit initial single-track nodes.
+ Set<BasicBlock> seen = Sets.newIdentityHashSet();
+ seen.addAll(ret);
+ ArrayDeque<BasicBlock> pathStack = new ArrayDeque<>();
+
+ pathStack.add(curBlock);
+ pathStack.add(curPredecessors.get(0));
+ while (true) {
+ curBlock = pathStack.getLast();
+ curPredecessors = curBlock.getPredecessors();
+ if (curBlock == sourceBlock) {
+ // Add every block for every connected path.
+ blockCounts.addAll(pathStack);
+ totalPathCount += 1;
+ if (totalPathCount > MAX_PATH_COUNT) {
+ if (DEBUG) {
+ debugLog(code, "computeSimpleCaseDominatorBlocks: Reached MAX_PATH_COUNT");
+ }
+ return null;
+ }
+ } else if (!seen.contains(curBlock)) {
+ if (curPredecessors.isEmpty()) {
+ // Finding the entry block means the sourceBlock is not a dominator.
+ if (DEBUG) {
+ debugLog(code, "computeSimpleCaseDominatorBlocks: sourceBlock not a dominator");
+ }
+ return null;
+ }
+ // Going deeper.
+ BasicBlock nextBlock = findNextUnseen(0, curPredecessors, seen);
+ if (nextBlock != null) {
+ seen.add(curBlock);
+ pathStack.add(nextBlock);
+ continue;
+ }
+ } else {
+ seen.remove(curBlock);
+ }
+ // Popping.
+ pathStack.removeLast();
+ List<BasicBlock> prevPredecessors = pathStack.getLast().getPredecessors();
+ int nextBlockIdx = prevPredecessors.indexOf(curBlock) + 1;
+ BasicBlock nextBlock = findNextUnseen(nextBlockIdx, prevPredecessors, seen);
+ if (nextBlock != null) {
+ pathStack.add(nextBlock);
+ } else if (pathStack.size() == 1) {
+ break;
+ }
+ }
+
+ for (var entry : blockCounts.entrySet()) {
+ if (entry.getCount() == totalPathCount) {
+ ret.add(entry.getElement());
+ }
+ }
+ if (DEBUG) {
+ debugLog(code, "computeSimpleCaseDominatorBlocks: PATH COUNT " + totalPathCount);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Attempts to determine all values for the given array. This will work only when:
+ *
+ * <pre>
+ * 1) The Array has a single users (other than array-puts)
+ * * This constraint is to ensure other users do not modify the array.
+ * * When users are in different blocks, their order is hard to know.
+ * 2) The array size is a constant.
+ * 3) All array-put instructions have constant and unique indices.
+ * * Indices must be unique because order is hard to know when multiple blocks are concerned.
+ * 4) The array-put instructions are guaranteed to be executed before singleUser.
+ * </pre>
+ *
+ * @param arrayValue The Value for the array.
+ * @param singleUser The only non-array-put user, or null to auto-detect.
+ * @return The computed array values, or null if they could not be determined.
+ */
+ public static ArrayValues computeSingleUseArrayValues(
+ Value arrayValue, Instruction singleUser, IRCode code) {
+ assert singleUser == null || arrayValue.uniqueUsers().contains(singleUser);
+ TypeElement arrayType = arrayValue.getType();
+ if (!arrayType.isArrayType() || arrayValue.hasDebugUsers() || arrayValue.isPhi()) {
+ return null;
+ }
+
+ Instruction definition = arrayValue.definition;
+ NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
+ NewArrayFilled newArrayFilled = definition.asNewArrayFilled();
+ if (newArrayFilled != null) {
+ // It would be possible to have new-array-filled followed by aput-array, but that sequence of
+ // instructions does not commonly occur, so we don't support it here.
+ if (!arrayValue.hasSingleUniqueUser() || arrayValue.hasPhiUsers()) {
+ return null;
+ }
+ return new ArrayValues(newArrayFilled.inValues());
+ } else if (newArrayEmpty == null) {
+ return null;
+ }
+
+ int arraySize = newArrayEmpty.sizeIfConst();
+ if (arraySize < 0 || arraySize > MAX_ARRAY_SIZE) {
+ // Array is non-const size.
+ return null;
+ }
+
+ if (singleUser == null) {
+ for (Instruction user : arrayValue.uniqueUsers()) {
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut == null || arrayPut.array() != arrayValue || arrayPut.value() == arrayValue) {
+ if (singleUser == null) {
+ singleUser = user;
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+
+ // Ensure that all paths from new-array-empty to |usage| contain all array-put instructions.
+ Set<BasicBlock> dominatorBlocks =
+ computeSimpleCaseDominatorBlocks(definition.getBlock(), singleUser.getBlock(), code);
+ if (dominatorBlocks == null) {
+ return null;
+ }
+ BasicBlock usageBlock = singleUser.getBlock();
+
+ ArrayPut[] arrayPutsByIndex = new ArrayPut[arraySize];
+ for (Instruction user : arrayValue.uniqueUsers()) {
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut == null || arrayPut.array() != arrayValue || arrayPut.value() == arrayValue) {
+ if (user == singleUser) {
+ continue;
+ }
+ // Found a second non-array-put user.
+ return null;
+ }
+ int index = arrayPut.indexIfConstAndInBounds(arraySize);
+ if (index < 0) {
+ return null;
+ }
+ if (arrayPut.getBlock() == usageBlock) {
+ // Process these later.
+ continue;
+ } else if (!dominatorBlocks.contains(arrayPut.getBlock())) {
+ return null;
+ }
+ // We do not know what order blocks are in, so do not allow re-assignment.
+ if (arrayPutsByIndex[index] != null) {
+ return null;
+ }
+ arrayPutsByIndex[index] = arrayPut;
+ }
+ boolean seenSingleUser = false;
+ for (Instruction inst : usageBlock.getInstructions()) {
+ if (inst == singleUser) {
+ seenSingleUser = true;
+ continue;
+ }
+ ArrayPut arrayPut = inst.asArrayPut();
+ if (arrayPut == null || arrayPut.array() != arrayValue) {
+ continue;
+ }
+ if (seenSingleUser) {
+ // Found an array-put after the array was used. This is too uncommon of a thing to support.
+ return null;
+ }
+ int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+ // We can allow reassignment at this point since we are visiting in order.
+ arrayPutsByIndex[index] = arrayPut;
+ }
+
+ return new ArrayValues(arrayPutsByIndex);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 5aa700a..cdfb34c 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -6,7 +6,6 @@
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
-import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
@@ -166,7 +165,7 @@
}
public Set<T> getSeenSet() {
- return Collections.unmodifiableSet(seen);
+ return SetUtils.unmodifiableForTesting(seen);
}
public Set<T> getMutableSeenSet() {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
index bbc27a2..eb747e4 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
@@ -12,6 +12,7 @@
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -47,6 +48,10 @@
backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
}
+ public void forEachValue(Consumer<V> consumer) {
+ backing.values().forEach(consumer);
+ }
+
public V get(K member) {
return backing.get(wrap(member));
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
new file mode 100644
index 0000000..44b2d2c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
@@ -0,0 +1,861 @@
+// Copyright (c) 2023, 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.verticalclassmerging;
+
+import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.code.InvokeType.DIRECT;
+import static com.android.tools.r8.ir.code.InvokeType.STATIC;
+import static com.android.tools.r8.ir.code.InvokeType.VIRTUAL;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature.ClassSignatureBuilder;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
+import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Streams;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+class ClassMerger {
+
+ private enum Rename {
+ ALWAYS,
+ IF_NEEDED,
+ NEVER
+ }
+
+ private static final OptimizationFeedbackSimple feedback =
+ OptimizationFeedback.getSimpleFeedback();
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final VerticalClassMergerGraphLens.Builder deferredRenamings;
+ private final DexItemFactory dexItemFactory;
+ private final VerticalClassMergerGraphLens.Builder lensBuilder;
+ private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses;
+
+ private final DexProgramClass source;
+ private final DexProgramClass target;
+
+ private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
+
+ private boolean abortMerge = false;
+
+ ClassMerger(
+ AppView<AppInfoWithLiveness> appView,
+ VerticalClassMergerGraphLens.Builder lensBuilder,
+ MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses,
+ DexProgramClass source,
+ DexProgramClass target) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.appView = appView;
+ this.deferredRenamings = new VerticalClassMergerGraphLens.Builder(dexItemFactory);
+ this.dexItemFactory = dexItemFactory;
+ this.lensBuilder = lensBuilder;
+ this.mergedClasses = mergedClasses;
+ this.source = source;
+ this.target = target;
+ }
+
+ public boolean merge() throws ExecutionException {
+ // Merge the class [clazz] into [targetClass] by adding all methods to
+ // targetClass that are not currently contained.
+ // Step 1: Merge methods
+ Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
+ addAll(existingMethods, target.methods(), MethodSignatureEquivalence.get());
+
+ Map<Wrapper<DexMethod>, DexEncodedMethod> directMethods = new HashMap<>();
+ Map<Wrapper<DexMethod>, DexEncodedMethod> virtualMethods = new HashMap<>();
+
+ Predicate<DexMethod> availableMethodSignatures =
+ (method) -> {
+ Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method);
+ return !existingMethods.contains(wrapped)
+ && !directMethods.containsKey(wrapped)
+ && !virtualMethods.containsKey(wrapped);
+ };
+
+ source.forEachProgramDirectMethod(
+ directMethod -> {
+ DexEncodedMethod definition = directMethod.getDefinition();
+ if (definition.isInstanceInitializer()) {
+ DexEncodedMethod resultingConstructor =
+ renameConstructor(
+ definition,
+ candidate ->
+ availableMethodSignatures.test(candidate)
+ && source.lookupVirtualMethod(candidate) == null);
+ add(directMethods, resultingConstructor, MethodSignatureEquivalence.get());
+ blockRedirectionOfSuperCalls(resultingConstructor.getReference());
+ } else {
+ DexEncodedMethod resultingDirectMethod =
+ renameMethod(
+ definition,
+ availableMethodSignatures,
+ definition.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
+ add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
+ deferredRenamings.map(
+ directMethod.getReference(), resultingDirectMethod.getReference());
+ deferredRenamings.recordMove(
+ directMethod.getReference(), resultingDirectMethod.getReference());
+ blockRedirectionOfSuperCalls(resultingDirectMethod.getReference());
+
+ // Private methods in the parent class may be targeted with invoke-super if the two
+ // classes are in the same nest. Ensure such calls are mapped to invoke-direct.
+ if (definition.isInstance()
+ && definition.isPrivate()
+ && AccessControl.isMemberAccessible(directMethod, source, target, appView)
+ .isTrue()) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ directMethod.getReference(),
+ prototypeChanges ->
+ new MethodLookupResult(
+ resultingDirectMethod.getReference(), null, DIRECT, prototypeChanges),
+ target.getType());
+ }
+ }
+ });
+
+ for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
+ DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod);
+ if (shadowedBy != null) {
+ if (virtualMethod.isAbstract()) {
+ // Remove abstract/interface methods that are shadowed. The identity mapping below is
+ // needed to ensure we correctly fixup the mapping in case the signature refers to
+ // merged classes.
+ deferredRenamings
+ .map(virtualMethod.getReference(), shadowedBy.getReference())
+ .map(shadowedBy.getReference(), shadowedBy.getReference())
+ .recordMerge(virtualMethod.getReference(), shadowedBy.getReference());
+
+ // The override now corresponds to the method in the parent, so unset its synthetic flag
+ // if the method in the parent is not synthetic.
+ if (!virtualMethod.isSyntheticMethod() && shadowedBy.isSyntheticMethod()) {
+ shadowedBy.accessFlags.demoteFromSynthetic();
+ }
+ continue;
+ }
+ } else {
+ if (abortMerge) {
+ // If [virtualMethod] does not resolve to a single method in [target], abort.
+ assert restoreDebuggingState(
+ Streams.concat(directMethods.values().stream(), virtualMethods.values().stream()));
+ return false;
+ }
+
+ // The method is not shadowed. If it is abstract, we can simply move it to the subclass.
+ // Non-abstract methods are handled below (they cannot simply be moved to the subclass as
+ // a virtual method, because they might be the target of an invoke-super instruction).
+ if (virtualMethod.isAbstract()) {
+ // Abort if target is non-abstract and does not override the abstract method.
+ if (!target.isAbstract()) {
+ assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
+ abortMerge = true;
+ return false;
+ }
+ // Update the holder of [virtualMethod] using renameMethod().
+ DexEncodedMethod resultingVirtualMethod =
+ renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
+ resultingVirtualMethod.setLibraryMethodOverride(virtualMethod.isLibraryMethodOverride());
+ deferredRenamings.map(
+ virtualMethod.getReference(), resultingVirtualMethod.getReference());
+ deferredRenamings.recordMove(
+ virtualMethod.getReference(), resultingVirtualMethod.getReference());
+ add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
+ continue;
+ }
+ }
+
+ DexEncodedMethod resultingMethod;
+ if (source.accessFlags.isInterface()) {
+ // Moving a default interface method into its subtype. This method could be hit directly
+ // via an invoke-super instruction from any of the transitive subtypes of this interface,
+ // due to the way invoke-super works on default interface methods. In order to be able
+ // to hit this method directly after the merge, we need to make it public, and find a
+ // method name that does not collide with one in the hierarchy of this class.
+ String resultingMethodBaseName =
+ virtualMethod.getName().toString() + '$' + source.getTypeName().replace('.', '$');
+ DexMethod resultingMethodReference =
+ dexItemFactory.createMethod(
+ target.getType(),
+ virtualMethod.getProto().prependParameter(source.getType(), dexItemFactory),
+ dexItemFactory.createGloballyFreshMemberString(resultingMethodBaseName));
+ assert availableMethodSignatures.test(resultingMethodReference);
+ resultingMethod =
+ virtualMethod.toTypeSubstitutedMethodAsInlining(
+ resultingMethodReference, dexItemFactory);
+ makeStatic(resultingMethod);
+ } else {
+ // This virtual method could be called directly from a sub class via an invoke-super in-
+ // struction. Therefore, we translate this virtual method into an instance method with a
+ // unique name, such that relevant invoke-super instructions can be rewritten to target
+ // this method directly.
+ resultingMethod = renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
+ if (appView.options().getProguardConfiguration().isAccessModificationAllowed()) {
+ makePublic(resultingMethod);
+ } else {
+ makePrivate(resultingMethod);
+ }
+ }
+
+ add(
+ resultingMethod.belongsToDirectPool() ? directMethods : virtualMethods,
+ resultingMethod,
+ MethodSignatureEquivalence.get());
+
+ // Record that invoke-super instructions in the target class should be redirected to the
+ // newly created direct method.
+ redirectSuperCallsInTarget(virtualMethod, resultingMethod);
+ blockRedirectionOfSuperCalls(resultingMethod.getReference());
+
+ if (shadowedBy == null) {
+ // In addition to the newly added direct method, create a virtual method such that we do
+ // not accidentally remove the method from the interface of this class.
+ // Note that this method is added independently of whether it will actually be used. If
+ // it turns out that the method is never used, it will be removed by the final round
+ // of tree shaking.
+ shadowedBy = buildBridgeMethod(virtualMethod, resultingMethod);
+ deferredRenamings.recordCreationOfBridgeMethod(
+ virtualMethod.getReference(), shadowedBy.getReference());
+ add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
+ }
+
+ // Copy over any keep info from the original virtual method.
+ ProgramMethod programMethod = new ProgramMethod(target, shadowedBy);
+ appView
+ .getKeepInfo()
+ .mutate(
+ mutableKeepInfoCollection ->
+ mutableKeepInfoCollection.joinMethod(
+ programMethod,
+ info ->
+ info.merge(
+ mutableKeepInfoCollection
+ .getMethodInfo(virtualMethod, source)
+ .joiner())));
+
+ deferredRenamings.map(virtualMethod.getReference(), shadowedBy.getReference());
+ deferredRenamings.recordMove(
+ virtualMethod.getReference(), resultingMethod.getReference(), resultingMethod.isStatic());
+ }
+
+ if (abortMerge) {
+ assert restoreDebuggingState(
+ Streams.concat(directMethods.values().stream(), virtualMethods.values().stream()));
+ return false;
+ }
+
+ // Rewrite generic signatures before we merge a base with a generic signature.
+ rewriteGenericSignatures(target, source, directMethods.values(), virtualMethods.values());
+
+ // Convert out of DefaultInstanceInitializerCode, since this piece of code will require lens
+ // code rewriting.
+ target.forEachProgramInstanceInitializerMatching(
+ method -> method.getCode().isDefaultInstanceInitializerCode(),
+ method -> DefaultInstanceInitializerCode.uncanonicalizeCode(appView, method));
+
+ // Step 2: Merge fields
+ Set<DexString> existingFieldNames = new HashSet<>();
+ for (DexEncodedField field : target.fields()) {
+ existingFieldNames.add(field.getReference().name);
+ }
+
+ // In principle, we could allow multiple fields with the same name, and then only rename the
+ // field in the end when we are done merging all the classes, if it it turns out that the two
+ // fields ended up having the same type. This would not be too expensive, since we visit the
+ // entire program using VerticalClassMerger.TreeFixer anyway.
+ //
+ // For now, we conservatively report that a signature is already taken if there is a field
+ // with the same name. If minification is used with -overloadaggressively, this is solved
+ // later anyway.
+ Predicate<DexField> availableFieldSignatures =
+ field -> !existingFieldNames.contains(field.name);
+
+ DexEncodedField[] mergedInstanceFields =
+ mergeFields(
+ source.instanceFields(),
+ target.instanceFields(),
+ availableFieldSignatures,
+ existingFieldNames);
+
+ DexEncodedField[] mergedStaticFields =
+ mergeFields(
+ source.staticFields(),
+ target.staticFields(),
+ availableFieldSignatures,
+ existingFieldNames);
+
+ // Step 3: Merge interfaces
+ Set<DexType> interfaces = mergeArrays(target.interfaces.values, source.interfaces.values);
+ // Now destructively update the class.
+ // Step 1: Update supertype or fix interfaces.
+ if (source.isInterface()) {
+ interfaces.remove(source.type);
+ } else {
+ assert !target.isInterface();
+ target.superType = source.superType;
+ }
+ target.interfaces =
+ interfaces.isEmpty()
+ ? DexTypeList.empty()
+ : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
+ // Step 2: ensure -if rules cannot target the members that were merged into the target class.
+ directMethods.values().forEach(feedback::markMethodCannotBeKept);
+ virtualMethods.values().forEach(feedback::markMethodCannotBeKept);
+ for (int i = 0; i < source.instanceFields().size(); i++) {
+ feedback.markFieldCannotBeKept(mergedInstanceFields[i]);
+ }
+ for (int i = 0; i < source.staticFields().size(); i++) {
+ feedback.markFieldCannotBeKept(mergedStaticFields[i]);
+ }
+ // Step 3: replace fields and methods.
+ target.addDirectMethods(directMethods.values());
+ target.addVirtualMethods(virtualMethods.values());
+ target.setInstanceFields(mergedInstanceFields);
+ target.setStaticFields(mergedStaticFields);
+ // Step 4: Clear the members of the source class since they have now been moved to the target.
+ source.getMethodCollection().clearDirectMethods();
+ source.getMethodCollection().clearVirtualMethods();
+ source.clearInstanceFields();
+ source.clearStaticFields();
+ // Step 5: Record merging.
+ assert !abortMerge;
+ assert GenericSignatureCorrectnessHelper.createForVerification(
+ appView, GenericSignatureContextBuilder.createForSingleClass(appView, target))
+ .evaluateSignaturesForClass(target)
+ .isValid();
+ return true;
+ }
+
+ /**
+ * The rewriting of generic signatures is pretty simple, but require some bookkeeping. We take the
+ * arguments to the base type:
+ *
+ * <pre>
+ * class Sub<X> extends Base<X, String>
+ * </pre>
+ *
+ * <p>for
+ *
+ * <pre>
+ * class Base<T,R> extends OtherBase<T> implements I<R> {
+ * T t() { ... };
+ * }
+ * </pre>
+ *
+ * <p>and substitute T -> X and R -> String
+ */
+ private void rewriteGenericSignatures(
+ DexProgramClass target,
+ DexProgramClass source,
+ Collection<DexEncodedMethod> directMethods,
+ Collection<DexEncodedMethod> virtualMethods) {
+ ClassSignature targetSignature = target.getClassSignature();
+ if (targetSignature.hasNoSignature()) {
+ // Null out all source signatures that is moved, but do not clear out the class since this
+ // could be referred to by other generic signatures.
+ // TODO(b/147504070): If merging classes with enclosing/innerclasses, this needs to be
+ // reconsidered.
+ directMethods.forEach(DexEncodedMethod::clearGenericSignature);
+ virtualMethods.forEach(DexEncodedMethod::clearGenericSignature);
+ source.fields().forEach(DexEncodedMember::clearGenericSignature);
+ return;
+ }
+ GenericSignaturePartialTypeArgumentApplier classApplier =
+ getGenericSignatureArgumentApplier(target, source);
+ if (classApplier == null) {
+ target.clearClassSignature();
+ target.members().forEach(DexEncodedMember::clearGenericSignature);
+ return;
+ }
+ // We could generate a substitution map.
+ ClassSignature rewrittenSource = classApplier.visitClassSignature(source.getClassSignature());
+ // The variables in the class signature is now rewritten to use the targets argument.
+ ClassSignatureBuilder builder = ClassSignature.builder();
+ builder.addFormalTypeParameters(targetSignature.getFormalTypeParameters());
+ if (!source.isInterface()) {
+ if (rewrittenSource.hasSignature()) {
+ builder.setSuperClassSignature(rewrittenSource.getSuperClassSignatureOrNull());
+ } else {
+ builder.setSuperClassSignature(new ClassTypeSignature(source.superType));
+ }
+ } else {
+ builder.setSuperClassSignature(targetSignature.getSuperClassSignatureOrNull());
+ }
+ // Compute the seen set for interfaces to add. This is similar to the merging of interfaces
+ // but allow us to maintain the type arguments.
+ Set<DexType> seenInterfaces = new HashSet<>();
+ if (source.isInterface()) {
+ seenInterfaces.add(source.type);
+ }
+ for (ClassTypeSignature iFace : targetSignature.getSuperInterfaceSignatures()) {
+ if (seenInterfaces.add(iFace.type())) {
+ builder.addSuperInterfaceSignature(iFace);
+ }
+ }
+ if (rewrittenSource.hasSignature()) {
+ for (ClassTypeSignature iFace : rewrittenSource.getSuperInterfaceSignatures()) {
+ if (!seenInterfaces.contains(iFace.type())) {
+ builder.addSuperInterfaceSignature(iFace);
+ }
+ }
+ } else {
+ // Synthesize raw uses of interfaces to align with the actual class
+ for (DexType iFace : source.interfaces) {
+ if (!seenInterfaces.contains(iFace)) {
+ builder.addSuperInterfaceSignature(new ClassTypeSignature(iFace));
+ }
+ }
+ }
+ target.setClassSignature(builder.build(dexItemFactory));
+
+ // Go through all type-variable references for members and update them.
+ CollectionUtils.forEach(
+ method -> {
+ MethodTypeSignature methodSignature = method.getGenericSignature();
+ if (methodSignature.hasNoSignature()) {
+ return;
+ }
+ method.setGenericSignature(
+ classApplier
+ .buildForMethod(methodSignature.getFormalTypeParameters())
+ .visitMethodSignature(methodSignature));
+ },
+ directMethods,
+ virtualMethods);
+
+ source.forEachField(
+ field -> {
+ if (field.getGenericSignature().hasNoSignature()) {
+ return;
+ }
+ field.setGenericSignature(
+ classApplier.visitFieldTypeSignature(field.getGenericSignature()));
+ });
+ }
+
+ private GenericSignaturePartialTypeArgumentApplier getGenericSignatureArgumentApplier(
+ DexProgramClass target, DexProgramClass source) {
+ assert target.getClassSignature().hasSignature();
+ // We can assert proper structure below because the generic signature validator has run
+ // before and pruned invalid signatures.
+ List<FieldTypeSignature> genericArgumentsToSuperType =
+ target.getClassSignature().getGenericArgumentsToSuperType(source.type, dexItemFactory);
+ if (genericArgumentsToSuperType == null) {
+ assert false : "Type should be present in generic signature";
+ return null;
+ }
+ Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
+ List<FormalTypeParameter> formals = source.getClassSignature().getFormalTypeParameters();
+ if (genericArgumentsToSuperType.size() != formals.size()) {
+ if (!genericArgumentsToSuperType.isEmpty()) {
+ assert false : "Invalid argument count to formals";
+ return null;
+ }
+ } else {
+ for (int i = 0; i < formals.size(); i++) {
+ // It is OK to override a generic type variable so we just use put.
+ substitutionMap.put(formals.get(i).getName(), genericArgumentsToSuperType.get(i));
+ }
+ }
+ return GenericSignaturePartialTypeArgumentApplier.build(
+ appView,
+ TypeParameterContext.empty().addPrunedSubstitutions(substitutionMap),
+ (type1, type2) -> true,
+ type -> true);
+ }
+
+ private boolean restoreDebuggingState(Stream<DexEncodedMethod> toBeDiscarded) {
+ toBeDiscarded.forEach(
+ method -> {
+ assert !method.isObsolete();
+ method.setObsolete();
+ });
+ source.forEachMethod(
+ method -> {
+ if (method.isObsolete()) {
+ method.unsetObsolete();
+ }
+ });
+ assert Streams.concat(Streams.stream(source.methods()), Streams.stream(target.methods()))
+ .allMatch(method -> !method.isObsolete());
+ return true;
+ }
+
+ public VerticalClassMergerGraphLens.Builder getRenamings() {
+ return deferredRenamings;
+ }
+
+ public List<SynthesizedBridgeCode> getSynthesizedBridges() {
+ return synthesizedBridges;
+ }
+
+ private void redirectSuperCallsInTarget(DexEncodedMethod oldTarget, DexEncodedMethod newTarget) {
+ DexMethod oldTargetReference = oldTarget.getReference();
+ DexMethod newTargetReference = newTarget.getReference();
+ InvokeType newTargetType = newTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT;
+ if (source.accessFlags.isInterface()) {
+ // If we merge a default interface method from interface I to its subtype C, then we need
+ // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()".
+ //
+ // Unlike when we merge a class into its subclass (the else-branch below), we should *not*
+ // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
+ // if I has a supertype J. This is due to the fact that invoke-super instructions that
+ // resolve to a method on an interface never hit an implementation below that interface.
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ oldTargetReference,
+ prototypeChanges ->
+ new MethodLookupResult(newTargetReference, null, STATIC, prototypeChanges),
+ target.type);
+ } else {
+ // If we merge class B into class C, and class C contains an invocation super.m(), then it
+ // is insufficient to rewrite "invoke-super B.m()" to "invoke-{direct,virtual} C.m$B()" (the
+ // method C.m$B denotes the direct/virtual method that has been created in C for B.m). In
+ // particular, there might be an instruction "invoke-super A.m()" in C that resolves to B.m
+ // at runtime (A is a superclass of B), which also needs to be rewritten to
+ // "invoke-{direct,virtual} C.m$B()".
+ //
+ // We handle this by adding a mapping for [target] and all of its supertypes.
+ DexProgramClass holder = target;
+ while (holder != null && holder.isProgramClass()) {
+ DexMethod signatureInHolder = oldTargetReference.withHolder(holder, dexItemFactory);
+ // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
+ boolean resolutionSucceeds =
+ holder.lookupVirtualMethod(signatureInHolder) != null
+ || appView.appInfo().lookupSuperTarget(signatureInHolder, holder, appView) != null;
+ if (resolutionSucceeds) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInHolder,
+ prototypeChanges ->
+ new MethodLookupResult(newTargetReference, null, newTargetType, prototypeChanges),
+ target.type);
+ } else {
+ break;
+ }
+
+ // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
+ // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
+ // the code above. However, instructions on the form "invoke-super A.m()" should also be
+ // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
+ // that have been merged into [holder].
+ Set<DexType> mergedTypes = mergedClasses.getKeys(holder.getType());
+ for (DexType type : mergedTypes) {
+ DexMethod signatureInType = oldTargetReference.withHolder(type, dexItemFactory);
+ // Resolution would have succeeded if the method used to be in [type], or if one of
+ // its super classes declared the method.
+ boolean resolutionSucceededBeforeMerge =
+ lensBuilder.hasMappingForSignatureInContext(holder, signatureInType)
+ || appView.appInfo().lookupSuperTarget(signatureInHolder, holder, appView)
+ != null;
+ if (resolutionSucceededBeforeMerge) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInType,
+ prototypeChanges ->
+ new MethodLookupResult(
+ newTargetReference, null, newTargetType, prototypeChanges),
+ target.type);
+ }
+ }
+ holder =
+ holder.hasSuperType()
+ ? asProgramClassOrNull(appView.definitionFor(holder.getSuperType()))
+ : null;
+ }
+ }
+ }
+
+ private void blockRedirectionOfSuperCalls(DexMethod method) {
+ // We are merging a class B into C. The methods from B are being moved into C, and then we
+ // subsequently rewrite the invoke-super instructions in C that hit a method in B, such that
+ // they use an invoke-direct instruction instead. In this process, we need to avoid rewriting
+ // the invoke-super instructions that originally was in the superclass B.
+ //
+ // Example:
+ // class A {
+ // public void m() {}
+ // }
+ // class B extends A {
+ // public void m() { super.m(); } <- invoke must not be rewritten to invoke-direct
+ // (this would lead to an infinite loop)
+ // }
+ // class C extends B {
+ // public void m() { super.m(); } <- invoke needs to be rewritten to invoke-direct
+ // }
+ deferredRenamings.markMethodAsMerged(method);
+ }
+
+ private DexEncodedMethod buildBridgeMethod(
+ DexEncodedMethod method, DexEncodedMethod invocationTarget) {
+ DexMethod newMethod = method.getReference().withHolder(target, dexItemFactory);
+ MethodAccessFlags accessFlags = method.getAccessFlags().copy();
+ accessFlags.setBridge();
+ accessFlags.setSynthetic();
+ accessFlags.unsetAbstract();
+
+ assert invocationTarget.isStatic()
+ || invocationTarget.isNonPrivateVirtualMethod()
+ || invocationTarget.isNonStaticPrivateMethod();
+ SynthesizedBridgeCode code =
+ new SynthesizedBridgeCode(
+ newMethod,
+ invocationTarget.getReference(),
+ invocationTarget.isStatic()
+ ? STATIC
+ : (invocationTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT),
+ target.isInterface());
+
+ // Add the bridge to the list of synthesized bridges such that the method signatures will
+ // be updated by the end of vertical class merging.
+ synthesizedBridges.add(code);
+
+ CfVersion classFileVersion = method.hasClassFileVersion() ? method.getClassFileVersion() : null;
+ DexEncodedMethod bridge =
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(newMethod)
+ .setAccessFlags(accessFlags)
+ .setCode(code)
+ .setClassFileVersion(classFileVersion)
+ .setApiLevelForDefinition(method.getApiLevelForDefinition())
+ .setApiLevelForCode(method.getApiLevelForDefinition())
+ .setIsLibraryMethodOverride(method.isLibraryMethodOverride())
+ .setGenericSignature(method.getGenericSignature())
+ .build();
+ // The bridge is now the public method serving the role of the original method, and should
+ // reflect that this method was publicized.
+ assert !method.getAccessFlags().isPromotedToPublic()
+ || bridge.getAccessFlags().isPromotedToPublic();
+ return bridge;
+ }
+
+ // Returns the method that shadows the given method, or null if method is not shadowed.
+ private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
+ SingleResolutionResult<?> resolutionResult =
+ appView.appInfo().resolveMethodOnLegacy(target, method.getReference()).asSingleResolution();
+ if (resolutionResult == null) {
+ // May happen in case of missing classes, or if multiple implementations were found.
+ abortMerge = true;
+ return null;
+ }
+ DexEncodedMethod actual = resolutionResult.getResolvedMethod();
+ if (ObjectUtils.notIdentical(actual, method)) {
+ assert actual.isVirtualMethod() == method.isVirtualMethod();
+ return actual;
+ }
+ // The method is not actually overridden. This means that we will move `method` to the
+ // subtype. If `method` is abstract, then so should the subtype be.
+ return null;
+ }
+
+ private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void add(
+ Map<Wrapper<R>, D> map, D item, Equivalence<R> equivalence) {
+ map.put(equivalence.wrap(item.getReference()), item);
+ }
+
+ private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> void addAll(
+ Collection<Wrapper<R>> collection, Iterable<D> items, Equivalence<R> equivalence) {
+ for (D item : items) {
+ collection.add(equivalence.wrap(item.getReference()));
+ }
+ }
+
+ private <T> Set<T> mergeArrays(T[] one, T[] other) {
+ Set<T> merged = new LinkedHashSet<>();
+ Collections.addAll(merged, one);
+ Collections.addAll(merged, other);
+ return merged;
+ }
+
+ private DexEncodedField[] mergeFields(
+ Collection<DexEncodedField> sourceFields,
+ Collection<DexEncodedField> targetFields,
+ Predicate<DexField> availableFieldSignatures,
+ Set<DexString> existingFieldNames) {
+ DexEncodedField[] result = new DexEncodedField[sourceFields.size() + targetFields.size()];
+ // Add fields from source
+ int i = 0;
+ for (DexEncodedField field : sourceFields) {
+ DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
+ existingFieldNames.add(resultingField.getReference().name);
+ deferredRenamings.map(field.getReference(), resultingField.getReference());
+ result[i] = resultingField;
+ i++;
+ }
+ // Add fields from target.
+ for (DexEncodedField field : targetFields) {
+ result[i] = field;
+ i++;
+ }
+ return result;
+ }
+
+ // Note that names returned by this function are not necessarily unique. Clients should
+ // repeatedly try to generate a fresh name until it is unique.
+ private DexString getFreshName(String nameString, int index, DexType holder) {
+ String freshName = nameString + "$" + holder.toSourceString().replace('.', '$');
+ if (index > 1) {
+ freshName += index;
+ }
+ return dexItemFactory.createString(freshName);
+ }
+
+ private DexEncodedMethod renameConstructor(
+ DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures) {
+ assert method.isInstanceInitializer();
+ DexType oldHolder = method.getHolderType();
+
+ DexMethod newSignature;
+ int count = 1;
+ do {
+ DexString newName = getFreshName(TEMPORARY_INSTANCE_INITIALIZER_PREFIX, count, oldHolder);
+ newSignature = dexItemFactory.createMethod(target.getType(), method.getProto(), newName);
+ count++;
+ } while (!availableMethodSignatures.test(newSignature));
+
+ DexEncodedMethod result =
+ method.toTypeSubstitutedMethodAsInlining(newSignature, dexItemFactory);
+ result.getMutableOptimizationInfo().markForceInline();
+ deferredRenamings.map(method.getReference(), result.getReference());
+ deferredRenamings.recordMove(method.getReference(), result.getReference());
+ // Renamed constructors turn into ordinary private functions. They can be private, as
+ // they are only references from their direct subclass, which they were merged into.
+ result.getAccessFlags().unsetConstructor();
+ makePrivate(result);
+ return result;
+ }
+
+ private DexEncodedMethod renameMethod(
+ DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures, Rename strategy) {
+ return renameMethod(method, availableMethodSignatures, strategy, method.getProto());
+ }
+
+ private DexEncodedMethod renameMethod(
+ DexEncodedMethod method,
+ Predicate<DexMethod> availableMethodSignatures,
+ Rename strategy,
+ DexProto newProto) {
+ // We cannot handle renaming static initializers yet and constructors should have been
+ // renamed already.
+ assert !method.accessFlags.isConstructor() || strategy == Rename.NEVER;
+ DexString oldName = method.getName();
+ DexType oldHolder = method.getHolderType();
+
+ DexMethod newSignature;
+ switch (strategy) {
+ case IF_NEEDED:
+ newSignature = dexItemFactory.createMethod(target.getType(), newProto, oldName);
+ if (availableMethodSignatures.test(newSignature)) {
+ break;
+ }
+ // Fall-through to ALWAYS so that we assign a new name.
+
+ case ALWAYS:
+ int count = 1;
+ do {
+ DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
+ newSignature = dexItemFactory.createMethod(target.getType(), newProto, newName);
+ count++;
+ } while (!availableMethodSignatures.test(newSignature));
+ break;
+
+ case NEVER:
+ newSignature = dexItemFactory.createMethod(target.getType(), newProto, oldName);
+ assert availableMethodSignatures.test(newSignature);
+ break;
+
+ default:
+ throw new Unreachable();
+ }
+
+ return method.toTypeSubstitutedMethodAsInlining(newSignature, dexItemFactory);
+ }
+
+ private DexEncodedField renameFieldIfNeeded(
+ DexEncodedField field, Predicate<DexField> availableFieldSignatures) {
+ DexString oldName = field.getName();
+ DexType oldHolder = field.getHolderType();
+
+ DexField newSignature = dexItemFactory.createField(target.getType(), field.getType(), oldName);
+ if (!availableFieldSignatures.test(newSignature)) {
+ int count = 1;
+ do {
+ DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
+ newSignature = dexItemFactory.createField(target.getType(), field.getType(), newName);
+ count++;
+ } while (!availableFieldSignatures.test(newSignature));
+ }
+
+ return field.toTypeSubstitutedField(appView, newSignature);
+ }
+
+ private static void makePrivate(DexEncodedMethod method) {
+ MethodAccessFlags accessFlags = method.getAccessFlags();
+ assert !accessFlags.isAbstract();
+ accessFlags.unsetPublic();
+ accessFlags.unsetProtected();
+ accessFlags.setPrivate();
+ }
+
+ private static void makePublic(DexEncodedMethod method) {
+ MethodAccessFlags accessFlags = method.getAccessFlags();
+ assert !accessFlags.isAbstract();
+ accessFlags.unsetPrivate();
+ accessFlags.unsetProtected();
+ accessFlags.setPublic();
+ }
+
+ private void makeStatic(DexEncodedMethod method) {
+ method.getAccessFlags().setStatic();
+ if (!method.getCode().isCfCode()) {
+ // Due to member rebinding we may have inserted bridge methods with synthesized code.
+ // Currently, there is no easy way to make such code static.
+ abortMerge = true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/CollisionDetector.java b/src/main/java/com/android/tools/r8/verticalclassmerging/CollisionDetector.java
new file mode 100644
index 0000000..ca22d3d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/CollisionDetector.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2023, 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.verticalclassmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+class CollisionDetector {
+
+ private static final int NOT_FOUND = Integer.MIN_VALUE;
+
+ private final DexItemFactory dexItemFactory;
+ private final Collection<DexMethod> invokes;
+ private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses;
+
+ private final DexType source;
+ private final Reference2IntMap<DexProto> sourceProtoCache;
+
+ private final DexType target;
+ private final Reference2IntMap<DexProto> targetProtoCache;
+
+ private final Map<DexString, Int2IntMap> seenPositions = new IdentityHashMap<>();
+
+ CollisionDetector(
+ AppView<AppInfoWithLiveness> appView,
+ Collection<DexMethod> invokes,
+ MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses,
+ DexType source,
+ DexType target) {
+ this.dexItemFactory = appView.dexItemFactory();
+ this.invokes = invokes;
+ this.mergedClasses = mergedClasses;
+ this.source = source;
+ this.sourceProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
+ this.sourceProtoCache.defaultReturnValue(NOT_FOUND);
+ this.target = target;
+ this.targetProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
+ this.targetProtoCache.defaultReturnValue(NOT_FOUND);
+ }
+
+ boolean mayCollide(Timing timing) {
+ timing.begin("collision detection");
+ fillSeenPositions();
+ boolean result = false;
+ // If the type is not used in methods at all, there cannot be any conflict.
+ if (!seenPositions.isEmpty()) {
+ for (DexMethod method : invokes) {
+ Int2IntMap positionsMap = seenPositions.get(method.getName());
+ if (positionsMap != null) {
+ int arity = method.getArity();
+ int previous = positionsMap.get(arity);
+ if (previous != NOT_FOUND) {
+ assert previous != 0;
+ int positions = computePositionsFor(method.getProto(), source, sourceProtoCache);
+ if ((positions & previous) != 0) {
+ result = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ timing.end();
+ return result;
+ }
+
+ private void fillSeenPositions() {
+ for (DexMethod method : invokes) {
+ int arity = method.getArity();
+ int positions = computePositionsFor(method.getProto(), target, targetProtoCache);
+ if (positions != 0) {
+ Int2IntMap positionsMap =
+ seenPositions.computeIfAbsent(
+ method.getName(),
+ k -> {
+ Int2IntMap result = new Int2IntOpenHashMap();
+ result.defaultReturnValue(NOT_FOUND);
+ return result;
+ });
+ int value = 0;
+ int previous = positionsMap.get(arity);
+ if (previous != NOT_FOUND) {
+ value = previous;
+ }
+ value |= positions;
+ positionsMap.put(arity, value);
+ }
+ }
+ }
+
+ // Given a method signature and a type, this method computes a bit vector that denotes the
+ // positions at which the given type is used in the method signature.
+ private int computePositionsFor(DexProto proto, DexType type, Reference2IntMap<DexProto> cache) {
+ int result = cache.getInt(proto);
+ if (result != NOT_FOUND) {
+ return result;
+ }
+ result = 0;
+ int bitsUsed = 0;
+ int accumulator = 0;
+ for (DexType parameterBaseType : proto.getParameterBaseTypes(dexItemFactory)) {
+ // Substitute the type with the already merged class to estimate what it will look like.
+ DexType mappedType = mergedClasses.getOrDefault(parameterBaseType, parameterBaseType);
+ accumulator <<= 1;
+ bitsUsed++;
+ if (mappedType.isIdenticalTo(type)) {
+ accumulator |= 1;
+ }
+ // Handle overflow on 31 bit boundary.
+ if (bitsUsed == Integer.SIZE - 1) {
+ result |= accumulator;
+ accumulator = 0;
+ bitsUsed = 0;
+ }
+ }
+ // We also take the return type into account for potential conflicts.
+ DexType returnBaseType = proto.getReturnType().toBaseType(dexItemFactory);
+ DexType mappedReturnType = mergedClasses.getOrDefault(returnBaseType, returnBaseType);
+ accumulator <<= 1;
+ if (mappedReturnType.isIdenticalTo(type)) {
+ accumulator |= 1;
+ }
+ result |= accumulator;
+ cache.put(proto, result);
+ return result;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
new file mode 100644
index 0000000..c71a51d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
@@ -0,0 +1,207 @@
+package com.android.tools.r8.verticalclassmerging;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+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.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistryWithResult;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.utils.OptionalBool;
+
+// Searches for a reference to a non-private, non-public class, field or method declared in the
+// same package as [source].
+public class IllegalAccessDetector extends UseRegistryWithResult<Boolean, ProgramMethod> {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy;
+
+ public IllegalAccessDetector(
+ AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy,
+ ProgramMethod context) {
+ super(appViewWithClassHierarchy, context, false);
+ this.appViewWithClassHierarchy = appViewWithClassHierarchy;
+ }
+
+ protected boolean checkFoundPackagePrivateAccess() {
+ assert getResult();
+ return true;
+ }
+
+ protected boolean setFoundPackagePrivateAccess() {
+ setResult(true);
+ return true;
+ }
+
+ protected static boolean continueSearchForPackagePrivateAccess() {
+ return false;
+ }
+
+ private boolean checkFieldReference(DexField field) {
+ return checkRewrittenFieldReference(appViewWithClassHierarchy.graphLens().lookupField(field));
+ }
+
+ private boolean checkRewrittenFieldReference(DexField field) {
+ assert field.getHolderType().isClassType();
+ DexType fieldHolder = field.getHolderType();
+ if (fieldHolder.isSamePackage(getContext().getHolderType())) {
+ if (checkRewrittenTypeReference(fieldHolder)) {
+ return checkFoundPackagePrivateAccess();
+ }
+ DexClassAndField resolvedField =
+ appViewWithClassHierarchy.appInfo().resolveField(field).getResolutionPair();
+ if (resolvedField == null) {
+ return setFoundPackagePrivateAccess();
+ }
+ if (resolvedField.getHolder() != getContext().getHolder()
+ && !resolvedField.getAccessFlags().isPublic()) {
+ return setFoundPackagePrivateAccess();
+ }
+ if (checkRewrittenFieldType(resolvedField)) {
+ return checkFoundPackagePrivateAccess();
+ }
+ }
+ return continueSearchForPackagePrivateAccess();
+ }
+
+ protected boolean checkRewrittenFieldType(DexClassAndField field) {
+ return continueSearchForPackagePrivateAccess();
+ }
+
+ private boolean checkRewrittenMethodReference(
+ DexMethod rewrittenMethod, OptionalBool isInterface) {
+ DexType baseType =
+ rewrittenMethod.getHolderType().toBaseType(appViewWithClassHierarchy.dexItemFactory());
+ if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
+ if (checkTypeReference(rewrittenMethod.getHolderType())) {
+ return checkFoundPackagePrivateAccess();
+ }
+ MethodResolutionResult resolutionResult =
+ isInterface.isUnknown()
+ ? appViewWithClassHierarchy
+ .appInfo()
+ .unsafeResolveMethodDueToDexFormat(rewrittenMethod)
+ : appViewWithClassHierarchy
+ .appInfo()
+ .resolveMethod(rewrittenMethod, isInterface.isTrue());
+ if (!resolutionResult.isSingleResolution()) {
+ return setFoundPackagePrivateAccess();
+ }
+ DexClassAndMethod resolvedMethod = resolutionResult.asSingleResolution().getResolutionPair();
+ if (resolvedMethod.getHolder() != getContext().getHolder()
+ && !resolvedMethod.getAccessFlags().isPublic()) {
+ return setFoundPackagePrivateAccess();
+ }
+ }
+ return continueSearchForPackagePrivateAccess();
+ }
+
+ private boolean checkTypeReference(DexType type) {
+ return internalCheckTypeReference(type, appViewWithClassHierarchy.graphLens());
+ }
+
+ private boolean checkRewrittenTypeReference(DexType type) {
+ return internalCheckTypeReference(type, GraphLens.getIdentityLens());
+ }
+
+ private boolean internalCheckTypeReference(DexType type, GraphLens graphLens) {
+ DexType baseType =
+ graphLens.lookupType(type.toBaseType(appViewWithClassHierarchy.dexItemFactory()));
+ if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
+ DexClass clazz = appViewWithClassHierarchy.definitionFor(baseType);
+ if (clazz == null || !clazz.isPublic()) {
+ return setFoundPackagePrivateAccess();
+ }
+ }
+ return continueSearchForPackagePrivateAccess();
+ }
+
+ @Override
+ public void registerInitClass(DexType clazz) {
+ if (appViewWithClassHierarchy.initClassLens().isFinal()) {
+ // The InitClass lens is always rewritten up until the most recent graph lens, so first map
+ // the class type to the most recent graph lens.
+ DexType rewrittenType = appViewWithClassHierarchy.graphLens().lookupType(clazz);
+ DexField initClassField =
+ appViewWithClassHierarchy.initClassLens().getInitClassField(rewrittenType);
+ checkRewrittenFieldReference(initClassField);
+ } else {
+ checkTypeReference(clazz);
+ }
+ }
+
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ MethodLookupResult lookup =
+ appViewWithClassHierarchy.graphLens().lookupInvokeVirtual(method, getContext());
+ checkRewrittenMethodReference(lookup.getReference(), OptionalBool.FALSE);
+ }
+
+ @Override
+ public void registerInvokeDirect(DexMethod method) {
+ MethodLookupResult lookup =
+ appViewWithClassHierarchy.graphLens().lookupInvokeDirect(method, getContext());
+ checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
+ }
+
+ @Override
+ public void registerInvokeStatic(DexMethod method) {
+ MethodLookupResult lookup =
+ appViewWithClassHierarchy.graphLens().lookupInvokeStatic(method, getContext());
+ checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
+ }
+
+ @Override
+ public void registerInvokeInterface(DexMethod method) {
+ MethodLookupResult lookup =
+ appViewWithClassHierarchy.graphLens().lookupInvokeInterface(method, getContext());
+ checkRewrittenMethodReference(lookup.getReference(), OptionalBool.TRUE);
+ }
+
+ @Override
+ public void registerInvokeSuper(DexMethod method) {
+ MethodLookupResult lookup =
+ appViewWithClassHierarchy.graphLens().lookupInvokeSuper(method, getContext());
+ checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
+ }
+
+ @Override
+ public void registerInstanceFieldWrite(DexField field) {
+ checkFieldReference(field);
+ }
+
+ @Override
+ public void registerInstanceFieldRead(DexField field) {
+ checkFieldReference(field);
+ }
+
+ @Override
+ public void registerNewInstance(DexType type) {
+ checkTypeReference(type);
+ }
+
+ @Override
+ public void registerStaticFieldRead(DexField field) {
+ checkFieldReference(field);
+ }
+
+ @Override
+ public void registerStaticFieldWrite(DexField field) {
+ checkFieldReference(field);
+ }
+
+ @Override
+ public void registerTypeReference(DexType type) {
+ checkTypeReference(type);
+ }
+
+ @Override
+ public void registerInstanceOf(DexType type) {
+ checkTypeReference(type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/InvokeSpecialToDefaultLibraryMethodUseRegistry.java b/src/main/java/com/android/tools/r8/verticalclassmerging/InvokeSpecialToDefaultLibraryMethodUseRegistry.java
new file mode 100644
index 0000000..506a2af
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/InvokeSpecialToDefaultLibraryMethodUseRegistry.java
@@ -0,0 +1,31 @@
+package com.android.tools.r8.verticalclassmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class InvokeSpecialToDefaultLibraryMethodUseRegistry
+ extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
+
+ InvokeSpecialToDefaultLibraryMethodUseRegistry(
+ AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ super(appView, context, false);
+ assert context.getHolder().isInterface();
+ }
+
+ @Override
+ public void registerInvokeSpecial(DexMethod method) {
+ ProgramMethod context = getContext();
+ if (!method.getHolderType().isIdenticalTo(context.getHolderType())) {
+ return;
+ }
+
+ DexEncodedMethod definition = context.getHolder().lookupMethod(method);
+ if (definition != null && definition.belongsToVirtualPool()) {
+ setResult(true);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/SingleTypeMapperGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/SingleTypeMapperGraphLens.java
new file mode 100644
index 0000000..7042bd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/SingleTypeMapperGraphLens.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2023, 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.verticalclassmerging;
+
+import static com.android.tools.r8.ir.code.InvokeType.VIRTUAL;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+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.DexType;
+import com.android.tools.r8.graph.lens.FieldLookupResult;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
+
+public class SingleTypeMapperGraphLens extends NonIdentityGraphLens {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final VerticalClassMergerGraphLens.Builder lensBuilder;
+ private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses;
+
+ private final DexProgramClass source;
+ private final DexProgramClass target;
+
+ public SingleTypeMapperGraphLens(
+ AppView<AppInfoWithLiveness> appView,
+ VerticalClassMergerGraphLens.Builder lensBuilder,
+ MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses,
+ DexProgramClass source,
+ DexProgramClass target) {
+ super(appView.dexItemFactory(), GraphLens.getIdentityLens());
+ this.appView = appView;
+ this.lensBuilder = lensBuilder;
+ this.mergedClasses = mergedClasses;
+ this.source = source;
+ this.target = target;
+ }
+
+ @Override
+ public Iterable<DexType> getOriginalTypes(DexType type) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexType getPreviousClassType(DexType type) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public final DexType getNextClassType(DexType type) {
+ return type.isIdenticalTo(source.getType())
+ ? target.getType()
+ : mergedClasses.getOrDefault(type, type);
+ }
+
+ @Override
+ public DexField getPreviousFieldSignature(DexField field) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexField getNextFieldSignature(DexField field) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexMethod getPreviousMethodSignature(DexMethod method) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexMethod getNextMethodSignature(DexMethod method) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public MethodLookupResult lookupMethod(
+ DexMethod method, DexMethod context, InvokeType type, GraphLens codeLens) {
+ // First look up the method using the existing graph lens (for example, the type will have
+ // changed if the method was publicized by ClassAndMemberPublicizer).
+ MethodLookupResult lookup = appView.graphLens().lookupMethod(method, context, type, codeLens);
+ // Then check if there is a renaming due to the vertical class merger.
+ DexMethod newMethod = lensBuilder.methodMap.get(lookup.getReference());
+ if (newMethod == null) {
+ return lookup;
+ }
+ MethodLookupResult.Builder methodLookupResultBuilder =
+ MethodLookupResult.builder(this)
+ .setReference(newMethod)
+ .setPrototypeChanges(lookup.getPrototypeChanges())
+ .setType(lookup.getType());
+ if (lookup.getType() == InvokeType.INTERFACE) {
+ // If an interface has been merged into a class, invoke-interface needs to be translated
+ // to invoke-virtual.
+ DexClass clazz = appView.definitionFor(newMethod.holder);
+ if (clazz != null && !clazz.accessFlags.isInterface()) {
+ assert appView.definitionFor(method.holder).accessFlags.isInterface();
+ methodLookupResultBuilder.setType(VIRTUAL);
+ }
+ }
+ return methodLookupResultBuilder.build();
+ }
+
+ @Override
+ protected MethodLookupResult internalDescribeLookupMethod(
+ MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
+ // This is unreachable since we override the implementation of lookupMethod() above.
+ throw new Unreachable();
+ }
+
+ @Override
+ public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+ DexMethod method, GraphLens codeLens) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexField lookupField(DexField field, GraphLens codeLens) {
+ return lensBuilder.fieldMap.getOrDefault(field, field);
+ }
+
+ @Override
+ protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+ // This is unreachable since we override the implementation of lookupField() above.
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isContextFreeForMethods(GraphLens codeLens) {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java b/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java
new file mode 100644
index 0000000..4471ff5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java
@@ -0,0 +1,84 @@
+package com.android.tools.r8.verticalclassmerging;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
+import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class SynthesizedBridgeCode extends AbstractSynthesizedCode {
+
+ private DexMethod method;
+ private DexMethod invocationTarget;
+ private InvokeType type;
+ private final boolean isInterface;
+
+ public SynthesizedBridgeCode(
+ DexMethod method, DexMethod invocationTarget, InvokeType type, boolean isInterface) {
+ this.method = method;
+ this.invocationTarget = invocationTarget;
+ this.type = type;
+ this.isInterface = isInterface;
+ }
+
+ public DexMethod getMethod() {
+ return method;
+ }
+
+ public DexMethod getTarget() {
+ return invocationTarget;
+ }
+
+ // By the time the synthesized code object is created, vertical class merging still has not
+ // finished. Therefore it is possible that the method signatures `method` and `invocationTarget`
+ // will change as a result of additional class merging operations. To deal with this, the
+ // vertical class merger explicitly invokes this method to update `method` and `invocation-
+ // Target` when vertical class merging has finished.
+ //
+ // Note that, without this step, these method signatures might refer to intermediate signatures
+ // that are only present in the middle of vertical class merging, which means that the graph
+ // lens will not work properly (since the graph lens generated by vertical class merging only
+ // expects to be applied to method signatures from *before* vertical class merging or *after*
+ // vertical class merging).
+ public void updateMethodSignatures(Function<DexMethod, DexMethod> transformer) {
+ method = transformer.apply(method);
+ invocationTarget = transformer.apply(invocationTarget);
+ }
+
+ @Override
+ public SourceCodeProvider getSourceCodeProvider() {
+ ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
+ ForwardMethodSourceCode.builder(method);
+ forwardSourceCodeBuilder
+ .setReceiver(method.holder)
+ .setTargetReceiver(type.isStatic() ? null : method.holder)
+ .setTarget(invocationTarget)
+ .setInvokeType(type)
+ .setIsInterface(isInterface);
+ return forwardSourceCodeBuilder::build;
+ }
+
+ @Override
+ public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
+ return registry -> {
+ assert registry.getTraversalContinuation().shouldContinue();
+ switch (type) {
+ case DIRECT:
+ registry.registerInvokeDirect(invocationTarget);
+ break;
+ case STATIC:
+ registry.registerInvokeStatic(invocationTarget);
+ break;
+ case VIRTUAL:
+ registry.registerInvokeVirtual(invocationTarget);
+ break;
+ default:
+ throw new Unreachable("Unexpected invocation type: " + type);
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
new file mode 100644
index 0000000..6cb5e12
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -0,0 +1,848 @@
+// Copyright (c) 2017, 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.verticalclassmerging;
+
+import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
+
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
+import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.FieldSignatureEquivalence;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Merges Supertypes with a single implementation into their single subtype.
+ *
+ * <p>A common use-case for this is to merge an interface into its single implementation.
+ *
+ * <p>The class merger only fixes the structure of the graph but leaves the actual instructions
+ * untouched. Fixup of instructions is deferred via a {@link GraphLens} to the IR building phase.
+ */
+public class VerticalClassMerger {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
+ private final InternalOptions options;
+ private Collection<DexMethod> invokes;
+
+ // Set of merge candidates. Note that this must have a deterministic iteration order.
+ private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
+
+ // Map from source class to target class.
+ private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses =
+ BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
+
+ private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedInterfaces =
+ BidirectionalManyToOneHashMap.newIdentityHashMap();
+
+ // Set of types that must not be merged into their subtype.
+ private final Set<DexProgramClass> pinnedClasses = Sets.newIdentityHashSet();
+
+ // The resulting graph lens that should be used after class merging.
+ private final VerticalClassMergerGraphLens.Builder lensBuilder;
+
+ // All the bridge methods that have been synthesized during vertical class merging.
+ private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
+
+ private final MainDexInfo mainDexInfo;
+
+ public VerticalClassMerger(AppView<AppInfoWithLiveness> appView) {
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ this.mainDexInfo = appInfo.getMainDexInfo();
+ this.lensBuilder = new VerticalClassMergerGraphLens.Builder(dexItemFactory);
+ }
+
+ private void initializeMergeCandidates(ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+ for (DexProgramClass sourceClass : appView.appInfo().classesWithDeterministicOrder()) {
+ List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(sourceClass);
+ if (subclasses.size() != 1) {
+ continue;
+ }
+ DexProgramClass targetClass = ListUtils.first(subclasses);
+ if (!isMergeCandidate(sourceClass, targetClass)) {
+ continue;
+ }
+ if (!isStillMergeCandidate(sourceClass, targetClass)) {
+ continue;
+ }
+ if (mergeMayLeadToIllegalAccesses(sourceClass, targetClass)) {
+ continue;
+ }
+ mergeCandidates.add(sourceClass);
+ }
+ }
+
+ // Returns a set of types that must not be merged into other types.
+ private void initializePinnedTypes() {
+ // For all pinned fields, also pin the type of the field (because changing the type of the field
+ // implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
+ // the return type and the parameter types of the method.
+ // TODO(b/156715504): Compute referenced-by-pinned in the keep info objects.
+ List<DexReference> pinnedItems = new ArrayList<>();
+ KeepInfoCollection keepInfo = appView.getKeepInfo();
+ keepInfo.forEachPinnedType(pinnedItems::add, options);
+ keepInfo.forEachPinnedMethod(pinnedItems::add, options);
+ keepInfo.forEachPinnedField(pinnedItems::add, options);
+ extractPinnedItems(pinnedItems);
+
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (Iterables.any(clazz.methods(), method -> method.getAccessFlags().isNative())) {
+ markClassAsPinned(clazz);
+ }
+ }
+
+ // It is valid to have an invoke-direct instruction in a default interface method that targets
+ // another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
+ // SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
+ // Therefore, we disallow merging such interfaces into their subtypes.
+ for (DexMethod signature : appView.appInfo().getVirtualMethodsTargetedByInvokeDirect()) {
+ markTypeAsPinned(signature.getHolderType());
+ }
+
+ // The set of targets that must remain for proper resolution error cases should not be merged.
+ // TODO(b/192821424): Can be removed if handled.
+ extractPinnedItems(appView.appInfo().getFailedMethodResolutionTargets());
+ }
+
+ private <T extends DexReference> void extractPinnedItems(Iterable<T> items) {
+ for (DexReference item : items) {
+ if (item.isDexType()) {
+ markTypeAsPinned(item.asDexType());
+ } else if (item.isDexField()) {
+ // Pin the holder and the type of the field.
+ DexField field = item.asDexField();
+ markTypeAsPinned(field.getHolderType());
+ markTypeAsPinned(field.getType());
+ } else {
+ assert item.isDexMethod();
+ // Pin the holder, the return type and the parameter types of the method. If we were to
+ // merge any of these types into their sub classes, then we would implicitly change the
+ // signature of this method.
+ DexMethod method = item.asDexMethod();
+ markTypeAsPinned(method.getHolderType());
+ markTypeAsPinned(method.getReturnType());
+ for (DexType parameterType : method.getParameters()) {
+ markTypeAsPinned(parameterType);
+ }
+ }
+ }
+ }
+
+ private void markTypeAsPinned(DexType type) {
+ DexType baseType = type.toBaseType(dexItemFactory);
+ if (!baseType.isClassType() || appView.appInfo().isPinnedWithDefinitionLookup(baseType)) {
+ // We check for the case where the type is pinned according to appInfo.isPinned,
+ // so we only need to add it here if it is not the case.
+ return;
+ }
+
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(baseType));
+ if (clazz != null) {
+ markClassAsPinned(clazz);
+ }
+ }
+
+ private void markClassAsPinned(DexProgramClass clazz) {
+ pinnedClasses.add(clazz);
+ }
+
+ // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
+ // method do not change in response to any class merges.
+ private boolean isMergeCandidate(DexProgramClass sourceClass, DexProgramClass targetClass) {
+ assert targetClass != null;
+ ObjectAllocationInfoCollection allocationInfo =
+ appView.appInfo().getObjectAllocationInfoCollection();
+ if (allocationInfo.isInstantiatedDirectly(sourceClass)
+ || allocationInfo.isInterfaceWithUnknownSubtypeHierarchy(sourceClass)
+ || allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass)
+ || appView.getKeepInfo(sourceClass).isPinned(options)
+ || pinnedClasses.contains(sourceClass)
+ || appView.appInfo().isNoVerticalClassMergingOfType(sourceClass)) {
+ return false;
+ }
+
+ assert sourceClass
+ .traverseProgramMembers(
+ member -> {
+ assert !appView.getKeepInfo(member).isPinned(options);
+ return TraversalContinuation.doContinue();
+ })
+ .shouldContinue();
+
+ if (!FeatureSplitBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
+ sourceClass, targetClass, appView)) {
+ return false;
+ }
+ if (appView.appServices().allServiceTypes().contains(sourceClass.getType())
+ && appView.getKeepInfo(targetClass).isPinned(options)) {
+ return false;
+ }
+ if (sourceClass.isAnnotation()) {
+ return false;
+ }
+ if (!sourceClass.isInterface()
+ && targetClass.isSerializable(appView)
+ && !appView.appInfo().isSerializable(sourceClass.getType())) {
+ // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+ // 1.10 The Serializable Interface
+ // ...
+ // A Serializable class must do the following:
+ // ...
+ // * Have access to the no-arg constructor of its first non-serializable superclass
+ return false;
+ }
+
+ // If there is a constructor in the target, make sure that all source constructors can be
+ // inlined.
+ if (!Iterables.isEmpty(targetClass.programInstanceInitializers())) {
+ TraversalContinuation<?, ?> result =
+ sourceClass.traverseProgramInstanceInitializers(
+ method -> TraversalContinuation.breakIf(disallowInlining(method, targetClass)));
+ if (result.shouldBreak()) {
+ return false;
+ }
+ }
+ if (sourceClass.getEnclosingMethodAttribute() != null
+ || !sourceClass.getInnerClasses().isEmpty()) {
+ // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
+ return false;
+ }
+ // We abort class merging when merging across nests or from a nest to non-nest.
+ // Without nest this checks null == null.
+ if (ObjectUtils.notIdentical(targetClass.getNestHost(), sourceClass.getNestHost())) {
+ return false;
+ }
+
+ // If there is an invoke-special to a default interface method and we are not merging into an
+ // interface, then abort, since invoke-special to a virtual class method requires desugaring.
+ if (sourceClass.isInterface() && !targetClass.isInterface()) {
+ TraversalContinuation<?, ?> result =
+ sourceClass.traverseProgramMethods(
+ method -> {
+ boolean foundInvokeSpecialToDefaultLibraryMethod =
+ method.registerCodeReferencesWithResult(
+ new InvokeSpecialToDefaultLibraryMethodUseRegistry(appView, method));
+ return TraversalContinuation.breakIf(foundInvokeSpecialToDefaultLibraryMethod);
+ });
+ if (result.shouldBreak()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
+ // method may change in response to class merges. Therefore, this method should always be called
+ // before merging [clazz] into its subtype.
+ private boolean isStillMergeCandidate(DexProgramClass sourceClass, DexProgramClass targetClass) {
+ assert isMergeCandidate(sourceClass, targetClass);
+ assert !mergedClasses.containsValue(sourceClass.getType());
+ // For interface types, this is more complicated, see:
+ // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5
+ // We basically can't move the clinit, since it is not called when implementing classes have
+ // their clinit called - except when the interface has a default method.
+ if ((sourceClass.hasClassInitializer() && targetClass.hasClassInitializer())
+ || targetClass.classInitializationMayHaveSideEffects(
+ appView, type -> type.isIdenticalTo(sourceClass.getType()))
+ || (sourceClass.isInterface()
+ && sourceClass.classInitializationMayHaveSideEffects(appView))) {
+ // TODO(herhut): Handle class initializers.
+ return false;
+ }
+ boolean sourceCanBeSynchronizedOn =
+ appView.appInfo().isLockCandidate(sourceClass)
+ || sourceClass.hasStaticSynchronizedMethods();
+ boolean targetCanBeSynchronizedOn =
+ appView.appInfo().isLockCandidate(targetClass)
+ || targetClass.hasStaticSynchronizedMethods();
+ if (sourceCanBeSynchronizedOn && targetCanBeSynchronizedOn) {
+ return false;
+ }
+ if (targetClass.getEnclosingMethodAttribute() != null
+ || !targetClass.getInnerClasses().isEmpty()) {
+ // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
+ return false;
+ }
+ if (methodResolutionMayChange(sourceClass, targetClass)) {
+ return false;
+ }
+ // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
+ // to the super class.
+ if (fieldResolutionMayChange(sourceClass, targetClass)) {
+ return false;
+ }
+ // Only merge if api reference level of source class is equal to target class. The check is
+ // somewhat expensive.
+ if (appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()) {
+ AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
+ ComputedApiLevel sourceApiLevel =
+ getApiReferenceLevelForMerging(apiLevelCompute, sourceClass);
+ ComputedApiLevel targetApiLevel =
+ getApiReferenceLevelForMerging(apiLevelCompute, targetClass);
+ if (!sourceApiLevel.equals(targetApiLevel)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean mergeMayLeadToIllegalAccesses(DexProgramClass source, DexProgramClass target) {
+ if (source.isSamePackage(target)) {
+ // When merging two classes from the same package, we only need to make sure that [source]
+ // does not get less visible, since that could make a valid access to [source] from another
+ // package illegal after [source] has been merged into [target].
+ assert source.getAccessFlags().isPackagePrivateOrPublic();
+ assert target.getAccessFlags().isPackagePrivateOrPublic();
+ // TODO(b/287891322): Allow merging if `source` is only accessed from inside its own package.
+ return source.getAccessFlags().isPublic() && target.getAccessFlags().isPackagePrivate();
+ }
+
+ // Check that all accesses to [source] and its members from inside the current package of
+ // [source] will continue to work. This is guaranteed if [target] is public and all members of
+ // [source] are either private or public.
+ //
+ // (Deliberately not checking all accesses to [source] since that would be expensive.)
+ if (!target.isPublic()) {
+ return true;
+ }
+ for (DexType sourceInterface : source.getInterfaces()) {
+ DexClass sourceInterfaceClass = appView.definitionFor(sourceInterface);
+ if (sourceInterfaceClass != null && !sourceInterfaceClass.isPublic()) {
+ return true;
+ }
+ }
+ for (DexEncodedField field : source.fields()) {
+ if (!(field.isPublic() || field.isPrivate())) {
+ return true;
+ }
+ }
+ for (DexEncodedMethod method : source.methods()) {
+ if (!(method.isPublic() || method.isPrivate())) {
+ return true;
+ }
+ // Check if the target is overriding and narrowing the access.
+ if (method.isPublic()) {
+ DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.getReference());
+ if (targetOverride != null && !targetOverride.isPublic()) {
+ return true;
+ }
+ }
+ }
+ // Check that all accesses from [source] to classes or members from the current package of
+ // [source] will continue to work. This is guaranteed if the methods of [source] do not access
+ // any private or protected classes or members from the current package of [source].
+ TraversalContinuation<?, ?> result =
+ source.traverseProgramMethods(
+ method -> {
+ boolean foundIllegalAccess =
+ method.registerCodeReferencesWithResult(
+ new IllegalAccessDetector(appView, method));
+ if (foundIllegalAccess) {
+ return TraversalContinuation.doBreak();
+ }
+ return TraversalContinuation.doContinue();
+ });
+ return result.shouldBreak();
+ }
+
+ private Collection<DexMethod> getInvokes(ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+ if (invokes == null) {
+ invokes = new OverloadedMethodSignaturesRetriever(immediateSubtypingInfo).get();
+ }
+ return invokes;
+ }
+
+ // Collects all potentially overloaded method signatures that reference at least one type that
+ // may be the source or target of a merge operation.
+ private class OverloadedMethodSignaturesRetriever {
+ private final Reference2BooleanOpenHashMap<DexProto> cache =
+ new Reference2BooleanOpenHashMap<>();
+ private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+ private final Set<DexType> mergeeCandidates = new HashSet<>();
+
+ public OverloadedMethodSignaturesRetriever(
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+ for (DexProgramClass mergeCandidate : mergeCandidates) {
+ List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(mergeCandidate);
+ if (subclasses.size() == 1) {
+ mergeeCandidates.add(ListUtils.first(subclasses).getType());
+ }
+ }
+ }
+
+ public Collection<DexMethod> get() {
+ Map<DexString, DexProto> overloadingInfo = new HashMap<>();
+
+ // Find all signatures that may reference a type that could be the source or target of a
+ // merge operation.
+ Set<Wrapper<DexMethod>> filteredSignatures = new HashSet<>();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ for (DexEncodedMethod encodedMethod : clazz.methods()) {
+ DexMethod method = encodedMethod.getReference();
+ DexClass definition = appView.definitionFor(method.getHolderType());
+ if (definition != null
+ && definition.isProgramClass()
+ && protoMayReferenceMergedSourceOrTarget(method.getProto())) {
+ filteredSignatures.add(equivalence.wrap(method));
+
+ // Record that we have seen a method named [signature.name] with the proto
+ // [signature.proto]. If at some point, we find a method with the same name, but a
+ // different proto, it could be the case that a method with the given name is
+ // overloaded.
+ DexProto existing =
+ overloadingInfo.computeIfAbsent(method.getName(), key -> method.getProto());
+ if (existing.isNotIdenticalTo(DexProto.SENTINEL)
+ && !existing.equals(method.getProto())) {
+ // Mark that this signature is overloaded by mapping it to SENTINEL.
+ overloadingInfo.put(method.getName(), DexProto.SENTINEL);
+ }
+ }
+ }
+ }
+
+ List<DexMethod> result = new ArrayList<>();
+ for (Wrapper<DexMethod> wrappedSignature : filteredSignatures) {
+ DexMethod signature = wrappedSignature.get();
+
+ // Ignore those method names that are definitely not overloaded since they cannot lead to
+ // any collisions.
+ if (overloadingInfo.get(signature.getName()).isIdenticalTo(DexProto.SENTINEL)) {
+ result.add(signature);
+ }
+ }
+ return result;
+ }
+
+ private boolean protoMayReferenceMergedSourceOrTarget(DexProto proto) {
+ boolean result;
+ if (cache.containsKey(proto)) {
+ result = cache.getBoolean(proto);
+ } else {
+ result = false;
+ if (typeMayReferenceMergedSourceOrTarget(proto.getReturnType())) {
+ result = true;
+ } else {
+ for (DexType type : proto.getParameters()) {
+ if (typeMayReferenceMergedSourceOrTarget(type)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ cache.put(proto, result);
+ }
+ return result;
+ }
+
+ private boolean typeMayReferenceMergedSourceOrTarget(DexType type) {
+ type = type.toBaseType(dexItemFactory);
+ if (type.isClassType()) {
+ if (mergeeCandidates.contains(type)) {
+ return true;
+ }
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz != null && clazz.isProgramClass()) {
+ return mergeCandidates.contains(clazz.asProgramClass());
+ }
+ }
+ return false;
+ }
+ }
+
+ public static void runIfNecessary(
+ AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ timing.begin("VerticalClassMerger");
+ if (shouldRun(appView)) {
+ new VerticalClassMerger(appView).run(executorService, timing);
+ } else {
+ appView.setVerticallyMergedClasses(VerticallyMergedClasses.empty());
+ }
+ assert appView.hasVerticallyMergedClasses();
+ assert ArtProfileCompletenessChecker.verify(appView);
+ timing.end();
+ }
+
+ private static boolean shouldRun(AppView<AppInfoWithLiveness> appView) {
+ return appView.options().getVerticalClassMergerOptions().isEnabled()
+ && !appView.hasCfByteCodePassThroughMethods();
+ }
+
+ private void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+ ImmediateProgramSubtypingInfo.create(appView);
+
+ initializePinnedTypes(); // Must be initialized prior to mergeCandidates.
+ initializeMergeCandidates(immediateSubtypingInfo);
+
+ timing.begin("merge");
+ // Visit the program classes in a top-down order according to the class hierarchy.
+ TopDownClassHierarchyTraversal.forProgramClasses(appView)
+ .visit(
+ mergeCandidates, clazz -> mergeClassIfPossible(clazz, immediateSubtypingInfo, timing));
+ timing.end();
+
+ VerticallyMergedClasses verticallyMergedClasses =
+ new VerticallyMergedClasses(mergedClasses, mergedInterfaces);
+ appView.setVerticallyMergedClasses(verticallyMergedClasses);
+ if (verticallyMergedClasses.isEmpty()) {
+ return;
+ }
+
+ timing.begin("fixup");
+ VerticalClassMergerGraphLens lens =
+ new VerticalClassMergerTreeFixer(
+ appView, lensBuilder, verticallyMergedClasses, synthesizedBridges)
+ .fixupTypeReferences();
+ KeepInfoCollection keepInfo = appView.getKeepInfo();
+ keepInfo.mutate(
+ mutator ->
+ mutator.removeKeepInfoForMergedClasses(
+ PrunedItems.builder().setRemovedClasses(mergedClasses.keySet()).build()));
+ timing.end();
+
+ assert lens != null;
+ assert verifyGraphLens(lens);
+
+ // Include bridges in art profiles.
+ ProfileCollectionAdditions profileCollectionAdditions =
+ ProfileCollectionAdditions.create(appView);
+ if (!profileCollectionAdditions.isNop()) {
+ for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+ profileCollectionAdditions.applyIfContextIsInProfile(
+ lens.getPreviousMethodSignature(synthesizedBridge.getMethod()),
+ additionsBuilder -> additionsBuilder.addRule(synthesizedBridge.getMethod()));
+ }
+ }
+ profileCollectionAdditions.commit(appView);
+
+ // Rewrite collections using the lens.
+ appView.rewriteWithLens(lens, executorService, timing);
+
+ // Copy keep info to newly synthesized methods.
+ keepInfo.mutate(
+ mutator -> {
+ for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+ ProgramMethod bridge =
+ asProgramMethodOrNull(appView.definitionFor(synthesizedBridge.getMethod()));
+ ProgramMethod target =
+ asProgramMethodOrNull(appView.definitionFor(synthesizedBridge.getTarget()));
+ if (bridge != null && target != null) {
+ mutator.joinMethod(bridge, info -> info.merge(appView.getKeepInfo(target).joiner()));
+ continue;
+ }
+ assert false;
+ }
+ });
+
+ appView.notifyOptimizationFinishedForTesting();
+ }
+
+ private boolean verifyGraphLens(VerticalClassMergerGraphLens graphLens) {
+ // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
+ // getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
+ // for this check to succeed, since it is not guaranteed that calling lookupMethod() with a
+ // pinned method will return the method itself.
+ //
+ // Consider the following example.
+ //
+ // class A {
+ // public void method() {}
+ // }
+ // class B extends A {
+ // @Override
+ // public void method() {}
+ // }
+ // class C extends B {
+ // @Override
+ // public void method() {}
+ // }
+ //
+ // If A.method() is pinned, then A cannot be merged into B, but B can still be merged into C.
+ // Now, if there is an invoke-super instruction in C that hits B.method(), then this needs to
+ // be rewritten into an invoke-direct instruction. In particular, there could be an instruction
+ // `invoke-super A.method` in C. This would hit B.method(). Therefore, the graph lens records
+ // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
+ // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
+ // pinned, because this rewriting does not affect A.method() in any way.
+ assert graphLens.assertPinnedNotModified(appView);
+
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ for (DexEncodedMethod encodedMethod : clazz.methods()) {
+ DexMethod method = encodedMethod.getReference();
+ DexMethod originalMethod = graphLens.getOriginalMethodSignature(method);
+ DexMethod renamedMethod = graphLens.getRenamedMethodSignature(originalMethod);
+
+ // Must be able to map back and forth.
+ if (encodedMethod.hasCode() && encodedMethod.getCode() instanceof SynthesizedBridgeCode) {
+ // For virtual methods, the vertical class merger creates two methods in the sub class
+ // in order to deal with invoke-super instructions (one that is private and one that is
+ // virtual). Therefore, it is not possible to go back and forth. Instead, we check that
+ // the two methods map back to the same original method, and that the original method
+ // can be mapped to the implementation method.
+ DexMethod implementationMethod =
+ ((SynthesizedBridgeCode) encodedMethod.getCode()).getTarget();
+ DexMethod originalImplementationMethod =
+ graphLens.getOriginalMethodSignature(implementationMethod);
+ assert originalMethod.isIdenticalTo(originalImplementationMethod);
+ assert implementationMethod.isIdenticalTo(renamedMethod);
+ } else {
+ assert method.isIdenticalTo(renamedMethod);
+ }
+
+ // Verify that all types are up-to-date. After vertical class merging, there should be no
+ // more references to types that have been merged into another type.
+ assert !mergedClasses.containsKey(method.getReturnType());
+ assert Arrays.stream(method.getParameters().getBacking())
+ .noneMatch(mergedClasses::containsKey);
+ }
+ }
+ return true;
+ }
+
+ private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
+ for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
+ DexEncodedMethod directTargetMethod =
+ target.lookupDirectMethod(virtualSourceMethod.getReference());
+ if (directTargetMethod != null) {
+ // A private method shadows a virtual method. This situation is rare, since it is not
+ // allowed by javac. Therefore, we just give up in this case. (In principle, it would be
+ // possible to rename the private method in the subclass, and then move the virtual method
+ // to the subclass without changing its name.)
+ return true;
+ }
+ }
+
+ // When merging an interface into a class, all instructions on the form "invoke-interface
+ // [source].m" are changed into "invoke-virtual [target].m". We need to abort the merge if this
+ // transformation could hide IncompatibleClassChangeErrors.
+ if (source.isInterface() && !target.isInterface()) {
+ List<DexEncodedMethod> defaultMethods = new ArrayList<>();
+ for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
+ if (!virtualMethod.accessFlags.isAbstract()) {
+ defaultMethods.add(virtualMethod);
+ }
+ }
+
+ // For each of the default methods, the subclass [target] could inherit another default method
+ // with the same signature from another interface (i.e., there is a conflict). In such cases,
+ // instructions on the form "invoke-interface [source].foo()" will fail with an Incompatible-
+ // ClassChangeError.
+ //
+ // Example:
+ // interface I1 { default void m() {} }
+ // interface I2 { default void m() {} }
+ // class C implements I1, I2 {
+ // ... invoke-interface I1.m ... <- IncompatibleClassChangeError
+ // }
+ for (DexEncodedMethod method : defaultMethods) {
+ // Conservatively find all possible targets for this method.
+ LookupResultSuccess lookupResult =
+ appView
+ .appInfo()
+ .resolveMethodOnInterfaceLegacy(method.getHolderType(), method.getReference())
+ .lookupVirtualDispatchTargets(target, appView)
+ .asLookupResultSuccess();
+ assert lookupResult != null;
+ if (lookupResult == null) {
+ return true;
+ }
+ if (lookupResult.contains(method)) {
+ Box<Boolean> found = new Box<>(false);
+ lookupResult.forEach(
+ interfaceTarget -> {
+ if (ObjectUtils.identical(interfaceTarget.getDefinition(), method)) {
+ return;
+ }
+ DexClass enclosingClass = interfaceTarget.getHolder();
+ if (enclosingClass != null && enclosingClass.isInterface()) {
+ // Found a default method that is different from the one in [source], aborting.
+ found.set(true);
+ }
+ },
+ lambdaTarget -> {
+ // The merger should already have excluded lambda implemented interfaces.
+ assert false;
+ });
+ if (found.get()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void mergeClassIfPossible(
+ DexProgramClass clazz, ImmediateProgramSubtypingInfo immediateSubtypingInfo, Timing timing)
+ throws ExecutionException {
+ if (!mergeCandidates.contains(clazz)) {
+ return;
+ }
+ List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(clazz);
+ if (subclasses.size() != 1) {
+ return;
+ }
+ DexProgramClass targetClass = ListUtils.first(subclasses);
+ assert !mergedClasses.containsKey(targetClass.getType());
+ if (mergedClasses.containsValue(clazz.getType())) {
+ return;
+ }
+ assert isMergeCandidate(clazz, targetClass);
+ if (mergedClasses.containsValue(targetClass.getType())) {
+ if (!isStillMergeCandidate(clazz, targetClass)) {
+ return;
+ }
+ } else {
+ assert isStillMergeCandidate(clazz, targetClass);
+ }
+
+ // Guard against the case where we have two methods that may get the same signature
+ // if we replace types. This is rare, so we approximate and err on the safe side here.
+ CollisionDetector collisionDetector =
+ new CollisionDetector(
+ appView,
+ getInvokes(immediateSubtypingInfo),
+ mergedClasses,
+ clazz.getType(),
+ targetClass.getType());
+ if (collisionDetector.mayCollide(timing)) {
+ return;
+ }
+
+ // Check with main dex classes to see if we are allowed to merge.
+ if (!mainDexInfo.canMerge(clazz, targetClass, appView.getSyntheticItems())) {
+ return;
+ }
+
+ ClassMerger merger = new ClassMerger(appView, lensBuilder, mergedClasses, clazz, targetClass);
+ if (merger.merge()) {
+ mergedClasses.put(clazz.getType(), targetClass.getType());
+ if (clazz.isInterface()) {
+ mergedInterfaces.put(clazz.getType(), targetClass.getType());
+ }
+ // Commit the changes to the graph lens.
+ lensBuilder.merge(merger.getRenamings());
+ synthesizedBridges.addAll(merger.getSynthesizedBridges());
+ }
+ }
+
+ private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
+ if (source.getType().isIdenticalTo(target.getSuperType())) {
+ // If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class
+ // Target implements an interface that declares a static final field f, this should yield an
+ // IncompatibleClassChangeError.
+ // TODO(christofferqa): In the following we only check if a static field from an interface
+ // shadows an instance field from [source]. We could actually check if there is an iget/iput
+ // instruction whose resolution would be affected by the merge. The situation where a static
+ // field shadows an instance field is probably not widespread in practice, though.
+ FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
+ Set<Wrapper<DexField>> staticFieldsInInterfacesOfTarget = new HashSet<>();
+ for (DexType interfaceType : target.getInterfaces()) {
+ DexClass clazz = appView.definitionFor(interfaceType);
+ for (DexEncodedField staticField : clazz.staticFields()) {
+ staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.getReference()));
+ }
+ }
+ for (DexEncodedField instanceField : source.instanceFields()) {
+ if (staticFieldsInInterfacesOfTarget.contains(
+ equivalence.wrap(instanceField.getReference()))) {
+ // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an
+ // interface would now hit an instance field from [source], so that an IncompatibleClass-
+ // ChangeError would no longer be thrown. Abort merge.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean disallowInlining(ProgramMethod method, DexProgramClass context) {
+ if (appView.options().inlinerOptions().enableInlining) {
+ Code code = method.getDefinition().getCode();
+ if (code.isCfCode()) {
+ CfCode cfCode = code.asCfCode();
+ SingleTypeMapperGraphLens lens =
+ new SingleTypeMapperGraphLens(
+ appView, lensBuilder, mergedClasses, method.getHolder(), context);
+ ConstraintWithTarget constraint =
+ cfCode.computeInliningConstraint(
+ method, appView, lens, context.programInstanceInitializers().iterator().next());
+ if (constraint.isNever()) {
+ return true;
+ }
+ // Constructors can have references beyond the root main dex classes. This can increase the
+ // size of the main dex dependent classes and we should bail out.
+ if (mainDexInfo.disallowInliningIntoContext(
+ appView, context, method, appView.getSyntheticItems())) {
+ return true;
+ }
+ return false;
+ } else if (code.isDefaultInstanceInitializerCode()) {
+ return false;
+ }
+ // For non-jar/cf code we currently cannot guarantee that markForceInline() will succeed.
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
rename to src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index d4525f0..e3ad747 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -2,7 +2,7 @@
// 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.shaking;
+package com.android.tools.r8.verticalclassmerging;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.graph.lens.NestedGraphLens;
@@ -27,6 +26,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -134,16 +134,23 @@
}
}
}
+ MethodLookupResult lookupResult;
DexMethod newMethod = methodMap.apply(previous.getReference());
if (newMethod == null) {
- return previous;
+ lookupResult = previous;
+ } else {
+ lookupResult =
+ MethodLookupResult.builder(this)
+ .setReference(newMethod)
+ .setPrototypeChanges(
+ internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
+ .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+ .build();
}
- return MethodLookupResult.builder(this)
- .setReference(newMethod)
- .setPrototypeChanges(
- internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
- .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
- .build();
+ assert !appView.testing().enableVerticalClassMergerLensAssertion
+ || Streams.stream(lookupResult.getReference().getReferencedBaseTypes(dexItemFactory()))
+ .noneMatch(type -> mergedClasses.hasBeenMergedIntoSubtype(type));
+ return lookupResult;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
new file mode 100644
index 0000000..5205d49
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerOptions.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.verticalclassmerging;
+
+import com.android.tools.r8.utils.InternalOptions;
+
+public class VerticalClassMergerOptions {
+
+ private final InternalOptions options;
+
+ private boolean enabled = true;
+
+ public VerticalClassMergerOptions(InternalOptions options) {
+ this.options = options;
+ }
+
+ public void disable() {
+ setEnabled(false);
+ }
+
+ public boolean isDisabled() {
+ return !isEnabled();
+ }
+
+ public boolean isEnabled() {
+ return enabled && options.isOptimizing() && options.isShrinking();
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
new file mode 100644
index 0000000..e544c25
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2023, 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.verticalclassmerging;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexType;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
+import com.android.tools.r8.shaking.AnnotationFixer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.List;
+
+class VerticalClassMergerTreeFixer extends TreeFixerBase {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final VerticalClassMergerGraphLens.Builder lensBuilder;
+ private final VerticallyMergedClasses mergedClasses;
+ private final List<SynthesizedBridgeCode> synthesizedBridges;
+
+ VerticalClassMergerTreeFixer(
+ AppView<AppInfoWithLiveness> appView,
+ VerticalClassMergerGraphLens.Builder lensBuilder,
+ VerticallyMergedClasses mergedClasses,
+ List<SynthesizedBridgeCode> synthesizedBridges) {
+ super(appView);
+ this.appView = appView;
+ this.lensBuilder =
+ VerticalClassMergerGraphLens.Builder.createBuilderForFixup(lensBuilder, mergedClasses);
+ this.mergedClasses = mergedClasses;
+ this.synthesizedBridges = synthesizedBridges;
+ }
+
+ VerticalClassMergerGraphLens fixupTypeReferences() {
+ // Globally substitute merged class types in protos and holders.
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.getMethodCollection().replaceMethods(this::fixupMethod);
+ clazz.setStaticFields(fixupFields(clazz.staticFields()));
+ clazz.setInstanceFields(fixupFields(clazz.instanceFields()));
+ clazz.setPermittedSubclassAttributes(
+ fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()));
+ }
+ for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+ synthesizedBridge.updateMethodSignatures(this::fixupMethodReference);
+ }
+ VerticalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
+ if (lens != null) {
+ new AnnotationFixer(lens, appView.graphLens()).run(appView.appInfo().classes());
+ }
+ return lens;
+ }
+
+ @Override
+ public DexType mapClassType(DexType type) {
+ while (mergedClasses.hasBeenMergedIntoSubtype(type)) {
+ type = mergedClasses.getTargetFor(type);
+ }
+ return type;
+ }
+
+ @Override
+ public void recordClassChange(DexType from, DexType to) {
+ // Fixup of classes is not used so no class type should change.
+ throw new Unreachable();
+ }
+
+ @Override
+ public void recordFieldChange(DexField from, DexField to) {
+ if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
+ lensBuilder.map(from, to);
+ }
+ }
+
+ @Override
+ public void recordMethodChange(DexMethod from, DexMethod to) {
+ if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
+ lensBuilder.map(from, to).recordMove(from, to);
+ }
+ }
+
+ @Override
+ public DexEncodedMethod recordMethodChange(DexEncodedMethod method, DexEncodedMethod newMethod) {
+ recordMethodChange(method.getReference(), newMethod.getReference());
+ if (newMethod.isNonPrivateVirtualMethod()) {
+ // Since we changed the return type or one of the parameters, this method cannot be a
+ // classpath or library method override, since we only class merge program classes.
+ assert !method.isLibraryMethodOverride().isTrue();
+ newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+ }
+ return newMethod;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
rename to src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
index 78c6259..d6f3e64 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
@@ -2,10 +2,11 @@
// 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.graph.classmerging;
+package com.android.tools.r8.verticalclassmerging;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.classmerging.MergedClasses;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerCli.java b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerCli.java
index 3e30568..5f3ed0b 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerCli.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerCli.java
@@ -174,6 +174,9 @@
resourceUsageRecorders.add(
new ProtoAndroidManifestUsageRecorder(
fileSystemProto.getPath(ANDROID_MANIFEST_XML)));
+ for (String rawResource : options.getRawResources()) {
+ resourceUsageRecorders.add(new ToolsAttributeUsageRecorder(Paths.get(rawResource)));
+ }
// If the apk contains a raw folder, find keep rules in there
if (new ZipFile(options.getInput())
.stream().anyMatch(zipEntry -> zipEntry.getName().startsWith("res/raw"))) {
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
index 153f1ce..f49e811 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/ResourceShrinkerImpl.kt
@@ -243,7 +243,7 @@
}
zos.putNextEntry(outEntry)
if (!entry.isDirectory) {
- zos.write(ByteStreams.toByteArray(zis))
+ zis.transferTo(zos);
}
zos.closeEntry()
}
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
index 87158c9..ad7ddfb 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -22,9 +22,11 @@
import com.android.build.shrinker.usages.ToolsAttributeUsageRecorderKt;
import com.android.ide.common.resources.ResourcesUtil;
import com.android.ide.common.resources.usage.ResourceStore;
+import com.android.tools.r8.FeatureSplit;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.protobuf.InvalidProtocolBufferException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -40,39 +42,44 @@
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.parsers.ParserConfigurationException;
-import org.jetbrains.annotations.NotNull;
import org.xml.sax.SAXException;
public class LegacyResourceShrinker {
- private final Map<Integer, byte[]> dexInputs;
+ private final Map<String, byte[]> dexInputs;
private final List<PathAndBytes> resFolderInputs;
private final List<PathAndBytes> xmlInputs;
- private final PathAndBytes manifest;
- private final PathAndBytes resourceTable;
+ private final List<PathAndBytes> manifest;
+ private final Map<PathAndBytes, FeatureSplit> resourceTables;
public static class Builder {
- private final Map<Integer, byte[]> dexInputs = new HashMap<>();
+ private final Map<String, byte[]> dexInputs = new HashMap<>();
private final List<PathAndBytes> resFolderInputs = new ArrayList<>();
private final List<PathAndBytes> xmlInputs = new ArrayList<>();
- private PathAndBytes manifest;
- private PathAndBytes resourceTable;
+ private final List<PathAndBytes> manifests = new ArrayList<>();
+ private final Map<PathAndBytes, FeatureSplit> resourceTables = new HashMap<>();
private Builder() {}
- public Builder setManifest(Path path, byte[] bytes) {
- this.manifest = new PathAndBytes(bytes, path);
+ public Builder addManifest(Path path, byte[] bytes) {
+ manifests.add(new PathAndBytes(bytes, path));
return this;
}
- public Builder setResourceTable(Path path, byte[] bytes) {
- this.resourceTable = new PathAndBytes(bytes, path);
+ public Builder addResourceTable(Path path, byte[] bytes, FeatureSplit featureSplit) {
+ resourceTables.put(new PathAndBytes(bytes, path), featureSplit);
+ try {
+ ResourceTable resourceTable = ResourceTable.parseFrom(bytes);
+ System.currentTimeMillis();
+ } catch (InvalidProtocolBufferException e) {
+ throw new RuntimeException(e);
+ }
return this;
}
- public Builder addDexInput(int index, byte[] bytes) {
- dexInputs.put(index, bytes);
+ public Builder addDexInput(String classesLocation, byte[] bytes) {
+ dexInputs.put(classesLocation, bytes);
return this;
}
@@ -87,22 +94,22 @@
}
public LegacyResourceShrinker build() {
- assert manifest != null && resourceTable != null;
+ assert manifests != null && resourceTables != null;
return new LegacyResourceShrinker(
- dexInputs, resFolderInputs, manifest, resourceTable, xmlInputs);
+ dexInputs, resFolderInputs, manifests, resourceTables, xmlInputs);
}
}
private LegacyResourceShrinker(
- Map<Integer, byte[]> dexInputs,
+ Map<String, byte[]> dexInputs,
List<PathAndBytes> resFolderInputs,
- PathAndBytes manifest,
- PathAndBytes resourceTable,
+ List<PathAndBytes> manifests,
+ Map<PathAndBytes, FeatureSplit> resourceTables,
List<PathAndBytes> xmlInputs) {
this.dexInputs = dexInputs;
this.resFolderInputs = resFolderInputs;
- this.manifest = manifest;
- this.resourceTable = resourceTable;
+ this.manifest = manifests;
+ this.resourceTables = resourceTables;
this.xmlInputs = xmlInputs;
}
@@ -111,41 +118,52 @@
}
public ShrinkerResult run() throws IOException, ParserConfigurationException, SAXException {
- R8ResourceShrinkerModel model = new R8ResourceShrinkerModel(NoDebugReporter.INSTANCE, false);
- ResourceTable loadedResourceTable = ResourceTable.parseFrom(resourceTable.bytes);
- model.instantiateFromResourceTable(loadedResourceTable);
- for (Entry<Integer, byte[]> entry : dexInputs.entrySet()) {
+ R8ResourceShrinkerModel model = new R8ResourceShrinkerModel(NoDebugReporter.INSTANCE, true);
+ for (PathAndBytes pathAndBytes : resourceTables.keySet()) {
+ ResourceTable loadedResourceTable = ResourceTable.parseFrom(pathAndBytes.bytes);
+ model.instantiateFromResourceTable(loadedResourceTable);
+ }
+ return shrinkModel(model);
+ }
+
+ public ShrinkerResult shrinkModel(R8ResourceShrinkerModel model) throws IOException {
+ for (Entry<String, byte[]> entry : dexInputs.entrySet()) {
// The analysis needs an origin for the dex files, synthesize an easy recognizable one.
- Path inMemoryR8 = Paths.get("in_memory_r8_classes" + entry.getKey() + ".dex");
+ Path inMemoryR8 = Paths.get("in_memory_r8_" + entry.getKey() + ".dex");
R8ResourceShrinker.runResourceShrinkerAnalysis(
entry.getValue(), inMemoryR8, new DexFileAnalysisCallback(inMemoryR8, model));
}
- ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
- XmlNode.parseFrom(manifest.bytes), model);
+ for (PathAndBytes pathAndBytes : manifest) {
+ ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
+ XmlNode.parseFrom(pathAndBytes.bytes), model);
+ }
for (PathAndBytes xmlInput : xmlInputs) {
if (xmlInput.path.startsWith("res/raw")) {
ToolsAttributeUsageRecorderKt.processRawXml(getUtfReader(xmlInput.getBytes()), model);
}
}
- new ProtoResourcesGraphBuilder(
- new ResFolderFileTree() {
- Map<String, PathAndBytes> pathToBytes =
- new ImmutableMap.Builder<String, PathAndBytes>()
- .putAll(
- xmlInputs.stream()
- .collect(Collectors.toMap(PathAndBytes::getPathWithoutRes, a -> a)))
- .putAll(
- resFolderInputs.stream()
- .collect(Collectors.toMap(PathAndBytes::getPathWithoutRes, a -> a)))
- .build();
- @Override
- public byte[] getEntryByName(@NotNull String pathInRes) {
- return pathToBytes.get(pathInRes).getBytes();
- }
- },
- unused -> loadedResourceTable)
- .buildGraph(model);
+ ImmutableMap<String, PathAndBytes> resFolderMappings =
+ new ImmutableMap.Builder<String, PathAndBytes>()
+ .putAll(
+ xmlInputs.stream()
+ .collect(Collectors.toMap(PathAndBytes::getPathWithoutRes, a -> a)))
+ .putAll(
+ resFolderInputs.stream()
+ .collect(Collectors.toMap(PathAndBytes::getPathWithoutRes, a -> a)))
+ .build();
+ for (PathAndBytes pathAndBytes : resourceTables.keySet()) {
+ ResourceTable resourceTable = ResourceTable.parseFrom(pathAndBytes.bytes);
+ new ProtoResourcesGraphBuilder(
+ new ResFolderFileTree() {
+ @Override
+ public byte[] getEntryByName(String pathInRes) {
+ return resFolderMappings.get(pathInRes).getBytes();
+ }
+ },
+ unused -> resourceTable)
+ .buildGraph(model);
+ }
ResourceStore resourceStore = model.getResourceStore();
resourceStore.processToolsAttributes();
model.keepPossiblyReferencedResources();
@@ -164,9 +182,14 @@
.filter(r -> !r.isReachable())
.map(r -> r.value)
.collect(Collectors.toList());
- ResourceTable shrunkenResourceTable =
- ResourceTableUtilKt.nullOutEntriesWithIds(loadedResourceTable, resourceIdsToRemove);
- return new ShrinkerResult(resEntriesToKeep.build(), shrunkenResourceTable.toByteArray());
+ Map<FeatureSplit, ResourceTable> shrunkenTables = new HashMap<>();
+ for (Entry<PathAndBytes, FeatureSplit> entry : resourceTables.entrySet()) {
+ ResourceTable shrunkenResourceTable =
+ ResourceTableUtilKt.nullOutEntriesWithIds(
+ ResourceTable.parseFrom(entry.getKey().bytes), resourceIdsToRemove);
+ shrunkenTables.put(entry.getValue(), shrunkenResourceTable);
+ }
+ return new ShrinkerResult(resEntriesToKeep.build(), shrunkenTables);
}
// Lifted from com/android/utils/XmlUtils.java which we can't easily update internal dependency
@@ -236,15 +259,17 @@
public static class ShrinkerResult {
private final Set<String> resFolderEntriesToKeep;
- private final byte[] resourceTableInProtoFormat;
+ private final Map<FeatureSplit, ResourceTable> resourceTableInProtoFormat;
- public ShrinkerResult(Set<String> resFolderEntriesToKeep, byte[] resourceTableInProtoFormat) {
+ public ShrinkerResult(
+ Set<String> resFolderEntriesToKeep,
+ Map<FeatureSplit, ResourceTable> resourceTableInProtoFormat) {
this.resFolderEntriesToKeep = resFolderEntriesToKeep;
this.resourceTableInProtoFormat = resourceTableInProtoFormat;
}
- public byte[] getResourceTableInProtoFormat() {
- return resourceTableInProtoFormat;
+ public byte[] getResourceTableInProtoFormat(FeatureSplit featureSplit) {
+ return resourceTableInProtoFormat.get(featureSplit).toByteArray();
}
public Set<String> getResFolderEntriesToKeep() {
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index d8ac6a9..8273c96 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -19,6 +19,7 @@
import java.util.List;
public class R8ResourceShrinkerState {
+
private R8ResourceShrinkerModel r8ResourceShrinkerModel;
public List<String> trace(int id) {
@@ -32,6 +33,10 @@
r8ResourceShrinkerModel.instantiateFromResourceTable(inputStream);
}
+ public R8ResourceShrinkerModel getR8ResourceShrinkerModel() {
+ return r8ResourceShrinkerModel;
+ }
+
public static class R8ResourceShrinkerModel extends ResourceShrinkerModel {
public R8ResourceShrinkerModel(
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index efbc507..0bc2093 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -634,19 +634,12 @@
// null to static field.
.put(
"134-reg-promotion",
- TestCondition.or(
- TestCondition.match(
- compilers(CompilerUnderTest.R8),
- TestCondition.runtimes(DexVm.Version.V10_0_0, DexVm.Version.V12_0_0)),
- TestCondition.match(
- compilers(CompilerUnderTest.D8_AFTER_R8CF, CompilerUnderTest.R8_AFTER_D8),
- TestCondition.runtimes(
- DexVm.Version.V4_0_4,
- DexVm.Version.V8_1_0,
- DexVm.Version.V9_0_0,
- DexVm.Version.V10_0_0,
- DexVm.Version.V12_0_0,
- DexVm.Version.DEFAULT))))
+ TestCondition.match(
+ compilers(
+ CompilerUnderTest.R8,
+ CompilerUnderTest.D8_AFTER_R8CF,
+ CompilerUnderTest.R8_AFTER_D8),
+ TestCondition.runtimes(DexVm.Version.V10_0_0, DexVm.Version.V12_0_0)))
.put(
"461-get-reference-vreg",
TestCondition.match(
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index b24f608..7ae3463 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -51,6 +51,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -83,6 +84,7 @@
private final List<String> applyMappingMaps = new ArrayList<>();
private final List<Path> features = new ArrayList<>();
private Path resourceShrinkerOutput = null;
+ private HashMap<String, Path> resourceShrinkerOutputForFeatures = new HashMap<>();
private PartitionMapConsumer partitionMapConsumer = null;
@Override
@@ -170,7 +172,8 @@
getMinApiLevel(),
features,
residualArtProfiles,
- resourceShrinkerOutput);
+ resourceShrinkerOutput,
+ resourceShrinkerOutputForFeatures);
switch (allowedDiagnosticMessages) {
case ALL:
compileResult.getDiagnosticMessages().assertAllDiagnosticsMatch(new IsAnything<>());
@@ -878,19 +881,33 @@
testResource, getState().getNewTempFile("resourceshrinkeroutput.zip"));
}
- public T addAndroidResources(AndroidTestResource testResource, Path output) throws IOException {
- addResourceShrinkerProviderAndConsumer(testResource.getResourceZip(), output);
+ public T addFeatureSplitAndroidResources(AndroidTestResource testResource, String featureName)
+ throws IOException {
+ Path outputFile = getState().getNewTempFile("resourceshrinkeroutput_" + featureName + ".zip");
+ resourceShrinkerOutputForFeatures.put(featureName, outputFile);
+ getBuilder()
+ .addFeatureSplit(
+ featureSplitGenerator -> {
+ featureSplitGenerator.setAndroidResourceConsumer(
+ new ArchiveProtoAndroidResourceConsumer(outputFile));
+ Path resourceZip = testResource.getResourceZip();
+ featureSplitGenerator.setAndroidResourceProvider(
+ new ArchiveProtoAndroidResourceProvider(
+ resourceZip, new PathOrigin(resourceZip)));
+ return featureSplitGenerator.build();
+ });
return addProgramClassFileData(testResource.getRClass().getClassFileData());
}
- private T addResourceShrinkerProviderAndConsumer(Path resources, Path output) throws IOException {
+ public T addAndroidResources(AndroidTestResource testResource, Path output) throws IOException {
+ Path resources = testResource.getResourceZip();
resourceShrinkerOutput = output;
getBuilder()
.setAndroidResourceProvider(
new ArchiveProtoAndroidResourceProvider(resources, new PathOrigin(resources)));
- getBuilder()
- .setAndroidResourceConsumer(
- new ArchiveProtoAndroidResourceConsumer(resourceShrinkerOutput));
- return self();
+ getBuilder().setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output));
+ self();
+ return addProgramClassFileData(testResource.getRClass().getClassFileData());
}
+
}
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 82997e9..20b9f2d 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -27,7 +27,9 @@
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@@ -40,6 +42,7 @@
private final List<Path> features;
private final List<ExternalArtProfile> residualArtProfiles;
private final Path resourceShrinkerOutput;
+ private final Map<String, Path> resourceShrinkerOutputForFeatures;
R8TestCompileResult(
TestState state,
@@ -53,7 +56,8 @@
int minApiLevel,
List<Path> features,
List<ExternalArtProfile> residualArtProfiles,
- Path resourceShrinkerOutput) {
+ Path resourceShrinkerOutput,
+ HashMap<String, Path> resourceShrinkerOutputForFeatures) {
super(state, app, minApiLevel, outputMode, libraryDesugaringTestConfiguration);
this.proguardConfiguration = proguardConfiguration;
this.syntheticProguardRules = syntheticProguardRules;
@@ -62,6 +66,7 @@
this.features = features;
this.residualArtProfiles = residualArtProfiles;
this.resourceShrinkerOutput = resourceShrinkerOutput;
+ this.resourceShrinkerOutputForFeatures = resourceShrinkerOutputForFeatures;
}
@Override
@@ -164,6 +169,14 @@
return self();
}
+ public <E extends Throwable> R8TestCompileResult inspectShrunkenResourcesForFeature(
+ Consumer<ResourceTableInspector> consumer, String featureName) throws IOException {
+ Path path = resourceShrinkerOutputForFeatures.get(featureName);
+ assertNotNull(path);
+ consumer.accept(new ResourceTableInspector(ZipUtils.readSingleEntry(path, "resources.pb")));
+ return self();
+ }
+
public GraphInspector graphInspector() throws IOException {
assert graphConsumer != null;
return new GraphInspector(graphConsumer, inspector());
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 6b4518d..9d47152 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1742,8 +1742,25 @@
return AndroidApiLevel.O;
}
- public static boolean canUseNativeRecords(TestParameters parameters) {
- return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
+ public static AndroidApiLevel apiLevelWithRecordSupport() {
+ // TODO(b/293591931): Return something when records are stable in Platform (expecting Android
+ // V).
+ throw new Unreachable();
+ }
+
+ public static boolean isRecordsDesugaredForD8(TestParameters parameters) {
+ assert parameters.getApiLevel() != null;
+ // TODO(b/293591931): Return true for some API level when records are stable in Platform
+ // (expecting Android V) using TestBase.apiLevelWithRecordSupport().
+ return true;
+ }
+
+ public static boolean isRecordsDesugaredForR8(TestParameters parameters) {
+ assert parameters.getApiLevel() != null;
+ // TODO(b/293591931): Also return true for some API level when records are stable in Platform
+ // (expecting Android V) using TestBase.apiLevelWithRecordSupport(). Note that R8 with class
+ // file output never performs desugaring.
+ return !parameters.getRuntime().isCf();
}
public static boolean canUseJavaUtilObjects(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 7db5069..5349d73 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.benchmarks.BenchmarkResults;
import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.testing.AndroidBuildVersion;
@@ -25,6 +26,8 @@
import com.android.tools.r8.utils.codeinspector.ArgumentPropagatorCodeScannerResultInspector;
import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.MinificationInspector;
+import com.android.tools.r8.utils.codeinspector.RepackagingInspector;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
@@ -43,6 +46,7 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -195,13 +199,22 @@
public T addHorizontallyMergedClassesInspector(
ThrowableConsumer<HorizontallyMergedClassesInspector> inspector) {
+ return addHorizontallyMergedClassesInspector(inspector, HorizontalClassMerger.Mode::isFinal);
+ }
+
+ public T addHorizontallyMergedClassesInspector(
+ ThrowableConsumer<HorizontallyMergedClassesInspector> inspector,
+ Predicate<HorizontalClassMerger.Mode> predicate) {
return addOptionsModification(
options ->
options.testing.horizontallyMergedClassesConsumer =
- ((dexItemFactory, horizontallyMergedClasses) ->
+ ((dexItemFactory, horizontallyMergedClasses, mode) -> {
+ if (predicate.test(mode)) {
inspector.acceptWithRuntimeException(
new HorizontallyMergedClassesInspector(
- dexItemFactory, horizontallyMergedClasses))));
+ dexItemFactory, horizontallyMergedClasses));
+ }
+ }));
}
public T addHorizontallyMergedClassesInspectorIf(
@@ -212,6 +225,24 @@
return self();
}
+ public T addMinificationInspector(ThrowableConsumer<MinificationInspector> inspector) {
+ return addOptionsModification(
+ options ->
+ options.testing.namingLensConsumer =
+ ((dexItemFactory, namingLens) ->
+ inspector.acceptWithRuntimeException(
+ new MinificationInspector(dexItemFactory, namingLens))));
+ }
+
+ public T addRepackagingInspector(ThrowableConsumer<RepackagingInspector> inspector) {
+ return addOptionsModification(
+ options ->
+ options.testing.repackagingLensConsumer =
+ ((dexItemFactory, repackagingLens) ->
+ inspector.acceptWithRuntimeException(
+ new RepackagingInspector(dexItemFactory, repackagingLens))));
+ }
+
public T addVerticallyMergedClassesInspector(
Consumer<VerticallyMergedClassesInspector> inspector) {
return addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index c03fadc..2d7a5c0 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -416,6 +416,7 @@
}
public boolean hasRecordsSupport() {
+ // Records support is present from Android U.
return isNewerThanOrEqual(V14_0_0);
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
index f345fa5..f2ca6d3 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
@@ -186,9 +186,9 @@
.addProgramClasses(mainClass)
.addProgramClasses(CLASSES)
.addOptionsModification(
- o -> {
- o.inlinerOptions().enableInlining = false;
- o.enableVerticalClassMerging = false;
+ options -> {
+ options.inlinerOptions().enableInlining = false;
+ options.getVerticalClassMergerOptions().disable();
})
.addDontObfuscate()
.addKeepMainRule(mainClass)
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index d41508e..f1cb778 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -155,7 +155,9 @@
testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()))
.addKeepMainRule(mainClass)
- .addOptionsModification(o -> o.enableVerticalClassMerging = enableVerticalClassMerging)
+ .addOptionsModification(
+ options ->
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
.enableConstantArgumentAnnotations()
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index 63de716..0ee66c7 100644
--- a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -14,10 +14,12 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StreamUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.MoreCollectors;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -27,6 +29,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -41,6 +44,7 @@
enum RClassType {
STRING,
DRAWABLE,
+ STYLEABLE,
XML;
public static RClassType fromClass(Class clazz) {
@@ -141,6 +145,10 @@
return mapping.containsKey(type) && mapping.get(type).containsValueFor(name);
}
+ public Collection<String> entriesForType(String type) {
+ return mapping.get(type).mapping.keySet();
+ }
+
public static class ResourceNameToValueMapping {
private final Map<String, List<ResourceValue>> mapping = new HashMap<>();
@@ -198,20 +206,28 @@
}
public void assertContainsResourceWithName(String type, String name) {
- Assert.assertTrue(testResourceTable.containsValueFor(type, name));
+ Assert.assertTrue(
+ StringUtils.join(",", entries(type)), testResourceTable.containsValueFor(type, name));
}
public void assertDoesNotContainResourceWithName(String type, String name) {
- Assert.assertFalse(testResourceTable.containsValueFor(type, name));
+ Assert.assertFalse(
+ StringUtils.join(",", entries(type)), testResourceTable.containsValueFor(type, name));
+ }
+
+ public Collection<String> entries(String type) {
+ return testResourceTable.entriesForType(type);
}
}
public static class AndroidTestResourceBuilder {
private String manifest;
private final Map<String, String> stringValues = new TreeMap<>();
+ private final Map<String, Integer> styleables = new TreeMap<>();
private final Map<String, byte[]> drawables = new TreeMap<>();
private final Map<String, String> xmlFiles = new TreeMap<>();
private final List<Class<?>> classesToRemap = new ArrayList<>();
+ private int packageId = 0x7f;
// Create the android resources from the passed in R classes
// All values will be generated based on the fields in the class.
@@ -230,6 +246,10 @@
if (rClassType == RClassType.DRAWABLE) {
addDrawable(name, TINY_PNG);
}
+ if (rClassType == RClassType.STYLEABLE) {
+ // Add 4 different values, i.e., the array will be 4 integers.
+ addStyleable(name, 4);
+ }
}
}
return this;
@@ -252,11 +272,21 @@
return this;
}
+ AndroidTestResourceBuilder addStyleable(String name, int numberOfValues) {
+ styleables.put(name, numberOfValues);
+ return this;
+ }
+
AndroidTestResourceBuilder addStringValue(String name, String value) {
stringValues.put(name, value);
return this;
}
+ AndroidTestResourceBuilder setPackageId(int packageId) {
+ this.packageId = packageId;
+ return this;
+ }
+
AndroidTestResourceBuilder addDrawable(String name, byte[] value) {
drawables.put(name, value);
return this;
@@ -266,11 +296,13 @@
Path manifestPath =
FileUtils.writeTextFile(temp.newFile("AndroidManifest.xml").toPath(), this.manifest);
Path resFolder = temp.newFolder("res").toPath();
+ Path valuesFolder = temp.newFolder("res", "values").toPath();
if (stringValues.size() > 0) {
+ FileUtils.writeTextFile(valuesFolder.resolve("strings.xml"), createStringResourceXml());
+ }
+ if (styleables.size() > 0) {
FileUtils.writeTextFile(
- temp.newFolder("res", "values").toPath().resolve("strings.xml"),
- createStringResourceXml());
-
+ valuesFolder.resolve("styleables.xml"), createStyleableResourceXml());
}
if (drawables.size() > 0) {
File drawableFolder = temp.newFolder("res", "drawable");
@@ -288,7 +320,7 @@
Path output = temp.newFile("resources.zip").toPath();
Path rClassOutputDir = temp.newFolder("aapt_R_class").toPath();
- compileWithAapt2(resFolder, manifestPath, rClassOutputDir, output, temp);
+ compileWithAapt2(resFolder, manifestPath, rClassOutputDir, output, temp, packageId);
Path rClassJavaFile =
Files.walk(rClassOutputDir)
.filter(path -> path.endsWith("R.java"))
@@ -349,6 +381,19 @@
// Don't make the inner<>outer class connection
}
})
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitFieldInsn(
+ int opcode, String owner, String name, String descriptor) {
+ String maybeTransformedOwner =
+ isInnerRClass(owner)
+ ? noNamespaceToProgramMap.getOrDefault(
+ rClassWithoutNamespaceAndOuter(owner), owner)
+ : owner;
+ super.visitFieldInsn(opcode, maybeTransformedOwner, name, descriptor);
+ }
+ })
.transform());
}
});
@@ -364,10 +409,36 @@
stringBuilder.append("</resources>");
return stringBuilder.toString();
}
+
+ private String createStyleableResourceXml() {
+ StringBuilder stringBuilder = new StringBuilder("<resources>\n");
+ styleables.forEach(
+ (name, value) -> {
+ stringBuilder.append("<declare-styleable name=\"" + name + "\">\n");
+ // For every entry we add here we will have an additional array entry pointing
+ // at the boolean attr in the resource table. We will also get a name_attri R class
+ // entry to select into the generated array.
+ for (Integer i = 0; i < value; i++) {
+ stringBuilder.append("<attr name=\"attr_" + name + i + "\" format=\"boolean\" />\n");
+ }
+ stringBuilder.append("</declare-styleable>");
+ });
+ stringBuilder.append("</resources>");
+ return stringBuilder.toString();
+ }
+ }
+
+ public static void dumpWithAapt2(Path path) throws IOException {
+ System.out.println(ToolHelper.runAapt2("dump", "resources", path.toString()));
}
public static void compileWithAapt2(
- Path resFolder, Path manifest, Path rClassFolder, Path resourceZip, TemporaryFolder temp)
+ Path resFolder,
+ Path manifest,
+ Path rClassFolder,
+ Path resourceZip,
+ TemporaryFolder temp,
+ int packageId)
throws IOException {
Path compileOutput = temp.newFile("compiled.zip").toPath();
ProcessResult compileProcessResult =
@@ -384,8 +455,14 @@
resourceZip.toString(),
"--java",
rClassFolder.toString(),
+ "--non-final-ids",
"--manifest",
manifest.toString(),
+ "--package-id",
+ "" + packageId,
+ "--allow-reserved-package-id",
+ "--rename-resources-package",
+ "thepackage" + packageId + ".foobar",
"--proto-format",
compileOutput.toString());
failOnError(linkProcesResult);
diff --git a/src/test/java/com/android/tools/r8/androidresources/RClassResourceGeneration.java b/src/test/java/com/android/tools/r8/androidresources/RClassResourceGeneration.java
index c09a5b3..7cb981b 100644
--- a/src/test/java/com/android/tools/r8/androidresources/RClassResourceGeneration.java
+++ b/src/test/java/com/android/tools/r8/androidresources/RClassResourceGeneration.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.androidresources;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -75,15 +76,15 @@
CodeInspector inspector = new CodeInspector(resourceApp);
ClassSubject stringClazz = inspector.clazz(R.string.class);
// Implicitly added with the manifest
- ensureIntFieldWithValue(stringClazz, "app_name", 0x7f020000);
+ ensureIntFieldWithValue(stringClazz, "app_name");
- ensureIntFieldWithValue(stringClazz, "bar", 0x7f020001);
- ensureIntFieldWithValue(stringClazz, "foo", 0x7f020002);
- ensureIntFieldWithValue(inspector.clazz(R.drawable.class), "foobar", 0x7f010000);
+ ensureIntFieldWithValue(stringClazz, "bar");
+ ensureIntFieldWithValue(stringClazz, "foo");
+ ensureIntFieldWithValue(inspector.clazz(R.drawable.class), "foobar");
}
- private void ensureIntFieldWithValue(ClassSubject clazz, String name, int value) {
- assertEquals(clazz.field("int", name).getStaticValue().asDexValueInt().value, value);
+ private void ensureIntFieldWithValue(ClassSubject clazz, String name) {
+ assertTrue(clazz.field("int", name).isPresent());
}
public static class FooBar {
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingWithFeatures.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingWithFeatures.java
new file mode 100644
index 0000000..2b257b4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingWithFeatures.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2023, 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;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.androidresources.ResourceShrinkingWithFeatures.FeatureSplit.FeatureSplitMain;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 ResourceShrinkingWithFeatures extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class)
+ .build(temp);
+ }
+
+ public static AndroidTestResource getFeatureSplitTestResources(TemporaryFolder temp)
+ throws IOException {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .setPackageId(0x7E)
+ .addRClassInitializeWithDefaultValues(FeatureSplit.R.string.class)
+ .build(temp);
+ }
+
+ @Test
+ public void testFailureIfNotResourcesOrCode() throws Exception {
+ try {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(Base.class)
+ .addFeatureSplit(builder -> builder.build())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorThatMatches(diagnosticException(AssertionError.class));
+ });
+ } catch (CompilationFailedException e) {
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testR8ReferenceFeatureResourcesFromFeature() throws Exception {
+ // We reference a feature resource from a feature.
+ testR8(false);
+ }
+
+ @Test
+ public void testR8ReferenceFeatureResourcesFromBase() throws Exception {
+ // We reference a feature resource from the base.
+ testR8(true);
+ }
+
+ private void testR8(boolean referenceFromBase) throws Exception {
+ TemporaryFolder featureSplitTemp = ToolHelper.getTemporaryFolderForTest();
+ featureSplitTemp.create();
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend()).setMinApi(parameters).addProgramClasses(Base.class);
+ if (referenceFromBase) {
+ r8FullTestBuilder.addProgramClasses(FeatureSplit.FeatureSplitMain.class);
+ } else {
+ r8FullTestBuilder.addFeatureSplit(FeatureSplit.FeatureSplitMain.class);
+ }
+ R8TestCompileResult compile =
+ r8FullTestBuilder
+ .addAndroidResources(getTestResources(temp))
+ .addFeatureSplitAndroidResources(
+ getFeatureSplitTestResources(featureSplitTemp), FeatureSplit.class.getName())
+ .addKeepMainRule(Base.class)
+ .addKeepMainRule(FeatureSplitMain.class)
+ .compile();
+ compile
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName("string", "base_used");
+ resourceTableInspector.assertDoesNotContainResourceWithName("string", "base_unused");
+ })
+ .inspectShrunkenResourcesForFeature(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName("string", "feature_used");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "feature_unused");
+ },
+ FeatureSplit.class.getName())
+ .run(parameters.getRuntime(), Base.class)
+ .assertSuccess();
+ }
+
+ public static class Base {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(R.string.base_used);
+ }
+ }
+ }
+
+ public static class R {
+
+ public static class string {
+
+ public static int base_used;
+ public static int base_unused;
+ }
+ }
+
+ public static class FeatureSplit {
+ public static class FeatureSplitMain {
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(R.string.feature_used);
+ }
+ }
+ }
+
+ public static class R {
+ public static class string {
+ public static int feature_used;
+ public static int feature_unused;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/TestOptimizedShrinking.java b/src/test/java/com/android/tools/r8/androidresources/TestOptimizedShrinking.java
new file mode 100644
index 0000000..7a396ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/TestOptimizedShrinking.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2023, 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;
+
+import com.android.tools.r8.ResourceShrinkerConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.androidresources.TestOptimizedShrinking.R.styleable;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 TestOptimizedShrinking extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean debug;
+
+ @Parameter(2)
+ public boolean optimized;
+
+ @Parameters(name = "{0}, debug: {1}, optimized: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class, R.drawable.class, R.styleable.class)
+ .build(temp);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ AndroidTestResource testResources = getTestResources(temp);
+
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(FooBar.class)
+ .addAndroidResources(testResources)
+ .addKeepMainRule(FooBar.class)
+ .applyIf(
+ optimized,
+ builder ->
+ builder.addOptionsModification(
+ internalOptions ->
+ internalOptions.resourceShrinkerConfiguration =
+ ResourceShrinkerConfiguration.builder(null)
+ .enableOptimizedShrinkingWithR8()
+ .build()))
+ .applyIf(debug, builder -> builder.debug())
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName("string", "bar");
+ resourceTableInspector.assertContainsResourceWithName("string", "foo");
+ resourceTableInspector.assertContainsResourceWithName("drawable", "foobar");
+ // In debug mode legacy shrinker will not attribute our $R inner class as an R class
+ // (this is only used for testing, _real_ R classes are not inner classes.
+ if (!debug || optimized) {
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "unused_string");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "drawable", "unused_drawable");
+ }
+ resourceTableInspector.assertContainsResourceWithName("styleable", "our_styleable");
+ // The resource shrinker is currently keeping all styleables,
+ // so we don't remove this even when it is unused.
+ resourceTableInspector.assertContainsResourceWithName(
+ "styleable", "unused_styleable");
+ // We do remove the attributes pointed at by the unreachable styleable.
+ for (int i = 0; i < 4; i++) {
+ resourceTableInspector.assertContainsResourceWithName(
+ "attr", "attr_our_styleable" + i);
+ if (!debug || optimized) {
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "attr", "attr_unused_styleable" + i);
+ }
+ }
+ })
+ .run(parameters.getRuntime(), FooBar.class)
+ .assertSuccess();
+ }
+
+ public static class FooBar {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(R.drawable.foobar);
+ System.out.println(R.string.bar);
+ System.out.println(R.string.foo);
+ System.out.println(styleable.our_styleable);
+ }
+ }
+ }
+
+ public static class R {
+
+ public static class string {
+ public static int bar;
+ public static int foo;
+ public static int unused_string;
+ }
+
+ public static class styleable {
+ public static int[] our_styleable;
+ public static int[] unused_styleable;
+ }
+
+ public static class drawable {
+
+ public static int foobar;
+ public static int unused_drawable;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java
index 27a1252..1dab3a8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java
@@ -148,6 +148,7 @@
@KeepConstantArguments
@KeepUnusedArguments
+ @NeverInline
public Parent(Parent parent) {}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
index c67f07e..f3fe7df 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -10,9 +10,8 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.BooleanUtils;
-import com.google.common.collect.ImmutableSet;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import java.util.List;
import org.junit.Test;
import org.junit.runners.Parameterized;
@@ -39,8 +38,7 @@
.addKeepMainRule(Main.class)
.enableNeverClassInliningAnnotations()
.addKeepAttributeInnerClassesAndEnclosingMethod()
- .addOptionsModification(
- options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.setMinApi(parameters)
.addOptionsModification(
options ->
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
index 5405ddb..e925268 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
@@ -11,9 +11,8 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.google.common.collect.ImmutableSet;
import org.junit.Test;
public class LargeConstructorsMergingTest extends HorizontalClassMergingTestBase {
@@ -26,11 +25,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .addOptionsModification(
- options -> {
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
- options.testing.verificationSizeLimitInBytesOverride = 4;
- })
+ .addOptionsModification(options -> options.testing.verificationSizeLimitInBytesOverride = 4)
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.enableNeverClassInliningAnnotations()
.setMinApi(parameters)
.addHorizontallyMergedClassesInspector(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
index def0d9e..215fe76 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
@@ -12,8 +12,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.classmerging.horizontal.testclasses.A;
import com.android.tools.r8.classmerging.horizontal.testclasses.B;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.google.common.collect.ImmutableSet;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import org.junit.Test;
public class PackagePrivateMemberAccessTest extends HorizontalClassMergingTestBase {
@@ -28,8 +27,7 @@
.addInnerClasses(getClass())
.addProgramClasses(A.class, B.class)
.addKeepMainRule(Main.class)
- .addOptionsModification(
- options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
index 6e898e9..46b277b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
@@ -12,9 +12,8 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.google.common.collect.ImmutableSet;
import org.junit.Test;
public class SynchronizedClassesTest extends HorizontalClassMergingTestBase {
@@ -33,8 +32,7 @@
.assertMergedInto(C.class, A.class)
.assertMergedInto(D.class, B.class)
.assertNoOtherClassesMerged())
- .addOptionsModification(
- options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
index 93aad99..8cd641d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
@@ -4,10 +4,16 @@
package com.android.tools.r8.classmerging.vertical;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -21,22 +27,41 @@
@Parameter(0)
public TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameter(1)
+ public boolean verifyLensLookup;
+
+ @Parameters(name = "{0}, verify: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(IncorrectRewritingOfInvokeSuperTest.class)
- .addKeepMainRule(TestClass.class)
- .addOptionsModification(options -> options.enableUnusedInterfaceRemoval = false)
- .enableInliningAnnotations()
- .addDontObfuscate()
- .setMinApi(parameters)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccess();
+ Box<R8TestCompileResult> compileResult = new Box<>();
+ AssertUtils.assertFailsCompilationIf(
+ verifyLensLookup,
+ () ->
+ compileResult.set(
+ testForR8(parameters.getBackend())
+ .addInnerClasses(IncorrectRewritingOfInvokeSuperTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> {
+ options.enableUnusedInterfaceRemoval = false;
+ options.testing.enableVerticalClassMergerLensAssertion = verifyLensLookup;
+ })
+ .enableInliningAnnotations()
+ .addDontObfuscate()
+ .setMinApi(parameters)
+ .compile()));
+
+ if (!compileResult.isSet()) {
+ assertTrue(verifyLensLookup);
+ return;
+ }
+
+ compileResult.get().run(parameters.getRuntime(), TestClass.class).assertSuccess();
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceAccessibleAfterVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceAccessibleAfterVerticalClassMergingTest.java
new file mode 100644
index 0000000..83063ce
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/InterfaceAccessibleAfterVerticalClassMergingTest.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2023, 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.classmerging.vertical;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.classmerging.vertical.testclasses.InterfaceAccessibleAfterVerticalClassMergingTestClasses;
+import com.android.tools.r8.classmerging.vertical.testclasses.InterfaceAccessibleAfterVerticalClassMergingTestClasses.A;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+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 InterfaceAccessibleAfterVerticalClassMergingTest 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(), InterfaceAccessibleAfterVerticalClassMergingTestClasses.class)
+ .addKeepMainRule(Main.class)
+ .addVerticallyMergedClassesInspector(
+ VerticallyMergedClassesInspector::assertNoClassesMerged)
+ .enableNoAccessModificationAnnotationsForClasses()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("B");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new B());
+ }
+ }
+
+ public static class B extends A {
+
+ @Override
+ public String toString() {
+ return "B";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/SyntheticBridgeSignaturesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/SyntheticBridgeSignaturesTest.java
index b7bb268..a16c9fb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/SyntheticBridgeSignaturesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/SyntheticBridgeSignaturesTest.java
@@ -13,11 +13,10 @@
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
-import com.google.common.collect.ImmutableSet;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,12 +50,9 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
- .addOptionsModification(
- options -> {
- if (!allowInlining) {
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
- }
- })
+ .applyIf(
+ !allowInlining,
+ builder -> builder.addOptionsModification(InlinerOptions::setOnlyForceInlining))
.addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
index b42cd2e..466e9fd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
@@ -12,10 +12,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
-import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
@@ -52,9 +51,9 @@
.addOptionsModification(
options -> {
options.forceProguardCompatibility = true;
- // The initializer is small in this example so only allow FORCE inlining.
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
})
+ // The initializer is small in this example so only allow FORCE inlining.
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.addVerticallyMergedClassesInspector(
VerticallyMergedClassesInspector::assertNoClassesMerged)
.compile()
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 7ec72c7..7db55ae 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.utils.AndroidApp.Builder;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -271,9 +272,7 @@
testForR8(parameters.getBackend())
.addKeepRules(getProguardConfig(EXAMPLE_KEEP))
.addOptionsModification(this::configure)
- .addOptionsModification(
- options ->
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.allowUnusedProguardConfigurationRules(),
main,
readProgramFiles(programFiles),
@@ -504,10 +503,8 @@
.addKeepRules(getProguardConfig(EXAMPLE_KEEP))
.addOptionsModification(this::configure)
.addOptionsModification(
- options -> {
- options.enableVerticalClassMerging = false;
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
- })
+ options -> options.getVerticalClassMergerOptions().disable())
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.allowUnusedProguardConfigurationRules(),
main,
readProgramFiles(programFiles),
@@ -527,8 +524,7 @@
testForR8(parameters.getBackend())
.addKeepRules(getProguardConfig(EXAMPLE_KEEP))
.addOptionsModification(this::configure)
- .addOptionsModification(
- options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.allowUnusedProguardConfigurationRules(),
main,
readProgramFiles(programFiles),
@@ -577,10 +573,8 @@
.addKeepRules(getProguardConfig(EXAMPLE_KEEP))
.addOptionsModification(this::configure)
.addOptionsModification(
- options -> {
- options.enableVerticalClassMerging = false;
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
- })
+ options -> options.getVerticalClassMergerOptions().disable())
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.allowUnusedProguardConfigurationRules(),
main,
readProgramFiles(programFiles),
@@ -608,8 +602,7 @@
testForR8(parameters.getBackend())
.addKeepRules(getProguardConfig(EXAMPLE_KEEP))
.addOptionsModification(this::configure)
- .addOptionsModification(
- options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.allowUnusedProguardConfigurationRules(),
main,
readProgramFiles(programFiles),
@@ -663,9 +656,8 @@
.addOptionsModification(this::configure)
.addOptionsModification(
options -> {
- options.enableVerticalClassMerging = false;
- options.testing.validInliningReasons =
- ImmutableSet.of(Reason.ALWAYS, Reason.FORCE);
+ options.getVerticalClassMergerOptions().disable();
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.ALWAYS);
})
.allowUnusedProguardConfigurationRules(),
main,
@@ -703,10 +695,8 @@
+ " method(); }"))
.addOptionsModification(this::configure)
.addOptionsModification(
- options -> {
- options.testing.validInliningReasons =
- ImmutableSet.of(Reason.ALWAYS, Reason.FORCE);
- })
+ options ->
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.ALWAYS))
.allowUnusedProguardConfigurationRules(),
main,
readProgramFiles(programFiles),
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/InterfaceAccessibleAfterVerticalClassMergingTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/InterfaceAccessibleAfterVerticalClassMergingTestClasses.java
new file mode 100644
index 0000000..6331ea7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/InterfaceAccessibleAfterVerticalClassMergingTestClasses.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2023, 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.classmerging.vertical.testclasses;
+
+import com.android.tools.r8.NoAccessModification;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+
+public class InterfaceAccessibleAfterVerticalClassMergingTestClasses {
+
+ @NoAccessModification
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {}
+
+ public static class A implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
index 7c7a868..abf86b0 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
@@ -9,17 +9,33 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.compatproguard.CompatProguard.CompatProguardOptions;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-public class CompatProguardTest {
+@RunWith(Parameterized.class)
+public class CompatProguardTest extends TestBase {
- private CompatProguardOptions parseArgs(String... args) {
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public CompatProguardTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ private static CompatProguardOptions parseArgs(String... args) {
return CompatProguard.CompatProguardOptions.parse(args);
}
@Test
- public void testDefaultDataResources() throws Exception {
+ public void testDefaultDataResources() {
CompatProguardOptions options = parseArgs();
assertNull(options.output);
assertEquals(1, options.minApi);
@@ -31,13 +47,13 @@
}
@Test
- public void testShortLine() throws Exception {
+ public void testShortLine() {
CompatProguardOptions options = parseArgs("-");
assertEquals(1, options.proguardConfig.size());
}
@Test
- public void testProguardOptions() throws Exception {
+ public void testProguardOptions() {
CompatProguardOptions options;
options = parseArgs("-xxx");
@@ -51,7 +67,7 @@
}
@Test
- public void testInjarsAndOutput() throws Exception {
+ public void testInjarsAndOutput() {
CompatProguardOptions options;
String injars = "input.jar";
String output = "outputdir";
@@ -63,7 +79,7 @@
}
@Test
- public void testMainDexList() throws Exception {
+ public void testMainDexList() {
CompatProguardOptions options;
String mainDexList = "maindexlist.txt";
@@ -78,30 +94,21 @@
}
@Test
- public void testInclude() throws Exception {
+ public void testInclude() {
CompatProguardOptions options = parseArgs("-include --my-include-file.txt");
assertEquals(1, options.proguardConfig.size());
assertEquals("-include --my-include-file.txt", options.proguardConfig.get(0));
}
@Test
- public void testNoLocalsOption() throws Exception {
+ public void testNoLocalsOption() {
CompatProguardOptions options = parseArgs("--no-locals");
assertEquals(0, options.proguardConfig.size());
}
@Test
- public void testNoDataResources() throws Exception {
+ public void testNoDataResources() {
CompatProguardOptions options = parseArgs("--no-data-resources");
assertFalse(options.includeDataResources);
}
-
- @Test
- public void testDisableVerticalClassMerging() throws Exception {
- CompatProguardOptions enabledOptions = parseArgs();
- assertFalse(enabledOptions.disableVerticalClassMerging);
-
- CompatProguardOptions disabledOptions = parseArgs("--no-vertical-class-merging");
- assertTrue(disabledOptions.disableVerticalClassMerging);
- }
}
diff --git a/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
new file mode 100644
index 0000000..af5c177
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compose/NestedComposableArgumentPropagationTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2023, 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.compose;
+
+import static com.google.common.base.Predicates.alwaysTrue;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.MinificationInspector;
+import com.android.tools.r8.utils.codeinspector.RepackagingInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.EnumMap;
+import java.util.function.BiFunction;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NestedComposableArgumentPropagationTest extends TestBase {
+
+ enum ComposableFunction {
+ A,
+ B,
+ C
+ }
+
+ static class CodeStats {
+
+ final int numberOfIfInstructions;
+
+ CodeStats(MethodSubject methodSubject) {
+ this.numberOfIfInstructions =
+ (int) methodSubject.streamInstructions().filter(InstructionSubject::isIf).count();
+ }
+ }
+
+ private static Path dump;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @BeforeClass
+ public static void setup() throws IOException {
+ dump = getStaticTemp().newFolder().toPath();
+ ZipUtils.unzip(
+ Paths.get(
+ ToolHelper.THIRD_PARTY_DIR,
+ "opensource-apps/compose-examples/changed-bitwise-value-propagation/dump.zip"),
+ dump);
+ }
+
+ public NestedComposableArgumentPropagationTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ @Test
+ public void test() throws Exception {
+ EnumMap<ComposableFunction, CodeStats> defaultCodeStats = build(false);
+ EnumMap<ComposableFunction, CodeStats> optimizedCodeStats = build(true);
+ for (ComposableFunction composableFunction : ComposableFunction.values()) {
+ CodeStats defaultCodeStatsForFunction = defaultCodeStats.get(composableFunction);
+ CodeStats optimizedCodeStatsForFunction = optimizedCodeStats.get(composableFunction);
+ assertTrue(
+ composableFunction
+ + ": "
+ + defaultCodeStatsForFunction.numberOfIfInstructions
+ + " vs "
+ + optimizedCodeStatsForFunction.numberOfIfInstructions,
+ defaultCodeStatsForFunction.numberOfIfInstructions
+ > optimizedCodeStatsForFunction.numberOfIfInstructions);
+ }
+ }
+
+ private EnumMap<ComposableFunction, CodeStats> build(boolean enableComposeOptimizations)
+ throws Exception {
+ Box<ClassReference> mainActivityKtClassReference =
+ new Box<>(Reference.classFromTypeName("com.example.MainActivityKt"));
+ R8TestCompileResult compileResult =
+ testForR8(Backend.DEX)
+ .addProgramFiles(dump.resolve("program.jar"))
+ .addClasspathFiles(dump.resolve("classpath.jar"))
+ .addLibraryFiles(dump.resolve("library.jar"))
+ .addKeepRuleFiles(dump.resolve("proguard.config"))
+ .addHorizontallyMergedClassesInspector(
+ updateMainActivityKt(
+ HorizontallyMergedClassesInspector::getTarget,
+ mainActivityKtClassReference,
+ false),
+ alwaysTrue())
+ .addRepackagingInspector(
+ updateMainActivityKt(
+ RepackagingInspector::getTarget, mainActivityKtClassReference, true))
+ .addMinificationInspector(
+ updateMainActivityKt(
+ MinificationInspector::getTarget, mainActivityKtClassReference, true))
+ .addOptionsModification(
+ options -> {
+ options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces();
+ options.testing.enableComposableOptimizationPass = enableComposeOptimizations;
+ options.testing.modelUnknownChangedAndDefaultArgumentsToComposableFunctions =
+ enableComposeOptimizations;
+ })
+ .setMinApi(AndroidApiLevel.N)
+ .allowDiagnosticMessages()
+ .allowUnnecessaryDontWarnWildcards()
+ .allowUnusedDontWarnPatterns()
+ .allowUnusedProguardConfigurationRules()
+ .compile();
+ return createCodeStats(compileResult.inspector().clazz(mainActivityKtClassReference.get()));
+ }
+
+ private EnumMap<ComposableFunction, CodeStats> createCodeStats(
+ ClassSubject mainActivityKtClassSubject) {
+ EnumMap<ComposableFunction, CodeStats> result = new EnumMap<>(ComposableFunction.class);
+ result.put(
+ ComposableFunction.A,
+ new CodeStats(mainActivityKtClassSubject.uniqueMethodWithOriginalName("A")));
+ result.put(
+ ComposableFunction.B,
+ new CodeStats(mainActivityKtClassSubject.uniqueMethodWithOriginalName("B")));
+ result.put(
+ ComposableFunction.C,
+ new CodeStats(mainActivityKtClassSubject.uniqueMethodWithOriginalName("C")));
+ return result;
+ }
+
+ private static <T> ThrowableConsumer<T> updateMainActivityKt(
+ BiFunction<T, ClassReference, ClassReference> targetFn,
+ Box<ClassReference> mainActivityKtClassReference,
+ boolean failIfUnchanged) {
+ return inspector -> {
+ ClassReference targetClass = targetFn.apply(inspector, mainActivityKtClassReference.get());
+ if (failIfUnchanged) {
+ assertNotEquals(mainActivityKtClassReference.get(), targetClass);
+ }
+ mainActivityKtClassReference.set(targetClass);
+ };
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
index 7404eed..8ffb905 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
@@ -5,17 +5,13 @@
package com.android.tools.r8.desugar.backports;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static org.hamcrest.CoreMatchers.containsString;
-import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.nio.file.Paths;
-import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@@ -43,16 +39,4 @@
registerTarget(AndroidApiLevel.T, 17);
}
-
- @Test
- @Override
- public void testD8() throws Exception {
- testD8(
- runResult ->
- runResult.applyIf(
- parameters.getDexRuntimeVersion().isEqualTo(Version.V6_0_1)
- && parameters.getApiLevel().isGreaterThan(AndroidApiLevel.B),
- rr -> rr.assertFailureWithErrorThatMatches(containsString("SIGSEGV")),
- SingleTestRunResult::assertSuccess));
- }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
index c134dc8..1d3b636 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
@@ -131,10 +131,10 @@
BackportedMethodRewriter.generateListOfBackportedMethods(libHolder, options);
Map<DexMethod, Object> failures = new IdentityHashMap<>();
for (FoundClassSubject clazz : inspector.allClasses()) {
- if (clazz.toString().startsWith("j$.sun.nio.cs.UTF_8")
+ if (clazz.toString().startsWith("j$.sun.nio.cs.")
&& parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
- // At high API level, the class UTF_8 is there just for resolution, the field access is
- // retargeted and the code is unused so it's ok if it does not resolve.
+ // At high API level, the sun.nio.cs classes are there just for resolution, the field
+ // access is retargeted and the code is unused so it's ok if it does not resolve.
continue;
}
for (FoundMethodSubject method : clazz.allMethods()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
index 7d29fa4..2d01320 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -217,14 +217,10 @@
&& api.isLessThan(AndroidApiLevel.T)) {
expectedFailures.addAll(FAILURES_FILE_STORE);
}
- if (librarySpecification != JDK11_MINIMAL && api.isLessThan(AndroidApiLevel.T)) {
- if (librarySpecification == JDK8) {
- if (api.isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
- expectedFailures.addAll(FAILURES_TO_ARRAY);
- }
- } else {
- expectedFailures.addAll(FAILURES_TO_ARRAY);
- }
+ if (librarySpecification == JDK8
+ && api.isLessThan(AndroidApiLevel.T)
+ && api.isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
+ expectedFailures.addAll(FAILURES_TO_ARRAY);
}
if (jdk11NonMinimal
&& api.isGreaterThanOrEqualTo(AndroidApiLevel.O)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java
index bd5554af..6f1b750 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java
@@ -18,7 +18,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -54,7 +53,6 @@
this.compilationSpecification = compilationSpecification;
}
- @Ignore("b/266401747")
@Test
public void test() throws Throwable {
testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
index e2c0f74..fd41179 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary.specification;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.*;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -64,7 +65,7 @@
public void testMultiLevelLegacy() throws IOException {
Assume.assumeTrue(ToolHelper.isLocalDevelopment());
- LibraryDesugaringSpecification legacySpec = LibraryDesugaringSpecification.JDK8;
+ LibraryDesugaringSpecification legacySpec = JDK8;
LegacyToHumanSpecificationConverter converter =
new LegacyToHumanSpecificationConverter(Timing.empty());
@@ -108,13 +109,13 @@
@Test
public void testMultiLevelLegacyUsingMain() throws IOException {
- LibraryDesugaringSpecification legacySpec = LibraryDesugaringSpecification.JDK8;
+ LibraryDesugaringSpecification legacySpec = JDK8;
testMultiLevelUsingMain(legacySpec);
}
@Test
public void testMultiLevelHumanUsingMain() throws IOException {
- LibraryDesugaringSpecification humanSpec = LibraryDesugaringSpecification.JDK11;
+ LibraryDesugaringSpecification humanSpec = JDK11;
testMultiLevelUsingMain(humanSpec);
}
@@ -133,12 +134,6 @@
output,
options);
- MachineDesugaredLibrarySpecification machineSpecParsed =
- new MachineDesugaredLibrarySpecificationParser(
- options.dexItemFactory(), options.reporter, true, AndroidApiLevel.B.getLevel())
- .parse(StringResource.fromFile(output));
- assertFalse(machineSpecParsed.getRewriteType().isEmpty());
-
if (humanSpec == null) {
return;
}
@@ -154,24 +149,35 @@
assertSpecEquals(humanSpec, writtenHumanSpec);
// Validate converted machine spec is identical to the written one.
- HumanDesugaredLibrarySpecification humanSimpleSpec =
- new HumanDesugaredLibrarySpecificationParser(
- options.dexItemFactory(), options.reporter, true, AndroidApiLevel.B.getLevel())
- .parse(StringResource.fromString(json.get(), Origin.unknown()));
- HumanToMachineSpecificationConverter converter =
- new HumanToMachineSpecificationConverter(Timing.empty());
- DexApplication app = spec.getAppForTesting(options, true);
- MachineDesugaredLibrarySpecification machineSimpleSpec =
- converter.convert(humanSimpleSpec, app);
+ for (AndroidApiLevel api :
+ new AndroidApiLevel[] {AndroidApiLevel.B, AndroidApiLevel.N, AndroidApiLevel.O}) {
+ MachineDesugaredLibrarySpecification machineSpecParsed =
+ new MachineDesugaredLibrarySpecificationParser(
+ options.dexItemFactory(), options.reporter, true, api.getLevel())
+ .parse(StringResource.fromFile(output));
+ assertEquals(
+ api.isGreaterThanOrEqualTo(AndroidApiLevel.O) && spec == JDK8,
+ machineSpecParsed.getRewriteType().isEmpty());
- assertSpecEquals(machineSimpleSpec, machineSpecParsed);
+ HumanDesugaredLibrarySpecification humanSimpleSpec =
+ new HumanDesugaredLibrarySpecificationParser(
+ options.dexItemFactory(), options.reporter, true, api.getLevel())
+ .parse(StringResource.fromString(json.get(), Origin.unknown()));
+ HumanToMachineSpecificationConverter converter =
+ new HumanToMachineSpecificationConverter(Timing.empty());
+ DexApplication app = spec.getAppForTesting(options, true);
+ MachineDesugaredLibrarySpecification machineSimpleSpec =
+ converter.convert(humanSimpleSpec, app);
+
+ assertSpecEquals(machineSimpleSpec, machineSpecParsed);
+ }
}
@Test
public void testSingleLevel() throws IOException {
Assume.assumeTrue(ToolHelper.isLocalDevelopment());
- LibraryDesugaringSpecification humanSpec = LibraryDesugaringSpecification.JDK11_PATH;
+ LibraryDesugaringSpecification humanSpec = JDK11_PATH;
InternalOptions options = new InternalOptions();
@@ -296,6 +302,8 @@
assertEquals(
humanRewritingFlags1.getAmendLibraryMethod(), humanRewritingFlags2.getAmendLibraryMethod());
+ assertEquals(
+ humanRewritingFlags1.getAmendLibraryField(), humanRewritingFlags2.getAmendLibraryField());
}
private void assertTopLevelFlagsEquals(
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesNotInDexWithForceNestDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesNotInDexWithForceNestDesugaringTest.java
new file mode 100644
index 0000000..86eff4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesNotInDexWithForceNestDesugaringTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2023, 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.desugar.nestaccesscontrol;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesNotInDexWithForceNestDesugaringTest.Host.Member1;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesNotInDexWithForceNestDesugaringTest.Host.Member2;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+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 NestAttributesNotInDexWithForceNestDesugaringTest extends NestAttributesInDexTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean forceNestDesugaring;
+
+ @Parameters(name = "{0}, forceNestDesugaring: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimesAndAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8()
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters)
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addOptionsModification(options -> options.forceNestDesugaring = forceNestDesugaring)
+ .compile()
+ .inspect(
+ inspector -> {
+ assertThat(
+ inspector
+ .clazz(Member1.class)
+ .uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestInstanceMethodAccessor(
+ Member1.class.getDeclaredMethod("m1"))
+ .getMethodName()),
+ isPresentIf(forceNestDesugaring));
+ assertThat(
+ inspector
+ .clazz(Member2.class)
+ .uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestStaticMethodAccessor(
+ Member2.class.getDeclaredMethod("m2"))
+ .getMethodName()),
+ isPresentIf(forceNestDesugaring));
+ });
+ }
+
+ @Test
+ public void testD8NoDesugar() {
+ assumeTrue(forceNestDesugaring);
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .disableDesugaring()
+ .setMinApi(parameters)
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addOptionsModification(
+ options -> options.forceNestDesugaring = forceNestDesugaring)
+ .compile());
+ }
+
+ public Collection<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ withNest(Host.class)
+ .setAccessFlags(MethodPredicate.onName("h"), MethodAccessFlags::setPrivate)
+ .transform(),
+ withNest(Member1.class)
+ .setAccessFlags(MethodPredicate.onName("m1"), MethodAccessFlags::setPrivate)
+ .transform(),
+ withNest(Member2.class)
+ .setAccessFlags(MethodPredicate.onName("m2"), MethodAccessFlags::setPrivate)
+ .transform());
+ }
+
+ private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
+ return transformer(clazz).setNest(Host.class, Member1.class, Member2.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {}
+ }
+
+ static class Host {
+ static class Member1 {
+ void m1() { // Will be private.
+ Member2.m2();
+ }
+ }
+
+ static class Member2 {
+ static void m2() { // Will be private.
+ }
+ }
+
+ void h() { // Will be private.
+ new Member1().m1();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index 4dbc4c3e..65df54e 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -60,7 +60,7 @@
.addOptionsModification(
options -> {
options.enableClassInlining = false;
- options.enableVerticalClassMerging = false;
+ options.getVerticalClassMergerOptions().disable();
})
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
index c2846c5..5c08537 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
@@ -59,15 +59,14 @@
.compile()
.run(parameters.getRuntime(), MAIN_TYPE)
.applyIf(
- canUseNativeRecords(parameters),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_RECORD),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD));
+ isRecordsDesugaredForD8(parameters),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_RECORD));
}
@Test
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
- boolean willDesugarRecords = parameters.isDexRuntime() && !canUseNativeRecords(parameters);
testForR8(parameters.getBackend())
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addProgramClassFileData(PROGRAM_DATA)
@@ -77,13 +76,15 @@
.addKeepRules("-keep class records.EmptyRecordAnnotation$Empty")
.addKeepMainRule(MAIN_TYPE)
// This is used to avoid renaming com.android.tools.r8.RecordTag.
- .applyIf(willDesugarRecords, b -> b.addKeepRules("-keep class java.lang.Record"))
+ .applyIf(
+ isRecordsDesugaredForR8(parameters),
+ b -> b.addKeepRules("-keep class java.lang.Record"))
.compile()
.applyIf(parameters.isCfRuntime(), r -> r.inspect(RecordTestUtils::assertRecordsAreRecords))
.run(parameters.getRuntime(), MAIN_TYPE)
.applyIf(
- !willDesugarRecords,
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_RECORD),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD));
+ isRecordsDesugaredForR8(parameters),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_RECORD));
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
index 0fcb6da..05fdb8d 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
@@ -83,9 +83,10 @@
.setMinApi(parameters)
.run(parameters.getRuntime(), MAIN_TYPE)
.applyIf(
- canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
- r -> r.assertSuccessWithOutput(computeOutput(REFERENCE_OUTPUT_FORMAT)));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(computeOutput(REFERENCE_OUTPUT_FORMAT)),
+ r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
index 8ded32a..d2c05af 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
@@ -16,8 +16,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
@@ -36,7 +34,7 @@
private static final String RECORD_NAME = "RecordWithAnnotations";
private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
- private static final String JVM_EXPECTED_RESULT =
+ private static final String JVM_UNTIL_20_EXPECTED_RESULT =
StringUtils.lines(
"Jane Doe",
"42",
@@ -63,6 +61,9 @@
"2",
"@records.RecordWithAnnotations$Annotation(\"a\")",
"@records.RecordWithAnnotations$AnnotationFieldOnly(\"b\")");
+ private static final String JVM_FROM_21_EXPECTED_RESULT =
+ JVM_UNTIL_20_EXPECTED_RESULT.replaceAll(
+ "RecordWithAnnotations\\$Annotation", "RecordWithAnnotations.Annotation");
private static final String ART_EXPECTED_RESULT =
StringUtils.lines(
"Jane Doe",
@@ -219,29 +220,28 @@
testForJvm(parameters)
.addProgramClassFileData(PROGRAM_DATA)
.run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(JVM_EXPECTED_RESULT);
+ .assertSuccessWithOutput(
+ parameters.getRuntime().asCf().getVm().isLessThanOrEqualTo(CfVm.JDK20)
+ ? JVM_UNTIL_20_EXPECTED_RESULT
+ : JVM_FROM_21_EXPECTED_RESULT);
}
@Test
public void testDesugaring() throws Exception {
parameters.assumeDexRuntime();
assumeTrue(keepAnnotations);
- // Android U will support records.
- boolean compilingForNativeRecordSupport =
- parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
- boolean runtimeWithNativeRecordSupport =
- parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V14_0_0);
testForDesugaring(parameters)
.addProgramClassFileData(PROGRAM_DATA)
.run(parameters.getRuntime(), MAIN_TYPE)
.applyIf(
- compilingForNativeRecordSupport,
- r -> r.assertSuccessWithOutput(ART_EXPECTED_RESULT),
+ isRecordsDesugaredForD8(parameters),
r ->
r.assertSuccessWithOutput(
- !runtimeWithNativeRecordSupport
- ? EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT
- : EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT)
+ runtimeWithRecordsSupport(parameters.getRuntime())
+ ? EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT
+ : EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT),
+ r ->
+ r.assertSuccessWithOutput(ART_EXPECTED_RESULT)
.inspect(
inspector -> {
ClassSubject person =
@@ -250,74 +250,60 @@
assertThat(name, isPresentAndNotRenamed());
FieldSubject age = person.uniqueFieldWithOriginalName("age");
assertThat(age, isPresentAndNotRenamed());
- if (compilingForNativeRecordSupport) {
- assertEquals(2, person.getFinalRecordComponents().size());
+ assertEquals(2, person.getFinalRecordComponents().size());
- assertEquals(
- name.getFinalName(),
- person.getFinalRecordComponents().get(0).getName());
- assertTrue(
- person
- .getFinalRecordComponents()
- .get(0)
- .getType()
- .is("java.lang.String"));
- assertNull(person.getFinalRecordComponents().get(0).getSignature());
- assertEquals(
- 2,
- person.getFinalRecordComponents().get(0).getAnnotations().size());
- assertThat(
- person.getFinalRecordComponents().get(0).getAnnotations(),
- hasAnnotationTypes(
- inspector.getTypeSubject(
- "records.RecordWithAnnotations$Annotation"),
- inspector.getTypeSubject(
- "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
- assertThat(
- person.getFinalRecordComponents().get(0).getAnnotations().get(0),
- hasElements(new Pair<>("value", "a")));
- assertThat(
- person.getFinalRecordComponents().get(0).getAnnotations().get(1),
- hasElements(new Pair<>("value", "c")));
+ assertEquals(
+ name.getFinalName(),
+ person.getFinalRecordComponents().get(0).getName());
+ assertTrue(
+ person
+ .getFinalRecordComponents()
+ .get(0)
+ .getType()
+ .is("java.lang.String"));
+ assertNull(person.getFinalRecordComponents().get(0).getSignature());
+ assertEquals(
+ 2, person.getFinalRecordComponents().get(0).getAnnotations().size());
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations(),
+ hasAnnotationTypes(
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$Annotation"),
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations().get(0),
+ hasElements(new Pair<>("value", "a")));
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations().get(1),
+ hasElements(new Pair<>("value", "c")));
- assertEquals(
- age.getFinalName(),
- person.getFinalRecordComponents().get(1).getName());
- assertTrue(
- person.getFinalRecordComponents().get(1).getType().is("int"));
- assertNull(person.getFinalRecordComponents().get(1).getSignature());
- assertEquals(
- 2,
- person.getFinalRecordComponents().get(1).getAnnotations().size());
- assertThat(
- person.getFinalRecordComponents().get(1).getAnnotations(),
- hasAnnotationTypes(
- inspector.getTypeSubject(
- "records.RecordWithAnnotations$Annotation"),
- inspector.getTypeSubject(
- "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
- assertThat(
- person.getFinalRecordComponents().get(1).getAnnotations().get(0),
- hasElements(new Pair<>("value", "x")));
- assertThat(
- person.getFinalRecordComponents().get(1).getAnnotations().get(1),
- hasElements(new Pair<>("value", "z")));
- } else {
- assertEquals(0, person.getFinalRecordComponents().size());
- }
+ assertEquals(
+ age.getFinalName(),
+ person.getFinalRecordComponents().get(1).getName());
+ assertTrue(person.getFinalRecordComponents().get(1).getType().is("int"));
+ assertNull(person.getFinalRecordComponents().get(1).getSignature());
+ assertEquals(
+ 2, person.getFinalRecordComponents().get(1).getAnnotations().size());
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations(),
+ hasAnnotationTypes(
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$Annotation"),
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations().get(0),
+ hasElements(new Pair<>("value", "x")));
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations().get(1),
+ hasElements(new Pair<>("value", "z")));
}));
}
@Test
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
- // Android U will support records.
- boolean compilingForNativeRecordSupport =
- parameters.isCfRuntime()
- || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
- boolean runtimeWithNativeRecordSupport =
- parameters.isCfRuntime()
- || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V14_0_0);
testForR8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
// TODO(b/231930852): Change to android.jar for Androud U when that contains
@@ -337,7 +323,7 @@
ClassSubject person = inspector.clazz("records.RecordWithAnnotations$Person");
FieldSubject name = person.uniqueFieldWithOriginalName("name");
FieldSubject age = person.uniqueFieldWithOriginalName("age");
- if (compilingForNativeRecordSupport) {
+ if (!isRecordsDesugaredForR8(parameters)) {
assertEquals(2, person.getFinalRecordComponents().size());
assertEquals(
@@ -390,7 +376,7 @@
keepAnnotations
? JVM_EXPECTED_RESULT_R8
: JVM_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS),
- compilingForNativeRecordSupport,
+ !isRecordsDesugaredForR8(parameters),
r ->
r.assertSuccessWithOutput(
keepAnnotations
@@ -398,7 +384,7 @@
: ART_EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS),
r ->
r.assertSuccessWithOutput(
- runtimeWithNativeRecordSupport
+ runtimeWithRecordsSupport(parameters.getRuntime())
? EXPECTED_RESULT_DESUGARED_RECORD_SUPPORT
: EXPECTED_RESULT_DESUGARED_NO_RECORD_SUPPORT));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
index 9004d73..58f0da0 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
@@ -13,8 +13,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -50,9 +48,9 @@
"0");
private static final String EXPECTED_RESULT_R8 =
StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "true", "0");
- private static final String EXPECTED_RESULT_DESUGARED =
+ private static final String EXPECTED_RESULT_DESUGARED_NO_NATIVE_RECORDS_SUPPORT =
StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "Class.isRecord not present");
- private static final String EXPECTED_RESULT_DESUGARED_ART_14 =
+ private static final String EXPECTED_RESULT_DESUGARED_NATIVE_RECORD_SUPPORT =
StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "false");
@Parameter(0)
@@ -86,29 +84,23 @@
public void testDesugaring() throws Exception {
parameters.assumeDexRuntime();
assumeTrue(keepSignatures);
- // Android U will support records.
- boolean compilingForNativeRecordSupport =
- parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
- boolean runningWithNativeRecordSupport =
- parameters.getRuntime().isDex()
- && parameters.getRuntime().asDex().getVersion().isNewerThanOrEqual(Version.V14_0_0);
testForDesugaring(parameters)
.addProgramClassFileData(PROGRAM_DATA)
.run(parameters.getRuntime(), MAIN_TYPE)
.applyIf(
- compilingForNativeRecordSupport,
+ !isRecordsDesugaredForD8(parameters),
// Current Art 14 build does not support the java.lang.Record class.
r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
r ->
r.assertSuccessWithOutput(
- runningWithNativeRecordSupport
- ? EXPECTED_RESULT_DESUGARED_ART_14
- : EXPECTED_RESULT_DESUGARED)
+ runtimeWithRecordsSupport(parameters.getRuntime())
+ ? EXPECTED_RESULT_DESUGARED_NATIVE_RECORD_SUPPORT
+ : EXPECTED_RESULT_DESUGARED_NO_NATIVE_RECORDS_SUPPORT)
.inspect(
inspector -> {
ClassSubject person =
inspector.clazz("records.RecordWithSignature$Person");
- if (compilingForNativeRecordSupport) {
+ if (!isRecordsDesugaredForD8(parameters)) {
assertEquals(2, person.getFinalRecordComponents().size());
assertEquals(
@@ -146,16 +138,10 @@
@Test
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
- boolean compilingForNativeRecordSupport =
- parameters.isCfRuntime()
- || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
- boolean runningWithNativeRecordSupport =
- parameters.getRuntime().isDex()
- && parameters.getRuntime().asDex().getVersion().isNewerThanOrEqual(Version.V14_0_0);
testForR8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
// TODO(b/231930852): Change to android.jar for Android U when that contains
- // java.lang.Record.
+ // java.lang.Record.
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.addKeepMainRule(MAIN_TYPE)
.applyIf(keepSignatures, TestShrinkerBuilder::addKeepAttributeSignature)
@@ -169,14 +155,13 @@
assertEquals(0, person.getFinalRecordComponents().size());
})
.run(parameters.getRuntime(), MAIN_TYPE)
- // No Art VM actually supports the java.lang.Record class.
.applyIf(
- compilingForNativeRecordSupport,
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_R8),
+ runtimeWithRecordsSupport(parameters.getRuntime()),
r ->
r.assertSuccessWithOutput(
- runningWithNativeRecordSupport
- ? EXPECTED_RESULT_DESUGARED_ART_14
- : EXPECTED_RESULT_DESUGARED));
+ isRecordsDesugaredForR8(parameters)
+ ? EXPECTED_RESULT_DESUGARED_NATIVE_RECORD_SUPPORT
+ : EXPECTED_RESULT_R8),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_NO_NATIVE_RECORDS_SUPPORT));
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
index 0c63278..7e1d2c7 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
@@ -66,9 +66,10 @@
.setMinApi(parameters)
.run(parameters.getRuntime(), MAIN_TYPE)
.applyIf(
- canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
}
@Test
@@ -80,11 +81,11 @@
testForD8()
.addProgramFiles(path)
.applyIf(
- canUseNativeRecords(parameters),
- b -> assertFalse(globals.hasGlobals()),
+ isRecordsDesugaredForD8(parameters),
b ->
b.getBuilder()
- .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
+ .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()),
+ b -> assertFalse(globals.hasGlobals()))
.apply(b -> b.getBuilder().setDesugarGraphConsumer(consumer))
.setMinApi(parameters)
.setIncludeClassesChecksum(true)
@@ -104,11 +105,11 @@
testForD8()
.addProgramFiles(path)
.applyIf(
- canUseNativeRecords(parameters),
- b -> assertFalse(globals.hasGlobals()),
+ isRecordsDesugaredForD8(parameters),
b ->
b.getBuilder()
- .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
+ .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()),
+ b -> assertFalse(globals.hasGlobals()))
.apply(b -> b.getBuilder().setDesugarGraphConsumer(consumer))
.setMinApi(parameters)
.setIncludeClassesChecksum(true)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
index 214afea..fa22262 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
@@ -4,12 +4,20 @@
package com.android.tools.r8.desugar.records;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.DuplicateTypeInProgramAndLibraryDiagnostic;
+import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
import java.nio.file.Path;
@@ -41,8 +49,6 @@
String.format(EXPECTED_RESULT, "Empty", "Person", "name", "age");
private static final String EXPECTED_RESULT_R8 =
String.format(EXPECTED_RESULT, "a", "b", "name", "age");
- private static final String EXPECTED_RESULT_R8_2 =
- String.format(EXPECTED_RESULT, "a", "b", "a", "b");
private final TestParameters parameters;
@@ -52,7 +58,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build();
+ return getTestParameters().withDexRuntimesAndAllApiLevels().build();
}
@Test
@@ -80,17 +86,37 @@
.setMinApi(parameters)
.compile()
.writeToZip();
- if (canUseNativeRecords(parameters)) {
- assertFalse(ZipUtils.containsEntry(desugared, "com/android/tools/r8/RecordTag.class"));
- } else {
+ if (isRecordsDesugaredForD8(parameters)) {
assertTrue(ZipUtils.containsEntry(desugared, "com/android/tools/r8/RecordTag.class"));
+ } else {
+ assertFalse(ZipUtils.containsEntry(desugared, "com/android/tools/r8/RecordTag.class"));
}
testForR8(parameters.getBackend())
.addProgramFiles(desugared)
.setMinApi(parameters)
.addKeepMainRule(MAIN_TYPE)
+ .allowDiagnosticMessages()
+ .compileWithExpectedDiagnostics(
+ // Class com.android.tools.r8.RecordTag in desugared input is seen as java.lang.Record
+ // when reading causing the duplicate class.
+ diagnostics -> {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U)) {
+ diagnostics
+ .assertNoErrors()
+ .assertInfosMatch(
+ allOf(
+ diagnosticType(DuplicateTypesDiagnostic.class),
+ diagnosticType(DuplicateTypeInProgramAndLibraryDiagnostic.class),
+ diagnosticMessage(containsString("java.lang.Record"))))
+ .assertWarningsMatch(
+ allOf(
+ diagnosticType(StringDiagnostic.class),
+ diagnosticMessage(containsString("java.lang.Record"))));
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ })
.run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(
- canUseNativeRecords(parameters) ? EXPECTED_RESULT_R8_2 : EXPECTED_RESULT_R8);
+ .assertSuccessWithOutput(EXPECTED_RESULT_R8);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index 38954bf..a46c400 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -74,9 +74,7 @@
.addProgramClassFileData(PROGRAM_DATA_1)
.setMinApi(parameters)
.setIntermediate(true);
- if (canUseNativeRecords(parameters)) {
- builder.compile();
- } else {
+ if (isRecordsDesugaredForD8(parameters)) {
assertThrows(
CompilationFailedException.class,
() ->
@@ -86,6 +84,8 @@
.assertOnlyErrors()
.assertErrorsMatch(
diagnosticType(MissingGlobalSyntheticsConsumerDiagnostic.class))));
+ } else {
+ builder.compile();
}
}
@@ -129,8 +129,8 @@
.inspect(this::assertDoesNotHaveRecordTag)
.writeToZip();
- assertTrue(canUseNativeRecords(parameters) ^ globals1.hasGlobals());
- assertTrue(canUseNativeRecords(parameters) ^ globals2.hasGlobals());
+ assertTrue(isRecordsDesugaredForD8(parameters) ^ !globals1.hasGlobals());
+ assertTrue(isRecordsDesugaredForD8(parameters) ^ !globals2.hasGlobals());
D8TestCompileResult result =
testForD8(parameters.getBackend())
@@ -147,15 +147,17 @@
result
.run(parameters.getRuntime(), MAIN_TYPE_1)
.applyIf(
- canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
result
.run(parameters.getRuntime(), MAIN_TYPE_2)
.applyIf(
- canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
}
@Test
@@ -181,15 +183,17 @@
result
.run(parameters.getRuntime(), MAIN_TYPE_1)
.applyIf(
- canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
result
.run(parameters.getRuntime(), MAIN_TYPE_2)
.applyIf(
- canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
}
@Test
@@ -210,7 +214,7 @@
.inspect(this::assertHasRecordTag)
.writeToZip();
- if (canUseNativeRecords(parameters)) {
+ if (!isRecordsDesugaredForD8(parameters)) {
D8TestCompileResult result =
testForD8(parameters.getBackend())
.addProgramFiles(output1, output2)
@@ -219,17 +223,17 @@
result
.run(parameters.getRuntime(), MAIN_TYPE_1)
.applyIf(
- canUseNativeRecords(parameters)
- && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_1),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
result
.run(parameters.getRuntime(), MAIN_TYPE_2)
.applyIf(
- canUseNativeRecords(parameters)
- && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_2),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
} else {
assertThrows(
CompilationFailedException.class,
@@ -247,7 +251,8 @@
private void assertHasRecordTag(CodeInspector inspector) {
// Note: this should be asserting on record tag.
- assertThat(inspector.clazz("java.lang.Record"), isPresentIf(!canUseNativeRecords(parameters)));
+ assertThat(
+ inspector.clazz("java.lang.Record"), isPresentIf(isRecordsDesugaredForD8(parameters)));
}
private void assertDoesNotHaveRecordTag(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index 5ed96ab..24757af 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.desugar.records;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
@@ -77,7 +76,6 @@
@Test
public void testD8() throws Exception {
assumeFalse(forceInvokeRangeForInvokeCustom);
- boolean runningWithNativeRecordSupport = runtimeWithRecordsSupport(parameters.getRuntime());
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
.setMinApi(parameters)
@@ -87,9 +85,10 @@
options -> options.testing.disableRecordApplicationReaderMap = true)
.run(parameters.getRuntime(), MAIN_TYPE)
.applyIf(
- canUseNativeRecords(parameters) && !runningWithNativeRecordSupport,
- r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
;
}
@@ -101,9 +100,7 @@
Path path = compileIntermediate(globals);
testForD8()
.addProgramFiles(path)
- .applyIf(
- parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U),
- b -> assertFalse(globals.hasGlobals()),
+ .apply(
b ->
b.getBuilder()
.addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
@@ -122,9 +119,7 @@
// In Android Studio they disable desugaring at this point to improve build speed.
testForD8()
.addProgramFiles(path)
- .applyIf(
- parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U),
- b -> assertFalse(globals.hasGlobals()),
+ .apply(
b ->
b.getBuilder()
.addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsVerticalMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsVerticalMergeTest.java
index c833f8a..6b9d3f9 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsVerticalMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsVerticalMergeTest.java
@@ -72,9 +72,7 @@
.addKeepPermittedSubclasses(Super.class, Sub2.class, UnrelatedSuper.class)
.addKeepMainRule(TestClass.class)
.addVerticallyMergedClassesInspector(
- inspector -> {
- inspector.assertMergedIntoSubtype(Sub1.class);
- })
+ inspector -> inspector.assertMergedIntoSubtype(Sub1.class))
.addHorizontallyMergedClassesInspector(
HorizontallyMergedClassesInspector::assertNoClassesMerged)
.compile()
@@ -99,8 +97,7 @@
static class TestClass {
public static void main(String[] args) {
- new SubSub();
- System.out.println("Success!");
+ System.out.println(new SubSub());
}
}
@@ -110,7 +107,13 @@
static class Sub2 extends Super {}
- static class SubSub extends Sub1 {}
+ static class SubSub extends Sub1 {
+
+ @Override
+ public String toString() {
+ return "Success!";
+ }
+ }
abstract static class UnrelatedSuper /* permits Sub1, Sub2 */ {}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsVerticalMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsVerticalMergeTest.java
index 3f218f0..1b9c9a1 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsVerticalMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsVerticalMergeTest.java
@@ -108,8 +108,7 @@
static class TestClass {
public static void main(String[] args) {
- new SubSub();
- System.out.println("Success!");
+ System.out.println(new SubSub());
}
}
@@ -128,5 +127,11 @@
static class Sub2 extends Super implements Iface1 {}
- static class SubSub extends Sub1 {}
+ static class SubSub extends Sub1 {
+
+ @Override
+ public String toString() {
+ return "Success!";
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/dex/TryCatchRangeOverflowTest.java b/src/test/java/com/android/tools/r8/dex/TryCatchRangeOverflowTest.java
index 6e9d85c..c59fc1c 100644
--- a/src/test/java/com/android/tools/r8/dex/TryCatchRangeOverflowTest.java
+++ b/src/test/java/com/android/tools/r8/dex/TryCatchRangeOverflowTest.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Arrays;
@@ -80,10 +79,7 @@
compile(addCount)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("" + addCount)
- .inspect(
- inspector ->
- checkTryCatchHandlers(
- 1 + BooleanUtils.intValue(addCount > UNSPLIT_LIMIT + 1), inspector));
+ .inspect(inspector -> checkTryCatchHandlers(2, inspector));
}
}
@@ -115,7 +111,7 @@
compile(addCount)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("" + addCount)
- .inspect(inspector -> checkTryCatchHandlers(2, inspector));
+ .inspect(inspector -> checkTryCatchHandlers(3, inspector));
}
private D8TestBuilder compile(int addCount) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
index e706afa..6876898 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
@@ -120,7 +120,8 @@
.addProgramClassFileData(programClassFileData)
.addKeepMainRule("TestClass")
.addOptionsModification(
- options -> options.enableVerticalClassMerging = enableVerticalClassMerging)
+ options ->
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
.allowDiagnosticWarningMessages()
.setMinApi(parameters)
.compile();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index b00cc7e..4817266 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -20,6 +20,7 @@
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.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.BooleanUtils;
@@ -102,7 +103,9 @@
}
private void fixInliningNullabilityClass(
- DexItemFactory dexItemFactory, HorizontallyMergedClasses horizontallyMergedClasses) {
+ DexItemFactory dexItemFactory,
+ HorizontallyMergedClasses horizontallyMergedClasses,
+ HorizontalClassMerger.Mode mode) {
DexType originalType =
dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor("inlining.Nullability"));
nullabilityClass =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/BoxedPrimitiveFromGenericUnboxingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/BoxedPrimitiveFromGenericUnboxingTest.java
new file mode 100644
index 0000000..5a23263
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/BoxedPrimitiveFromGenericUnboxingTest.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2023, 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.boxedprimitives;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+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.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+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 BoxedPrimitiveFromGenericUnboxingTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableBridgeHoistingToSharedSyntheticSuperclass;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, opt: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ boolean optimize =
+ enableBridgeHoistingToSharedSyntheticSuperclass
+ && parameters.canHaveNonReboundConstructorInvoke();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options ->
+ options.testing.enableBridgeHoistingToSharedSyntheticSuperclass =
+ enableBridgeHoistingToSharedSyntheticSuperclass)
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ // Function should be removed as a result of bridge hoisting + inlining when adding a
+ // shared superclass to Increment and Decrement, and another shared superclass to
+ // StdoutPrinter and StderrPrinter.
+ ClassSubject functionClassSubject = inspector.clazz(Function.class);
+ assertThat(functionClassSubject, isAbsentIf(optimize));
+
+ // Check that the cast to java.lang.Integer in Increment.apply has been removed as a
+ // result of devirtualization.
+ ClassSubject incrementClassSubject = inspector.clazz(Increment.class);
+ assertThat(incrementClassSubject, isPresent());
+
+ MethodSubject incrementApplyMethodSubject =
+ incrementClassSubject.uniqueMethodWithOriginalName("apply");
+ assertThat(incrementApplyMethodSubject, isPresent());
+ assertEquals(
+ optimize,
+ incrementApplyMethodSubject
+ .streamInstructions()
+ .noneMatch(
+ instruction -> instruction.isCheckCast(Integer.class.getTypeName())));
+
+ // Check that the cast to java.lang.String in StdoutPrinter.apply has been removed as
+ // result of devirtualization (in fact the `Void apply(String)` method has been
+ // optimized to `void apply()` as a result of constant propagation).
+ ClassSubject stdoutPrinterClassSubject = inspector.clazz(StdoutPrinter.class);
+ assertThat(stdoutPrinterClassSubject, isPresent());
+
+ MethodSubject stdoutPrinterApplyMethodSubject =
+ stdoutPrinterClassSubject.uniqueMethodWithOriginalName("apply");
+ assertThat(stdoutPrinterApplyMethodSubject, isPresent());
+ assertEquals(
+ optimize,
+ stdoutPrinterApplyMethodSubject.getProgramMethod().getReturnType().isVoidType());
+ assertEquals(
+ optimize ? 0 : 1, stdoutPrinterApplyMethodSubject.getParameters().size());
+ assertEquals(
+ optimize,
+ stdoutPrinterApplyMethodSubject
+ .streamInstructions()
+ .noneMatch(
+ instruction -> instruction.isCheckCast(String.class.getTypeName())));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42", "42", "42");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Function<Integer, Integer> inc =
+ System.currentTimeMillis() > 0 ? new Increment() : new Decrement();
+ Function<Integer, Integer> dec =
+ System.currentTimeMillis() > 0 ? new Decrement() : new Increment();
+ Function<String, Void> printer =
+ System.currentTimeMillis() > 0 ? new StdoutPrinter() : new StderrPrinter();
+ System.out.println(inc.apply(41));
+ System.out.println(dec.apply(43));
+ printer.apply("42");
+ }
+ }
+
+ interface Function<S, T> {
+
+ T apply(S s);
+ }
+
+ @NoHorizontalClassMerging
+ static class Increment implements Function<Integer, Integer> {
+
+ @Override
+ public Integer apply(Integer i) {
+ return i + 1;
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class Decrement implements Function<Integer, Integer> {
+
+ @Override
+ public Integer apply(Integer i) {
+ return i - 1;
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class StdoutPrinter implements Function<String, Void> {
+
+ @Override
+ public Void apply(String obj) {
+ System.out.println(obj);
+ return null;
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class StderrPrinter implements Function<String, Void> {
+
+ @Override
+ public Void apply(String obj) {
+ System.err.println(obj);
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/ReverseBoxingOperationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/ReverseBoxingOperationsTest.java
new file mode 100644
index 0000000..e19798b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/ReverseBoxingOperationsTest.java
@@ -0,0 +1,345 @@
+// Copyright (c) 2023, 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.boxedprimitives;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+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 ReverseBoxingOperationsTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testUnboxingRemoved() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::assertBoxOperationsRemoved)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ private void assertBoxOperationsRemoved(CodeInspector codeInspector) {
+ DexItemFactory factory = codeInspector.getFactory();
+
+ Set<DexMethod> unboxMethods = Sets.newIdentityHashSet();
+ Set<DexMethod> boxMethods = Sets.newIdentityHashSet();
+ for (DexType primitiveType : factory.unboxPrimitiveMethod.keySet()) {
+ unboxMethods.add(factory.getUnboxPrimitiveMethod(primitiveType));
+ boxMethods.add(factory.getBoxPrimitiveMethod(primitiveType));
+ }
+
+ // All box operations should have been removed, except for the unbox methods that act as null
+ // checks, which may or may not be replaced by null-checks depending if they are reprocessed.
+ assertEquals(
+ 24,
+ codeInspector
+ .clazz(Main.class)
+ .allMethods(m -> !m.getOriginalName().equals("main"))
+ .size());
+ codeInspector
+ .clazz(Main.class)
+ .allMethods(m -> !m.getOriginalName().equals("main"))
+ .forEach(
+ m ->
+ assertTrue(
+ m.streamInstructions()
+ .noneMatch(i -> i.isInvoke() && boxMethods.contains(i.getMethod()))));
+ codeInspector
+ .clazz(Main.class)
+ .allMethods(
+ m ->
+ !m.getOriginalName().equals("main")
+ && (m.getOriginalName().contains("Unbox")
+ || m.getOriginalName().contains("NonNull")))
+ .forEach(
+ m ->
+ assertTrue(
+ m.streamInstructions()
+ .noneMatch(i -> i.isInvoke() && unboxMethods.contains(i.getMethod()))));
+ }
+
+ private String getExpectedResult() {
+ String[] resultItems = {"1", "1", "1.0", "1.0", "1", "1", "c", "true"};
+ String[] resultItems2 = {"2", "2", "2.0", "2.0", "2", "2", "e", "false"};
+ List<String> result = new ArrayList<>();
+ for (int i = 0; i < resultItems.length; i++) {
+ String item = resultItems[i];
+ String item2 = resultItems2[i];
+ result.add(">" + item);
+ result.add(">" + item);
+ result.add(">npe failure");
+ result.add(">" + item);
+ result.add(">" + item2);
+ }
+ return StringUtils.lines(result);
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ int i = System.currentTimeMillis() > 0 ? 1 : 0;
+ System.out.println(intUnboxTest(i));
+ System.out.println(intTest(i));
+ try {
+ System.out.println(intTest(null));
+ } catch (NullPointerException npe7) {
+ System.out.println("npe failure");
+ }
+ System.out.println(intTestNonNull(i));
+ System.out.println(intTestNonNull(i + 1));
+
+ long l = System.currentTimeMillis() > 0 ? 1L : 0L;
+ System.out.println(longUnboxTest(l));
+ System.out.println(longTest(l));
+ try {
+ System.out.println(longTest(null));
+ } catch (NullPointerException npe6) {
+ System.out.println("npe failure");
+ }
+ System.out.println(longTestNonNull(l));
+ System.out.println(longTestNonNull(l + 1));
+
+ double d = System.currentTimeMillis() > 0 ? 1.0 : 0.0;
+ System.out.println(doubleUnboxTest(d));
+ System.out.println(doubleTest(d));
+ try {
+ System.out.println(doubleTest(null));
+ } catch (NullPointerException npe5) {
+ System.out.println("npe failure");
+ }
+ System.out.println(doubleTestNonNull(d));
+ System.out.println(doubleTestNonNull(d + 1));
+
+ float f = System.currentTimeMillis() > 0 ? 1.0f : 0.0f;
+ System.out.println(floatUnboxTest(f));
+ System.out.println(floatTest(f));
+ try {
+ System.out.println(floatTest(null));
+ } catch (NullPointerException npe4) {
+ System.out.println("npe failure");
+ }
+ System.out.println(floatTestNonNull(f));
+ System.out.println(floatTestNonNull(f + 1));
+
+ byte b = (byte) (System.currentTimeMillis() > 0 ? 1 : 0);
+ System.out.println(byteUnboxTest(b));
+ System.out.println(byteTest(b));
+ try {
+ System.out.println(byteTest(null));
+ } catch (NullPointerException npe3) {
+ System.out.println("npe failure");
+ }
+ System.out.println(byteTestNonNull(b));
+ System.out.println(byteTestNonNull((byte) (b + 1)));
+
+ short s = (short) (System.currentTimeMillis() > 0 ? 1 : 0);
+ System.out.println(shortUnboxTest(s));
+ System.out.println(shortTest(s));
+ try {
+ System.out.println(shortTest(null));
+ } catch (NullPointerException npe2) {
+ System.out.println("npe failure");
+ }
+ System.out.println(shortTestNonNull(s));
+ System.out.println(shortTestNonNull((short) (s + 1)));
+
+ char c = System.currentTimeMillis() > 0 ? 'c' : 'd';
+ System.out.println(charUnboxTest(c));
+ System.out.println(charTest(c));
+ try {
+ System.out.println(charTest(null));
+ } catch (NullPointerException npe1) {
+ System.out.println("npe failure");
+ }
+ System.out.println(charTestNonNull(c));
+ System.out.println(charTestNonNull('e'));
+
+ boolean bool = System.currentTimeMillis() > 0;
+ System.out.println(booleanUnboxTest(bool));
+ System.out.println(booleanTest(bool));
+ try {
+ System.out.println(booleanTest(null));
+ } catch (NullPointerException npe) {
+ System.out.println("npe failure");
+ }
+ System.out.println(booleanTestNonNull(bool));
+ System.out.println(booleanTestNonNull(false));
+ }
+
+ @NeverInline
+ public static int intUnboxTest(int i) {
+ System.out.print(">");
+ return Integer.valueOf(i).intValue();
+ }
+
+ @NeverInline
+ public static Integer intTest(Integer i) {
+ System.out.print(">");
+ return Integer.valueOf(i.intValue());
+ }
+
+ @NeverInline
+ public static Integer intTestNonNull(Integer i) {
+ System.out.print(">");
+ return Integer.valueOf(i.intValue());
+ }
+
+ @NeverInline
+ public static double doubleUnboxTest(double d) {
+ System.out.print(">");
+ return Double.valueOf(d).doubleValue();
+ }
+
+ @NeverInline
+ public static Double doubleTest(Double d) {
+ System.out.print(">");
+ return Double.valueOf(d.doubleValue());
+ }
+
+ @NeverInline
+ public static Double doubleTestNonNull(Double d) {
+ System.out.print(">");
+ return Double.valueOf(d.doubleValue());
+ }
+
+ @NeverInline
+ public static long longUnboxTest(long l) {
+ System.out.print(">");
+ return Long.valueOf(l).longValue();
+ }
+
+ @NeverInline
+ public static Long longTest(Long l) {
+ System.out.print(">");
+ return Long.valueOf(l.longValue());
+ }
+
+ @NeverInline
+ public static Long longTestNonNull(Long l) {
+ System.out.print(">");
+ return Long.valueOf(l.longValue());
+ }
+
+ @NeverInline
+ public static float floatUnboxTest(float f) {
+ System.out.print(">");
+ return Float.valueOf(f).floatValue();
+ }
+
+ @NeverInline
+ public static Float floatTest(Float f) {
+ System.out.print(">");
+ return Float.valueOf(f.floatValue());
+ }
+
+ @NeverInline
+ public static Float floatTestNonNull(Float f) {
+ System.out.print(">");
+ return Float.valueOf(f.floatValue());
+ }
+
+ @NeverInline
+ public static short shortUnboxTest(short s) {
+ System.out.print(">");
+ return Short.valueOf(s).shortValue();
+ }
+
+ @NeverInline
+ public static Short shortTest(Short s) {
+ System.out.print(">");
+ return Short.valueOf(s.shortValue());
+ }
+
+ @NeverInline
+ public static Short shortTestNonNull(Short s) {
+ System.out.print(">");
+ return Short.valueOf(s.shortValue());
+ }
+
+ @NeverInline
+ public static char charUnboxTest(char c) {
+ System.out.print(">");
+ return Character.valueOf(c).charValue();
+ }
+
+ @NeverInline
+ public static Character charTest(Character c) {
+ System.out.print(">");
+ return Character.valueOf(c.charValue());
+ }
+
+ @NeverInline
+ public static Character charTestNonNull(Character c) {
+ System.out.print(">");
+ return Character.valueOf(c.charValue());
+ }
+
+ @NeverInline
+ public static byte byteUnboxTest(byte b) {
+ System.out.print(">");
+ return Byte.valueOf(b).byteValue();
+ }
+
+ @NeverInline
+ public static Byte byteTest(Byte b) {
+ System.out.print(">");
+ return Byte.valueOf(b.byteValue());
+ }
+
+ @NeverInline
+ public static Byte byteTestNonNull(Byte b) {
+ System.out.print(">");
+ return Byte.valueOf(b.byteValue());
+ }
+
+ @NeverInline
+ public static boolean booleanUnboxTest(boolean b) {
+ System.out.print(">");
+ return Boolean.valueOf(b).booleanValue();
+ }
+
+ @NeverInline
+ public static Boolean booleanTest(Boolean b) {
+ System.out.print(">");
+ return Boolean.valueOf(b.booleanValue());
+ }
+
+ @NeverInline
+ public static Boolean booleanTestNonNull(Boolean b) {
+ System.out.print(">");
+ return Boolean.valueOf(b.booleanValue());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/UnboxToCheckNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/UnboxToCheckNotNullTest.java
new file mode 100644
index 0000000..2001277
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/boxedprimitives/UnboxToCheckNotNullTest.java
@@ -0,0 +1,163 @@
+// Copyright (c) 2023, 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.boxedprimitives;
+
+import static org.junit.Assert.assertTrue;
+
+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.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+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 UnboxToCheckNotNullTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testValue() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::assertCheckNotNull)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ private void assertCheckNotNull(CodeInspector codeInspector) {
+ DexItemFactory factory = codeInspector.getFactory();
+
+ Set<DexMethod> unboxMethods = Sets.newIdentityHashSet();
+ unboxMethods.addAll(factory.unboxPrimitiveMethod.values());
+ Set<DexType> boxedTypes = Sets.newIdentityHashSet();
+ boxedTypes.addAll(factory.unboxPrimitiveMethod.keySet());
+
+ // All unbox operations should have been replaced by checkNotNull operations.
+ codeInspector
+ .clazz(Main.class)
+ .allMethods(
+ m ->
+ !m.getParameters().isEmpty()
+ && boxedTypes.contains(
+ factory.createType(
+ DescriptorUtils.javaTypeToDescriptor(m.getParameter(0).getTypeName()))))
+ .forEach(
+ m ->
+ assertTrue(
+ m.streamInstructions()
+ .noneMatch(i -> i.isInvoke() && unboxMethods.contains(i.getMethod()))));
+ }
+
+ private String getExpectedResult() {
+ List<String> result = new ArrayList<>();
+ for (int i = 0; i < 8; i++) {
+ result.add("run succeeded");
+ result.add("npe failure");
+ }
+ return StringUtils.lines(result);
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ testCheckingNPE(() -> intTest(1));
+ testCheckingNPE(() -> intTest(null));
+
+ testCheckingNPE(() -> longTest(1L));
+ testCheckingNPE(() -> longTest(null));
+
+ testCheckingNPE(() -> doubleTest(1.0));
+ testCheckingNPE(() -> doubleTest(null));
+
+ testCheckingNPE(() -> floatTest(1.0f));
+ testCheckingNPE(() -> floatTest(null));
+
+ testCheckingNPE(() -> byteTest((byte) 1));
+ testCheckingNPE(() -> byteTest(null));
+
+ testCheckingNPE(() -> shortTest((short) 1));
+ testCheckingNPE(() -> shortTest(null));
+
+ testCheckingNPE(() -> charTest('c'));
+ testCheckingNPE(() -> charTest(null));
+
+ testCheckingNPE(() -> booleanTest(true));
+ testCheckingNPE(() -> booleanTest(null));
+ }
+
+ public static void testCheckingNPE(Runnable runnable) {
+ try {
+ runnable.run();
+ System.out.println("run succeeded");
+ } catch (NullPointerException npe) {
+ System.out.println("npe failure");
+ }
+ }
+
+ @NeverInline
+ public static void intTest(Integer i) {
+ i.intValue();
+ }
+
+ @NeverInline
+ public static void doubleTest(Double d) {
+ d.doubleValue();
+ }
+
+ @NeverInline
+ public static void longTest(Long l) {
+ l.longValue();
+ }
+
+ @NeverInline
+ public static void floatTest(Float f) {
+ f.floatValue();
+ }
+
+ @NeverInline
+ public static void shortTest(Short s) {
+ s.shortValue();
+ }
+
+ @NeverInline
+ public static void charTest(Character c) {
+ c.charValue();
+ }
+
+ @NeverInline
+ public static void byteTest(Byte b) {
+ b.byteValue();
+ }
+
+ @NeverInline
+ public static void booleanTest(Boolean b) {
+ b.booleanValue();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index 5a65791..41dc6bb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -41,7 +41,7 @@
return testForR8(parameters.getBackend())
.addProgramClassesAndInnerClasses(A.class, B.class, C.class, MAIN)
.addKeepMainRule(MAIN)
- .addOptionsModification(options -> options.enableVerticalClassMerging = false)
+ .addOptionsModification(options -> options.getVerticalClassMergerOptions().disable())
.debug()
.enableInliningAnnotations()
.addDontObfuscate()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
index ec1f304..269b2f9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -57,11 +58,11 @@
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> {
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
// Here we modify the IR when it is processed normally.
options.testing.irModifier = this::modifyIr;
options.enableClassInlining = false;
})
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(EXPECTED);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/BridgeWithCastsInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/BridgeWithCastsInliningTest.java
new file mode 100644
index 0000000..cf1b311
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/BridgeWithCastsInliningTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2023, 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;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+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 BridgeWithCastsInliningTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableSimpleInliningInstructionLimitIncrement;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, enable increment: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // Disable multi caller inlining.
+ .addOptionsModification(
+ options -> {
+ InlinerOptions inlinerOptions = options.inlinerOptions();
+ inlinerOptions.enableSimpleInliningInstructionLimitIncrement =
+ enableSimpleInliningInstructionLimitIncrement;
+ inlinerOptions.multiCallerInliningInstructionLimits = new int[0];
+ })
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(StringUtils.times(StringUtils.lines("A"), 10));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(Main.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject barMethodSubject = classSubject.uniqueMethodWithOriginalName("bar");
+ assertThat(barMethodSubject, isPresent());
+
+ MethodSubject fooMethodSubject = classSubject.uniqueMethodWithOriginalName("foo");
+ assertThat(fooMethodSubject, isPresent());
+ assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
+
+ MethodSubject invokeWithATestMethodSubject =
+ classSubject.uniqueMethodWithOriginalName("invokeWithATest");
+ assertThat(invokeWithATestMethodSubject, isPresent());
+ assertThat(
+ invokeWithATestMethodSubject,
+ invokesMethod(
+ enableSimpleInliningInstructionLimitIncrement ? barMethodSubject : fooMethodSubject));
+
+ MethodSubject invokeWithObjectTestMethodSubject =
+ classSubject.uniqueMethodWithOriginalName("invokeWithObjectTest");
+ assertThat(invokeWithObjectTestMethodSubject, isPresent());
+ assertThat(invokeWithObjectTestMethodSubject, invokesMethod(fooMethodSubject));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ invokeWithATest();
+ invokeWithObjectTest();
+ }
+
+ @NeverInline
+ static void invokeWithATest() {
+ A a = new A();
+ foo(a, a, a, a, a);
+ }
+
+ @NeverInline
+ static void invokeWithObjectTest() {
+ Object o = System.currentTimeMillis() > 0 ? new A() : new Object();
+ foo(o, o, o, o, o);
+ }
+
+ static void foo(Object o1, Object o2, Object o3, Object o4, Object o5) {
+ A a1 = (A) o1;
+ A a2 = (A) o2;
+ A a3 = (A) o3;
+ A a4 = (A) o4;
+ A a5 = (A) o5;
+ bar(a1, a2, a3, a4, a5);
+ }
+
+ @NeverInline
+ static void bar(A a1, A a2, A a3, A a4, A a5) {
+ System.out.println(a1);
+ System.out.println(a2);
+ System.out.println(a3);
+ System.out.println(a4);
+ System.out.println(a5);
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @Override
+ public String toString() {
+ return "A";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/BridgeWithUnboxingInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/BridgeWithUnboxingInliningTest.java
new file mode 100644
index 0000000..2623bc0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/BridgeWithUnboxingInliningTest.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2023, 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;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+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 BridgeWithUnboxingInliningTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableSimpleInliningInstructionLimitIncrement;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, enable increment: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // Disable multi caller inlining.
+ .addOptionsModification(
+ options -> {
+ InlinerOptions inlinerOptions = options.inlinerOptions();
+ inlinerOptions.enableSimpleInliningInstructionLimitIncrement =
+ enableSimpleInliningInstructionLimitIncrement;
+ inlinerOptions.multiCallerInliningInstructionLimits = new int[0];
+ })
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(StringUtils.times(StringUtils.lines("1", "2", "3", "4", "5"), 2));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(Main.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject barMethodSubject = classSubject.uniqueMethodWithOriginalName("bar");
+ assertThat(barMethodSubject, isPresent());
+
+ MethodSubject fooMethodSubject = classSubject.uniqueMethodWithOriginalName("foo");
+ assertThat(fooMethodSubject, isPresent());
+ assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
+
+ MethodSubject invokeWithATestMethodSubject =
+ classSubject.uniqueMethodWithOriginalName("invokeWithPrimitiveTest");
+ assertThat(invokeWithATestMethodSubject, isPresent());
+ assertThat(
+ invokeWithATestMethodSubject,
+ invokesMethod(
+ enableSimpleInliningInstructionLimitIncrement ? barMethodSubject : fooMethodSubject));
+
+ MethodSubject invokeWithBoxedPrimitiveTestMethodSubject =
+ classSubject.uniqueMethodWithOriginalName("invokeWithBoxedPrimitiveTest");
+ assertThat(invokeWithBoxedPrimitiveTestMethodSubject, isPresent());
+ assertThat(invokeWithBoxedPrimitiveTestMethodSubject, invokesMethod(fooMethodSubject));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ invokeWithPrimitiveTest();
+ invokeWithBoxedPrimitiveTest();
+ }
+
+ @NeverInline
+ static void invokeWithPrimitiveTest() {
+ Integer o1 = 1;
+ Integer o2 = 2;
+ Integer o3 = 3;
+ Integer o4 = 4;
+ Integer o5 = 5;
+ foo(o1, o2, o3, o4, o5);
+ }
+
+ @NeverInline
+ static void invokeWithBoxedPrimitiveTest() {
+ Integer o1 = System.currentTimeMillis() > 0 ? 1 : null;
+ Integer o2 = System.currentTimeMillis() > 0 ? 2 : null;
+ Integer o3 = System.currentTimeMillis() > 0 ? 3 : null;
+ Integer o4 = System.currentTimeMillis() > 0 ? 4 : null;
+ Integer o5 = System.currentTimeMillis() > 0 ? 5 : null;
+ foo(o1, o2, o3, o4, o5);
+ }
+
+ static void foo(Integer o1, Integer o2, Integer o3, Integer o4, Integer o5) {
+ int i1 = o1;
+ int i2 = o2;
+ int i3 = o3;
+ int i4 = o4;
+ int i5 = o5;
+ bar(i1, i2, i3, i4, i5);
+ }
+
+ @NeverInline
+ static void bar(int i1, int i2, int i3, int i4, int i5) {
+ System.out.println(i1);
+ System.out.println(i2);
+ System.out.println(i3);
+ System.out.println(i4);
+ System.out.println(i5);
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @Override
+ public String toString() {
+ return "A";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java
new file mode 100644
index 0000000..0f49f15
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringFormatTest.java
@@ -0,0 +1,405 @@
+// Copyright (c) 2023, 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.string;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.UnsupportedEncodingException;
+import java.util.Formattable;
+import java.util.Formatter;
+import java.util.IllegalFormatCodePointException;
+import java.util.IllegalFormatConversionException;
+import java.util.Locale;
+import java.util.MissingFormatArgumentException;
+import java.util.UnknownFormatConversionException;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringFormatTest extends TestBase {
+
+ private static final String JAVA_OUTPUT =
+ StringUtils.lines(
+ "No % Place %",
+ "X",
+ "X after",
+ "before 0",
+ "before null after",
+ "phiNull S null",
+ "catch 1 true",
+ "might throw 1 2",
+ "reuse 1 null null",
+ "reuse 2 null null",
+ "reuse 3 A null",
+ "extra",
+ "extra",
+ "int0a 0",
+ "int0b 0",
+ "int1 0",
+ "int2 1",
+ "int3 9223372036854775807",
+ "int4 null",
+ "bool0 true",
+ "bool1 false",
+ "bool2 false",
+ "bool3 true",
+ "nobool0 false",
+ "nobool1 true",
+ "nobool2 true",
+ "nobool3 true",
+ "Fancy 0 0 0 0 0.00 @",
+ "Formattable",
+ "NLS",
+ "en_CA",
+ "123456");
+
+ private static final Class<?> MAIN = TestClass.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public StringFormatTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvmOutput() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addTestClasspath()
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ @Test
+ public void testReleaseR8() throws Exception {
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MAIN, TestFormattable.class)
+ .addOptionsModification(options -> options.enableStringFormatOptimization = true)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, false);
+ }
+
+ @Test
+ public void testDebugR8() throws Exception {
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MAIN, TestFormattable.class)
+ .addOptionsModification(options -> options.enableStringFormatOptimization = true)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .setMinApi(parameters)
+ .debug()
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, true);
+ }
+
+ private static Predicate<InstructionSubject> invokeMatcher(String qualifiedName) {
+ return ins -> {
+ if (!ins.isInvoke()) {
+ return false;
+ }
+ InvokeInstructionSubject invoke = (InvokeInstructionSubject) ins;
+ return qualifiedName.equals(invoke.invokedMethod().qualifiedName());
+ };
+ }
+
+ private void test(SingleTestRunResult<?> result, boolean isDebug) throws Exception {
+ CodeInspector codeInspector = result.inspector();
+ ClassSubject mainClass = codeInspector.clazz(MAIN);
+
+ Predicate<InstructionSubject> stringFormatMatcher = invokeMatcher("java.lang.String.format");
+ Predicate<InstructionSubject> stringBuilderMatcher =
+ invokeMatcher("java.lang.StringBuilder.toString")
+ .or(invokeMatcher("java.lang.StringBuilder.append"));
+ Predicate<InstructionSubject> valueOfMatcher =
+ invokeMatcher("java.lang.String.valueOf")
+ .or(invokeMatcher("java.lang.Integer.valueOf"))
+ .or(invokeMatcher("java.lang.Long.valueOf"))
+ .or(invokeMatcher("java.lang.Boolean.valueOf"));
+ Predicate<InstructionSubject> arrayInstructionsMatcher =
+ ((Predicate<InstructionSubject>) InstructionSubject::isNewArray)
+ .or(InstructionSubject::isArrayPut)
+ .or(InstructionSubject::isFilledNewArray);
+
+ for (MethodSubject method : mainClass.allMethods()) {
+ String methodName = method.getOriginalName();
+ if (!methodName.contains("Should")) {
+ continue;
+ }
+ boolean shouldOptimize = !isDebug && methodName.contains("ShouldOptimize");
+ assertEquals(
+ methodName, shouldOptimize, method.streamInstructions().noneMatch(stringFormatMatcher));
+ assertEquals(
+ methodName,
+ shouldOptimize,
+ method.streamInstructions().noneMatch(arrayInstructionsMatcher));
+ if (shouldOptimize) {
+ // All Integer.valueOf() should be gone in non-phi tests.
+ if (!"intPlaceholderWithLocaleShouldOptimize".equals(methodName)) {
+ assertTrue(methodName, method.streamInstructions().noneMatch(valueOfMatcher));
+ }
+ }
+
+ if (shouldOptimize && methodName.endsWith("Fully")) {
+ // Should simplify into a single ConstString.
+ assertTrue(method.streamInstructions().noneMatch(stringBuilderMatcher));
+ } else if (shouldOptimize) {
+ assertEquals(
+ methodName,
+ !shouldOptimize,
+ method.streamInstructions().noneMatch(stringBuilderMatcher));
+ }
+ }
+ }
+
+ public static class TestFormattable implements Formattable {
+ @Override
+ public void formatTo(Formatter formatter, int flags, int width, int precision) {
+ formatter.format("Formattable");
+ }
+ }
+
+ public static class TestClass {
+ private static final boolean ALWAYS_TRUE = System.currentTimeMillis() > 0;
+
+ @NeverInline
+ private static void noPlaceholdersShouldOptimizeFully() {
+ System.out.println(String.format("No %% Place %%") + String.format("", (Object[]) null));
+ // Test no outValue().
+ // Non-english locale should still optimize when it's just %s.
+ String.format(Locale.FRENCH, "Just %s.", "percent s");
+ }
+
+ @NeverInline
+ private static void stringPlaceholderShouldOptimizeFully() {
+ System.out.println(String.format("%s", "X"));
+ System.out.println(String.format("%s after", "X"));
+ System.out.println(String.format("before %s", 0));
+ System.out.println(String.format("before %s after", (Object) null));
+ }
+
+ @NeverInline
+ private static void phiAndNullShouldOptimize() {
+ Object[] args = new Object[2];
+ while (!ALWAYS_TRUE) {}
+ args[0] = ALWAYS_TRUE ? "S" : null;
+ System.out.println(String.format("phiNull %s %s", args));
+ }
+
+ @NeverInline
+ private static void catchHandlersShouldOptimize() {
+ try {
+ Object[] args = new Object[2];
+ args[0] = 1;
+ try {
+ do {
+ // Excess conditionals to ensure coverage of non-fast paths in
+ // ValueUtils.computeSimpleCaseDominatorBlocks().
+ args[1] = ALWAYS_TRUE ? Boolean.TRUE : Boolean.FALSE;
+ } while (!ALWAYS_TRUE && System.currentTimeMillis() > 0
+ || System.currentTimeMillis() < 0);
+ System.out.println(String.format("catch %s %s", args));
+ } catch (RuntimeException e) {
+ System.out.println("threw");
+ }
+ } catch (AssertionError e) {
+ throw e;
+ }
+ }
+
+ @NeverInline
+ private static void catchHandlersShouldNotOptimize() {
+ Object[] args = new Object[2];
+ try {
+ args[0] = 1;
+ System.out.print("might throw ");
+ args[1] = 2;
+ } catch (RuntimeException e) {
+ }
+ System.out.println(String.format("%s %s", args));
+ }
+
+ @NeverInline
+ private static void arrayReuseShouldNotOptimize() {
+ Object[] args = new Object[2];
+ System.out.println(String.format("reuse 1 %s %s", args));
+ System.out.println(String.format("reuse 2 %s %s", args));
+
+ // Also tests array-put after usage while in the same block.
+ args = new Object[2];
+ args[0] = "A";
+ System.out.println(String.format("reuse 3 %s %s", args));
+ args[1] = "B";
+ }
+
+ @NeverInline
+ private static void tooManyArgsShouldOptimizeFully() {
+ // We could remove excess args, but it's very rare, so we don't bother.
+ System.out.println(String.format("extra", 1));
+ System.out.println(String.format("extra", 1, 2));
+ }
+
+ @NeverInline
+ private static void exceptionalCallsShouldNotOptimize() {
+ try {
+ String.format(null);
+ throw new AssertionError("Expect to raise NPE");
+ } catch (NullPointerException npe) {
+ // expected
+ System.out.print("1");
+ }
+ try {
+ String.format("%d", "str");
+ throw new AssertionError("Expected to raise IllegalFormatConversionException");
+ } catch (IllegalFormatConversionException e) {
+ // expected
+ System.out.print("2");
+ }
+ try {
+ String.format("%s");
+ throw new AssertionError("Expected to raise MissingFormatArgumentException");
+ } catch (MissingFormatArgumentException e) {
+ // expected
+ System.out.print("3");
+ }
+ try {
+ String.format("%s %s", "");
+ throw new AssertionError("Expected to raise MissingFormatArgumentException");
+ } catch (MissingFormatArgumentException e) {
+ // expected
+ System.out.print("4");
+ }
+ try {
+ String.format("%c", 0x1200000);
+ throw new AssertionError("Expected to raise IllegalFormatCodePointException");
+ } catch (IllegalFormatCodePointException e) {
+ // expected
+ System.out.print("5");
+ }
+ try {
+ String.format("trailing %");
+ throw new AssertionError("Expected to raise UnknownFormatConversionException");
+ } catch (UnknownFormatConversionException e) {
+ // expected
+ System.out.println("6");
+ }
+ }
+
+ @NeverInline
+ private static void intPlaceholderWithoutLocaleShouldNotOptimize() {
+ System.out.println(String.format("int0a %d", 0));
+ // new Locale("ar") produces different results on different ART versions.
+ System.out.println(String.format(Locale.FRENCH, "int0b %d", 0));
+ }
+
+ @NeverInline
+ private static Integer returnsInteger() {
+ return ALWAYS_TRUE ? null : 1;
+ }
+
+ @NeverInline
+ private static void intPlaceholderWithLocaleShouldOptimize() {
+ System.out.println(String.format((Locale) null, "int1 %d", 0));
+ System.out.println(String.format(Locale.US, "int2 %d", ALWAYS_TRUE ? 1 : 0));
+ System.out.println(String.format(Locale.ROOT, "int3 %d", Long.MAX_VALUE));
+ System.out.println(String.format(Locale.ENGLISH, "int4 %d", returnsInteger()));
+ }
+
+ @NeverInline
+ private static void booleanPlaceholderShouldOptimize() {
+ // Boolean.valueOf()
+ System.out.println(String.format("bool0 %b", true));
+ // isDefinitelyNull() -> "false"
+ System.out.println(String.format("bool1 %b", (Object) null));
+ // null param --> "false"
+ System.out.println(String.format("bool2 %b", new Object[1]));
+ // Type == Boolean without Boolean.valueOf().
+ System.out.println(String.format("bool3 %b", Boolean.TRUE));
+ }
+
+ @NeverInline
+ private static Boolean returnsBoolean() {
+ return ALWAYS_TRUE ? true : null;
+ }
+
+ @NeverInline
+ private static void booleanPlaceholderShouldNotOptimize() {
+ Boolean maybeNull = ALWAYS_TRUE ? null : Boolean.TRUE;
+ // Might be null, so cannot optimize.
+ System.out.println(String.format("nobool0 %b", maybeNull));
+ // Not using Boolean.valueOf(), so don't optimize.
+ System.out.println(String.format("nobool1 %b", 0));
+ // Not the correct type, so don't optimize.
+ System.out.println(String.format("nobool2 %b", ""));
+ System.out.println(String.format("nobool3 %b", returnsBoolean()));
+ }
+
+ @NeverInline
+ private static void fancyPlaceholdersShouldNotOptimize() {
+ System.out.print("Fancy");
+ System.out.print(String.format(" %1$d", 0));
+ System.out.print(String.format(" %1s", 0));
+ System.out.print(String.format(" %o", 0));
+ System.out.print(String.format(" %x", 0));
+ System.out.print(String.format(" %(,.2f", 0f));
+ System.out.println(String.format(" %c", 64));
+ }
+
+ @NeverInline
+ private static void formattableShouldNotOptimize() {
+ System.out.println(String.format("%s", new TestFormattable()));
+ }
+
+ @NeverInline
+ private static void nonLiteralSpecShouldNotOptimize() {
+ String spec = ALWAYS_TRUE ? "NLS" : null;
+ System.out.println(String.format(spec));
+ System.out.println(String.format(Locale.CANADA.toString()));
+ }
+
+ public static void main(String[] args) throws UnsupportedEncodingException {
+ noPlaceholdersShouldOptimizeFully();
+ stringPlaceholderShouldOptimizeFully();
+ phiAndNullShouldOptimize();
+ catchHandlersShouldOptimize();
+ catchHandlersShouldNotOptimize();
+ arrayReuseShouldNotOptimize();
+ tooManyArgsShouldOptimizeFully();
+ intPlaceholderWithoutLocaleShouldNotOptimize();
+ intPlaceholderWithLocaleShouldOptimize();
+ booleanPlaceholderShouldOptimize();
+ booleanPlaceholderShouldNotOptimize();
+ fancyPlaceholdersShouldNotOptimize();
+ formattableShouldNotOptimize();
+ nonLiteralSpecShouldNotOptimize();
+ exceptionalCallsShouldNotOptimize();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java b/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java
index 82653f9..e2bad4a 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B140588497.java
@@ -54,7 +54,7 @@
while (it.hasNext()) {
numbers.add(it.next().getConstNumber());
}
- assertEquals(new LongArrayList(new long[] {0, 1, 2, 3, 4, 5}), numbers);
+ assertEquals(new LongArrayList(new long[] {4, 5, 0, 1, 2, 3}), numbers);
});
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index c579123..4759e94 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Collection;
@@ -94,7 +95,11 @@
@Override
public void replaceCurrentInstructionWithConstClass(
- AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+ AppView<?> appView,
+ IRCode code,
+ DexType type,
+ DebugLocalInfo localInfo,
+ AffectedValues affectedValues) {
throw new Unimplemented();
}
@@ -105,7 +110,7 @@
@Override
public void replaceCurrentInstructionWithConstString(
- AppView<?> appView, IRCode code, DexString value) {
+ AppView<?> appView, IRCode code, DexString value, AffectedValues affectedValues) {
throw new Unimplemented();
}
@@ -200,7 +205,7 @@
public InstructionListIterator addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
IRCode code,
BasicBlockIterator blockIterator,
- Instruction[] instructions,
+ Collection<Instruction> instructionsToAdd,
InternalOptions options) {
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
deleted file mode 100644
index fd8154f..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ /dev/null
@@ -1,255 +0,0 @@
-// Copyright (c) 2022, 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.keepanno;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.JavaCompilerTool;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
-import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
-import com.android.tools.r8.keepanno.asm.KeepEdgeWriter.AnnotationVisitorInterface;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
-import com.android.tools.r8.keepanno.ast.KeepDeclaration;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.processor.KeepEdgeProcessor;
-import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
-import com.android.tools.r8.keepanno.testsource.KeepDependentFieldSource;
-import com.android.tools.r8.keepanno.testsource.KeepFieldSource;
-import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.transformers.ClassTransformer;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.objectweb.asm.AnnotationVisitor;
-
-@Ignore("b/248408342: These test break on r8lib builds because of src&test using ASM classes.")
-@RunWith(Parameterized.class)
-public class KeepEdgeAnnotationsTest extends TestBase {
-
- private static class ParamWrapper {
- private final Class<?> clazz;
- private final TestParameters params;
-
- public ParamWrapper(Class<?> clazz, TestParameters params) {
- this.clazz = clazz;
- this.params = params;
- }
-
- @Override
- public String toString() {
- return clazz.getSimpleName() + ", " + params.toString();
- }
- }
-
- private static List<Class<?>> getTestClasses() {
- return ImmutableList.of(
- KeepClassAndDefaultConstructorSource.class,
- KeepFieldSource.class,
- KeepDependentFieldSource.class);
- }
-
- private final TestParameters parameters;
- private final Class<?> source;
-
- @Parameterized.Parameters(name = "{0}")
- public static List<ParamWrapper> data() {
- TestParametersCollection params =
- getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
- return getTestClasses().stream()
- .flatMap(c -> params.stream().map(p -> new ParamWrapper(c, p)))
- .collect(Collectors.toList());
- }
-
- public KeepEdgeAnnotationsTest(ParamWrapper wrapper) {
- this.parameters = wrapper.params;
- this.source = wrapper.clazz;
- }
-
- private String getExpected() {
- return KeepSourceEdges.getExpected(source);
- }
-
- @Test
- public void testProcessorClassfiles() throws Exception {
- assumeTrue(parameters.isCfRuntime());
- Path out =
- JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
- .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
- .addClasspathFiles(ToolHelper.getKeepAnnoPath())
- .addClassNames(Collections.singletonList(typeName(source)))
- .addClasspathFiles(Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "test"))
- .addClasspathFiles(ToolHelper.getR8WithRelocatedDeps())
- .compile();
-
- CodeInspector inspector = new CodeInspector(out);
- checkSynthesizedKeepEdgeClass(inspector, out);
- // The source is added as a classpath name but not part of the compilation unit output.
- assertThat(inspector.clazz(source), isAbsent());
-
- testForJvm(parameters)
- .addProgramClassesAndInnerClasses(source)
- .addProgramFiles(out)
- .run(parameters.getRuntime(), source)
- .assertSuccessWithOutput(getExpected());
- }
-
- @Test
- public void testProcessorJavaSource() throws Exception {
- assumeTrue(parameters.isCfRuntime());
- Path out =
- JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
- .addSourceFiles(ToolHelper.getSourceFileForTestClass(source))
- .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
- .addClasspathFiles(ToolHelper.getKeepAnnoPath())
- .addClasspathFiles(ToolHelper.getDeps())
- .compile();
- testForJvm(parameters)
- .addProgramFiles(out)
- .run(parameters.getRuntime(), source)
- .assertSuccessWithOutput(getExpected())
- .inspect(
- inspector -> {
- assertThat(inspector.clazz(source), isPresent());
- checkSynthesizedKeepEdgeClass(inspector, out);
- });
- }
-
- public static List<byte[]> getInputClassesWithoutKeepAnnotations(Collection<Class<?>> classes)
- throws Exception {
- List<byte[]> transformed = new ArrayList<>(classes.size());
- for (Class<?> clazz : classes) {
- transformed.add(
- transformer(clazz).removeAnnotations(AnnotationConstants::isKeepAnnotation).transform());
- }
- return transformed;
- }
-
- /** Wrapper to bridge ASM visitors when using the r8lib compiled version of the keepanno lib. */
- private AnnotationVisitorInterface wrap(AnnotationVisitor visitor) {
- if (visitor == null) {
- return null;
- }
- return new AnnotationVisitorInterface() {
- @Override
- public int version() {
- return KeepEdgeReader.ASM_VERSION;
- }
-
- @Override
- public void visit(String name, Object value) {
- visitor.visit(name, value);
- }
-
- @Override
- public void visitEnum(String name, String descriptor, String value) {
- visitor.visitEnum(name, descriptor, value);
- }
-
- @Override
- public AnnotationVisitorInterface visitAnnotation(String name, String descriptor) {
- AnnotationVisitor v = visitor.visitAnnotation(name, descriptor);
- return v == visitor ? this : wrap(v);
- }
-
- @Override
- public AnnotationVisitorInterface visitArray(String name) {
- AnnotationVisitor v = visitor.visitArray(name);
- return v == visitor ? this : wrap(v);
- }
-
- @Override
- public void visitEnd() {
- visitor.visitEnd();
- }
- };
- }
-
- @Test
- public void testAsmReader() throws Exception {
- assumeTrue(parameters.isCfRuntime());
- List<KeepEdge> expectedEdges = KeepSourceEdges.getExpectedEdges(source);
- ClassReference clazz = Reference.classFromClass(source);
- // Original bytes of the test class.
- byte[] original = ToolHelper.getClassAsBytes(source);
- // Strip out all the annotations to ensure they are actually added again.
- byte[] stripped =
- getInputClassesWithoutKeepAnnotations(Collections.singletonList(source)).get(0);
- // Manually add in the expected edges again.
- byte[] readded =
- transformer(stripped, clazz)
- .addClassTransformer(
- new ClassTransformer() {
-
- @Override
- public void visitEnd() {
- for (KeepEdge edge : expectedEdges) {
- KeepEdgeWriter.writeEdge(
- edge, (desc, visible) -> wrap(super.visitAnnotation(desc, visible)));
- }
- super.visitEnd();
- }
- })
- .transform();
-
- // Read the edges from each version.
- List<KeepDeclaration> originalEdges = KeepEdgeReader.readKeepEdges(original);
- List<KeepDeclaration> strippedEdges = KeepEdgeReader.readKeepEdges(stripped);
- List<KeepDeclaration> readdedEdges = KeepEdgeReader.readKeepEdges(readded);
-
- // The edges are compared to the "expected" ast to ensure we don't hide failures in reading or
- // writing.
- assertEquals(Collections.emptyList(), strippedEdges);
- assertEquals(expectedEdges, originalEdges);
- assertEquals(expectedEdges, readdedEdges);
- }
-
- @Test
- public void testExtractAndRun() throws Exception {
- testForR8(parameters.getBackend())
- .enableExperimentalKeepAnnotations()
- .addProgramClassesAndInnerClasses(source)
- .addKeepMainRule(source)
- .setMinApi(parameters)
- .run(parameters.getRuntime(), source)
- .assertSuccessWithOutput(getExpected());
- }
-
- private void checkSynthesizedKeepEdgeClass(CodeInspector inspector, Path data)
- throws IOException {
- String synthesizedEdgesClassName =
- KeepEdgeProcessor.getClassTypeNameForSynthesizedEdges(source.getTypeName());
- ClassSubject synthesizedEdgesClass = inspector.clazz(synthesizedEdgesClassName);
- assertThat(synthesizedEdgesClass, isPresent());
- assertThat(synthesizedEdgesClass.annotation(Edge.CLASS.getTypeName()), isPresent());
- String entry = ZipUtils.zipEntryNameForClass(synthesizedEdgesClass.getFinalReference());
- byte[] bytes = ZipUtils.readSingleEntry(data, entry);
- List<KeepDeclaration> keepEdges = KeepEdgeReader.readKeepEdges(bytes);
- assertEquals(KeepSourceEdges.getExpectedEdges(source), keepEdges);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInclusiveInstanceOfTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInclusiveInstanceOfTest.java
new file mode 100644
index 0000000..5991554
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInclusiveInstanceOfTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2023, 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.keepanno;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepInclusiveInstanceOfTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("on Base", "on Sub");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepInclusiveInstanceOfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, Base.class, Sub.class, A.class);
+ }
+
+ static class Base {
+ static void hiddenMethod() {
+ System.out.println("on Base");
+ }
+ }
+
+ static class Sub extends Base {
+ static void hiddenMethod() {
+ System.out.println("on Sub");
+ }
+ }
+
+ static class A {
+
+ @UsesReflection({
+ // Because the method is static, this works whereas `classConstant = Base.class` won't
+ // keep the method on `Sub`.
+ @KeepTarget(instanceOfClassConstant = Base.class, methodName = "hiddenMethod")
+ })
+ public void foo(Base base) throws Exception {
+ base.getClass().getDeclaredMethod("hiddenMethod").invoke(null);
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new A().foo(new Base());
+ new A().foo(new Sub());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
index b95f700..eb5791a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
@@ -80,6 +80,22 @@
}
@Test
+ public void testInvalidClassDeclWithBinding() {
+ assertThrowsWith(
+ () -> extractRuleForClass(BindingAndClassDeclarations.class),
+ allOf(containsString("class binding"), containsString("class patterns")));
+ }
+
+ static class BindingAndClassDeclarations {
+
+ // Both properties are using the "default" value of an empty string, but should still fail.
+ @UsesReflection({@KeepTarget(classFromBinding = "", className = "")})
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+
+ @Test
public void testInvalidExtendsDecl() {
assertThrowsWith(
() -> extractRuleForClass(MultipleExtendsDeclarations.class),
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepNameAndInstanceOfTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepNameAndInstanceOfTest.java
new file mode 100644
index 0000000..19505ca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepNameAndInstanceOfTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2023, 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.keepanno;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepNameAndInstanceOfTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("on Base", "on Sub");
+ static final String EXPECTED_R8 = StringUtils.lines("on Base", "No method on Sub");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepNameAndInstanceOfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_R8);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, Base.class, Sub.class, A.class);
+ }
+
+ static class Base {
+
+ static void hiddenMethod() {
+ System.out.println("on Base");
+ }
+ }
+
+ static class Sub extends Base {
+
+ static void hiddenMethod() {
+ System.out.println("on Sub");
+ }
+ }
+
+ static class A {
+
+ @UsesReflection({
+ @KeepTarget(
+ // Restricting the matching to Base will cause Sub to be stripped.
+ classConstant = Base.class,
+ instanceOfClassConstant = Base.class,
+ methodName = "hiddenMethod")
+ })
+ public void foo(Base base) throws Exception {
+ base.getClass().getDeclaredMethod("hiddenMethod").invoke(null);
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ try {
+ new A().foo(new Base());
+ } catch (Exception e) {
+ System.out.println("No method on Base");
+ }
+ try {
+ new A().foo(new Sub());
+ } catch (Exception e) {
+ System.out.println("No method on Sub");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index 602ee63..8baa5ce 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
+import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
import com.android.tools.r8.utils.StringUtils;
@@ -137,7 +137,7 @@
KeepConsequences.builder()
.addTarget(
target(
- buildClassItem(CLASS)
+ buildMemberItem(CLASS)
.setMemberPattern(defaultInitializerPattern())
.build()))
.build())
@@ -176,7 +176,7 @@
.addTarget(target(classItem(CLASS)))
.addTarget(
target(
- buildClassItem(CLASS)
+ buildMemberItem(CLASS)
.setMemberPattern(defaultInitializerPattern())
.build()))
.build())
@@ -191,22 +191,24 @@
@Test
public void testKeepInstanceAndInitIfReferencedWithBinding() {
KeepBindings.Builder bindings = KeepBindings.builder();
- BindingSymbol classSymbol = bindings.create("CLASS");
+ KeepBindingSymbol classSymbol = bindings.create("CLASS");
KeepEdge edge =
KeepEdge.builder()
.setBindings(bindings.addBinding(classSymbol, classItem(CLASS)).build())
.setPreconditions(
KeepPreconditions.builder()
.addCondition(
- KeepCondition.builder().setItemReference(itemBinding(classSymbol)).build())
+ KeepCondition.builder()
+ .setItemReference(classItemBinding(classSymbol))
+ .build())
.build())
.setConsequences(
KeepConsequences.builder()
- .addTarget(target(itemBinding(classSymbol)))
+ .addTarget(target(classItemBinding(classSymbol)))
.addTarget(
target(
- KeepItemPattern.builder()
- .setClassReference(classBinding(classSymbol))
+ KeepMemberItemPattern.builder()
+ .setClassReference(classItemBinding(classSymbol))
.setMemberPattern(defaultInitializerPattern())
.build()))
.build())
@@ -221,12 +223,8 @@
extract(edge));
}
- private KeepItemReference itemBinding(BindingSymbol bindingName) {
- return KeepItemReference.fromBindingReference(bindingName);
- }
-
- private KeepClassReference classBinding(BindingSymbol bindingName) {
- return KeepClassReference.fromBindingReference(bindingName);
+ private KeepClassItemReference classItemBinding(KeepBindingSymbol bindingName) {
+ return KeepBindingReference.forClass(bindingName).toClassItemReference();
}
private KeepTarget target(KeepItemPattern item) {
@@ -241,10 +239,15 @@
return buildClassItem(typeName).build();
}
- private KeepItemPattern.Builder buildClassItem(String typeName) {
- return KeepItemPattern.builder().setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
+ private KeepClassItemPattern.Builder buildClassItem(String typeName) {
+ return KeepClassItemPattern.builder()
+ .setClassNamePattern(KeepQualifiedClassNamePattern.exact(typeName));
}
+ private KeepMemberItemPattern.Builder buildMemberItem(String typeName) {
+ return KeepMemberItemPattern.builder()
+ .setClassReference(buildClassItem(typeName).build().toClassItemReference());
+ }
private KeepMemberPattern defaultInitializerPattern() {
return KeepMethodPattern.builder()
diff --git a/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java
new file mode 100644
index 0000000..0914396
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionDocumentationTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2023, 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.keepanno.doctests;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UsesReflectionDocumentationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("on Base", "on Sub");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public UsesReflectionDocumentationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, BaseClass.class, SubClass.class, MyClass.class);
+ }
+
+ static class BaseClass {
+ void hiddenMethod() {
+ System.out.println("on Base");
+ }
+ }
+
+ static class SubClass extends BaseClass {
+ void hiddenMethod() {
+ System.out.println("on Sub");
+ }
+ }
+
+ /// DOC START: UsesReflection on virtual method
+ /* DOC TEXT START:
+ <p>If for example, your program is reflectively invoking a virtual method on some base class, you
+ should annotate the method that is performing the reflection with an annotation describing what
+ assumptions the reflective code is making.
+
+ <p>In the following example, the method `foo` is looking up the method with the name
+ `hiddenMethod` on objects that are instances of `BaseClass`. It is then invoking the method with
+ no other arguments than the receiver.
+
+ <p>The minimal requirement for this code to work is therefore that all methods with the name
+ `hiddenMethod` and the empty list of parameters are targeted if they are objects that are
+ instances of the class `BaseClass` or subclasses thereof.
+
+ <p>By placing the `UsesReflection` annotation on the method `foo` the annotation is only in
+ effect if the method `foo` is determined to be used by the shrinker.
+ So, if `foo` turns out to be dead code then the shrinker can remove `foo` and also ignore the
+ keep annotation.
+ DOC TEXT END */
+ static class MyClass {
+
+ @UsesReflection({
+ @KeepTarget(
+ instanceOfClassConstant = BaseClass.class,
+ methodName = "hiddenMethod",
+ methodParameters = {})
+ })
+ public void foo(BaseClass base) throws Exception {
+ base.getClass().getDeclaredMethod("hiddenMethod").invoke(base);
+ }
+ }
+
+ // DOC END
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new MyClass().foo(new BaseClass());
+ new MyClass().foo(new SubClass());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index ac50217..0b2265a 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.testsource;
+import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepConsequences.Builder;
@@ -10,6 +12,7 @@
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
import com.android.tools.r8.keepanno.ast.KeepPreconditions;
@@ -108,26 +111,36 @@
// Ast helpers.
- static KeepItemPattern mkClass(Class<?> clazz) {
+ static KeepClassItemPattern mkClass(Class<?> clazz) {
KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
- return KeepItemPattern.builder().setClassPattern(name).build();
+ return KeepClassItemPattern.builder().setClassNamePattern(name).build();
}
- static KeepItemPattern mkMethod(Class<?> clazz, String methodName) {
+ static KeepMemberItemPattern mkMethod(Class<?> clazz, String methodName) {
KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+ KeepClassItemReference classReference =
+ KeepClassItemPattern.builder().setClassNamePattern(name).build().toClassItemReference();
KeepMethodPattern methodPattern =
KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact(methodName)).build();
- KeepItemPattern methodItem =
- KeepItemPattern.builder().setClassPattern(name).setMemberPattern(methodPattern).build();
+ KeepMemberItemPattern methodItem =
+ KeepMemberItemPattern.builder()
+ .setClassReference(classReference)
+ .setMemberPattern(methodPattern)
+ .build();
return methodItem;
}
static KeepItemPattern mkField(Class<?> clazz, String fieldName) {
KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+ KeepClassItemReference classReference =
+ KeepClassItemPattern.builder().setClassNamePattern(name).build().toClassItemReference();
KeepFieldPattern fieldPattern =
KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build();
- KeepItemPattern fieldItem =
- KeepItemPattern.builder().setClassPattern(name).setMemberPattern(fieldPattern).build();
+ KeepMemberItemPattern fieldItem =
+ KeepMemberItemPattern.builder()
+ .setClassReference(classReference)
+ .setMemberPattern(fieldPattern)
+ .build();
return fieldItem;
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinter.java b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinter.java
new file mode 100644
index 0000000..05b2ede
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinter.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2023, 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.keepanno.utils;
+
+public class DocPrinter extends DocPrinterBase<DocPrinter> {
+
+ public static DocPrinter printer() {
+ return new DocPrinter();
+ }
+
+ @Override
+ public DocPrinter self() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java
new file mode 100644
index 0000000..f193da6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/DocPrinterBase.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2023, 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.keepanno.utils;
+
+import com.android.tools.r8.examples.sync.Sync.Consumer;
+import com.google.common.html.HtmlEscapers;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public abstract class DocPrinterBase<T> {
+
+ private String title = null;
+ private final List<String> additionalLines = new ArrayList<>();
+
+ public abstract T self();
+
+ public T clearDocLines() {
+ additionalLines.clear();
+ return self();
+ }
+
+ public T setDocTitle(String title) {
+ assert this.title == null;
+ assert title.endsWith(".");
+ this.title = title;
+ return self();
+ }
+
+ public T addParagraph(String... lines) {
+ return addParagraph(Arrays.asList(lines));
+ }
+
+ public T addParagraph(List<String> lines) {
+ assert lines.isEmpty() || !lines.get(0).startsWith("<p>");
+ additionalLines.add("<p>");
+ additionalLines.addAll(lines);
+ return self();
+ }
+
+ public T addCodeBlock(String... lines) {
+ return addCodeBlock(Arrays.asList(lines));
+ }
+
+ public T addCodeBlock(List<String> lines) {
+ additionalLines.add("<pre>");
+ for (String line : lines) {
+ additionalLines.add(HtmlEscapers.htmlEscaper().escape(line).replace("@", "@"));
+ }
+ additionalLines.add("</pre>");
+ return self();
+ }
+
+ public T addUnorderedList(String... items) {
+ return addUnorderedList(Arrays.asList(items));
+ }
+
+ public T addUnorderedList(List<String> items) {
+ additionalLines.add("<ul>");
+ for (String item : items) {
+ additionalLines.add("<li>" + item);
+ }
+ additionalLines.add("</ul>");
+ return self();
+ }
+
+ public void printDoc(Consumer<String> println) {
+ assert additionalLines.isEmpty() || title != null;
+ if (title == null) {
+ return;
+ }
+ if (additionalLines.isEmpty()) {
+ println.accept("/** " + title + " */");
+ return;
+ }
+ println.accept("/**");
+ println.accept(" * " + title);
+ for (String line : additionalLines) {
+ if (line.startsWith("<p>")) {
+ println.accept(" *");
+ }
+ println.accept(" * " + line);
+ }
+ println.accept(" */");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
new file mode 100644
index 0000000..67a5fe0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -0,0 +1,1263 @@
+// Copyright (c) 2023, 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.keepanno.utils;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cfmethodgeneration.CodeGenerationBase;
+import com.android.tools.r8.keepanno.annotations.CheckOptimizedOut;
+import com.android.tools.r8.keepanno.annotations.CheckRemoved;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepBinding;
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepOption;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
+import com.android.tools.r8.keepanno.annotations.MethodAccessFlags;
+import com.android.tools.r8.keepanno.annotations.UsedByNative;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class KeepItemAnnotationGenerator {
+
+ public static void main(String[] args) throws IOException {
+ Generator.class.getClassLoader().setDefaultAssertionStatus(true);
+ Generator.run();
+ }
+
+ private static String quote(String str) {
+ return "\"" + str + "\"";
+ }
+
+ private static String simpleName(Class<?> clazz) {
+ return clazz.getSimpleName();
+ }
+
+ private static class GroupMember extends DocPrinterBase<GroupMember> {
+
+ final String name;
+ String valueType = null;
+ String valueDefault = null;
+
+ GroupMember(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public GroupMember self() {
+ return this;
+ }
+
+ void generate(Generator generator) {
+ printDoc(generator::println);
+ if (valueDefault == null) {
+ generator.println(valueType + " " + name + "();");
+ } else {
+ generator.println(valueType + " " + name + "() default " + valueDefault + ";");
+ }
+ }
+
+ public void generateConstants(Generator generator) {
+ generator.println("public static final String " + name + " = " + quote(name) + ";");
+ }
+
+ public GroupMember requiredStringValue() {
+ assert valueDefault == null;
+ return defaultType("String");
+ }
+
+ public GroupMember requiredValueOfType(String type) {
+ assert valueDefault == null;
+ return defaultType(type);
+ }
+
+ public GroupMember requiredValueOfType(Class<?> type) {
+ assert valueDefault == null;
+ return defaultType(simpleName(type));
+ }
+
+ public GroupMember requiredValueOfArrayType(Class<?> type) {
+ assert valueDefault == null;
+ return defaultType(simpleName(type) + "[]");
+ }
+
+ public GroupMember defaultType(String type) {
+ valueType = type;
+ return this;
+ }
+
+ public GroupMember defaultValue(String value) {
+ valueDefault = value;
+ return this;
+ }
+
+ public GroupMember defaultEmptyString() {
+ return defaultType("String").defaultValue(quote(""));
+ }
+
+ public GroupMember defaultObjectClass() {
+ return defaultType("Class<?>").defaultValue("Object.class");
+ }
+
+ public GroupMember defaultEmptyArray(String valueType) {
+ return defaultType(valueType + "[]").defaultValue("{}");
+ }
+
+ public GroupMember defaultEmptyArray(Class<?> type) {
+ return defaultEmptyArray(simpleName(type));
+ }
+ }
+
+ private static class Group {
+
+ final String name;
+ final List<GroupMember> members = new ArrayList<>();
+ final List<String> footers = new ArrayList<>();
+ final LinkedHashMap<String, Group> mutuallyExclusiveGroups = new LinkedHashMap<>();
+
+ private Group(String name) {
+ this.name = name;
+ }
+
+ Group addMember(GroupMember member) {
+ members.add(member);
+ return this;
+ }
+
+ Group addDocFooterParagraph(String footer) {
+ footers.add(footer);
+ return this;
+ }
+
+ void generate(Generator generator) {
+ assert !members.isEmpty();
+ for (GroupMember member : members) {
+ if (member != members.get(0)) {
+ generator.println();
+ }
+ List<String> mutuallyExclusiveProperties = new ArrayList<>();
+ for (GroupMember other : members) {
+ if (!member.name.equals(other.name)) {
+ mutuallyExclusiveProperties.add(other.name);
+ }
+ }
+ mutuallyExclusiveGroups.forEach(
+ (unused, group) -> {
+ group.members.forEach(m -> mutuallyExclusiveProperties.add(m.name));
+ });
+ if (mutuallyExclusiveProperties.size() == 1) {
+ member.addParagraph(
+ "Mutually exclusive with the property `"
+ + mutuallyExclusiveProperties.get(0)
+ + "` also defining "
+ + name
+ + ".");
+ } else if (mutuallyExclusiveProperties.size() > 1) {
+ member.addParagraph(
+ "Mutually exclusive with the following other properties defining " + name + ":");
+ member.addUnorderedList(mutuallyExclusiveProperties);
+ }
+ footers.forEach(member::addParagraph);
+ member.generate(generator);
+ }
+ }
+
+ void generateConstants(Generator generator) {
+ for (GroupMember member : members) {
+ member.generateConstants(generator);
+ }
+ }
+
+ public void addMutuallyExclusiveGroups(Group... groups) {
+ for (Group group : groups) {
+ mutuallyExclusiveGroups.computeIfAbsent(
+ group.name,
+ k -> {
+ // Mutually exclusive is bidirectional so link in with other group.
+ group.mutuallyExclusiveGroups.put(name, this);
+ return group;
+ });
+ }
+ }
+ }
+
+ private static class Generator {
+
+ private static final List<Class<?>> ANNOTATION_IMPORTS =
+ ImmutableList.of(ElementType.class, Retention.class, RetentionPolicy.class, Target.class);
+
+ private final PrintStream writer;
+ private int indent = 0;
+
+ public Generator(PrintStream writer) {
+ this.writer = writer;
+ }
+
+ private void withIndent(Runnable fn) {
+ indent += 2;
+ fn.run();
+ indent -= 2;
+ }
+
+ private void println() {
+ // Don't indent empty lines.
+ writer.println();
+ }
+
+ private void println(String line) {
+ assert line.length() > 0;
+ writer.print(Strings.repeat(" ", indent));
+ writer.println(line);
+ }
+
+ private void printCopyRight(int year) {
+ println(
+ CodeGenerationBase.getHeaderString(
+ year, KeepItemAnnotationGenerator.class.getSimpleName()));
+ }
+
+ private void printPackage(String pkg) {
+ println("package com.android.tools.r8.keepanno." + pkg + ";");
+ println();
+ }
+
+ private void printImports(Class<?>... imports) {
+ printImports(Arrays.asList(imports));
+ }
+
+ private void printImports(List<Class<?>> imports) {
+ for (Class<?> clazz : imports) {
+ println("import " + clazz.getCanonicalName() + ";");
+ }
+ println();
+ }
+
+ private static String KIND_GROUP = "kind";
+ private static String OPTIONS_GROUP = "options";
+ private static String CLASS_GROUP = "class";
+ private static String CLASS_NAME_GROUP = "class-name";
+ private static String INSTANCE_OF_GROUP = "instance-of";
+
+ private Group createDescriptionGroup() {
+ return new Group("description")
+ .addMember(
+ new GroupMember("description")
+ .setDocTitle("Optional description to document the reason for this annotation.")
+ .defaultEmptyString());
+ }
+
+ private Group createBindingsGroup() {
+ return new Group("bindings")
+ .addMember(new GroupMember("bindings").defaultEmptyArray(KeepBinding.class));
+ }
+
+ private Group createPreconditionsGroup() {
+ return new Group("preconditions")
+ .addMember(
+ new GroupMember("preconditions")
+ .setDocTitle(
+ "Conditions that should be satisfied for the annotation to be in effect.")
+ .addParagraph(
+ "Defaults to no conditions, thus trivially/unconditionally satisfied.")
+ .defaultEmptyArray(KeepCondition.class));
+ }
+
+ private Group createConsequencesGroup() {
+ return new Group("consequences")
+ .addMember(
+ new GroupMember("consequences")
+ .setDocTitle("Consequences that must be kept if the annotation is in effect.")
+ .requiredValueOfArrayType(KeepTarget.class));
+ }
+
+ private Group createConsequencesAsValueGroup() {
+ return new Group("consequences")
+ .addMember(
+ new GroupMember("value")
+ .setDocTitle("Consequences that must be kept if the annotation is in effect.")
+ .requiredValueOfArrayType(KeepTarget.class));
+ }
+
+ private Group createAdditionalPreconditionsGroup() {
+ return new Group("additional-preconditions")
+ .addMember(
+ new GroupMember("additionalPreconditions")
+ .setDocTitle("Additional preconditions for the annotation to be in effect.")
+ .addParagraph("Defaults to no additional preconditions.")
+ .defaultEmptyArray("KeepCondition"));
+ }
+
+ private Group createAdditionalTargetsGroup(String docTitle) {
+ return new Group("additional-targets")
+ .addMember(
+ new GroupMember("additionalTargets")
+ .setDocTitle(docTitle)
+ .addParagraph("Defaults to no additional targets.")
+ .defaultEmptyArray("KeepTarget"));
+ }
+
+ private Group getKindGroup() {
+ return new Group(KIND_GROUP).addMember(getKindMember());
+ }
+
+ private static GroupMember getKindMember() {
+ return new GroupMember("kind")
+ .defaultType("KeepItemKind")
+ .defaultValue("KeepItemKind.DEFAULT")
+ .setDocTitle("Specify the kind of this item pattern.")
+ .addParagraph("Possible values are:")
+ .addUnorderedList(
+ KeepItemKind.ONLY_CLASS.name(),
+ KeepItemKind.ONLY_MEMBERS.name(),
+ KeepItemKind.CLASS_AND_MEMBERS.name())
+ .addParagraph(
+ "If unspecified the default for an item with no member patterns is",
+ KeepItemKind.ONLY_CLASS.name(),
+ "and if it does have member patterns the default is",
+ KeepItemKind.ONLY_MEMBERS.name());
+ }
+
+ private Group getKeepOptionsGroup() {
+ return new Group(OPTIONS_GROUP)
+ .addMember(
+ new GroupMember("allow")
+ .setDocTitle(
+ "Define the "
+ + OPTIONS_GROUP
+ + " that do not need to be preserved for the target.")
+ .defaultEmptyArray("KeepOption"))
+ .addMember(
+ new GroupMember("disallow")
+ .setDocTitle(
+ "Define the " + OPTIONS_GROUP + " that *must* be preserved for the target.")
+ .defaultEmptyArray("KeepOption"))
+ .addDocFooterParagraph(
+ "If nothing is specified for "
+ + OPTIONS_GROUP
+ + " the default is "
+ + quote("allow none")
+ + " / "
+ + quote("disallow all")
+ + ".");
+ }
+
+ private GroupMember bindingName() {
+ return new GroupMember("bindingName")
+ .setDocTitle(
+ "Name with which other bindings, conditions or targets "
+ + "can reference the bound item pattern.")
+ .requiredStringValue();
+ }
+
+ private GroupMember classFromBinding() {
+ return new GroupMember("classFromBinding")
+ .setDocTitle("Define the " + CLASS_GROUP + " pattern by reference to a binding.")
+ .defaultEmptyString();
+ }
+
+ private Group createClassBindingGroup() {
+ return new Group(CLASS_GROUP)
+ .addMember(classFromBinding())
+ .addDocFooterParagraph("If none are specified the default is to match any class.");
+ }
+
+ private GroupMember className() {
+ return new GroupMember("className")
+ .setDocTitle("Define the " + CLASS_NAME_GROUP + " pattern by fully qualified class name.")
+ .defaultEmptyString();
+ }
+
+ private GroupMember classConstant() {
+ return new GroupMember("classConstant")
+ .setDocTitle(
+ "Define the " + CLASS_NAME_GROUP + " pattern by reference to a Class constant.")
+ .defaultObjectClass();
+ }
+
+ private Group createClassNamePatternGroup() {
+ return new Group(CLASS_NAME_GROUP)
+ .addMember(className())
+ .addMember(classConstant())
+ .addDocFooterParagraph("If none are specified the default is to match any class name.");
+ }
+
+ private GroupMember instanceOfClassName() {
+ return new GroupMember("instanceOfClassName")
+ .setDocTitle(
+ "Define the "
+ + INSTANCE_OF_GROUP
+ + " pattern as classes that are instances of the fully qualified class name.")
+ .defaultEmptyString();
+ }
+
+ private GroupMember instanceOfClassConstant() {
+ return new GroupMember("instanceOfClassConstant")
+ .setDocTitle(
+ "Define the "
+ + INSTANCE_OF_GROUP
+ + " pattern as classes that are instances the referenced Class constant.")
+ .defaultObjectClass();
+ }
+
+ private String getInstanceOfExclusiveDoc() {
+ return "The pattern is exclusive in that it does not match classes that are"
+ + " instances of the pattern, but only those that are instances of classes that"
+ + " are subclasses of the pattern.";
+ }
+
+ private GroupMember instanceOfClassNameExclusive() {
+ return new GroupMember("instanceOfClassNameExclusive")
+ .setDocTitle(
+ "Define the "
+ + INSTANCE_OF_GROUP
+ + " pattern as classes that are instances of the fully qualified class name.")
+ .addParagraph(getInstanceOfExclusiveDoc())
+ .defaultEmptyString();
+ }
+
+ private GroupMember instanceOfClassConstantExclusive() {
+ return new GroupMember("instanceOfClassConstantExclusive")
+ .setDocTitle(
+ "Define the "
+ + INSTANCE_OF_GROUP
+ + " pattern as classes that are instances the referenced Class constant.")
+ .addParagraph(getInstanceOfExclusiveDoc())
+ .defaultObjectClass();
+ }
+
+ private GroupMember extendsClassName() {
+ return new GroupMember("extendsClassName")
+ .setDocTitle(
+ "Define the "
+ + INSTANCE_OF_GROUP
+ + " pattern as classes extending the fully qualified class name.")
+ .addParagraph(getInstanceOfExclusiveDoc())
+ .addParagraph("This property is deprecated, use instanceOfClassName instead.")
+ .defaultEmptyString();
+ }
+
+ private GroupMember extendsClassConstant() {
+ return new GroupMember("extendsClassConstant")
+ .setDocTitle(
+ "Define the "
+ + INSTANCE_OF_GROUP
+ + " pattern as classes extending the referenced Class constant.")
+ .addParagraph(getInstanceOfExclusiveDoc())
+ .addParagraph("This property is deprecated, use instanceOfClassConstant instead.")
+ .defaultObjectClass();
+ }
+
+ private Group createClassInstanceOfPatternGroup() {
+ return new Group(INSTANCE_OF_GROUP)
+ .addMember(instanceOfClassName())
+ .addMember(instanceOfClassNameExclusive())
+ .addMember(instanceOfClassConstant())
+ .addMember(instanceOfClassConstantExclusive())
+ .addMember(extendsClassName())
+ .addMember(extendsClassConstant())
+ .addDocFooterParagraph(
+ "If none are specified the default is to match any class instance.");
+ }
+
+ private Group createMemberBindingGroup() {
+ return new Group("member")
+ .addMember(
+ new GroupMember("memberFromBinding")
+ .setDocTitle("Define the member pattern in full by a reference to a binding.")
+ .addParagraph(
+ "Mutually exclusive with all other class and member pattern properties.",
+ "When a member binding is referenced this item is defined to be that item,",
+ "including its class and member patterns.")
+ .defaultEmptyString());
+ }
+
+ private Group createMemberAccessGroup() {
+ return new Group("member-access")
+ .addMember(
+ new GroupMember("memberAccess")
+ .setDocTitle("Define the member-access pattern by matching on access flags.")
+ .addParagraph(
+ "Mutually exclusive with all field and method properties",
+ "as use restricts the match to both types of members.")
+ .defaultEmptyArray("MemberAccessFlags"));
+ }
+
+ private String getMutuallyExclusiveForMethodProperties() {
+ return "Mutually exclusive with all field properties.";
+ }
+
+ private String getMutuallyExclusiveForFieldProperties() {
+ return "Mutually exclusive with all method properties.";
+ }
+
+ private String getMethodDefaultDoc(String suffix) {
+ return "If none, and other properties define this item as a method, the default matches "
+ + suffix
+ + ".";
+ }
+
+ private String getFieldDefaultDoc(String suffix) {
+ return "If none, and other properties define this item as a field, the default matches "
+ + suffix
+ + ".";
+ }
+
+ private Group createMethodAccessGroup() {
+ return new Group("method-access")
+ .addMember(
+ new GroupMember("methodAccess")
+ .setDocTitle("Define the method-access pattern by matching on access flags.")
+ .addParagraph(getMutuallyExclusiveForMethodProperties())
+ .addParagraph(getMethodDefaultDoc("any method-access flags"))
+ .defaultEmptyArray("MethodAccessFlags"));
+ }
+
+ private Group createMethodNameGroup() {
+ return new Group("method-name")
+ .addMember(
+ new GroupMember("methodName")
+ .setDocTitle("Define the method-name pattern by an exact method name.")
+ .addParagraph(getMutuallyExclusiveForMethodProperties())
+ .addParagraph(getMethodDefaultDoc("any method name"))
+ .defaultEmptyString());
+ }
+
+ private Group createMethodReturnTypeGroup() {
+ return new Group("return-type")
+ .addMember(
+ new GroupMember("methodReturnType")
+ .setDocTitle(
+ "Define the method return-type pattern by a fully qualified type or 'void'.")
+ .addParagraph(getMutuallyExclusiveForMethodProperties())
+ .addParagraph(getMethodDefaultDoc("any return type"))
+ .defaultEmptyString());
+ }
+
+ private Group createMethodParametersGroup() {
+ return new Group("parameters")
+ .addMember(
+ new GroupMember("methodParameters")
+ .setDocTitle(
+ "Define the method parameters pattern by a list of fully qualified types.")
+ .addParagraph(getMutuallyExclusiveForMethodProperties())
+ .addParagraph(getMethodDefaultDoc("any parameters"))
+ .defaultType("String[]")
+ .defaultValue("{\"<default>\"}"));
+ }
+
+ private Group createFieldAccessGroup() {
+ return new Group("field-access")
+ .addMember(
+ new GroupMember("fieldAccess")
+ .setDocTitle("Define the field-access pattern by matching on access flags.")
+ .addParagraph(getMutuallyExclusiveForFieldProperties())
+ .addParagraph(getFieldDefaultDoc("any field-access flags"))
+ .defaultEmptyArray("FieldAccessFlags"));
+ }
+
+ private Group createFieldNameGroup() {
+ return new Group("field-name")
+ .addMember(
+ new GroupMember("fieldName")
+ .setDocTitle("Define the field-name pattern by an exact field name.")
+ .addParagraph(getMutuallyExclusiveForFieldProperties())
+ .addParagraph(getFieldDefaultDoc("any field name"))
+ .defaultEmptyString());
+ }
+
+ private Group createFieldTypeGroup() {
+ return new Group("field-type")
+ .addMember(
+ new GroupMember("fieldType")
+ .setDocTitle("Define the field-type pattern by a fully qualified type.")
+ .addParagraph(getMutuallyExclusiveForFieldProperties())
+ .addParagraph(getFieldDefaultDoc("any type"))
+ .defaultEmptyString());
+ }
+
+ private void generateClassAndMemberPropertiesWithClassAndMemberBinding() {
+ internalGenerateClassAndMemberPropertiesWithBinding(true);
+ }
+
+ private void generateClassAndMemberPropertiesWithClassBinding() {
+ internalGenerateClassAndMemberPropertiesWithBinding(false);
+ }
+
+ private void internalGenerateClassAndMemberPropertiesWithBinding(boolean includeMemberBinding) {
+ // Class properties.
+ {
+ Group bindingGroup = createClassBindingGroup();
+ Group classNameGroup = createClassNamePatternGroup();
+ Group classInstanceOfGroup = createClassInstanceOfPatternGroup();
+ bindingGroup.addMutuallyExclusiveGroups(classNameGroup, classInstanceOfGroup);
+
+ bindingGroup.generate(this);
+ println();
+ classNameGroup.generate(this);
+ println();
+ classInstanceOfGroup.generate(this);
+ println();
+ }
+
+ // Member binding properties.
+ if (includeMemberBinding) {
+ createMemberBindingGroup().generate(this);
+ println();
+ }
+
+ // The remaining member properties.
+ generateMemberPropertiesNoBinding();
+ }
+
+ private void generateMemberPropertiesNoBinding() {
+ // General member properties.
+ createMemberAccessGroup().generate(this);
+ println();
+
+ // Method properties.
+ createMethodAccessGroup().generate(this);
+ println();
+ createMethodNameGroup().generate(this);
+ println();
+ createMethodReturnTypeGroup().generate(this);
+ println();
+ createMethodParametersGroup().generate(this);
+ println();
+
+ // Field properties.
+ createFieldAccessGroup().generate(this);
+ println();
+ createFieldNameGroup().generate(this);
+ println();
+ createFieldTypeGroup().generate(this);
+ }
+
+ private void generateKeepBinding() {
+ printCopyRight(2022);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("A binding of a keep item.")
+ .addParagraph(
+ "Bindings allow referencing the exact instance of a match from a condition in other "
+ + " conditions and/or targets. It can also be used to reduce duplication of"
+ + " targets by sharing patterns.")
+ .addParagraph("An item can be:")
+ .addUnorderedList(
+ "a pattern on classes;", "a pattern on methods; or", "a pattern on fields.")
+ .printDoc(this::println);
+ println("@Target(ElementType.ANNOTATION_TYPE)");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface KeepBinding {");
+ println();
+ withIndent(
+ () -> {
+ new GroupMember("bindingName")
+ .setDocTitle(
+ "Name with which other bindings, conditions or targets can reference the bound"
+ + " item pattern.")
+ .requiredValueOfType("String")
+ .generate(this);
+ println();
+ getKindGroup().generate(this);
+ println();
+ generateClassAndMemberPropertiesWithClassBinding();
+ });
+ println();
+ println("}");
+ }
+
+ private void generateKeepTarget() {
+ printCopyRight(2022);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("A target for a keep edge.")
+ .addParagraph(
+ "The target denotes an item along with options for what to keep. An item can be:")
+ .addUnorderedList(
+ "a pattern on classes;", "a pattern on methods; or", "a pattern on fields.")
+ .printDoc(this::println);
+ println("@Target(ElementType.ANNOTATION_TYPE)");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface KeepTarget {");
+ println();
+ withIndent(
+ () -> {
+ getKindGroup().generate(this);
+ println();
+ getKeepOptionsGroup().generate(this);
+ println();
+ generateClassAndMemberPropertiesWithClassAndMemberBinding();
+ });
+ println();
+ println("}");
+ }
+
+ private void generateKeepCondition() {
+ printCopyRight(2022);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("A condition for a keep edge.")
+ .addParagraph(
+ "The condition denotes an item used as a precondition of a rule. An item can be:")
+ .addUnorderedList(
+ "a pattern on classes;", "a pattern on methods; or", "a pattern on fields.")
+ .printDoc(this::println);
+ println("@Target(ElementType.ANNOTATION_TYPE)");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface KeepCondition {");
+ println();
+ withIndent(
+ () -> {
+ generateClassAndMemberPropertiesWithClassAndMemberBinding();
+ });
+ println();
+ println("}");
+ }
+
+ private void generateKeepForApi() {
+ printCopyRight(2023);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle(
+ "Annotation to mark a class, field or method as part of a library API surface.")
+ .addParagraph(
+ "When a class is annotated, member patterns can be used to define which members are"
+ + " to be kept. When no member patterns are specified the default pattern matches"
+ + " all public and protected members.")
+ .addParagraph(
+ "When a member is annotated, the member patterns cannot be used as the annotated"
+ + " member itself fully defines the item to be kept (i.e., itself).")
+ .printDoc(this::println);
+ println(
+ "@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,"
+ + " ElementType.CONSTRUCTOR})");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface KeepForApi {");
+ println();
+ withIndent(
+ () -> {
+ createDescriptionGroup().generate(this);
+ println();
+ createAdditionalTargetsGroup(
+ "Additional targets to be kept as part of the API surface.")
+ .generate(this);
+ println();
+ GroupMember kindProperty = getKindMember();
+ kindProperty
+ .clearDocLines()
+ .addParagraph(
+ "Default kind is",
+ KeepItemKind.CLASS_AND_MEMBERS.name(),
+ ", meaning the annotated class and/or member is to be kept.",
+ "When annotating a class this can be set to",
+ KeepItemKind.ONLY_CLASS.name(),
+ "to avoid patterns on any members.",
+ "That can be useful when the API members are themselves explicitly annotated.")
+ .addParagraph(
+ "It is not possible to use",
+ KeepItemKind.ONLY_CLASS.name(),
+ "if annotating a member. Also, it is never valid to use kind",
+ KeepItemKind.ONLY_MEMBERS.name(),
+ "as the API surface must keep the class if any member is to be accessible.")
+ .generate(this);
+ println();
+ generateMemberPropertiesNoBinding();
+ });
+ println();
+ println("}");
+ }
+
+ private void generateUsesReflection() {
+ printCopyRight(2022);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle(
+ "Annotation to declare the reflective usages made by a class, method or field.")
+ .addParagraph(
+ "The annotation's 'value' is a list of targets to be kept if the annotated item is"
+ + " used. The annotated item is a precondition for keeping any of the specified"
+ + " targets. Thus, if an annotated method is determined to be unused by the"
+ + " program, the annotation itself will not be in effect and the targets will not"
+ + " be kept (assuming nothing else is otherwise keeping them).")
+ .addParagraph(
+ "The annotation's 'additionalPreconditions' is optional and can specify additional"
+ + " conditions that should be satisfied for the annotation to be in effect.")
+ .addParagraph(
+ "The translation of the "
+ + docLink(UsesReflection.class)
+ + " annotation into a "
+ + docLink(KeepEdge.class)
+ + " is as follows:")
+ .addParagraph(
+ "Assume the item of the annotation is denoted by 'CTX' and referred to as its"
+ + " context.")
+ .addCodeBlock(
+ annoSimpleName(UsesReflection.class)
+ + "(value = targets, [additionalPreconditions = preconditions])",
+ "==>",
+ annoSimpleName(KeepEdge.class) + "(",
+ " consequences = targets,",
+ " preconditions = {createConditionFromContext(CTX)} + preconditions",
+ ")",
+ "",
+ "where",
+ " KeepCondition createConditionFromContext(ctx) {",
+ " if (ctx.isClass()) {",
+ " return new KeepCondition(classTypeName = ctx.getClassTypeName());",
+ " }",
+ " if (ctx.isMethod()) {",
+ " return new KeepCondition(",
+ " classTypeName = ctx.getClassTypeName(),",
+ " methodName = ctx.getMethodName(),",
+ " methodReturnType = ctx.getMethodReturnType(),",
+ " methodParameterTypes = ctx.getMethodParameterTypes());",
+ " }",
+ " if (ctx.isField()) {",
+ " return new KeepCondition(",
+ " classTypeName = ctx.getClassTypeName(),",
+ " fieldName = ctx.getFieldName()",
+ " fieldType = ctx.getFieldType());",
+ " }",
+ " // unreachable",
+ " }")
+ .printDoc(this::println);
+ println(
+ "@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,"
+ + " ElementType.CONSTRUCTOR})");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface " + simpleName(UsesReflection.class) + " {");
+ println();
+ withIndent(
+ () -> {
+ createDescriptionGroup().generate(this);
+ println();
+ createConsequencesAsValueGroup().generate(this);
+ println();
+ createAdditionalPreconditionsGroup().generate(this);
+ });
+ println("}");
+ }
+
+ private void generateUsedByX(String annotationClassName, String doc) {
+ printCopyRight(2023);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("Annotation to mark a class, field or method as being " + doc + ".")
+ .addParagraph(
+ "Note: Before using this annotation, consider if instead you can annotate the code"
+ + " that is doing reflection with "
+ + docLink(UsesReflection.class)
+ + ". Annotating the"
+ + " reflecting code is generally more clear and maintainable, and it also"
+ + " naturally gives rise to edges that describe just the reflected aspects of the"
+ + " program. The "
+ + docLink(UsedByReflection.class)
+ + " annotation is suitable for cases where"
+ + " the reflecting code is not under user control, or in migrating away from"
+ + " rules.")
+ .addParagraph(
+ "When a class is annotated, member patterns can be used to define which members are"
+ + " to be kept. When no member patterns are specified the default pattern is to"
+ + " match just the class.")
+ .addParagraph(
+ "When a member is annotated, the member patterns cannot be used as the annotated"
+ + " member itself fully defines the item to be kept (i.e., itself).")
+ .printDoc(this::println);
+ println(
+ "@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,"
+ + " ElementType.CONSTRUCTOR})");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface " + annotationClassName + " {");
+ println();
+ withIndent(
+ () -> {
+ createDescriptionGroup().generate(this);
+ println();
+ createPreconditionsGroup().generate(this);
+ println();
+ createAdditionalTargetsGroup(
+ "Additional targets to be kept in addition to the annotated class/members.")
+ .generate(this);
+ println();
+ GroupMember kindProperty = getKindMember();
+ kindProperty
+ .clearDocLines()
+ .addParagraph(
+ "When annotating a class without member patterns, the default kind is "
+ + docLink(KeepItemKind.ONLY_CLASS)
+ + ".")
+ .addParagraph(
+ "When annotating a class with member patterns, the default kind is "
+ + docLink(KeepItemKind.CLASS_AND_MEMBERS)
+ + ".")
+ .addParagraph(
+ "When annotating a member, the default kind is "
+ + docLink(KeepItemKind.ONLY_MEMBERS)
+ + ".")
+ .addParagraph("It is not possible to use ONLY_CLASS if annotating a member.")
+ .generate(this);
+ println();
+ generateMemberPropertiesNoBinding();
+ });
+ println();
+ println("}");
+ }
+
+ private String annoSimpleName(Class<?> clazz) {
+ return "@" + simpleName(clazz);
+ }
+
+ private String docLink(Class<?> clazz) {
+ return "{@link " + simpleName(clazz) + "}";
+ }
+
+ private String docLink(KeepItemKind kind) {
+ return "{@link KeepItemKind#" + kind.name() + "}";
+ }
+
+ private void generateConstants() {
+ printCopyRight(2023);
+ printPackage("ast");
+ printImports();
+ DocPrinter.printer()
+ .setDocTitle(
+ "Utility class for referencing the various keep annotations and their structure.")
+ .addParagraph(
+ "Use of these references avoids polluting the Java namespace with imports of the java"
+ + " annotations which overlap in name with the actual semantic AST types.")
+ .printDoc(this::println);
+ println("public final class AnnotationConstants {");
+ withIndent(
+ () -> {
+ // Root annotations.
+ generateKeepEdgeConstants();
+ generateKeepForApiConstants();
+ generateUsesReflectionConstants();
+ generateUsedByReflectionConstants();
+ generateUsedByNativeConstants();
+ generateCheckRemovedConstants();
+ generateCheckOptimizedOutConstants();
+ // Common item fields.
+ generateItemConstants();
+ // Inner annotation classes.
+ generateBindingConstants();
+ generateConditionConstants();
+ generateTargetConstants();
+ generateKindConstants();
+ generateOptionConstants();
+ generateMemberAccessConstants();
+ generateMethodAccessConstants();
+ generateFieldAccessConstants();
+ });
+ println("}");
+ }
+
+ private void generateAnnotationConstants(Class<?> clazz) {
+ String name = simpleName(clazz);
+ String desc = TestBase.descriptor(clazz);
+ println("public static final String SIMPLE_NAME = " + quote(name) + ";");
+ println("public static final String DESCRIPTOR = " + quote(desc) + ";");
+ }
+
+ private void generateKeepEdgeConstants() {
+ println("public static final class Edge {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(KeepEdge.class);
+ createDescriptionGroup().generateConstants(this);
+ createBindingsGroup().generateConstants(this);
+ createPreconditionsGroup().generateConstants(this);
+ createConsequencesGroup().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateKeepForApiConstants() {
+ println("public static final class ForApi {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(KeepForApi.class);
+ createDescriptionGroup().generateConstants(this);
+ createAdditionalTargetsGroup(".").generateConstants(this);
+ createMemberAccessGroup().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateUsesReflectionConstants() {
+ println("public static final class UsesReflection {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(UsesReflection.class);
+ createDescriptionGroup().generateConstants(this);
+ createConsequencesAsValueGroup().generateConstants(this);
+ createAdditionalPreconditionsGroup().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateUsedByReflectionConstants() {
+ println("public static final class UsedByReflection {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(UsedByReflection.class);
+ createDescriptionGroup().generateConstants(this);
+ createPreconditionsGroup().generateConstants(this);
+ createAdditionalTargetsGroup(".").generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateUsedByNativeConstants() {
+ println("public static final class UsedByNative {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(UsedByNative.class);
+ println("// Content is the same as " + simpleName(UsedByReflection.class) + ".");
+ });
+ println("}");
+ println();
+ }
+
+ private void generateCheckRemovedConstants() {
+ println("public static final class CheckRemoved {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(CheckRemoved.class);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateCheckOptimizedOutConstants() {
+ println("public static final class CheckOptimizedOut {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(CheckOptimizedOut.class);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateItemConstants() {
+ DocPrinter.printer()
+ .setDocTitle("Item properties common to binding items, conditions and targets.")
+ .printDoc(this::println);
+ println("public static final class Item {");
+ withIndent(
+ () -> {
+ // Bindings.
+ createClassBindingGroup().generateConstants(this);
+ createMemberBindingGroup().generateConstants(this);
+ // Classes.
+ createClassNamePatternGroup().generateConstants(this);
+ createClassInstanceOfPatternGroup().generateConstants(this);
+ // Members.
+ createMemberAccessGroup().generateConstants(this);
+ // Methods.
+ createMethodAccessGroup().generateConstants(this);
+ createMethodNameGroup().generateConstants(this);
+ createMethodReturnTypeGroup().generateConstants(this);
+ createMethodParametersGroup().generateConstants(this);
+ // Fields.
+ createFieldAccessGroup().generateConstants(this);
+ createFieldNameGroup().generateConstants(this);
+ createFieldTypeGroup().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateBindingConstants() {
+ println("public static final class Binding {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(KeepBinding.class);
+ bindingName().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateConditionConstants() {
+ println("public static final class Condition {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(KeepCondition.class);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateTargetConstants() {
+ println("public static final class Target {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(KeepTarget.class);
+ getKindGroup().generateConstants(this);
+ getKeepOptionsGroup().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
+ private void generateKindConstants() {
+ println("public static final class Kind {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(KeepItemKind.class);
+ for (KeepItemKind value : KeepItemKind.values()) {
+ if (value != KeepItemKind.DEFAULT) {
+ println(
+ "public static final String "
+ + value.name()
+ + " = "
+ + quote(value.name())
+ + ";");
+ }
+ }
+ });
+ println("}");
+ println();
+ }
+
+ private void generateOptionConstants() {
+ println("public static final class Option {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(KeepOption.class);
+ for (KeepOption value : KeepOption.values()) {
+ println(
+ "public static final String " + value.name() + " = " + quote(value.name()) + ";");
+ }
+ });
+ println("}");
+ println();
+ }
+
+ private boolean isMemberAccessProperty(String name) {
+ for (MemberAccessFlags value : MemberAccessFlags.values()) {
+ if (value.name().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void generateMemberAccessConstants() {
+ println("public static final class MemberAccess {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(MemberAccessFlags.class);
+ println("public static final String NEGATION_PREFIX = \"NON_\";");
+ for (MemberAccessFlags value : MemberAccessFlags.values()) {
+ if (!value.name().startsWith("NON_")) {
+ println(
+ "public static final String "
+ + value.name()
+ + " = "
+ + quote(value.name())
+ + ";");
+ }
+ }
+ });
+ println("}");
+ println();
+ }
+
+ private void generateMethodAccessConstants() {
+ println("public static final class MethodAccess {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(MethodAccessFlags.class);
+ for (MethodAccessFlags value : MethodAccessFlags.values()) {
+ if (value.name().startsWith("NON_") || isMemberAccessProperty(value.name())) {
+ continue;
+ }
+ println(
+ "public static final String " + value.name() + " = " + quote(value.name()) + ";");
+ }
+ });
+ println("}");
+ println();
+ }
+
+ private void generateFieldAccessConstants() {
+ println("public static final class FieldAccess {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(FieldAccessFlags.class);
+ for (FieldAccessFlags value : FieldAccessFlags.values()) {
+ if (value.name().startsWith("NON_") || isMemberAccessProperty(value.name())) {
+ continue;
+ }
+ println(
+ "public static final String " + value.name() + " = " + quote(value.name()) + ";");
+ }
+ });
+ println("}");
+ println();
+ }
+
+ private static void writeFile(Path file, Consumer<Generator> fn) throws IOException {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ PrintStream printStream = new PrintStream(byteStream);
+ Generator generator = new Generator(printStream);
+ fn.accept(generator);
+ String formatted = CodeGenerationBase.formatRawOutput(byteStream.toString());
+ Files.write(Paths.get(ToolHelper.getProjectRoot()).resolve(file), formatted.getBytes());
+ }
+
+ public static void run() throws IOException {
+ Path keepAnnoRoot = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno");
+
+ Path astPkg = keepAnnoRoot.resolve("ast");
+ writeFile(astPkg.resolve("AnnotationConstants.java"), Generator::generateConstants);
+
+ Path annoPkg = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno/annotations");
+ writeFile(annoPkg.resolve("KeepBinding.java"), Generator::generateKeepBinding);
+ writeFile(annoPkg.resolve("KeepTarget.java"), Generator::generateKeepTarget);
+ writeFile(annoPkg.resolve("KeepCondition.java"), Generator::generateKeepCondition);
+ writeFile(annoPkg.resolve("KeepForApi.java"), Generator::generateKeepForApi);
+ writeFile(annoPkg.resolve("UsesReflection.java"), Generator::generateUsesReflection);
+ writeFile(
+ annoPkg.resolve("UsedByReflection.java"),
+ g -> g.generateUsedByX("UsedByReflection", "accessed reflectively"));
+ writeFile(
+ annoPkg.resolve("UsedByNative.java"),
+ g -> g.generateUsedByX("UsedByNative", "accessed from native code via JNI"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index ba11cfa..c7956f8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -89,9 +89,9 @@
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
private final Consumer<InternalOptions> disableAggressiveClassOptimizations =
- o -> {
- o.enableClassInlining = false;
- o.enableVerticalClassMerging = false;
+ options -> {
+ options.enableClassInlining = false;
+ options.getVerticalClassMergerOptions().disable();
};
@Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingClasspathBridgeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingClasspathBridgeTest.java
new file mode 100644
index 0000000..8450c93
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingClasspathBridgeTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2023, 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.memberrebinding;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.memberrebinding.classpathbridge.Main;
+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 MemberRebindingClasspathBridgeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public void runTest(ThrowableConsumer<R8FullTestBuilder> configure) throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(ProgramInterface.class)
+ .addProgramFiles(ToolHelper.getClassFilesForTestPackage(Main.class.getPackage()))
+ .addKeepMainRule(Main.class)
+ .addKeepAllClassesRule()
+ .setMode(CompilationMode.RELEASE)
+ .apply(configure)
+ .run(parameters.getRuntime(), Main.class);
+ }
+
+ @Test
+ public void runTestLibrary() throws Exception {
+ runTest(
+ builder ->
+ builder
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(ClasspathInterface.class));
+ }
+
+ @Test
+ public void runTestClasspath() throws Exception {
+ runTest(builder -> builder.addClasspathClasses(ClasspathInterface.class));
+ }
+
+ @Test
+ public void runTestProgram() throws Exception {
+ runTest(builder -> builder.addProgramClasses(ClasspathInterface.class));
+ }
+
+ /* package private */ interface ClasspathInterface {
+ void m();
+ }
+
+ public interface ProgramInterface extends ClasspathInterface {}
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/Main.java b/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/Main.java
new file mode 100644
index 0000000..9e99ecb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/Main.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2023, 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.memberrebinding.classpathbridge;
+
+public class Main {
+ public static void main(String[] args) {
+ new ProgramInterfaceInvoker().add(new ProgramInterfaceImpl());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/ProgramInterfaceImpl.java b/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/ProgramInterfaceImpl.java
new file mode 100644
index 0000000..1f3c2b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/ProgramInterfaceImpl.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2023, 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.memberrebinding.classpathbridge;
+
+import com.android.tools.r8.memberrebinding.MemberRebindingClasspathBridgeTest.ProgramInterface;
+
+public class ProgramInterfaceImpl implements ProgramInterface {
+ public void m() {}
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/ProgramInterfaceInvoker.java b/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/ProgramInterfaceInvoker.java
new file mode 100644
index 0000000..43ecc24
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/classpathbridge/ProgramInterfaceInvoker.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.memberrebinding.classpathbridge;
+
+import com.android.tools.r8.memberrebinding.MemberRebindingClasspathBridgeTest.ProgramInterface;
+
+/* package private */ class ProgramInterfaceInvoker {
+ public void add(ProgramInterface p) {
+ p.m();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index cda3c7d..c71f930 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -53,10 +53,11 @@
dexItemFactory = appView.dexItemFactory();
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
- return new Minifier(appView).run(executor, Timing.empty());
+ new Minifier(appView).run(executor, Timing.empty());
} finally {
executor.shutdown();
}
+ return appView.getNamingLens();
}
protected static <T> Collection<Object[]> createTests(
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index 7517540..a72ff12 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -108,7 +108,7 @@
.addOptionsModification(
options -> {
options.inlinerOptions().enableInlining = false;
- options.enableVerticalClassMerging = false;
+ options.getVerticalClassMergerOptions().disable();
options.enableClassInlining = false;
})
.compile();
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
index d4afe52..e040cee 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
@@ -109,7 +109,7 @@
.addOptionsModification(
options -> {
options.inlinerOptions().enableInlining = false;
- options.enableVerticalClassMerging = false;
+ options.getVerticalClassMergerOptions().disable();
})
.run(parameters.getRuntime(), noMappingMain)
.assertSuccessWithOutput(noMappingExpected)
@@ -208,7 +208,7 @@
.addOptionsModification(
options -> {
options.inlinerOptions().enableInlining = false;
- options.enableVerticalClassMerging = false;
+ options.getVerticalClassMergerOptions().disable();
})
.compile();
diff --git a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
index 27320eb..36ee26d 100644
--- a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
+++ b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
@@ -12,14 +12,13 @@
import com.android.tools.r8.ProguardVersion;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -138,12 +137,11 @@
.addKeepRules(RULES)
.enableNeverClassInliningAnnotations()
.setMinApi(parameters)
- .addOptionsModification(o -> o.enableVerticalClassMerging = enableClassMerging)
+ .addOptionsModification(
+ options -> options.getVerticalClassMergerOptions().setEnabled(enableClassMerging))
.applyIf(
onlyForceInlining,
- builder ->
- builder.addOptionsModification(
- o -> o.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE)))
+ builder -> builder.addOptionsModification(InlinerOptions::setOnlyForceInlining))
.compile()
.inspect(this::inspect);
}
diff --git a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
index 357b665..eb04750 100644
--- a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
@@ -13,8 +13,7 @@
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.google.common.collect.ImmutableSet;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -64,11 +63,8 @@
kotlinJars.getForConfiguration(kotlinParameters), kotlinc.getKotlinAnnotationJar())
.addKeepMainRule(Main.class)
.addKeepAllAttributes()
- .addOptionsModification(
- options -> {
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
- options.enableClassInlining = false;
- })
+ .addOptionsModification(options -> options.enableClassInlining = false)
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.addHorizontallyMergedClassesInspector(
inspector ->
inspector.assertIsCompleteMergeGroup(
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
new file mode 100644
index 0000000..755096b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/numberunboxing/SimpleNumberUnboxingTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2023, 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.numberunboxing;
+
+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 java.util.Objects;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimpleNumberUnboxingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SimpleNumberUnboxingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNumberUnboxing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> opt.testing.enableNumberUnboxer = true)
+ .setMinApi(parameters)
+ .allowDiagnosticWarningMessages()
+ .compile()
+ .assertWarningMessageThatMatches(
+ CoreMatchers.containsString(
+ "Unboxing of arg 0 of void"
+ + " com.android.tools.r8.numberunboxing.SimpleNumberUnboxingTest$Main.print(java.lang.Integer)"))
+ .assertWarningMessageThatMatches(
+ CoreMatchers.containsString(
+ "Unboxing of arg 0 of void"
+ + " com.android.tools.r8.numberunboxing.SimpleNumberUnboxingTest$Main.forwardToPrint2(java.lang.Integer)"))
+ .assertWarningMessageThatMatches(
+ CoreMatchers.containsString(
+ "Unboxing of arg 0 of void"
+ + " com.android.tools.r8.numberunboxing.SimpleNumberUnboxingTest$Main.directPrintUnbox(java.lang.Integer)"))
+ .assertWarningMessageThatMatches(
+ CoreMatchers.containsString(
+ "Unboxing of arg 0 of void"
+ + " com.android.tools.r8.numberunboxing.SimpleNumberUnboxingTest$Main.forwardToPrint(java.lang.Integer)"))
+ .assertWarningMessageThatMatches(
+ CoreMatchers.containsString(
+ "Unboxing of return value of java.lang.Integer"
+ + " com.android.tools.r8.numberunboxing.SimpleNumberUnboxingTest$Main.get()"))
+ .assertWarningMessageThatMatches(
+ CoreMatchers.containsString(
+ "Unboxing of return value of java.lang.Integer"
+ + " com.android.tools.r8.numberunboxing.SimpleNumberUnboxingTest$Main.forwardGet()"))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("32", "33", "42", "43", "51", "52", "2");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // The number unboxer should immediately find this method is worth unboxing.
+ directPrintUnbox(31);
+ directPrintUnbox(32);
+
+ // The number unboxer should find the chain of calls is worth unboxing.
+ forwardToPrint(41);
+ forwardToPrint(42);
+
+ // The number unboxer should find this method is *not* worth unboxing.
+ Integer decode1 = Integer.decode("51");
+ Objects.requireNonNull(decode1);
+ directPrintNotUnbox(decode1);
+ Integer decode2 = Integer.decode("52");
+ Objects.requireNonNull(decode2);
+ directPrintNotUnbox(decode2);
+
+ // The number unboxer should unbox the return values.
+ System.out.println(forwardGet() + 1);
+ }
+
+ @NeverInline
+ private static Integer get() {
+ return System.currentTimeMillis() > 0 ? 1 : -1;
+ }
+
+ @NeverInline
+ private static Integer forwardGet() {
+ return get();
+ }
+
+ @NeverInline
+ private static void forwardToPrint(Integer boxed) {
+ forwardToPrint2(boxed);
+ }
+
+ @NeverInline
+ private static void forwardToPrint2(Integer boxed) {
+ print(boxed);
+ }
+
+ @NeverInline
+ private static void print(Integer boxed) {
+ System.out.println(boxed + 1);
+ }
+
+ @NeverInline
+ private static void directPrintUnbox(Integer boxed) {
+ System.out.println(boxed + 1);
+ }
+
+ @NeverInline
+ private static void directPrintNotUnbox(Integer boxed) {
+ System.out.println(boxed);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
index 31b779c..0704294 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.desugar.records.RecordTestUtils;
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
@@ -102,9 +101,10 @@
options -> options.testing.disableRecordApplicationReaderMap = true))
.run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
.applyIf(
- canUseNativeRecords(parameters) && !runtimeWithRecordsSupport(parameters.getRuntime()),
- r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class),
- r -> r.assertSuccessWithOutput(EXPECTED_RESULT));
+ isRecordsDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+ r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class));
}
@Test
@@ -166,7 +166,7 @@
SyntheticItemsTestUtils.syntheticRecordTagClass(),
false,
parameters.canUseNestBasedAccessesWhenDesugaring(),
- canUseNativeRecords(parameters));
+ !isRecordsDesugaredForD8(parameters));
}
private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) {
@@ -176,7 +176,7 @@
RECORD_REFERENCE,
parameters.canHaveNonReboundConstructorInvoke(),
parameters.canUseNestBasedAccesses(),
- canUseNativeRecords(parameters) || parameters.isCfRuntime());
+ !isRecordsDesugaredForR8(parameters));
}
private void inspect(
@@ -211,15 +211,7 @@
? inspector.getTypeSubject(RECORD_REFERENCE.getTypeName())
: recordTagClassSubject.asTypeSubject(),
personRecordClassSubject.getSuperType());
- assertEquals(
- canUseRecords
- ? (parameters.isCfRuntime()
- && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)
- && !canUseNativeRecords(parameters)
- ? 6
- : 8)
- : 10,
- personRecordClassSubject.allMethods().size());
+ assertEquals(canUseRecords ? 6 : 10, personRecordClassSubject.allMethods().size());
MethodSubject personInstanceInitializerSubject =
personRecordClassSubject.uniqueInstanceInitializer();
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithoutAnyOptimizationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithoutAnyOptimizationTest.java
new file mode 100644
index 0000000..4d9ce90
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithoutAnyOptimizationTest.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2023, 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.repackage;
+
+
+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 RepackageWithoutAnyOptimizationTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .addDontOptimize()
+ .addDontObfuscate()
+ .addDontShrink()
+ .addKeepRules("-repackageclasses")
+ .compile();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index 3cbd047..022698c 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -180,7 +180,9 @@
.addProgramClassFileData(DUMP)
.addKeepMainRule(Main.class)
.setMinApi(parameters)
- .addOptionsModification(o -> o.enableVerticalClassMerging = enableVerticalClassMerging)
+ .addOptionsModification(
+ options ->
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
}
diff --git a/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java b/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
new file mode 100644
index 0000000..9e45fe5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2023, 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.retrace;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvalidMappingRangesB309080420Test extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public InvalidMappingRangesB309080420Test(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ private static String MAPPING =
+ StringUtils.unixLines(
+ "a.q -> a.q:",
+ " 1:1:void a(com.example.Foo) -> a",
+ " 2:0:void a() -> a", // Unexpected line range [2:0] - interpreting as [2:2]
+ " 12:21:void a(android.content.Intent) -> a",
+ "a.x -> a.x:",
+ " 1:1:void a(com.example.Foo) -> a",
+ " 11:2:void a() -> a", // Unexpected line range [11:2] - interpreting as [2:11]
+ " 12:21:void a(android.content.Intent) -> a");
+
+ @Test
+ public void test() throws Exception {
+ TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+ ProguardMappingSupplier.builder()
+ .setProguardMapProducer(ProguardMapProducer.fromString(MAPPING))
+ .setLoadAllDefinitions(true)
+ .build()
+ .createRetracer(handler);
+ handler.assertNoMessages();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index edb6475..95c8b5b 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -7,7 +7,6 @@
import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -487,19 +486,15 @@
}
@Test
- public void testInvalidMinifiedRangeStackTrace() {
+ public void testInvalidMinifiedRangeStackTrace() throws Exception {
assumeFalse(external);
- assertThrows(
- InvalidMappingFileException.class,
- () -> runRetraceTest(new InvalidMinifiedRangeStackTrace()));
+ runRetraceTest(new InvalidMinifiedRangeStackTrace());
}
@Test
- public void testInvalidOriginalRangeStackTrace() {
+ public void testInvalidOriginalRangeStackTrace() throws Exception {
assumeFalse(external);
- assertThrows(
- InvalidMappingFileException.class,
- () -> runRetraceTest(new InvalidOriginalRangeStackTrace()));
+ runRetraceTest(new InvalidOriginalRangeStackTrace());
}
private void inspectRetraceTest(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidMinifiedRangeStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidMinifiedRangeStackTrace.java
index ad16a40..92e6567 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidMinifiedRangeStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidMinifiedRangeStackTrace.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.utils.StringUtils;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
public class InvalidMinifiedRangeStackTrace implements StackTraceForTest {
@@ -20,12 +19,16 @@
@Override
public List<String> retracedStackTrace() {
- return Collections.emptyList();
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)");
}
@Override
public List<String> retraceVerboseStackTrace() {
- return Collections.emptyList();
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:3)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidOriginalRangeStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidOriginalRangeStackTrace.java
index 3b8fa63..9a6bcde 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidOriginalRangeStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidOriginalRangeStackTrace.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.utils.StringUtils;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
public class InvalidOriginalRangeStackTrace implements StackTraceForTest {
@@ -20,12 +19,16 @@
@Override
public List<String> retracedStackTrace() {
- return Collections.emptyList();
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)");
}
@Override
public List<String> retraceVerboseStackTrace() {
- return Collections.emptyList();
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:3)");
}
@Override
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 1aaffaf..07f3f2b 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -17,14 +17,13 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Objects;
@@ -130,9 +129,12 @@
.addKeepMainRule(NonVirtualOverrideTestClass.class)
.addOptionsModification(
options -> {
- options.enableVerticalClassMerging = dimensions.enableVerticalClassMerging;
- options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.enableClassInlining = false;
+ options
+ .getVerticalClassMergerOptions()
+ .setEnabled(dimensions.enableVerticalClassMerging);
})
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
.setMinApi(AndroidApiLevel.B)
.compile();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index d4bcbbd..9d3f2c5 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -131,7 +131,7 @@
options -> {
options.inlinerOptions().enableInlining = false;
options.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval;
- options.enableVerticalClassMerging = enableVerticalClassMerging;
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging);
})
.enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index 5b4b206..091dc98 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -49,7 +49,7 @@
null,
ImmutableList.of("src/test/examples/shaking19/keep-rules.txt"),
// Disable vertical class merging to prevent A from being merged into B.
- opt -> opt.enableVerticalClassMerging = false);
+ options -> options.getVerticalClassMergerOptions().disable());
}
private static void unusedRemoved(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index d6ac5bf..f41bf57 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -514,12 +514,12 @@
.enableGraphInspector()
.setMinApi(parameters)
.addOptionsModification(
- o -> {
- o.enableClassInlining = false;
+ options -> {
+ options.enableClassInlining = false;
// Prevent InterfaceWithDefaultMethods from being merged into
// ClassImplementingInterface.
- o.enableVerticalClassMerging = false;
+ options.getVerticalClassMergerOptions().disable();
})
.compile();
inspection.accept(compileResult.inspector());
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index f6e0b49..468ae54 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -336,8 +336,12 @@
"}");
AndroidApp processedApp =
- runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config,
- o -> o.enableVerticalClassMerging = enableVerticalClassMerging);
+ runShrinker(
+ shrinker,
+ CLASSES_FOR_SERIALIZABLE,
+ config,
+ options ->
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging));
// TODO(b/117302947): Need to update ART binary.
if (shrinker.generatesCf()) {
String output = runOnVM(
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index f2b946e..d1775a6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -83,7 +83,7 @@
}
private void configure(InternalOptions options) {
- options.enableVerticalClassMerging = enableVerticalClassMerging;
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging);
// TODO(b/141093535): The precondition set for conditionals is currently based on the syntactic
// form, when merging is enabled, if the precondition is merged to a differently named type, the
// rule will still fire, but the reported precondition type is incorrect.
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
index da7c397..ed578cd 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -77,7 +77,8 @@
public void configure(R8FullTestBuilder builder) {
builder
.addOptionsModification(
- options -> options.enableVerticalClassMerging = enableVerticalClassMerging)
+ options ->
+ options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
.enableNoAccessModificationAnnotationsForClasses();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java b/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java
index 1b5c958..91e2a57 100644
--- a/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java
@@ -31,10 +31,10 @@
.addProgramClassesAndInnerClasses(Task.class, OuterClass.class)
.addKeepMainRule(Main.class)
.addOptionsModification(
- internalOptions -> {
- internalOptions.inlinerOptions().enableInlining = false;
- internalOptions.enableClassInlining = false;
- internalOptions.enableVerticalClassMerging = false;
+ options -> {
+ options.inlinerOptions().enableInlining = false;
+ options.enableClassInlining = false;
+ options.getVerticalClassMergerOptions().disable();
})
.setMinApi(parameters)
.run(parameters.getRuntime(), Main.class.getTypeName())
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
index 6f27362..0258428 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
@@ -69,8 +69,7 @@
.addProgramClasses(I.class, J.class)
.setMinApi(parameters)
.addKeepMethodRules(J.class, "void foo()")
- .addOptionsModification(
- internalOptions -> internalOptions.enableVerticalClassMerging = false)
+ .addOptionsModification(options -> options.getVerticalClassMergerOptions().disable())
.addDontObfuscate()
.compile();
// TODO(b/144269679): We should be able to compile and run this.
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index e6ba0f6..4348acf 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1118,14 +1118,12 @@
if (object instanceof String) {
local[i] = rewriteASMInternalTypeName((String) object);
}
- i++;
}
for (int i = 0; i < numStack; i++) {
Object object = stack[i];
if (object instanceof String) {
stack[i] = rewriteASMInternalTypeName((String) object);
}
- i++;
}
super.visitFrame(type, numLocal, local, numStack, stack);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 91a9a80..7f74f87 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ClassReferenceUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Sets;
@@ -61,6 +62,12 @@
return horizontallyMergedClasses.getSources();
}
+ public ClassReference getTarget(ClassReference classReference) {
+ DexType sourceType = ClassReferenceUtils.toDexType(classReference, dexItemFactory);
+ DexType targetType = getTarget(sourceType);
+ return targetType.asClassReference();
+ }
+
public DexType getTarget(DexType clazz) {
return horizontallyMergedClasses.getMergeTargetOrDefault(clazz);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MinificationInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MinificationInspector.java
new file mode 100644
index 0000000..5f3621a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MinificationInspector.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+
+public class MinificationInspector {
+
+ private final DexItemFactory dexItemFactory;
+ private final NamingLens namingLens;
+
+ public MinificationInspector(DexItemFactory dexItemFactory, NamingLens namingLens) {
+ this.dexItemFactory = dexItemFactory;
+ this.namingLens = namingLens;
+ }
+
+ public ClassReference getTarget(ClassReference classReference) {
+ DexType sourceType = ClassReferenceUtils.toDexType(classReference, dexItemFactory);
+ DexString targetDescriptor = namingLens.lookupClassDescriptor(sourceType);
+ return Reference.classFromDescriptor(targetDescriptor.toString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/RepackagingInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/RepackagingInspector.java
new file mode 100644
index 0000000..9ee6e1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/RepackagingInspector.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.repackaging.RepackagingLens;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+
+public class RepackagingInspector {
+
+ private final DexItemFactory dexItemFactory;
+ private final RepackagingLens repackagingLens;
+
+ public RepackagingInspector(DexItemFactory dexItemFactory, RepackagingLens repackagingLens) {
+ this.dexItemFactory = dexItemFactory;
+ this.repackagingLens = repackagingLens;
+ }
+
+ public ClassReference getTarget(ClassReference classReference) {
+ DexType sourceType = ClassReferenceUtils.toDexType(classReference, dexItemFactory);
+ DexType targetType = repackagingLens.getNextClassType(sourceType);
+ return targetType.asClassReference();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
index d1f04cb..5b8f002 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
@@ -8,9 +8,9 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
public class VerticallyMergedClassesInspector {
diff --git a/third_party/dependencies_plugin.tar.gz.sha1 b/third_party/dependencies_plugin.tar.gz.sha1
index fa66acc..ba75d12 100644
--- a/third_party/dependencies_plugin.tar.gz.sha1
+++ b/third_party/dependencies_plugin.tar.gz.sha1
@@ -1 +1 @@
-ebc6c2750dc911f0a974f79395ef28d523c89d09
\ No newline at end of file
+369870d45ae8721ad1379b8bf108e107ad1b5175
\ No newline at end of file
diff --git a/third_party/opensource-apps/compose-examples/changed-bitwise-value-propagation.tar.gz.sha1 b/third_party/opensource-apps/compose-examples/changed-bitwise-value-propagation.tar.gz.sha1
new file mode 100644
index 0000000..7c3bb09
--- /dev/null
+++ b/third_party/opensource-apps/compose-examples/changed-bitwise-value-propagation.tar.gz.sha1
@@ -0,0 +1 @@
+0aac95399bcbeb352820e651c02d422a2ee53a3c
\ No newline at end of file
diff --git a/tools/cherry-pick.py b/tools/cherry-pick.py
index 6e505c5..4a9bca0 100755
--- a/tools/cherry-pick.py
+++ b/tools/cherry-pick.py
@@ -32,17 +32,16 @@
metavar='<hash>',
nargs='+',
help='Hashed to merge')
-
+ parser.add_argument('--remote',
+ default='origin',
+ help='The remote name (defaults to "origin")')
return parser.parse_args()
def run(args):
- # Checkout the branch.
- subprocess.check_output(['git', 'checkout', args.branch])
-
- if (args.current_checkout):
+ if args.current_checkout:
for i in range(len(args.hashes) + 1):
- branch = 'cherry-%d' % (i + 1)
+ branch = 'cherry-%s-%d' % (args.branch, i + 1)
print('Deleting branch %s' % branch)
subprocess.run(['git', 'branch', branch, '-D'])
@@ -50,12 +49,12 @@
count = 1
for hash in args.hashes:
- branch = 'cherry-%d' % count
+ branch = 'cherry-%s-%d' % (args.branch, count)
print('Cherry-picking %s in %s' % (hash, branch))
- if (count == 1):
+ if count == 1:
subprocess.run([
'git', 'new-branch', branch, '--upstream',
- 'origin/%s' % args.branch
+ '%s/%s' % (args.remote, args.branch)
])
else:
subprocess.run(['git', 'new-branch', branch, '--upstream-current'])
@@ -64,7 +63,7 @@
confirm_and_upload(branch, args, bugs)
count = count + 1
- branch = 'cherry-%d' % count
+ branch = 'cherry-%s-%d' % (args.branch, count)
subprocess.run(['git', 'new-branch', branch, '--upstream-current'])
old_version = 'unknown'
@@ -102,7 +101,7 @@
subprocess.run(['git', 'commit', '-a', '-m', message])
confirm_and_upload(branch, args, None)
- if (not args.current_checkout):
+ if not args.current_checkout:
while True:
try:
answer = input(
@@ -140,12 +139,17 @@
l.strip() for l in commit_message.decode('UTF-8').split('\n')
]
for line in commit_lines:
+ bug = None
if line.startswith('Bug: '):
- normalized = line.replace('Bug: ', '').replace('b/', '')
- if len(normalized) > 0:
- bugs.add(normalized)
+ bug = line.replace('Bug: ', '')
+ elif line.startswith('Fixed: '):
+ bug = line.replace('Fixed: ', '')
+ elif line.startswith('Fixes: '):
+ bug = line.replace('Fixes: ', '')
+ if bug:
+ bugs.add(bug.replace('b/', '').strip())
- if (not args.no_upload):
+ if not args.no_upload:
subprocess.run(['git', 'cl', 'upload', '--bypass-hooks'])
diff --git a/tools/create_local_maven_with_dependencies.py b/tools/create_local_maven_with_dependencies.py
index 4f63ef9..05abfb5 100755
--- a/tools/create_local_maven_with_dependencies.py
+++ b/tools/create_local_maven_with_dependencies.py
@@ -102,9 +102,10 @@
'org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.9.10',
'net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:3.0.1',
- # Patched version of org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.2.0.
- # See commit message for a13217f333cc65fb602502ac446698dd74d10b7f.
- 'org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.4.0-r8-patch01',
+ # Patched version of org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.4.0.
+ # See
+ # https://github.com/spdx/spdx-gradle-plugin/issues/69#issuecomment-1799122543.
+ 'org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.4.0-r8-patch02',
# See https://github.com/FasterXML/jackson-core/issues/999.
'ch.randelshofer:fastdoubleparser:0.8.0',
]
diff --git a/tools/create_r8lib.py b/tools/create_r8lib.py
index e8bf4f2..c8eb00e 100755
--- a/tools/create_r8lib.py
+++ b/tools/create_r8lib.py
@@ -103,6 +103,7 @@
cmd.extend(['--map-id-template', map_id_template])
cmd.extend(['--source-file-template', source_file_template])
cmd.extend(['--output', args.output])
+ cmd.extend(['--pg-conf-output', args.output + '.config'])
cmd.extend(['--pg-map-output', args.output + '.map'])
cmd.extend(['--partition-map-output', args.output + '_map.zip'])
cmd.extend(['--lib', jdk.GetJdkHome()])
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a5d7a4c..116a52c 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -452,7 +452,7 @@
g4_open('METADATA')
metadata_path = os.path.join(third_party_r8, 'METADATA')
match_count = 0
- match_count_expected = 11
+ match_count_expected = 10
version_match_regexp = r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev'
for line in open(metadata_path, 'r'):
result = re.search(version_match_regexp, line)
diff --git a/tools/retrace.py b/tools/retrace.py
index 911c646..9768ff3 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -188,7 +188,7 @@
)
if not r8jar:
- r8jar = utils.R8_JAR if no_r8lib else utils.R8LIB
+ r8jar = utils.R8_JAR if no_r8lib else utils.R8LIB_JAR
retrace_args += [
'-cp', r8jar, 'com.android.tools.r8.retrace.Retrace', map_path
diff --git a/tools/test.py b/tools/test.py
index efbe5a1..dcff8c9 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -51,7 +51,7 @@
'jdk9',
'jdk11',
'jdk17',
- 'jdk20',
+ 'jdk21',
] + ['dex-%s' % dexvm for dexvm in ALL_ART_VMS]