Merge commit '5b37636b77ffdb29b8314921071f842a566a3b54' into dev-release Change-Id: Id2b9a5a47f2d47f16a4eed0cc2fcf29885627ea8
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt index e809804..25d24e38 100644 --- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt +++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -388,6 +388,7 @@ object JvmCompatibility { val sourceCompatibility = JavaVersion.VERSION_11 val targetCompatibility = JavaVersion.VERSION_11 + val release = 11 } object Versions { @@ -398,7 +399,7 @@ const val guavaVersion = "32.1.2-jre" const val javassist = "3.29.2-GA" const val junitVersion = "4.13-beta-2" - const val kotlinVersion = "1.9.0" + const val kotlinVersion = "1.9.20" const val kotlinMetadataVersion = "2.0.0-Beta5" const val mockito = "2.10.0" const val smaliVersion = "3.0.3"
diff --git a/d8_r8/gradle.properties b/d8_r8/gradle.properties index a82d85e..473e9a7 100644 --- a/d8_r8/gradle.properties +++ b/d8_r8/gradle.properties
@@ -16,3 +16,28 @@ # Do not download any jdks or detect them. We provide them. org.gradle.java.installations.auto-detect=false org.gradle.java.installations.auto-download=false +# Configure Java toolchains +# +# Would have liked to include third_party/openjdk/jdk8/linux-x86, but Gradle +# does not fully recognize it as a JDK-8. +# +# Run +# tools/gradle.py -q javaToolchains +# to check the actual JDK's picked up by Gradle. +# +# NOTE: Gradle will ignore directories which does not exist, as will be the +# case for JDK's for platforms different from the one this is running on +# when using the default downloadDeps setup. +org.gradle.java.installations.paths=\ +../third_party/openjdk/jdk-11/linux,\ +../third_party/openjdk/jdk-17/linux,\ +../third_party/openjdk/jdk-21/linux,\ +../third_party/openjdk/jdk-22/linux,\ +../third_party/openjdk/jdk-11/osx,\ +../third_party/openjdk/jdk-17/osx,\ +../third_party/openjdk/jdk-21/osx,\ +../third_party/openjdk/jdk-22/osx,\ +../third_party/openjdk/jdk-11/windows,\ +../third_party/openjdk/jdk-17/windows,\ +../third_party/openjdk/jdk-21/windows,\ +../third_party/openjdk/jdk-22/windows
diff --git a/d8_r8/keepanno/build.gradle.kts b/d8_r8/keepanno/build.gradle.kts index 3d981f6..e58f672 100644 --- a/d8_r8/keepanno/build.gradle.kts +++ b/d8_r8/keepanno/build.gradle.kts
@@ -13,6 +13,9 @@ } sourceCompatibility = JvmCompatibility.sourceCompatibility targetCompatibility = JvmCompatibility.targetCompatibility + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } withSourcesJar() }
diff --git a/d8_r8/library_desugar/build.gradle.kts b/d8_r8/library_desugar/build.gradle.kts index 4094e64..0211cb6 100644 --- a/d8_r8/library_desugar/build.gradle.kts +++ b/d8_r8/library_desugar/build.gradle.kts
@@ -14,6 +14,9 @@ } sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } } dependencies {
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts index f365f9f..9eaf601 100644 --- a/d8_r8/main/build.gradle.kts +++ b/d8_r8/main/build.gradle.kts
@@ -31,6 +31,9 @@ } sourceCompatibility = JvmCompatibility.sourceCompatibility targetCompatibility = JvmCompatibility.targetCompatibility + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } withSourcesJar() }
diff --git a/d8_r8/resourceshrinker/build.gradle.kts b/d8_r8/resourceshrinker/build.gradle.kts index 183d1ed..db14c2f 100644 --- a/d8_r8/resourceshrinker/build.gradle.kts +++ b/d8_r8/resourceshrinker/build.gradle.kts
@@ -16,6 +16,9 @@ } sourceCompatibility = JvmCompatibility.sourceCompatibility targetCompatibility = JvmCompatibility.targetCompatibility + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } withSourcesJar() } @@ -47,7 +50,7 @@ kotlinOptions { // We cannot use languageVersion.set(JavaLanguageVersion.of(8)) because gradle cannot figure // out that the jdk is 1_8 and will try to download it. - jvmTarget = "11" + jvmTarget = "${JvmCompatibility.release}" } }
diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts index 14b066a..7b2de4e 100644 --- a/d8_r8/test/build.gradle.kts +++ b/d8_r8/test/build.gradle.kts
@@ -12,8 +12,11 @@ } java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JvmCompatibility.sourceCompatibility + targetCompatibility = JvmCompatibility.targetCompatibility + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } } dependencies { }
diff --git a/d8_r8/test_modules/testbase/build.gradle.kts b/d8_r8/test_modules/testbase/build.gradle.kts index 2d66a1c..336033b 100644 --- a/d8_r8/test_modules/testbase/build.gradle.kts +++ b/d8_r8/test_modules/testbase/build.gradle.kts
@@ -24,6 +24,9 @@ // compatible with java toolchains. sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } } // If we depend on keepanno by referencing the project source outputs we get an error regarding
diff --git a/d8_r8/test_modules/tests_bootstrap/build.gradle.kts b/d8_r8/test_modules/tests_bootstrap/build.gradle.kts index f6120cc..7b900dc 100644 --- a/d8_r8/test_modules/tests_bootstrap/build.gradle.kts +++ b/d8_r8/test_modules/tests_bootstrap/build.gradle.kts
@@ -23,6 +23,9 @@ // compatible with java toolchains. sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } } val testbaseJavaCompileTask = projectTask("testbase", "compileJava")
diff --git a/d8_r8/test_modules/tests_java_11/build.gradle.kts b/d8_r8/test_modules/tests_java_11/build.gradle.kts index 3c99b88..f0748b2 100644 --- a/d8_r8/test_modules/tests_java_11/build.gradle.kts +++ b/d8_r8/test_modules/tests_java_11/build.gradle.kts
@@ -29,6 +29,7 @@ implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile())) implementation(testbaseJavaCompileTask.outputs.files) implementation(mainCompileTask.outputs.files) + implementation(projectTask("main", "processResources").outputs.files) } // We just need to register the examples jars for it to be referenced by other modules.
diff --git a/d8_r8/test_modules/tests_java_17/build.gradle.kts b/d8_r8/test_modules/tests_java_17/build.gradle.kts index 2079c40..d46f487 100644 --- a/d8_r8/test_modules/tests_java_17/build.gradle.kts +++ b/d8_r8/test_modules/tests_java_17/build.gradle.kts
@@ -32,6 +32,7 @@ implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile())) implementation(testbaseJavaCompileTask.outputs.files) implementation(mainCompileTask.outputs.files) + implementation(projectTask("main", "processResources").outputs.files) } // We just need to register the examples jars for it to be referenced by other modules.
diff --git a/d8_r8/test_modules/tests_java_21/build.gradle.kts b/d8_r8/test_modules/tests_java_21/build.gradle.kts index afc55d0..403eb85 100644 --- a/d8_r8/test_modules/tests_java_21/build.gradle.kts +++ b/d8_r8/test_modules/tests_java_21/build.gradle.kts
@@ -28,6 +28,7 @@ implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile())) implementation(testbaseJavaCompileTask.outputs.files) implementation(mainCompileTask.outputs.files) + implementation(projectTask("main", "processResources").outputs.files) } // We just need to register the examples jars for it to be referenced by other modules.
diff --git a/d8_r8/test_modules/tests_java_22/build.gradle.kts b/d8_r8/test_modules/tests_java_22/build.gradle.kts index 546d795..938e1cb 100644 --- a/d8_r8/test_modules/tests_java_22/build.gradle.kts +++ b/d8_r8/test_modules/tests_java_22/build.gradle.kts
@@ -28,6 +28,7 @@ implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile())) implementation(testbaseJavaCompileTask.outputs.files) implementation(mainCompileTask.outputs.files) + implementation(projectTask("main", "processResources").outputs.files) } tasks {
diff --git a/d8_r8/test_modules/tests_java_8/build.gradle.kts b/d8_r8/test_modules/tests_java_8/build.gradle.kts index 779cd6c..31914df 100644 --- a/d8_r8/test_modules/tests_java_8/build.gradle.kts +++ b/d8_r8/test_modules/tests_java_8/build.gradle.kts
@@ -21,11 +21,13 @@ srcDir(root.resolveAll("build", "generated", "test", "java")) } } - // We are using a new JDK to compile to an older language version, which is not directly // compatible with java toolchains. sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + toolchain { + languageVersion = JavaLanguageVersion.of(JvmCompatibility.release) + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java index cfd4302..0327246 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
@@ -69,7 +69,7 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern namePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern namePattern() default @ClassNamePattern(unqualifiedName = ""); /** * Specify which retention policies must be set for the annotations.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java index 58b9fe8..3c9e261 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
@@ -29,8 +29,8 @@ * * <ul> * <li>constant - * <li>simpleName - * <li>simpleNamePattern + * <li>unqualifiedName + * <li>unqualifiedNamePattern * <li>packageName * </ul> * @@ -45,8 +45,8 @@ * * <ul> * <li>name - * <li>simpleName - * <li>simpleNamePattern + * <li>unqualifiedName + * <li>unqualifiedNamePattern * <li>packageName * </ul> * @@ -55,38 +55,40 @@ Class<?> constant() default Object.class; /** - * Exact simple name of the class or interface. + * Exact and unqualified name of the class or interface. * - * <p>For example, the simple name of {@code com.example.MyClass} is {@code MyClass}. + * <p>For example, the unqualified name of {@code com.example.MyClass} is {@code MyClass}. Note + * that for inner classes a `$` will appear in the unqualified name,such as, {@code + * MyClass$MyInnerClass}. * - * <p>The default matches any simple name. + * <p>The default matches any unqualified name. * - * <p>Mutually exclusive with the following other properties defining class-simple-name: + * <p>Mutually exclusive with the following other properties defining class-unqualified-name: * * <ul> - * <li>simpleNamePattern + * <li>unqualifiedNamePattern * <li>name * <li>constant * </ul> */ - String simpleName() default ""; + String unqualifiedName() default ""; /** - * Define the simple-name pattern by a string pattern. + * Define the unqualified class-name pattern by a string pattern. * - * <p>The default matches any simple name. + * <p>The default matches any unqualified name. * - * <p>Mutually exclusive with the following other properties defining class-simple-name: + * <p>Mutually exclusive with the following other properties defining class-unqualified-name: * * <ul> - * <li>simpleName + * <li>unqualifiedName * <li>name * <li>constant * </ul> * - * @return The string pattern of the class simple name. + * @return The string pattern of the unqualified class name. */ - StringPattern simpleNamePattern() default @StringPattern(exact = ""); + StringPattern unqualifiedNamePattern() default @StringPattern(exact = ""); /** * Exact package name of the class or interface.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java index 591d750..975f144 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java
@@ -31,5 +31,5 @@ boolean inclusive() default true; /** Instances of classes matching the class-name pattern. */ - ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = ""); }
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 f92ab49..68adfaa 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
@@ -140,7 +140,7 @@ * * @return The class-name pattern that defines the class. */ - ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = ""); /** * Define the instance-of pattern as classes that are instances of the fully qualified class name. @@ -295,7 +295,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member-annotated-by pattern by fully qualified class name. @@ -355,7 +356,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern memberAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member-access pattern by matching on access flags. @@ -422,7 +424,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern methodAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the method-access pattern by matching on access flags. @@ -604,7 +607,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern fieldAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the field-access pattern by matching on access flags.
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 65d13d8..90b641e 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
@@ -102,7 +102,7 @@ * * @return The class-name pattern that defines the class. */ - ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = ""); /** * Define the instance-of pattern as classes that are instances of the fully qualified class name. @@ -257,7 +257,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member pattern in full by a reference to a binding. @@ -330,7 +331,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern memberAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member-access pattern by matching on access flags. @@ -402,7 +404,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern methodAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the method-access pattern by matching on access flags. @@ -612,7 +615,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern fieldAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the field-access pattern by matching on access flags.
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 aac3246..6a06f60 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
@@ -115,7 +115,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern memberAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member-access pattern by matching on access flags. @@ -182,7 +183,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern methodAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the method-access pattern by matching on access flags. @@ -364,7 +366,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern fieldAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the field-access pattern by matching on access flags.
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 e7e8c74..04f1aac 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
@@ -199,7 +199,7 @@ * * @return The class-name pattern that defines the class. */ - ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = ""); /** * Define the instance-of pattern as classes that are instances of the fully qualified class name. @@ -354,7 +354,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member pattern in full by a reference to a binding. @@ -427,7 +428,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern memberAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member-access pattern by matching on access flags. @@ -499,7 +501,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern methodAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the method-access pattern by matching on access flags. @@ -709,7 +712,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern fieldAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java index 2fa0ffd..47fdb1d 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
@@ -62,5 +62,5 @@ * <li>constant * </ul> */ - ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = ""); }
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 076a93c..4bf6253 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
@@ -205,7 +205,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern memberAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member-access pattern by matching on access flags. @@ -272,7 +273,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern methodAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the method-access pattern by matching on access flags. @@ -454,7 +456,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern fieldAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the field-access pattern by matching on access flags.
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 cf2225d..4a0c692 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
@@ -205,7 +205,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern memberAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the member-access pattern by matching on access flags. @@ -272,7 +273,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern methodAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the method-access pattern by matching on access flags. @@ -454,7 +456,8 @@ * * @return The class-name pattern that defines the annotation. */ - ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = ""); + ClassNamePattern fieldAnnotatedByClassNamePattern() default + @ClassNamePattern(unqualifiedName = ""); /** * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java index e066873..538c8ca 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -5,7 +5,7 @@ package com.android.tools.r8.keepanno.asm; import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty; -import com.android.tools.r8.keepanno.asm.ClassSimpleNameParser.ClassSimpleNameProperty; +import com.android.tools.r8.keepanno.asm.ClassUnqualifiedNameParser.ClassUnqualifiedNameProperty; import com.android.tools.r8.keepanno.asm.PackageNameParser.PackageNameProperty; import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty; import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern; @@ -85,23 +85,27 @@ getParsingContext().property(name).annotation(descriptor); ClassNameParser fullNameParser = new ClassNameParser(parsingContext); PackageNameParser packageParser = new PackageNameParser(parsingContext); - ClassSimpleNameParser simpleNameParser = new ClassSimpleNameParser(parsingContext); + ClassUnqualifiedNameParser unqualifiedNameParser = + new ClassUnqualifiedNameParser(parsingContext); fullNameParser.setProperty(ClassNamePattern.name, ClassNameProperty.NAME); fullNameParser.setProperty(ClassNamePattern.constant, ClassNameProperty.CONSTANT); packageParser.setProperty(ClassNamePattern.packageName, PackageNameProperty.NAME); - simpleNameParser.setProperty(ClassNamePattern.simpleName, ClassSimpleNameProperty.NAME); - simpleNameParser.setProperty( - ClassNamePattern.simpleNamePattern, ClassSimpleNameProperty.PATTERN); + unqualifiedNameParser.setProperty( + ClassNamePattern.unqualifiedName, ClassUnqualifiedNameProperty.NAME); + unqualifiedNameParser.setProperty( + ClassNamePattern.unqualifiedNamePattern, ClassUnqualifiedNameProperty.PATTERN); return new ParserVisitor( parsingContext, - ImmutableList.of(fullNameParser, packageParser, simpleNameParser), + ImmutableList.of(fullNameParser, packageParser, unqualifiedNameParser), () -> { if (fullNameParser.isDeclared()) { - if (simpleNameParser.isDeclared() || packageParser.isDeclared()) { + if (unqualifiedNameParser.isDeclared() || packageParser.isDeclared()) { throw parsingContext.error( "Cannot specify both the full class name and its " - + (simpleNameParser.isDeclared() ? "simple name" : "package")); + + (unqualifiedNameParser.isDeclared() + ? "unqualified name" + : "package")); } setValue.accept(fullNameParser.getValue()); return; @@ -111,7 +115,7 @@ .setPackagePattern( packageParser.getValueOrDefault(KeepPackagePattern.any())) .setNamePattern( - simpleNameParser.getValueOrDefault( + unqualifiedNameParser.getValueOrDefault( KeepUnqualfiedClassNamePattern.any())) .build()); });
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassUnqualifiedNameParser.java similarity index 79% rename from src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java rename to src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassUnqualifiedNameParser.java index d65aa22..896fea9 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassUnqualifiedNameParser.java
@@ -1,34 +1,34 @@ -// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.asm; -import com.android.tools.r8.keepanno.asm.ClassSimpleNameParser.ClassSimpleNameProperty; +import com.android.tools.r8.keepanno.asm.ClassUnqualifiedNameParser.ClassUnqualifiedNameProperty; import com.android.tools.r8.keepanno.asm.StringPatternParser.StringProperty; import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern; import com.android.tools.r8.keepanno.ast.ParsingContext; import java.util.function.Consumer; import org.objectweb.asm.AnnotationVisitor; -public class ClassSimpleNameParser - extends PropertyParserBase<KeepUnqualfiedClassNamePattern, ClassSimpleNameProperty> { +public class ClassUnqualifiedNameParser + extends PropertyParserBase<KeepUnqualfiedClassNamePattern, ClassUnqualifiedNameProperty> { private final StringPatternParser parser; - public ClassSimpleNameParser(ParsingContext parsingContext) { + public ClassUnqualifiedNameParser(ParsingContext parsingContext) { super(parsingContext); parser = new StringPatternParser(parsingContext); } - public enum ClassSimpleNameProperty { + public enum ClassUnqualifiedNameProperty { NAME, PATTERN } @Override public boolean tryProperty( - ClassSimpleNameProperty property, + ClassUnqualifiedNameProperty property, String name, Object value, Consumer<KeepUnqualfiedClassNamePattern> setValue) { @@ -47,7 +47,7 @@ @Override AnnotationVisitor tryPropertyAnnotation( - ClassSimpleNameProperty property, + ClassUnqualifiedNameProperty property, String name, String descriptor, Consumer<KeepUnqualfiedClassNamePattern> setValue) {
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 07789a5..9151b1b 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
@@ -503,7 +503,7 @@ v.visit(ClassNamePattern.packageName, packagePattern.getExactPackageAsString()); } writeStringPattern( - clazz.getNamePattern().asStringPattern(), ClassNamePattern.simpleNamePattern, v); + clazz.getNamePattern().asStringPattern(), ClassNamePattern.unqualifiedNamePattern, v); }); }
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 f529bd1..9b0ae29 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
@@ -245,9 +245,9 @@ public static final String classNameGroup = "class-name"; public static final String name = "name"; public static final String constant = "constant"; - public static final String classSimpleNameGroup = "class-simple-name"; - public static final String simpleName = "simpleName"; - public static final String simpleNamePattern = "simpleNamePattern"; + public static final String classUnqualifiedNameGroup = "class-unqualified-name"; + public static final String unqualifiedName = "unqualifiedName"; + public static final String unqualifiedNamePattern = "unqualifiedNamePattern"; public static final String packageName = "packageName"; }
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 9932654..e2ce313 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
@@ -81,7 +81,7 @@ * ::= any * | PACKAGE_PATTERN UNQUALIFIED_CLASS_NAME_PATTERN * - * UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name + * UNQUALIFIED_CLASS_NAME_PATTERN ::= STRING_PATTERN * * INSTANCE_OF_PATTERN ::= INSTANCE_OF_PATTERN_INCLUSIVE | INSTANCE_OF_PATTERN_EXCLUSIVE * INSTANCE_OF_PATTERN_INCLUSIVE ::= QUALIFIED_CLASS_NAME_PATTERN @@ -128,6 +128,10 @@ * * RETENTION_POLICY * ::= RUNTIME | CLASS + * + * STRING_PATTERN + * ::= exact string-content + * | [prefix string-content] any [suffix string-content] * </pre> */ public final class KeepEdge extends KeepDeclaration {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java index abb24be..a974f67 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -24,26 +24,26 @@ return new Builder(); } - private final KeepStringPattern simpleNamePattern; + private final KeepStringPattern unqualifiedNamePattern; - private KeepUnqualfiedClassNamePattern(KeepStringPattern simpleNamePattern) { - this.simpleNamePattern = simpleNamePattern; + private KeepUnqualfiedClassNamePattern(KeepStringPattern unqualifiedNamePattern) { + this.unqualifiedNamePattern = unqualifiedNamePattern; } public boolean isAny() { - return simpleNamePattern.isAny(); + return unqualifiedNamePattern.isAny(); } public boolean isExact() { - return simpleNamePattern.isExact(); + return unqualifiedNamePattern.isExact(); } public String asExactString() { - return simpleNamePattern.asExactString(); + return unqualifiedNamePattern.asExactString(); } public KeepStringPattern asStringPattern() { - return simpleNamePattern; + return unqualifiedNamePattern; } @Override @@ -55,17 +55,17 @@ return false; } KeepUnqualfiedClassNamePattern that = (KeepUnqualfiedClassNamePattern) o; - return simpleNamePattern.equals(that.simpleNamePattern); + return unqualifiedNamePattern.equals(that.unqualifiedNamePattern); } @Override public int hashCode() { - return simpleNamePattern.hashCode(); + return unqualifiedNamePattern.hashCode(); } @Override public String toString() { - return simpleNamePattern.toString(); + return unqualifiedNamePattern.toString(); } public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java index a16189b..a6e5d5f 100644 --- a/src/main/java/com/android/tools/r8/D8CommandParser.java +++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -396,6 +396,8 @@ if (globalsOutputPath != null) { builder.setGlobalSyntheticsOutput(globalsOutputPath); } - return builder.setOutput(outputPath, outputMode); + builder.setOutput(outputPath, outputMode); + builder.setEnableExperimentalMissingLibraryApiModeling(true); + return builder; } }
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java index ccfe752..9b9989a 100644 --- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java +++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -53,6 +53,7 @@ import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import com.android.tools.r8.utils.SelfRetraceTest; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.ThreadUtils; @@ -266,7 +267,8 @@ DexEncodedField.EMPTY_ARRAY, MethodCollectionFactory.empty(), factory.getSkipNameValidationForTesting(), - DexProgramClass::invalidChecksumRequest); + DexProgramClass::invalidChecksumRequest, + ReachabilitySensitiveValue.DISABLED); } private static void createAllApiStubs( @@ -287,7 +289,7 @@ if (notModeledTypes.contains(libraryClass.getClassReference().getTypeName())) { return; } - if (ApiReferenceStubber.isJavaType(libraryClass.getType(), factory)) { + if (ApiReferenceStubber.isNeverStubbedType(libraryClass.getType(), factory)) { return; } KnownApiLevel knownApiLevel =
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 7cabfb6..851df61 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -465,7 +465,7 @@ new CfOpenClosedInterfacesAnalysis(appViewWithLiveness).run(executorService); // TODO(b/225838009): Move higher up. - LirConverter.enterLirSupportedPhase(appView, executorService); + LirConverter.enterLirSupportedPhase(appViewWithLiveness, executorService); assert verifyNoJarApplicationReaders(appView.appInfo().classes()); assert appView.checkForTesting(() -> allReferencesAssignedApiLevel(appViewWithLiveness));
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java index d3fafc4..f663fd4 100644 --- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java +++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -159,7 +159,7 @@ } private void findReferencedLibraryClasses(DexType type, DexProgramClass context) { - if (!type.isClassType() || isJavaType(type, appView.dexItemFactory())) { + if (!type.isClassType() || isNeverStubbedType(type, appView.dexItemFactory())) { return; } DexClass clazz = appView.definitionFor(type); @@ -182,10 +182,13 @@ } } - @SuppressWarnings("ReferenceEquality") - public static boolean isJavaType(DexType type, DexItemFactory factory) { + public static boolean isNeverStubbedType(DexType type, DexItemFactory factory) { + return isJavaType(type, factory); + } + + private static boolean isJavaType(DexType type, DexItemFactory factory) { DexString typeDescriptor = type.getDescriptor(); - return type == factory.objectType + return type.isIdenticalTo(factory.objectType) || typeDescriptor.startsWith(factory.comSunDescriptorPrefix) || typeDescriptor.startsWith(factory.javaDescriptorPrefix) || typeDescriptor.startsWith(factory.javaxDescriptorPrefix) @@ -201,7 +204,7 @@ ApiReferenceStubberEventConsumer eventConsumer) { DexItemFactory factory = appView.dexItemFactory(); // Do not stub the anything starting with java (including the object type). - if (isJavaType(libraryClass.getType(), factory)) { + if (isNeverStubbedType(libraryClass.getType(), factory)) { return; } // Check if desugared library will bridge the type.
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java index 5ddb8c1..22cf38f 100644 --- a/src/main/java/com/android/tools/r8/dex/DexParser.java +++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -73,6 +73,7 @@ import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import com.google.common.io.ByteStreams; import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -900,7 +901,10 @@ virtualMethods, dexItemFactory.getSkipNameValidationForTesting(), checksumSupplier, - null); + null, + // Interpreting reachability sensitivity from DEX inputs is not supported. + // The compiler does not support building IR from DEX with debug information. + ReachabilitySensitiveValue.DISABLED); classCollection.accept(clazz); // Update the application object. } }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java index 0e3e660..9d6df7a 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassKind.java +++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.synthesis.SyntheticMarker; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import java.util.List; import java.util.function.Predicate; @@ -38,7 +39,8 @@ virtualMethods, skipNameValidationForTesting, checksumSupplier, - syntheticMarker) -> + syntheticMarker, + reachabilitySensitive) -> new DexProgramClass( type, originKind, @@ -60,6 +62,7 @@ MethodCollectionFactory.fromMethods(directMethods, virtualMethods), skipNameValidationForTesting, checksumSupplier, + reachabilitySensitive, syntheticMarker), DexClass::isProgramClass); public static ClassKind<DexClasspathClass> CLASSPATH = @@ -85,7 +88,8 @@ virtualMethods, skipNameValidationForTesting, checksumSupplier, - syntheticMarker) -> + syntheticMarker, + unusedReachabilitySensitive) -> new DexClasspathClass( type, kind, @@ -130,7 +134,8 @@ virtualMethods, skipNameValidationForTesting, checksumSupplier, - syntheticMarker) -> + syntheticMarker, + unusedReachabilitySensitive) -> new DexLibraryClass( type, kind, @@ -176,7 +181,8 @@ DexEncodedMethod[] virtualMethods, boolean skipNameValidationForTesting, ChecksumSupplier checksumSupplier, - SyntheticMarker syntheticMarker); + SyntheticMarker syntheticMarker, + ReachabilitySensitiveValue reachabilitySensitive); } private final Factory<C> factory; @@ -209,7 +215,8 @@ DexEncodedMethod[] virtualMethods, boolean skipNameValidationForTesting, ChecksumSupplier checksumSupplier, - SyntheticMarker syntheticMarker) { + SyntheticMarker syntheticMarker, + ReachabilitySensitiveValue reachabilitySensitive) { return factory.create( type, kind, @@ -232,7 +239,8 @@ virtualMethods, skipNameValidationForTesting, checksumSupplier, - syntheticMarker); + syntheticMarker, + reachabilitySensitive); } public boolean isOfKind(DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java index 5f72db6..bdea90a 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -302,6 +302,7 @@ return true; } + @Override public FieldTypeSignature getGenericSignature() { return genericSignature; }
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 f7cc258..0365247 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.graph; import com.android.tools.r8.androidapi.ComputedApiLevel; +import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature; import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo; import com.android.tools.r8.kotlin.KotlinMemberLevelInfo; import java.util.function.Consumer; @@ -40,6 +41,8 @@ public abstract void clearKotlinInfo(); + public abstract DexDefinitionSignature<?> getGenericSignature(); + public abstract void clearGenericSignature(); public DexType getHolderType() {
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 6c86c27..bd27dfc 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1372,6 +1372,7 @@ } } + @Override public MethodTypeSignature getGenericSignature() { return genericSignature; }
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 c43043c..a7e37bc 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -809,8 +809,11 @@ public final DexType annotationCovariantReturnTypes = createStaticallyKnownType( "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;"); + + public final String annotationReachabilitySensitiveDesc = + "Ldalvik/annotation/optimization/ReachabilitySensitive;"; public final DexType annotationReachabilitySensitive = - createStaticallyKnownType("Ldalvik/annotation/optimization/ReachabilitySensitive;"); + createStaticallyKnownType(annotationReachabilitySensitiveDesc); private static final String METAFACTORY_METHOD_NAME = "metafactory"; private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
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 68bc137..5b0aec4 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -21,7 +21,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.synthesis.SyntheticMarker; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.OptionalBool; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import com.android.tools.r8.utils.TraversalContinuation; import com.android.tools.r8.utils.structural.Ordered; import com.android.tools.r8.utils.structural.StructuralItem; @@ -54,7 +54,7 @@ private CfVersion initialClassFileVersion = null; private boolean deprecated = false; private KotlinClassLevelInfo kotlinInfo = getNoKotlinInfo(); - private OptionalBool reachabilitySensitive = OptionalBool.unknown(); + private final ReachabilitySensitiveValue reachabilitySensitive; private final ChecksumSupplier checksumSupplier; @@ -81,6 +81,7 @@ MethodCollectionFactory methodCollectionFactory, boolean skipNameValidationForTesting, ChecksumSupplier checksumSupplier, + ReachabilitySensitiveValue reachabilitySensitive, SyntheticMarker syntheticMarker) { super( sourceFile, @@ -106,6 +107,7 @@ this.originKind = originKind; this.checksumSupplier = checksumSupplier; this.syntheticMarker = syntheticMarker; + this.reachabilitySensitive = reachabilitySensitive; } public DexProgramClass( @@ -128,7 +130,8 @@ DexEncodedField[] instanceFields, MethodCollectionFactory methodCollectionFactory, boolean skipNameValidationForTesting, - ChecksumSupplier checksumSupplier) { + ChecksumSupplier checksumSupplier, + ReachabilitySensitiveValue reachabilitySensitive) { this( type, originKind, @@ -150,6 +153,7 @@ methodCollectionFactory, skipNameValidationForTesting, checksumSupplier, + reachabilitySensitive, null); } @@ -174,7 +178,8 @@ DexEncodedField.EMPTY_ARRAY, MethodCollectionFactory.empty(), false, - DexProgramClass::invalidChecksumRequest); + DexProgramClass::invalidChecksumRequest, + ReachabilitySensitiveValue.DISABLED); } @Override @@ -204,24 +209,12 @@ * that is the case, dead reference elimination is disabled and locals are kept alive for their * entire scope. */ - public boolean getOrComputeReachabilitySensitive(AppView<?> appView) { - if (reachabilitySensitive.isUnknown()) { - reachabilitySensitive = OptionalBool.of(internalComputeReachabilitySensitive(appView)); - } - return reachabilitySensitive.isTrue(); + public boolean isReachabilitySensitive() { + return getReachabilitySensitiveValue().isEnabled(); } - @SuppressWarnings("ReferenceEquality") - private boolean internalComputeReachabilitySensitive(AppView<?> appView) { - DexItemFactory dexItemFactory = appView.dexItemFactory(); - for (DexEncodedMember<?, ?> member : members()) { - for (DexAnnotation annotation : member.annotations().annotations) { - if (annotation.annotation.type == dexItemFactory.annotationReachabilitySensitive) { - return true; - } - } - } - return false; + public ReachabilitySensitiveValue getReachabilitySensitiveValue() { + return reachabilitySensitive; } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java index 1f97831..52ae66a 100644 --- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -238,7 +238,11 @@ public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) { addProgramClass(clazz); pendingClasspathRemovalIfPresent.add(clazz.type); - if (libraryClasses.containsKey(clazz.type)) { + // When java.lang.Record is added remove the library class, as this program class will + // eventually be renamed to com.android.tools.r8.RecordTag so there should not be a + // multi-resolution result when looking up java.lang.Record. + if (clazz.type.isIdenticalTo(options.dexItemFactory().recordType) + && libraryClasses.containsKey(clazz.type)) { ensureMutableLibraryClassesMap(); libraryClasses.remove(clazz.type); }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java index a339bde..29fc029 100644 --- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java +++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -122,7 +122,7 @@ return EMPTY_TYPE_SIGNATURES; } - interface DexDefinitionSignature<T extends DexDefinition> { + public interface DexDefinitionSignature<T extends DexDefinition> { default boolean isClassSignature() { return false;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java index 089194a..57b427e 100644 --- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java +++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -48,10 +48,10 @@ 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.ReachabilitySensitiveValue; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.StringUtils; import com.google.common.base.Equivalence.Wrapper; -import com.google.common.collect.Iterables; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -258,7 +258,7 @@ private final List<DexEncodedMethod> directMethods = new ArrayList<>(); private final List<DexEncodedMethod> virtualMethods = new ArrayList<>(); private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>(); - private boolean hasReachabilitySensitiveMethod = false; + private boolean hasReachabilitySensitiveMember = false; private SyntheticMarker syntheticMarker = null; public CreateDexClassVisitor( @@ -492,7 +492,6 @@ addAnnotation(DexAnnotation.createAnnotationDefaultAnnotation( type, defaultAnnotations, application.getFactory())); } - checkReachabilitySensitivity(); checkRecord(); T clazz = classKind.create( @@ -517,7 +516,8 @@ virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY), application.getFactory().getSkipNameValidationForTesting(), getChecksumSupplier(classKind), - syntheticMarker); + syntheticMarker, + ReachabilitySensitiveValue.fromBoolean(hasReachabilitySensitiveMember)); application.checkClassForMethodHandlesLookup(clazz, classKind); InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass(); // A member class should not be a local or anonymous class. @@ -599,33 +599,6 @@ } } - // If anything is marked reachability sensitive, all methods need to be parsed including - // locals information. This propagates the reachability sensitivity bit so that if any field - // or method is annotated, all methods get parsed with locals information. - private void checkReachabilitySensitivity() { - if (hasReachabilitySensitiveMethod || hasReachabilitySensitiveField()) { - for (DexEncodedMethod method : Iterables.concat(directMethods, virtualMethods)) { - Code code = method.getCode(); - if (code != null && code.isCfCode()) { - code.asLazyCfCode().markReachabilitySensitive(); - } - } - } - } - - @SuppressWarnings("ReferenceEquality") - private boolean hasReachabilitySensitiveField() { - DexType reachabilitySensitive = application.getFactory().annotationReachabilitySensitive; - for (DexEncodedField field : Iterables.concat(instanceFields, staticFields)) { - for (DexAnnotation annotation : field.annotations().annotations) { - if (annotation.annotation.type == reachabilitySensitive) { - return true; - } - } - } - return false; - } - private void addDefaultAnnotation(String name, DexValue value) { if (defaultAnnotations == null) { defaultAnnotations = new ArrayList<>(); @@ -718,6 +691,11 @@ parsingContext, parent.application::addKeepDeclaration); } + + // Check for reachability sensitive annotation. + parent.hasReachabilitySensitiveMember |= + parent.application.getFactory().annotationReachabilitySensitiveDesc.equals(desc); + return createAnnotationVisitor( desc, visible, getAnnotations(), parent.application, DexAnnotation::new); } @@ -881,6 +859,11 @@ parsingContext, parent.application::addKeepDeclaration); } + + // Check for reachability sensitive annotation. + parent.hasReachabilitySensitiveMember |= + parent.application.getFactory().annotationReachabilitySensitiveDesc.equals(desc); + return createAnnotationVisitor( desc, visible, getAnnotations(), parent.application, DexAnnotation::new); } @@ -1030,7 +1013,6 @@ .build(); Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method); if (parent.methodSignatures.add(signature)) { - parent.hasReachabilitySensitiveMethod |= isReachabilitySensitive(); if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) { parent.directMethods.add(dexMethod); } else { @@ -1049,18 +1031,6 @@ } } - @SuppressWarnings("ReferenceEquality") - private boolean isReachabilitySensitive() { - DexType reachabilitySensitive = - parent.application.getFactory().annotationReachabilitySensitive; - for (DexAnnotation annotation : getAnnotations()) { - if (annotation.annotation.type == reachabilitySensitive) { - return true; - } - } - return false; - } - private List<DexAnnotation> getAnnotations() { if (annotations == null) { annotations = new ArrayList<>();
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 c41f8df..34c1b21 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -73,6 +73,7 @@ import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.RetracerForCodePrinting; import com.android.tools.r8.utils.StringDiagnostic; @@ -119,12 +120,6 @@ private JarApplicationReader application; private CfCode code; private ReparseContext context; - private boolean reachabilitySensitive = false; - - public void markReachabilitySensitive() { - assert code == null; - reachabilitySensitive = true; - } @Override public boolean isCfCode() { @@ -158,11 +153,17 @@ private void internalParseCode() { ReparseContext context = this.context; JarApplicationReader application = this.application; - assert application != null; assert context != null; + assert application != null; + DexProgramClass programOwner = context.owner.asProgramClass(); + ReachabilitySensitiveValue reachabilitySensitive = + programOwner != null + ? programOwner.getReachabilitySensitiveValue() + : ReachabilitySensitiveValue.DISABLED; + DebugParsingOptions parsingOptions = getParsingOptions(application, reachabilitySensitive); // The ClassCodeVisitor is in charge of setting this.context to null. try { - parseCode(context, false); + parseCode(context, false, parsingOptions); } catch (JsrEncountered e) { for (Code code : context.codeList) { code.asLazyCfCode().code = null; @@ -170,7 +171,7 @@ code.asLazyCfCode().application = application; } try { - parseCode(context, true); + parseCode(context, true, parsingOptions); } catch (JsrEncountered e1) { throw new Unreachable(e1); } @@ -204,9 +205,8 @@ } } - public void parseCode(ReparseContext context, boolean useJsrInliner) { - DebugParsingOptions parsingOptions = getParsingOptions(application, reachabilitySensitive); - + private void parseCode( + ReparseContext context, boolean useJsrInliner, DebugParsingOptions parsingOptions) { ClassCodeVisitor classVisitor = new ClassCodeVisitor( context.owner, @@ -301,7 +301,8 @@ @Override public String toString() { - return asCfCode().toString(); + // Don't force parsing in toString as it causes unexpected behavior when debugging. + return code != null ? code.toString() : "<lazy-code>"; } @Override @@ -1177,7 +1178,7 @@ } private static DebugParsingOptions getParsingOptions( - JarApplicationReader application, boolean reachabilitySensitive) { + JarApplicationReader application, ReachabilitySensitiveValue reachabilitySensitive) { // TODO(b/166841731): We should compute our own from the compressed format. int parsingOptions = application.options.canUseInputStackMaps() @@ -1194,7 +1195,7 @@ configuration.isKeepParameterNames() || keep.localVariableTable || keep.localVariableTypeTable - || reachabilitySensitive; + || reachabilitySensitive.isEnabled(); boolean lineInfo = (keep.lineNumberTable || application.options.canUseNativeDexPcInsteadOfDebugInfo()); boolean methodParaeters = keep.methodParameters;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java index 03758b2..54dc05d 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -180,8 +180,8 @@ return getDefinition().getKotlinInfo(); } - public boolean getOrComputeReachabilitySensitive(AppView<?> appView) { - return getHolder().getOrComputeReachabilitySensitive(appView); + public boolean isReachabilitySensitive() { + return getHolder().isReachabilitySensitive(); } public void setCode(Code newCode, AppView<?> appView) { @@ -202,7 +202,7 @@ if (appView.testing().noLocalsTableOnInput) { return false; } - return appView.options().debug || getOrComputeReachabilitySensitive(appView); + return appView.options().debug || isReachabilitySensitive(); } public ProgramMethod rewrittenWithLens(
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java index ec65897..9f40e9f 100644 --- a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java +++ b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
@@ -113,7 +113,6 @@ return newProgramClasses; } - @SuppressWarnings("ReferenceEquality") // Should remain private as the correctness of the fixup requires the lazy 'newProgramClasses'. private DexProgramClass fixupClass(DexProgramClass clazz) { DexProgramClass newClass = @@ -137,7 +136,8 @@ DexEncodedField.EMPTY_ARRAY, newHolder -> clazz.getMethodCollection().fixup(newHolder, this::fixupMethod), dexItemFactory.getSkipNameValidationForTesting(), - clazz.getChecksumSupplier()); + clazz.getChecksumSupplier(), + clazz.getReachabilitySensitiveValue()); newClass.setInstanceFields(fixupFields(clazz.instanceFields())); newClass.setStaticFields(fixupFields(clazz.staticFields())); // Transfer properties that are not passed to the constructor. @@ -151,7 +151,7 @@ newClass.setKotlinInfo(clazz.getKotlinInfo()); } // If the class type changed, record the move in the lens. - if (newClass.getType() != clazz.getType()) { + if (newClass.getType().isNotIdenticalTo(clazz.getType())) { return recordClassChange(clazz, newClass); } return newClass;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java index 63461b5..fe5ea8b 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -9,7 +9,9 @@ import com.android.tools.r8.graph.DexEncodedMember; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature; import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; +import com.android.tools.r8.shaking.KeepInfo; import com.android.tools.r8.shaking.KeepInfoCollection; import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.Iterables; @@ -33,9 +35,9 @@ private void processClass(DexProgramClass clazz) { DexType type = clazz.getType(); - boolean pinHolder = keepInfo.getClassInfo(clazz).isPinned(options); + boolean pinHolder = isPinned(keepInfo.getClassInfo(clazz), clazz.getClassSignature()); for (DexEncodedMember<?, ?> member : clazz.members()) { - if (keepInfo.getMemberInfo(member, clazz).isPinned(options)) { + if (isPinned(keepInfo.getMemberInfo(member, clazz), member.getGenericSignature())) { pinHolder = true; Iterables.addAll( dontMergeTypes, @@ -49,6 +51,13 @@ } } + private boolean isPinned(KeepInfo<?, ?> keepInfo, DexDefinitionSignature<?> genericSignature) { + return keepInfo.isPinned(options) + || (genericSignature.hasSignature() + && !options.isForceProguardCompatibilityEnabled() + && !keepInfo.isSignatureRemovalAllowed(options)); + } + @Override public boolean canMerge(DexProgramClass program) { return !dontMergeTypes.contains(program.getType());
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java index 0fbadd1..dba1c38 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -13,6 +13,7 @@ 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.DexType; import com.android.tools.r8.graph.FieldResolutionResult; import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult; import com.android.tools.r8.graph.ProgramMethod; @@ -197,15 +198,17 @@ if (clazz == null) { return true; } - if (clazz.superType == null) { - return false; - } DexItemFactory dexItemFactory = appView.dexItemFactory(); DexEncodedMethod resolutionResult = appInfo .resolveMethodOnClassLegacy(clazz, dexItemFactory.objectMembers.finalize) .getSingleTarget(); - return resolutionResult != null && resolutionResult.isProgramMethod(appView); + if (resolutionResult == null) { + return false; + } + DexType holderType = resolutionResult.getHolderType(); + return holderType.isNotIdenticalTo(dexItemFactory.objectType) + && holderType.isNotIdenticalTo(dexItemFactory.enumType); } return mayHaveFinalizeMethodDirectlyOrIndirectly(appView, baseType.asClassType());
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java index 488e60a..91e6d2d 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
@@ -12,6 +12,8 @@ public interface FieldPut { + BasicBlock getBlock(); + DexField getField(); Position getPosition();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index 7f7653b..5077746 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -632,7 +632,7 @@ public boolean isConsistentSSABeforeTypesAreCorrectAllowingRedundantBlocks(AppView<?> appView) { assert isConsistentGraph(appView, true); - assert consistentBlockInstructions(appView, true); + assert consistentBlockInstructions(true); assert consistentDefUseChains(); assert validThrowingInstructions(); assert noCriticalEdges(); @@ -689,7 +689,7 @@ assert consistentBlockNumbering(); assert consistentPredecessorSuccessors(); assert consistentCatchHandlers(); - assert consistentBlockInstructions(appView, ssa); + assert consistentBlockInstructions(ssa); assert consistentMetadata(); assert verifyAllThrowingInstructionsHavePositions(); return true; @@ -889,13 +889,11 @@ return true; } - private boolean consistentBlockInstructions(AppView<?> appView, boolean ssa) { + private boolean consistentBlockInstructions(boolean ssa) { boolean argumentsAllowed = true; for (BasicBlock block : blocks) { assert block.consistentBlockInstructions( - argumentsAllowed, - options.debug || context().getOrComputeReachabilitySensitive(appView), - ssa); + argumentsAllowed, options.debug || context().isReachabilitySensitive(), ssa); argumentsAllowed = false; } return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java index 15082ac..4d73577 100644 --- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java +++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -87,7 +87,7 @@ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) { InternalOptions options = appView.options(); if (options.debug - || code.context().getOrComputeReachabilitySensitive(appView) + || code.context().isReachabilitySensitive() || !code.getConversionOptions().isGeneratingDex()) { return DeadInstructionResult.notDead(); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java index 7bafe1b..a6f39aa 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Phi.java +++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -200,15 +200,6 @@ return operands; } - public boolean hasOperandThatMatches(Predicate<Value> predicate) { - for (Value operand : operands) { - if (predicate.test(operand)) { - return true; - } - } - return false; - } - public void removeOperand(int index) { removeOperand(index, null, alwaysFalse()); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index b95529b..8d03811 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -502,7 +502,7 @@ } public boolean isDebugMode() { - return appView.options().debug || getProgramMethod().getOrComputeReachabilitySensitive(appView); + return appView.options().debug || getProgramMethod().isReachabilitySensitive(); } public Int2ReferenceSortedMap<BlockInfo> getCFG() {
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 733a207..7383048 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
@@ -522,7 +522,7 @@ previous = printMethod(code, "IR after disable assertions (SSA)", previous); } - boolean isDebugMode = options.debug || context.getOrComputeReachabilitySensitive(appView); + boolean isDebugMode = options.debug || context.isReachabilitySensitive(); assert !method.isProcessed() || !isDebugMode : "Method already processed: " + context.toSourceString() @@ -598,13 +598,6 @@ CheckNotNullConverter.runIfNecessary(appView, code); previous = printMethod(code, "IR after disable assertions (SSA)", previous); - if (identifierNameStringMarker != null) { - timing.begin("Decouple identifier-name strings"); - identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(code); - timing.end(); - previous = printMethod(code, "IR after identifier-name strings (SSA)", previous); - } - timing.begin("Run proto shrinking tasks"); appView.withGeneratedExtensionRegistryShrinker(shrinker -> shrinker.rewriteCode(method, code));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java index c7f07e7..4a9e696 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -27,8 +27,10 @@ import com.android.tools.r8.lightir.IR2LirConverter; import com.android.tools.r8.lightir.LirCode; import com.android.tools.r8.lightir.LirStrategy; +import com.android.tools.r8.naming.IdentifierNameStringMarker; import com.android.tools.r8.naming.RecordInvokeDynamicInvokeCustomRewriter; import com.android.tools.r8.optimize.MemberRebindingIdentityLens; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.ObjectUtils; import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.ThreadUtils; @@ -40,14 +42,16 @@ public class LirConverter { public static void enterLirSupportedPhase( - AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService) + AppView<AppInfoWithLiveness> appView, ExecutorService executorService) throws ExecutionException { assert appView.testing().canUseLir(appView); assert appView.testing().isPreLirPhase(); appView.testing().enterLirSupportedPhase(); CodeRewriterPassCollection codeRewriterPassCollection = new CodeRewriterPassCollection( - new ConstResourceNumberRewriter(appView), new StringSwitchConverter(appView)); + new ConstResourceNumberRewriter(appView), + new StringSwitchConverter(appView), + new IdentifierNameStringMarker(appView)); // Convert code objects to LIR. ThreadUtils.processItems( appView.appInfo().classes(),
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java index c56ddbf..44cf6e7 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
@@ -76,7 +76,7 @@ } protected boolean isDebugMode(ProgramMethod context) { - return options.debug || context.getOrComputeReachabilitySensitive(appView); + return options.debug || context.isReachabilitySensitive(); } protected abstract String getRewriterId();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java index 8de8311..604299b 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.utils.IterableUtils; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; @@ -89,7 +90,8 @@ DexEncodedField.EMPTY_ARRAY, MethodCollectionFactory.fromMethods(newDirectMethods, newVirtualMethods), false, - emulatedInterface.getChecksumSupplier()); + emulatedInterface.getChecksumSupplier(), + ReachabilitySensitiveValue.DISABLED); newEmulatedInterface.addExtraInterfaces( getRewrittenInterfacesOfEmulatedInterface(emulatedInterface), appView.dexItemFactory()); return newEmulatedInterface;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java index c6d8ac4..b6804e7 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -151,7 +151,7 @@ } ProgramMethod context = code.context(); - if (context.getOrComputeReachabilitySensitive(appView)) { + if (context.isReachabilitySensitive()) { return ClassInitializerDefaultsResult.empty(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java index b6e87c2..f5e2f36 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -3,11 +3,11 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.optimize; -import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.ClassResolutionResult; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.CatchHandlers; @@ -224,7 +224,8 @@ if (block.hasCatchHandlers()) { if (block.canThrow()) { if (appView.enableWholeProgramOptimizations()) { - Collection<CatchHandler<BasicBlock>> deadCatchHandlers = getDeadCatchHandlers(block); + Collection<CatchHandler<BasicBlock>> deadCatchHandlers = + getDeadCatchHandlers(code, block); if (!deadCatchHandlers.isEmpty()) { for (CatchHandler<BasicBlock> catchHandler : deadCatchHandlers) { catchHandler.target.unlinkCatchHandlerForGuard(catchHandler.guard); @@ -249,10 +250,8 @@ return mayHaveIntroducedUnreachableBlocks; } - /** - * Returns the catch handlers of the given block that are dead, if any. - */ - private Collection<CatchHandler<BasicBlock>> getDeadCatchHandlers(BasicBlock block) { + /** Returns the catch handlers of the given block that are dead, if any. */ + private Collection<CatchHandler<BasicBlock>> getDeadCatchHandlers(IRCode code, BasicBlock block) { AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness(); ImmutableList.Builder<CatchHandler<BasicBlock>> builder = ImmutableList.builder(); CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers(); @@ -275,11 +274,19 @@ continue; } - // We can exploit that a catch handler must be dead if its guard is never instantiated - // directly or indirectly. if (appInfoWithLiveness != null) { - DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(guard)); - if (clazz != null && !appInfoWithLiveness.isInstantiatedDirectlyOrIndirectly(clazz)) { + ClassResolutionResult result = + appView.definitionForWithResolutionResult(guard, code.context().getContextClass()); + if (!result.hasClassResolutionResult() || result.isMultipleClassResolutionResult()) { + // With a multi resolution result one of the results is a library class, so the guard + // cannot be removed. + continue; + } + // We can exploit that a catch handler must be dead if its guard is never instantiated + // directly or indirectly. + DexClass clazz = result.toSingleClassWithProgramOverLibrary(); + if (clazz.isProgramClass() + && !appInfoWithLiveness.isInstantiatedDirectlyOrIndirectly(clazz.asProgramClass())) { builder.add(new CatchHandler<>(guard, target)); continue; }
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 337d90d..bc22ae8 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
@@ -17,6 +17,7 @@ import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.Code; +import com.android.tools.r8.graph.DefaultUseRegistryWithResult; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; @@ -480,6 +481,11 @@ return null; } + if (isInliningBlockedDueToArrayClone(context, singleTarget, appView)) { + whyAreYouNotInliningReporter.reportUnsafeDueToArrayCloneCall(); + return null; + } + // Make sure constructor inlining is legal. if (singleTarget.getDefinition().isInstanceInitializer() && !canInlineInstanceInitializer( @@ -624,6 +630,25 @@ return false; } + public static boolean isInliningBlockedDueToArrayClone( + ProgramMethod context, ProgramMethod singleTarget, AppView<?> appView) { + if (!appView.options().canHaveArtArrayCloneFromInterfaceMethodBug()) { + return false; + } + if (!context.getHolder().isInterface()) { + return false; + } + return singleTarget.registerCodeReferencesWithResult( + new DefaultUseRegistryWithResult<>(appView, context, false) { + @Override + public void registerInvokeVirtual(DexMethod method) { + if (appView.dexItemFactory().isArrayClone(method)) { + setResult(true); + } + } + }); + } + @Override @SuppressWarnings("ReferenceEquality") public boolean canInlineInstanceInitializer(
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 3d78982..4b9c547 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
@@ -160,7 +160,7 @@ } ProgramMethod method = code.context(); DexEncodedMethod definition = method.getDefinition(); - if (definition.isClassInitializer() || method.getOrComputeReachabilitySensitive(appView)) { + if (definition.isClassInitializer() || method.isReachabilitySensitive()) { return ConstraintWithTarget.NEVER; } KeepMethodInfo keepInfo = appView.getKeepInfo(method);
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 23ae714..ab67530 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
@@ -432,9 +432,7 @@ // instruction has side-effects that can change the value of fields. If so, it must be // handled above. If not, it can be safely added to the assert. assert instruction.isArgument() - || instruction.isArrayGet() || instruction.isArrayLength() - || instruction.isArrayPut() || instruction.isAssume() || instruction.isBinop() || instruction.isCheckCast() @@ -627,6 +625,10 @@ } private void handleArrayGet(InstructionListIterator it, ArrayGet arrayGet) { + if (arrayGet.instructionInstanceCanThrow(appView, method)) { + // The read might not happen if the array get can throw. + activeState.clearMostRecentFieldWrites(); + } if (arrayGet.array().hasLocalInfo()) { // The array may be modified through the debugger. Therefore subsequent reads of the same // array slot may not read this local. @@ -656,6 +658,12 @@ int index = arrayPut.indexOrDefault(-1); MemberType memberType = arrayPut.getMemberType(); + // If the instruction can throw, we can't use any previous field stores for store-after-store + // elimination. + if (arrayPut.instructionInstanceCanThrow(appView, method)) { + activeState.clearMostRecentFieldWrites(); + } + // An array-put instruction can potentially write the given array slot on all arrays because // of // aliases.
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 6359da4..403719e 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
@@ -57,6 +57,7 @@ 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.DefaultInliningOracle; import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.ir.optimize.Inliner.InliningInfo; import com.android.tools.r8.ir.optimize.InliningOracle; @@ -1340,6 +1341,9 @@ // Using -allowaccessmodification mitigates this. return false; } + if (DefaultInliningOracle.isInliningBlockedDueToArrayClone(method, singleTarget, appView)) { + return false; + } return true; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java index 1f116b4..cb7523f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -137,4 +137,7 @@ public boolean unsetReasonHasBeenReportedFlag() { return true; } + + @Override + public void reportUnsafeDueToArrayCloneCall() {} }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java index b333817..ca2d508 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -127,4 +127,6 @@ int numberOfMonitorEnterValuesAfterInlining, int threshold); public abstract boolean unsetReasonHasBeenReportedFlag(); + + public abstract void reportUnsafeDueToArrayCloneCall(); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java index 23391b7..8a04363 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -297,4 +297,10 @@ reasonHasBeenReported = false; return true; } + + @Override + public void reportUnsafeDueToArrayCloneCall() { + report( + "would lead to unsupported resolution of array clone() from within an interface method."); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java index bf3bc86..514ed67 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java
@@ -182,7 +182,10 @@ return new SingleValueOrValue(worklist.getSeenSet()); } if (foundDifferentOperandValuesWithSameAbstractValue) { - if (representativeOperandAbstractValue.isSingleValue()) { + if (representativeOperandAbstractValue.isSingleValue() + && representativeOperandAbstractValue + .asSingleValue() + .isMaterializableInContext(appView, code.context())) { return new SingleValueOrValue( worklist.getSeenSet(), representativeOperandAbstractValue.asSingleValue()); }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java index 2acd209..c099750 100644 --- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java +++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -233,7 +233,7 @@ // and we do not actually want locals information in the output. if (options().debug) { computeDebugInfo(code, blocks, liveIntervals, this, liveAtEntrySets); - } else if (code.context().getOrComputeReachabilitySensitive(appView)) { + } else if (code.context().isReachabilitySensitive()) { InstructionListIterator it = code.instructionListIterator(); while (it.hasNext()) { Instruction instruction = it.next(); @@ -1661,7 +1661,7 @@ // Set all free positions for possible registers to max integer. RegisterPositions freePositions = new RegisterPositionsImpl(registerConstraint + 1); - if ((options().debug || code.context().getOrComputeReachabilitySensitive(appView)) + if ((options().debug || code.context().isReachabilitySensitive()) && !code.method().accessFlags.isStatic()) { // If we are generating debug information or if the method is reachability sensitive, // we pin the this value register. The debugger expects to always be able to find it in @@ -2712,7 +2712,7 @@ } } } - if (appView.options().debug || code.context().getOrComputeReachabilitySensitive(appView)) { + if (appView.options().debug || code.context().isReachabilitySensitive()) { // In debug mode, or if the method is reachability sensitive, extend the live range // to cover the full scope of a local variable (encoded as debug values). int number = instruction.getNumber();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java index 0480ba2..0314b43 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -129,11 +129,11 @@ KmClass kmClass = metadata.getKmClass(); Map<String, DexEncodedField> fieldMap = new HashMap<>(); for (DexEncodedField field : hostClass.fields()) { - fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field); + fieldMap.put(toJvmFieldSignature(field.getReference()).toString(), field); } Map<String, DexEncodedMethod> methodMap = new HashMap<>(); for (DexEncodedMethod method : hostClass.methods()) { - methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method); + methodMap.put(toJvmMethodSignature(method.getReference()).toString(), method); } ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder(); KotlinMetadataMembersTracker originalMembersWithKotlinInfo = @@ -143,7 +143,7 @@ KotlinConstructorInfo.create(kmConstructor, factory, reporter); JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor); if (signature != null) { - DexEncodedMethod method = methodMap.get(signature.asString()); + DexEncodedMethod method = methodMap.get(signature.toString()); if (method != null) { method.setKotlinMemberInfo(constructorInfo); originalMembersWithKotlinInfo.add(method.getReference());
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java index b435950..21f5469 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -70,10 +70,10 @@ } KotlinFunctionInfo kotlinFunctionInfo = KotlinFunctionInfo.create(kmFunction, factory, reporter); - DexEncodedMethod method = methodSignatureMap.get(signature.asString()); + DexEncodedMethod method = methodSignatureMap.get(signature.toString()); if (method == null) { notBackedFunctions.add(kotlinFunctionInfo); - if (!isValidMethodDescriptor(signature.getDesc())) { + if (!isValidMethodDescriptor(signature.getDescriptor())) { // TODO(b/155536535): Enable this assert. // appView // .options() @@ -98,7 +98,7 @@ boolean hasBacking = false; if (propertyProcessor.fieldSignature() != null) { DexEncodedField field = - fieldSignatureMap.get(propertyProcessor.fieldSignature().asString()); + fieldSignatureMap.get(propertyProcessor.fieldSignature().toString()); if (field != null) { hasBacking = true; field.setKotlinMemberInfo(kotlinPropertyInfo); @@ -107,7 +107,7 @@ } if (propertyProcessor.getterSignature() != null) { DexEncodedMethod method = - methodSignatureMap.get(propertyProcessor.getterSignature().asString()); + methodSignatureMap.get(propertyProcessor.getterSignature().toString()); if (method != null) { hasBacking = true; keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode); @@ -118,7 +118,7 @@ } if (propertyProcessor.setterSignature() != null) { DexEncodedMethod method = - methodSignatureMap.get(propertyProcessor.setterSignature().asString()); + methodSignatureMap.get(propertyProcessor.setterSignature().toString()); if (method != null) { hasBacking = true; keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode); @@ -130,7 +130,7 @@ if (propertyProcessor.syntheticMethodForAnnotationsSignature() != null) { DexEncodedMethod method = methodSignatureMap.get( - propertyProcessor.syntheticMethodForAnnotationsSignature().asString()); + propertyProcessor.syntheticMethodForAnnotationsSignature().toString()); if (method != null) { hasBacking = true; method.setKotlinMemberInfo(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java index bcf19a1..2c5598d 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -34,7 +34,7 @@ } return new KotlinJvmFieldSignatureInfo( fieldSignature.getName(), - KotlinTypeReference.fromDescriptor(fieldSignature.getDesc(), factory)); + KotlinTypeReference.fromDescriptor(fieldSignature.getDescriptor(), factory)); } boolean rewrite(Consumer<JvmFieldSignature> consumer, DexEncodedField field, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java index 687156e..917f242 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -51,7 +51,7 @@ return null; } String name = methodSignature.getName(); - String descriptor = methodSignature.getDesc(); + String descriptor = methodSignature.getDescriptor(); if (!KotlinMetadataUtils.isValidMethodDescriptor(descriptor)) { // If the method descriptor is invalid, keep it as invalid. return new KotlinJvmMethodSignatureInfo(methodSignature.getName(), descriptor);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java index 09f6a41..77673c0 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -41,7 +41,7 @@ JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function); if (signature != null) { for (DexEncodedMethod method : clazz.methods()) { - if (toJvmMethodSignature(method.getReference()).asString().equals(signature.asString())) { + if (toJvmMethodSignature(method.getReference()).toString().equals(signature.toString())) { method.setKotlinMemberInfo(kotlinFunctionInfo); return new KotlinLambdaInfo(kotlinFunctionInfo, true); }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java index 6e532a3..c2f31ef 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -24,14 +24,11 @@ import java.util.List; import java.util.function.Consumer; import kotlin.Metadata; -import kotlin.metadata.KmExtensionType; import kotlin.metadata.KmProperty; -import kotlin.metadata.KmPropertyExtensionVisitor; -import kotlin.metadata.KmPropertyVisitor; +import kotlin.metadata.jvm.JvmExtensionsKt; import kotlin.metadata.jvm.JvmFieldSignature; +import kotlin.metadata.jvm.JvmMetadataVersion; import kotlin.metadata.jvm.JvmMethodSignature; -import kotlin.metadata.jvm.JvmPropertyExtensionVisitor; -import kotlin.metadata.jvm.KotlinClassMetadata; public class KotlinMetadataUtils { @@ -105,49 +102,25 @@ JvmMethodSignature methodSignature, int intArguments) { return new JvmMethodSignature( methodSignature.getName() + "$default", - methodSignature.getDesc().replace(")", "I".repeat(intArguments) + "Ljava/lang/Object;)")); + methodSignature + .getDescriptor() + .replace(")", "I".repeat(intArguments) + "Ljava/lang/Object;)")); } static class KmPropertyProcessor { - private JvmFieldSignature fieldSignature = null; + private final JvmFieldSignature fieldSignature; // Custom getter via @get:JvmName("..."). Otherwise, null. - private JvmMethodSignature getterSignature = null; + private final JvmMethodSignature getterSignature; // Custom getter via @set:JvmName("..."). Otherwise, null. - private JvmMethodSignature setterSignature = null; - private JvmMethodSignature syntheticMethodForAnnotationsSignature = null; + private final JvmMethodSignature setterSignature; + private final JvmMethodSignature syntheticMethodForAnnotationsSignature; KmPropertyProcessor(KmProperty kmProperty) { - kmProperty.accept( - new KmPropertyVisitor() { - @Override - @SuppressWarnings("ReferenceEquality") - public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) { - if (type != JvmPropertyExtensionVisitor.TYPE) { - return null; - } - return new JvmPropertyExtensionVisitor() { - @Override - public void visit( - int flags, - JvmFieldSignature fieldDesc, - JvmMethodSignature getterDesc, - JvmMethodSignature setterDesc) { - assert fieldSignature == null : fieldSignature.asString(); - fieldSignature = fieldDesc; - assert getterSignature == null : getterSignature.asString(); - getterSignature = getterDesc; - assert setterSignature == null : setterSignature.asString(); - setterSignature = setterDesc; - } - - @Override - public void visitSyntheticMethodForAnnotations(JvmMethodSignature signature) { - assert syntheticMethodForAnnotationsSignature == null : signature.asString(); - syntheticMethodForAnnotationsSignature = signature; - } - }; - } - }); + fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty); + getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty); + setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty); + syntheticMethodForAnnotationsSignature = + JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty); } JvmFieldSignature fieldSignature() { @@ -248,7 +221,12 @@ } static int[] getCompatibleKotlinInfo() { - return KotlinClassMetadata.COMPATIBLE_METADATA_VERSION; + // The kotlin metadata changelog recommends: + // "Main migration path here is to replace KotlinClassMetadata.COMPATIBLE_METADATA_VERSION + // with new value with the same meaning: JvmMetadataVersion.LATEST_STABLE_SUPPORTED." + // The inspection error "Usage of Kotlin internal declaration from different module" does not + // prevent the code to work correctly. + return JvmMetadataVersion.LATEST_STABLE_SUPPORTED.toIntArray(); } static <TKm> TKm consume(TKm tKm, Consumer<TKm> consumer) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java index 15883d2..24c5af9 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -15,6 +15,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -87,9 +88,7 @@ indent, "Metadata.Class", sb, - newIndent -> { - KotlinMetadataWriter.appendKmClass(newIndent, sb, kMetadata.getKmClass()); - }); + newIndent -> KotlinMetadataWriter.appendKmClass(newIndent, sb, kMetadata.getKmClass())); return sb.toString(); } @@ -100,9 +99,7 @@ indent, "Metadata.FileFacade", sb, - newIndent -> { - KotlinMetadataWriter.appendKmPackage(newIndent, sb, kMetadata.getKmPackage()); - }); + newIndent -> KotlinMetadataWriter.appendKmPackage(newIndent, sb, kMetadata.getKmPackage())); return sb.toString(); } @@ -144,9 +141,8 @@ newIndent, "function", sb, - nextIndent -> { - KotlinMetadataWriter.appendKmFunction(nextIndent, sb, kmLambda.function); - }); + nextIndent -> + KotlinMetadataWriter.appendKmFunction(nextIndent, sb, kmLambda.function)); } else { KotlinMetadataWriter.appendKeyValue(newIndent, "function", sb, "null"); } @@ -216,70 +212,62 @@ indent, "functions", sb, - newIndent -> { - appendKmList( - newIndent, - "KmFunction", - sb, - container.getFunctions().stream() - .sorted( - Comparator.comparing( - kmFunction -> JvmExtensionsKt.getSignature(kmFunction).asString())) - .collect(Collectors.toList()), - (nextIndent, kmFunction) -> { - appendKmFunction(nextIndent, sb, kmFunction); - }); - }); + newIndent -> + appendKmList( + newIndent, + "KmFunction", + sb, + container.getFunctions().stream() + .sorted( + Comparator.comparing( + kmFunction -> + Objects.toString(JvmExtensionsKt.getSignature(kmFunction)))) + .collect(Collectors.toList()), + (nextIndent, kmFunction) -> appendKmFunction(nextIndent, sb, kmFunction))); appendKeyValue( indent, "properties", sb, - newIndent -> { - appendKmList( - newIndent, - "KmProperty", - sb, - container.getProperties().stream() - .sorted( - Comparator.comparing( - kmProperty -> { - JvmMethodSignature signature = - JvmExtensionsKt.getGetterSignature(kmProperty); - if (signature != null) { - return signature.asString(); - } - signature = JvmExtensionsKt.getSetterSignature(kmProperty); - if (signature != null) { - return signature.asString(); - } - JvmFieldSignature fieldSignature = - JvmExtensionsKt.getFieldSignature(kmProperty); - if (fieldSignature != null) { - return fieldSignature.asString(); - } - return kmProperty.getName(); - })) - .collect(Collectors.toList()), - (nextIndent, kmProperty) -> { - appendKmProperty(nextIndent, sb, kmProperty); - }); - }); + newIndent -> + appendKmList( + newIndent, + "KmProperty", + sb, + container.getProperties().stream() + .sorted( + Comparator.comparing( + kmProperty -> { + JvmMethodSignature signature = + JvmExtensionsKt.getGetterSignature(kmProperty); + if (signature != null) { + return signature.toString(); + } + signature = JvmExtensionsKt.getSetterSignature(kmProperty); + if (signature != null) { + return signature.toString(); + } + JvmFieldSignature fieldSignature = + JvmExtensionsKt.getFieldSignature(kmProperty); + if (fieldSignature != null) { + return fieldSignature.toString(); + } + return kmProperty.getName(); + })) + .collect(Collectors.toList()), + (nextIndent, kmProperty) -> appendKmProperty(nextIndent, sb, kmProperty))); appendKeyValue( indent, "typeAliases", sb, - newIndent -> { - appendKmList( - newIndent, - "KmTypeAlias", - sb, - container.getTypeAliases().stream() - .sorted(Comparator.comparing(KmTypeAlias::getName)) - .collect(Collectors.toList()), - (nextIndent, kmTypeAlias) -> { - appendTypeAlias(nextIndent, sb, kmTypeAlias); - }); - }); + newIndent -> + appendKmList( + newIndent, + "KmTypeAlias", + sb, + container.getTypeAliases().stream() + .sorted(Comparator.comparing(KmTypeAlias::getName)) + .collect(Collectors.toList()), + (nextIndent, kmTypeAlias) -> appendTypeAlias(nextIndent, sb, kmTypeAlias))); } public static void appendKmPackage(String indent, StringBuilder sb, KmPackage kmPackage) { @@ -289,16 +277,13 @@ indent, "localDelegatedProperties", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmProperty", - sb, - JvmExtensionsKt.getLocalDelegatedProperties(kmPackage), - (nextNextIndent, kmProperty) -> { - appendKmProperty(nextNextIndent, sb, kmProperty); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmProperty", + sb, + JvmExtensionsKt.getLocalDelegatedProperties(kmPackage), + (nextNextIndent, kmProperty) -> appendKmProperty(nextNextIndent, sb, kmProperty))); } public static void appendKmClass(String indent, StringBuilder sb, KmClass kmClass) { @@ -309,23 +294,18 @@ indent, "typeParameters", sb, - newIndent -> { - appendTypeParameters(newIndent, sb, kmClass.getTypeParameters()); - }); + newIndent -> appendTypeParameters(newIndent, sb, kmClass.getTypeParameters())); appendKeyValue( indent, "superTypes", sb, - newIndent -> { - appendKmList( - newIndent, - "KmType", - sb, - kmClass.getSupertypes(), - (nextIndent, kmType) -> { - appendKmType(nextIndent, sb, kmType); - }); - }); + newIndent -> + appendKmList( + newIndent, + "KmType", + sb, + kmClass.getSupertypes(), + (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType))); if (kmClass.getInlineClassUnderlyingPropertyName() != null) { appendKeyValue( indent, @@ -338,9 +318,7 @@ indent, "inlineClassUnderlyingType", sb, - nextIndent -> { - appendKmType(nextIndent, sb, kmClass.getInlineClassUnderlyingType()); - }); + nextIndent -> appendKmType(nextIndent, sb, kmClass.getInlineClassUnderlyingType())); } String companionObject = kmClass.getCompanionObject(); appendKeyValue( @@ -364,16 +342,13 @@ indent, "localDelegatedProperties", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmProperty", - sb, - JvmExtensionsKt.getLocalDelegatedProperties(kmClass), - (nextNextIndent, kmProperty) -> { - appendKmProperty(nextNextIndent, sb, kmProperty); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmProperty", + sb, + JvmExtensionsKt.getLocalDelegatedProperties(kmClass), + (nextNextIndent, kmProperty) -> appendKmProperty(nextNextIndent, sb, kmProperty))); appendKmVersionRequirement(indent, sb, kmClass.getVersionRequirements()); appendKeyValue( indent, @@ -388,11 +363,9 @@ .sorted( Comparator.comparing( kmConstructor -> - JvmExtensionsKt.getSignature(kmConstructor).asString())) + Objects.toString(JvmExtensionsKt.getSignature(kmConstructor)))) .collect(Collectors.toList()), - (nextIndent, constructor) -> { - appendKmConstructor(nextIndent, sb, constructor); - })); + (nextIndent, constructor) -> appendKmConstructor(nextIndent, sb, constructor))); appendKeyValue( indent, "contextReceiverTypes", @@ -422,8 +395,7 @@ nextIndent -> appendValueParameters(nextIndent, sb, constructor.getValueParameters())); JvmMethodSignature signature = JvmExtensionsKt.getSignature(constructor); - appendKeyValue( - newIndent, "signature", sb, signature != null ? signature.asString() : "null"); + appendKeyValue(newIndent, "signature", sb, Objects.toString(signature)); appendKmVersionRequirement(newIndent, sb, constructor.getVersionRequirements()); }); } @@ -465,9 +437,7 @@ newIndent, "contract", sb, - nextIndent -> { - appendKmContract(nextIndent, sb, contract); - }); + nextIndent -> appendKmContract(nextIndent, sb, contract)); } appendKeyValue( newIndent, @@ -481,8 +451,7 @@ function.getContextReceiverTypes(), (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType))); JvmMethodSignature signature = JvmExtensionsKt.getSignature(function); - appendKeyValue( - newIndent, "signature", sb, signature != null ? signature.asString() : "null"); + appendKeyValue(newIndent, "signature", sb, Objects.toString(signature)); appendKeyValue( newIndent, "lambdaClassOriginName", @@ -535,39 +504,25 @@ (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType))); appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + ""); JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty); - appendKeyValue( - newIndent, - "fieldSignature", - sb, - fieldSignature != null ? fieldSignature.asString() : "null"); + appendKeyValue(newIndent, "fieldSignature", sb, Objects.toString(fieldSignature)); JvmMethodSignature getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty); - appendKeyValue( - newIndent, - "getterSignature", - sb, - getterSignature != null ? getterSignature.asString() : "null"); + appendKeyValue(newIndent, "getterSignature", sb, Objects.toString(getterSignature)); JvmMethodSignature setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty); - appendKeyValue( - newIndent, - "setterSignature", - sb, - setterSignature != null ? setterSignature.asString() : "null"); + appendKeyValue(newIndent, "setterSignature", sb, Objects.toString(setterSignature)); JvmMethodSignature syntheticMethodForAnnotations = JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty); appendKeyValue( newIndent, "syntheticMethodForAnnotations", sb, - syntheticMethodForAnnotations != null - ? syntheticMethodForAnnotations.asString() - : "null"); + Objects.toString(syntheticMethodForAnnotations)); JvmMethodSignature syntheticMethodForDelegate = JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty); appendKeyValue( newIndent, "syntheticMethodForDelegate", sb, - syntheticMethodForDelegate != null ? syntheticMethodForDelegate.asString() : "null"); + Objects.toString(syntheticMethodForDelegate)); }); } @@ -587,16 +542,14 @@ newIndent, "arguments", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmTypeProjection", - sb, - kmType.getArguments(), - (nextNextIndent, kmTypeProjection) -> { - appendKmTypeProjection(nextNextIndent, sb, kmTypeProjection); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmTypeProjection", + sb, + kmType.getArguments(), + (nextNextIndent, kmTypeProjection) -> + appendKmTypeProjection(nextNextIndent, sb, kmTypeProjection))); appendKeyValue( newIndent, "abbreviatedType", @@ -613,42 +566,39 @@ newIndent, "flexibleTypeUpperBound", sb, - nextIndent -> { - appendKmSection( - newIndent, - "FlexibleTypeUpperBound", - sb, - nextNextIndent -> { - appendKeyValue( - nextNextIndent, - "typeFlexibilityId", - sb, - flexibleTypeUpperBound.getTypeFlexibilityId()); - appendKeyValue( - nextNextIndent, - "type", - sb, - nextNextNextIndent -> - appendKmType( - nextNextNextIndent, sb, flexibleTypeUpperBound.getType())); - }); - }); + nextIndent -> + appendKmSection( + newIndent, + "FlexibleTypeUpperBound", + sb, + nextNextIndent -> { + appendKeyValue( + nextNextIndent, + "typeFlexibilityId", + sb, + flexibleTypeUpperBound.getTypeFlexibilityId()); + appendKeyValue( + nextNextIndent, + "type", + sb, + nextNextNextIndent -> + appendKmType( + nextNextNextIndent, sb, flexibleTypeUpperBound.getType())); + })); } appendKeyValue(newIndent, "raw", sb, JvmExtensionsKt.isRaw(kmType) + ""); appendKeyValue( newIndent, "annotations", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmAnnotion", - sb, - JvmExtensionsKt.getAnnotations(kmType), - (nextNextIndent, kmAnnotation) -> { - appendKmAnnotation(nextNextIndent, sb, kmAnnotation); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmAnnotion", + sb, + JvmExtensionsKt.getAnnotations(kmType), + (nextNextIndent, kmAnnotation) -> + appendKmAnnotation(nextNextIndent, sb, kmAnnotation))); }); } @@ -663,9 +613,7 @@ newIndent, "type", sb, - nextIndent -> { - appendKmType(nextIndent, sb, projection.getType()); - }); + nextIndent -> appendKmType(nextIndent, sb, projection.getType())); if (projection.getVariance() != null) { appendKeyValue(newIndent, "variance", sb, projection.getVariance().name()); } @@ -679,9 +627,7 @@ "KmValueParameter", sb, valueParameters, - (newIndent, parameter) -> { - appendValueParameter(newIndent, sb, parameter); - }); + (newIndent, parameter) -> appendValueParameter(newIndent, sb, parameter)); } private static void appendValueParameter( @@ -701,16 +647,12 @@ newIndent, "type", sb, - nextIndent -> { - appendKmType(nextIndent, sb, valueParameter.getType()); - }); + nextIndent -> appendKmType(nextIndent, sb, valueParameter.getType())); appendKeyValue( newIndent, "varargElementType", sb, - nextIndent -> { - appendKmType(nextIndent, sb, valueParameter.getVarargElementType()); - }); + nextIndent -> appendKmType(nextIndent, sb, valueParameter.getVarargElementType())); }); } @@ -721,9 +663,7 @@ "KmTypeParameter", sb, typeParameters, - (newIndent, parameter) -> { - appendTypeParameter(newIndent, sb, parameter); - }); + (newIndent, parameter) -> appendTypeParameter(newIndent, sb, parameter)); } private static void appendTypeParameter( @@ -741,30 +681,25 @@ newIndent, "upperBounds", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmType", - sb, - typeParameter.getUpperBounds(), - (nextNextIndent, kmType) -> { - appendKmType(nextNextIndent, sb, kmType); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmType", + sb, + typeParameter.getUpperBounds(), + (nextNextIndent, kmType) -> appendKmType(nextNextIndent, sb, kmType))); appendKeyValue( newIndent, "extensions", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmAnnotion", - sb, - JvmExtensionsKt.getAnnotations(typeParameter), - (nextNextIndent, kmAnnotation) -> { - appendKmAnnotation(nextNextIndent, sb, kmAnnotation); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmAnnotion", + sb, + JvmExtensionsKt.getAnnotations(typeParameter), + (nextNextIndent, kmAnnotation) -> + appendKmAnnotation(nextNextIndent, sb, kmAnnotation))); }); } @@ -778,39 +713,31 @@ newIndent, "annotations", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmAnnotation", - sb, - kmTypeAlias.getAnnotations(), - (nextNextIndent, kmAnnotation) -> { - appendKmAnnotation(nextNextIndent, sb, kmAnnotation); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmAnnotation", + sb, + kmTypeAlias.getAnnotations(), + (nextNextIndent, kmAnnotation) -> + appendKmAnnotation(nextNextIndent, sb, kmAnnotation))); appendKeyValue( newIndent, "expandedType", sb, - nextIndent -> { - appendKmType(nextIndent, sb, kmTypeAlias.expandedType); - }); + nextIndent -> appendKmType(nextIndent, sb, kmTypeAlias.expandedType)); appendKeyValue(newIndent, "flags", sb, kmTypeAlias.getFlags() + ""); appendKeyValue(newIndent, "name", sb, kmTypeAlias.getName()); appendKeyValue( newIndent, "typeParameters", sb, - nextIndent -> { - appendTypeParameters(nextIndent, sb, kmTypeAlias.getTypeParameters()); - }); + nextIndent -> appendTypeParameters(nextIndent, sb, kmTypeAlias.getTypeParameters())); appendKeyValue( newIndent, "underlyingType", sb, - nextIndent -> { - appendKmType(nextIndent, sb, kmTypeAlias.underlyingType); - }); + nextIndent -> appendKmType(nextIndent, sb, kmTypeAlias.underlyingType)); appendKmVersionRequirement(newIndent, sb, kmTypeAlias.getVersionRequirements()); }); } @@ -834,21 +761,19 @@ "{ key: String, value: KmAnnotationArgument<?> }", sb, arguments.keySet(), - (nextNextIndent, key) -> { - appendKmSection( - nextNextIndent, - "", - sb, - nextNextNextIndent -> { - appendKeyValue( - nextNextNextIndent, - key, - sb, - nextNextNextNextIndent -> { - appendKmArgument(nextNextNextIndent, sb, arguments.get(key)); - }); - }); - }); + (nextNextIndent, key) -> + appendKmSection( + nextNextIndent, + "", + sb, + nextNextNextIndent -> + appendKeyValue( + nextNextNextIndent, + key, + sb, + nextNextNextNextIndent -> + appendKmArgument( + nextNextNextIndent, sb, arguments.get(key))))); }); }); } @@ -862,9 +787,7 @@ "ArrayValue", sb, value, - (newIndent, annoArg) -> { - appendKmArgument(newIndent, sb, annoArg); - }); + (newIndent, annoArg) -> appendKmArgument(newIndent, sb, annoArg)); } else { sb.append(annotationArgument.toString()); } @@ -876,38 +799,37 @@ indent, "versionRequirements", sb, - newIndent -> { - appendKmList( - newIndent, - "KmVersionRequirement", - sb, - kmVersionRequirements, - (nextIndent, kmVersionRequirement) -> { - appendKmSection( - nextIndent, - "KmVersionRequirement", - sb, - nextNextIndent -> { - appendKeyValue(nextNextIndent, "kind", sb, kmVersionRequirement.kind.name()); - appendKeyValue( - nextNextIndent, "level", sb, kmVersionRequirement.level.name()); - appendKeyValue( - nextNextIndent, - "errorCode", - sb, - kmVersionRequirement.getErrorCode() == null - ? "null" - : kmVersionRequirement.getErrorCode().toString()); - appendKeyValue( - nextNextIndent, "message", sb, kmVersionRequirement.getMessage()); - appendKeyValue( - nextNextIndent, - "version", - sb, - kmVersionRequirement.getVersion().toString()); - }); - }); - }); + newIndent -> + appendKmList( + newIndent, + "KmVersionRequirement", + sb, + kmVersionRequirements, + (nextIndent, kmVersionRequirement) -> + appendKmSection( + nextIndent, + "KmVersionRequirement", + sb, + nextNextIndent -> { + appendKeyValue( + nextNextIndent, "kind", sb, kmVersionRequirement.kind.name()); + appendKeyValue( + nextNextIndent, "level", sb, kmVersionRequirement.level.name()); + appendKeyValue( + nextNextIndent, + "errorCode", + sb, + kmVersionRequirement.getErrorCode() == null + ? "null" + : kmVersionRequirement.getErrorCode().toString()); + appendKeyValue( + nextNextIndent, "message", sb, kmVersionRequirement.getMessage()); + appendKeyValue( + nextNextIndent, + "version", + sb, + kmVersionRequirement.getVersion().toString()); + }))); } private static void appendKmContract(String indent, StringBuilder sb, KmContract contract) { @@ -915,19 +837,18 @@ indent, "KmContract", sb, - newIndent -> { - appendKeyValue( - newIndent, - "effects", - sb, - nextIndent -> - appendKmList( - nextIndent, - "KmEffect", - sb, - contract.getEffects(), - (nextNextIndent, effect) -> appendKmEffect(nextNextIndent, sb, effect))); - }); + newIndent -> + appendKeyValue( + newIndent, + "effects", + sb, + nextIndent -> + appendKmList( + nextIndent, + "KmEffect", + sb, + contract.getEffects(), + (nextNextIndent, effect) -> appendKmEffect(nextNextIndent, sb, effect)))); } private static void appendKmEffect(String indent, StringBuilder sb, KmEffect effect) { @@ -946,16 +867,14 @@ newIndent, "constructorArguments", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmEffectExpression", - sb, - effect.getConstructorArguments(), - (nextNextIndent, expression) -> { - appendKmEffectExpression(nextNextIndent, sb, expression); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmEffectExpression", + sb, + effect.getConstructorArguments(), + (nextNextIndent, expression) -> + appendKmEffectExpression(nextNextIndent, sb, expression))); KmEffectExpression conclusion = effect.getConclusion(); if (conclusion == null) { appendKeyValue(newIndent, "conclusion", sb, "null"); @@ -995,37 +914,31 @@ newIndent, "isInstanceType", sb, - nextIndent -> { - appendKmType(nextIndent, sb, expression.isInstanceType()); - }); + nextIndent -> appendKmType(nextIndent, sb, expression.isInstanceType())); appendKeyValue( newIndent, "andArguments", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmEffectExpression", - sb, - expression.getAndArguments(), - (nextNextIndent, expr) -> { - appendKmEffectExpression(nextNextIndent, sb, expr); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmEffectExpression", + sb, + expression.getAndArguments(), + (nextNextIndent, expr) -> + appendKmEffectExpression(nextNextIndent, sb, expr))); appendKeyValue( newIndent, "orArguments", sb, - nextIndent -> { - appendKmList( - nextIndent, - "KmEffectExpression", - sb, - expression.getOrArguments(), - (nextNextIndent, expr) -> { - appendKmEffectExpression(nextNextIndent, sb, expr); - }); - }); + nextIndent -> + appendKmList( + nextIndent, + "KmEffectExpression", + sb, + expression.getOrArguments(), + (nextNextIndent, expr) -> + appendKmEffectExpression(nextNextIndent, sb, expr))); }); } }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java index c4cb846..2771257 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -45,11 +45,11 @@ Consumer<DexEncodedMethod> keepByteCode) { Map<String, DexEncodedField> fieldMap = new HashMap<>(); for (DexEncodedField field : clazz.fields()) { - fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field); + fieldMap.put(toJvmFieldSignature(field.getReference()).toString(), field); } Map<String, DexEncodedMethod> methodMap = new HashMap<>(); for (DexEncodedMethod method : clazz.methods()) { - methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method); + methodMap.put(toJvmMethodSignature(method.getReference()).toString(), method); } KotlinMetadataMembersTracker originalMembersWithKotlinInfo = new KotlinMetadataMembersTracker(appView);
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java index 14a837a..cdec5d9 100644 --- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java +++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -9,6 +9,7 @@ import static com.android.tools.r8.naming.IdentifierNameStringUtils.isClassNameComparison; import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod; +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; @@ -23,7 +24,7 @@ import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.ConstString; import com.android.tools.r8.ir.code.DexItemBasedConstString; -import com.android.tools.r8.ir.code.FieldInstruction; +import com.android.tools.r8.ir.code.FieldPut; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstancePut; import com.android.tools.r8.ir.code.Instruction; @@ -32,16 +33,19 @@ import com.android.tools.r8.ir.code.InvokeMethod; import com.android.tools.r8.ir.code.StaticPut; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.conversion.MethodProcessor; +import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass; +import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult; import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo; import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.TextPosition; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ArrayUtils; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.ThreadUtils; import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.objects.Object2BooleanMap; -import java.util.Arrays; import java.util.List; import java.util.ListIterator; import java.util.Objects; @@ -50,16 +54,33 @@ import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; -public class IdentifierNameStringMarker { +public class IdentifierNameStringMarker extends CodeRewriterPass<AppInfoWithLiveness> { - private final AppView<AppInfoWithLiveness> appView; private final Object2BooleanMap<DexMember<?, ?>> identifierNameStrings; public IdentifierNameStringMarker(AppView<AppInfoWithLiveness> appView) { - this.appView = appView; + super(appView); this.identifierNameStrings = appView.appInfo().identifierNameStrings; } + @Override + protected CodeRewriterResult rewriteCode( + IRCode code, + MethodProcessor methodProcessor, + MethodProcessingContext methodProcessingContext) { + return decoupleIdentifierNameStringsInBlocks(code, null); + } + + @Override + protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) { + return code.metadata().mayHaveConstString(); + } + + @Override + protected String getRewriterId() { + return "IdentifierNameStringMarker"; + } + public void decoupleIdentifierNameStringsInFields( ExecutorService executorService) throws ExecutionException { ThreadUtils.processItems( @@ -83,21 +104,18 @@ return; } DexString original = staticValue.getValue(); - DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original); + DexReference itemBasedString = inferMemberOrTypeFromNameString(appView(), original); if (itemBasedString != null) { encodedField.setStaticValue( new DexItemBasedValueString(itemBasedString, ClassNameComputationInfo.none())); } } - public void decoupleIdentifierNameStringsInMethod(IRCode code) { - decoupleIdentifierNameStringsInBlocks(code, null); - assert code.isConsistentSSA(appView); - } - - public void decoupleIdentifierNameStringsInBlocks(IRCode code, Set<BasicBlock> blocks) { + public CodeRewriterResult decoupleIdentifierNameStringsInBlocks( + IRCode code, Set<BasicBlock> blocks) { + CodeRewriterResult result = CodeRewriterResult.NO_CHANGE; if (!code.metadata().mayHaveConstString()) { - return; + return result; } ListIterator<BasicBlock> blockIterator = code.listIterator(); while (blockIterator.hasNext()) { @@ -119,56 +137,69 @@ // ... // v_n' <- DexItemBasedString("Lx/y/z;") // decoupled // this.fld <- v_n' // fieldPut - if (instruction.isStaticPut() || instruction.isInstancePut()) { - iterator = - decoupleIdentifierNameStringForFieldPutInstruction( - code, blockIterator, iterator, instruction.asFieldInstruction()); + if (instruction.isFieldPut()) { + FieldPut fieldPut = instruction.asFieldPut(); + DexReference itemBasedString = getItemBasedStringForFieldPut(code, fieldPut); + if (itemBasedString != null) { + iterator = + decoupleIdentifierNameStringForFieldPutInstruction( + code, blockIterator, iterator, fieldPut, itemBasedString); + result = CodeRewriterResult.HAS_CHANGED; + } } else if (instruction.isInvokeMethod()) { + InvokeMethod invoke = instruction.asInvokeMethod(); iterator = decoupleIdentifierNameStringForInvokeInstruction( - code, blockIterator, iterator, instruction.asInvokeMethod()); + code, blockIterator, iterator, invoke); + result = CodeRewriterResult.HAS_CHANGED; } } } + return result; + } + + private DexReference getItemBasedStringForFieldPut(IRCode code, FieldPut fieldPut) { + DexField field = fieldPut.getField(); + if (!identifierNameStrings.containsKey(field)) { + return null; + } + Value in = fieldPut.value(); + if (in.isDexItemBasedConstString()) { + return null; + } + if (!in.isConstString()) { + warnUndeterminedIdentifierIfNecessary( + field, code.context(), fieldPut.asFieldInstruction(), null); + return null; + } + DexString original = in.getConstInstruction().asConstString().getValue(); + DexReference itemBasedString = inferMemberOrTypeFromNameString(appView(), original); + if (itemBasedString == null) { + warnUndeterminedIdentifierIfNecessary( + field, code.context(), fieldPut.asFieldInstruction(), original); + return null; + } + return itemBasedString; } private InstructionListIterator decoupleIdentifierNameStringForFieldPutInstruction( IRCode code, ListIterator<BasicBlock> blocks, InstructionListIterator iterator, - FieldInstruction instruction) { - assert instruction.isInstancePut() || instruction.isStaticPut(); - FieldInstruction fieldPut = instruction.asFieldInstruction(); - DexField field = fieldPut.getField(); - if (!identifierNameStrings.containsKey(field)) { - return iterator; - } - Value in = instruction.value(); - if (in.isDexItemBasedConstString()) { - return iterator; - } - if (!in.isConstString()) { - warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, null); - return iterator; - } - DexString original = in.getConstInstruction().asConstString().getValue(); - DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original); - if (itemBasedString == null) { - warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, original); - return iterator; - } + FieldPut fieldPut, + DexReference itemBasedString) { // Move the cursor back to $fieldPut assert iterator.peekPrevious() == fieldPut; iterator.previous(); // Prepare $decoupled just before $fieldPut - Value newIn = code.createValue(in.getType(), in.getLocalInfo()); + Value newIn = code.createValue(fieldPut.value().getType(), fieldPut.value().getLocalInfo()); DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString, ClassNameComputationInfo.none()); decoupled.setPosition(fieldPut.getPosition()); // If the current block has catch handler, split into two blocks. // Because const-string we're about to add is also a throwing instr, we need to split // before adding it. - BasicBlock block = instruction.getBlock(); + BasicBlock block = fieldPut.getBlock(); BasicBlock blockWithFieldInstruction = block.hasCatchHandlers() ? iterator.split(code, blocks) : block; if (blockWithFieldInstruction != block) { @@ -186,12 +217,13 @@ assert iterator.peekNext() == fieldPut; iterator.next(); } - if (instruction.isStaticPut()) { - iterator.replaceCurrentInstruction(new StaticPut(newIn, field)); + if (fieldPut.isStaticPut()) { + iterator.replaceCurrentInstruction(new StaticPut(newIn, fieldPut.getField())); } else { - assert instruction.isInstancePut(); - InstancePut instancePut = instruction.asInstancePut(); - iterator.replaceCurrentInstruction(new InstancePut(field, instancePut.object(), newIn)); + assert fieldPut.isInstancePut(); + InstancePut instancePut = fieldPut.asInstancePut(); + iterator.replaceCurrentInstruction( + new InstancePut(fieldPut.getField(), instancePut.object(), newIn)); } return iterator; } @@ -282,7 +314,7 @@ continue; } DexString original = in.getConstInstruction().asConstString().getValue(); - DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original); + DexReference itemBasedString = inferMemberOrTypeFromNameString(appView(), original); if (itemBasedString == null) { warnUndeterminedIdentifierIfNecessary(invokedMethod, code.context(), invoke, original); continue; @@ -320,16 +352,17 @@ } } } - if (!Arrays.stream(changes).allMatch(Objects::isNull)) { - List<Value> newIns = - Streams.mapWithIndex( - ins.stream(), - (in, index) -> changes[(int) index] != null ? changes[(int) index] : in) - .collect(Collectors.toList()); - iterator.replaceCurrentInstruction( - Invoke.create( - invoke.getType(), invokedMethod, invokedMethod.proto, invoke.outValue(), newIns)); + if (ArrayUtils.none(changes, Objects::nonNull)) { + return iterator; } + List<Value> newIns = + Streams.mapWithIndex( + ins.stream(), + (in, index) -> changes[(int) index] != null ? changes[(int) index] : in) + .collect(Collectors.toList()); + iterator.replaceCurrentInstruction( + Invoke.create( + invoke.getType(), invokedMethod, invokedMethod.proto, invoke.outValue(), newIns)); return iterator; } @@ -372,8 +405,10 @@ } Origin origin = method.getOrigin(); String kind = member.isDexField() ? "field" : "method"; - String originalMessage = original == null ? "what identifier string flows to " - : "what '" + original.toString() + "' refers to, which flows to "; + String originalMessage = + original == null + ? "what identifier string flows to " + : "what '" + original + "' refers to, which flows to "; String message = "Cannot determine " + originalMessage + member.toSourceString() + " that is specified in -identifiernamestring rules."
diff --git a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java index 39d4bd8..cbaf559 100644 --- a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java +++ b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
@@ -26,6 +26,7 @@ import java.util.Optional; import kotlin.metadata.jvm.JvmMetadataVersion; import kotlin.metadata.jvm.KmModule; +import kotlin.metadata.jvm.KmPackageParts; import kotlin.metadata.jvm.KotlinModuleMetadata; /** @@ -171,15 +172,14 @@ String rewrittenName = pair.getSecond(); multiClassPartToOriginal .getOrDefault(originalName, Collections.emptyList()) - .forEach( - classPart -> { - newMultiFiles.put(classPart, rewrittenName); - }); + .forEach(classPart -> newMultiFiles.put(classPart, rewrittenName)); }); - kmModule.visitPackageParts( - newPackage, - newFacades.getOrDefault(newPackage, Collections.emptyList()), - newMultiFiles); + kmModule + .getPackageParts() + .put( + newPackage, + new KmPackageParts( + newFacades.getOrDefault(newPackage, Collections.emptyList()), newMultiFiles)); } return Optional.of( DataEntryResource.fromBytes(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java index e750309..80cf637 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -7,8 +7,6 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.Code; -import com.android.tools.r8.graph.DefaultUseRegistryWithResult; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramField; @@ -19,8 +17,6 @@ import com.android.tools.r8.ir.analysis.value.AbstractValue; import com.android.tools.r8.ir.analysis.value.AbstractValueFactory; import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.NewInstance; import com.android.tools.r8.ir.conversion.MethodConversionOptions; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState; @@ -30,6 +26,7 @@ import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.IterableUtils; import com.android.tools.r8.utils.MapUtils; import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.ThreadUtils; @@ -38,7 +35,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Deque; import java.util.IdentityHashMap; import java.util.List; @@ -48,7 +44,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; -import java.util.function.Predicate; public class DefaultFieldValueJoiner { @@ -74,9 +69,6 @@ // Find all the fields where we need to determine if each field read is guaranteed to be // dominated by a write. Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = getFieldsOfInterest(); - if (fieldsOfInterest.isEmpty()) { - return Collections.emptyMap(); - } // If constructor inlining is disabled, then we focus on whether each instance initializer // definitely assigns the given field before it is read. We do the same for final and static @@ -102,12 +94,12 @@ // constructor inlining, we find all new-instance instructions (including subtype allocations) // and check if the field is written on each allocation before it is possibly read. analyzeNewInstanceInstructions( - fieldsNotSubjectToInitializerAnalysis, fieldsWithLiveDefaultValue::add, executorService); + fieldsNotSubjectToInitializerAnalysis, fieldsWithLiveDefaultValue::add); return updateFlowGraphs(fieldsWithLiveDefaultValue, executorService); } - protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() { + private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() { Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>(); for (DexProgramClass clazz : appView.appInfo().classes()) { clazz.forEachProgramField( @@ -248,95 +240,13 @@ private void analyzeNewInstanceInstructions( Map<DexType, ProgramFieldSet> nonFinalInstanceFields, - Consumer<ProgramField> liveDefaultValueConsumer, - ExecutorService executorService) - throws ExecutionException { - // To simplify the analysis, we currently bail out for non-final classes. - // TODO(b/296030319): Handle non-final classes. - MapUtils.removeIf( - nonFinalInstanceFields, - (holderType, fields) -> { - assert !fields.isEmpty(); - DexProgramClass holder = fields.iterator().next().getHolder(); - if (holder.isFinal() || !appView.appInfo().isInstantiatedIndirectly(holder)) { - // When the class is not explicitly marked final, the class could in principle have - // injected subclasses if it is pinned. However, none of the fields are pinned, so we - // should be allowed to reason about the field assignments in the program. - assert fields.stream() - .allMatch( - field -> appView.getKeepInfo(field).isValuePropagationAllowed(appView, field)); - return false; - } - fields.forEach(liveDefaultValueConsumer); - return true; - }); - - // We analyze all allocations of the classes that declare one of the given fields. - ThreadUtils.processMethods( - appView, - method -> - analyzeNewInstanceInstructionsInMethod( - nonFinalInstanceFields, liveDefaultValueConsumer, method), - appView.options().getThreadingModule(), - executorService); - } - - private void analyzeNewInstanceInstructionsInMethod( - Map<DexType, ProgramFieldSet> nonFinalInstanceFields, - Consumer<ProgramField> liveDefaultValueConsumer, - ProgramMethod method) { - if (!maybeHasNewInstanceThatMatches(method, nonFinalInstanceFields::containsKey)) { - return; + Consumer<ProgramField> liveDefaultValueConsumer) { + // Conservatively treat all fields as maybe read before written. + // TODO(b/296030319): Implement analysis by building IR for all methods that instantiate the + // relevant classes and analyzing the puts to the newly created instances. + for (ProgramField field : IterableUtils.flatten(nonFinalInstanceFields.values())) { + liveDefaultValueConsumer.accept(field); } - IRCode code = method.buildIR(appView, MethodConversionOptions.nonConverting()); - for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) { - ProgramFieldSet fieldsOfInterest = nonFinalInstanceFields.get(newInstance.getType()); - if (fieldsOfInterest == null) { - continue; - } - FieldReadBeforeWriteDfsAnalysis analysis = - new FieldReadBeforeWriteDfsAnalysis(appView, code, fieldsOfInterest, newInstance) { - - @Override - public AnalysisContinuation acceptFieldMaybeReadBeforeWrite(ProgramField field) { - // Remove this field from the `fieldsOfInterest`, so that we do not spend more time - // analyzing it. - if (fieldsOfInterest.remove(field)) { - liveDefaultValueConsumer.accept(field); - } - return AnalysisContinuation.abortIf(fieldsOfInterest.isEmpty()); - } - }; - analysis.run(); - if (fieldsOfInterest.isEmpty()) { - nonFinalInstanceFields.remove(newInstance.getType()); - } - } - } - - private boolean maybeHasNewInstanceThatMatches( - ProgramMethod method, Predicate<DexType> predicate) { - Code code = method.getDefinition().getCode(); - if (code == null || code.isSharedCodeObject()) { - return false; - } - if (code.isLirCode()) { - return code.asLirCode() - .hasConstantItemThatMatches( - constant -> constant instanceof DexType && predicate.test((DexType) constant)); - } - assert appView.isCfByteCodePassThrough(method); - assert code.isCfCode(); - return method.registerCodeReferencesWithResult( - new DefaultUseRegistryWithResult<>(appView, method, false) { - - @Override - public void registerNewInstance(DexType type) { - if (predicate.test(type)) { - setResult(true); - } - } - }); } private Map<FlowGraph, Deque<FlowGraphNode>> updateFlowGraphs(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java deleted file mode 100644 index 1fdfb27..0000000 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java +++ /dev/null
@@ -1,387 +0,0 @@ -// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.optimize.argumentpropagation.propagation; - -import static com.android.tools.r8.ir.code.Opcodes.ASSUME; -import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST; -import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER; -import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING; -import static com.android.tools.r8.ir.code.Opcodes.GOTO; -import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT; -import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT; -import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE; -import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC; -import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER; -import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL; -import static com.android.tools.r8.ir.code.Opcodes.RETURN; -import static com.android.tools.r8.ir.code.Opcodes.THROW; - -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.ProgramField; -import com.android.tools.r8.ir.code.Assume; -import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.BasicBlockInstructionIterator; -import com.android.tools.r8.ir.code.CheckCast; -import com.android.tools.r8.ir.code.ConstNumber; -import com.android.tools.r8.ir.code.ConstString; -import com.android.tools.r8.ir.code.Goto; -import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.InstancePut; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InvokeDirect; -import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.NewInstance; -import com.android.tools.r8.ir.code.Phi; -import com.android.tools.r8.ir.code.Return; -import com.android.tools.r8.ir.code.Throw; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.TraversalContinuation; -import com.android.tools.r8.utils.WorkList; -import com.android.tools.r8.utils.collections.ProgramFieldSet; - -/** - * Analysis that is given an allocation site (a {@link NewInstance} instruction) and a set of fields - * that belong to that newly allocated instance. - * - * <p>The analysis computes the subset of the given fields which are maybe read before they are - * written. The default value of these fields is potentially read, whereas the default value of the - * complement field set are guaranteed to never be read. - * - * <p>The analysis works by exploring all possible paths starting from the given allocation site to - * the normal and the exceptional exits of the method, keeping track of which fields are definitely - * written before they are read and which fields have maybe been read. - */ -public abstract class FieldReadBeforeWriteDfsAnalysis extends FieldReadBeforeWriteDfsAnalysisState { - - private final AppView<AppInfoWithLiveness> appView; - private final IRCode code; - private final DexItemFactory dexItemFactory; - // The set of fields to consider. Note that this is a concurrent set and that the caller may - // concurrently remove fields from the set. This may happen if we concurrently find a - // read-before-write of one of the fields. - private final ProgramFieldSet fields; - private final WorkList<WorkItem> worklist = WorkList.newIdentityWorkList(); - - private final FieldReadBeforeWriteDfsAnalysis self = this; - - public FieldReadBeforeWriteDfsAnalysis( - AppView<AppInfoWithLiveness> appView, - IRCode code, - ProgramFieldSet fields, - NewInstance newInstance) { - super(newInstance); - this.appView = appView; - this.code = code; - this.dexItemFactory = appView.dexItemFactory(); - this.fields = fields; - } - - // Returns ABORT if all fields of interest are now maybe-read-before-written. - // Otherwise returns CONTINUE. - public abstract AnalysisContinuation acceptFieldMaybeReadBeforeWrite(ProgramField field); - - public void run() { - worklist.addIfNotSeen(new InitialWorkItem()); - worklist.run(WorkItem::process); - } - - public enum AnalysisContinuation { - // Signals to abort the analysis completely (i.e., to break out of the DFS). This is used when - // we've reported all fields as being maybe read before written. - ABORT, - // Signals to continue the current DFS. - CONTINUE, - // Signals that all fields have been written before they are read on the current program path, - // meaning that the algorithm does not need to explore any further. The algorithm should instead - // backtrack and explore any other program paths. - BREAK; - - static AnalysisContinuation abortIf(boolean condition) { - if (condition) { - return ABORT; - } - return CONTINUE; - } - - boolean isAbort() { - return this == ABORT; - } - - boolean isAbortOrContinue() { - return isAbort() || isContinue(); - } - - boolean isBreak() { - return this == BREAK; - } - - boolean isContinue() { - return this == CONTINUE; - } - - TraversalContinuation<?, ?> toTraversalContinuation() { - assert isAbortOrContinue(); - return TraversalContinuation.breakIf(isAbort()); - } - } - - abstract class WorkItem { - - abstract TraversalContinuation<?, ?> process(); - - void applyPhis(BasicBlock block) { - // TODO(b/339210038): When adding support for non-linear control flow, we need to implement - // backtracking of this (i.e., we should remove the out value from the object aliases again). - for (Phi phi : block.getPhis()) { - if (phi.hasOperandThatMatches(self::isMaybeInstance)) { - addInstanceAlias(phi); - } - } - } - - AnalysisContinuation applyInstructions(BasicBlockInstructionIterator instructionIterator) { - while (instructionIterator.hasNext()) { - Instruction instruction = instructionIterator.next(); - assert !instruction.hasOutValue() || !isMaybeInstance(instruction.outValue()); - AnalysisContinuation continuation; - // TODO(b/339210038): Extend this to many other instructions, such as ConstClass, - // InstanceOf, *Binop, etc. - switch (instruction.opcode()) { - case ASSUME: - continuation = applyAssumeInstruction(instruction.asAssume()); - break; - case CHECK_CAST: - continuation = applyCheckCastInstruction(instruction.asCheckCast()); - break; - case CONST_NUMBER: - continuation = applyConstNumber(instruction.asConstNumber()); - break; - case CONST_STRING: - continuation = applyConstString(instruction.asConstString()); - break; - case GOTO: - continuation = applyGotoInstruction(instruction.asGoto()); - break; - case INSTANCE_PUT: - continuation = applyInstancePut(instruction.asInstancePut()); - break; - case INVOKE_DIRECT: - case INVOKE_INTERFACE: - case INVOKE_STATIC: - case INVOKE_SUPER: - case INVOKE_VIRTUAL: - continuation = applyInvokeMethodInstruction(instruction.asInvokeMethod()); - break; - case RETURN: - continuation = applyReturnInstruction(instruction.asReturn()); - break; - case THROW: - continuation = applyThrowInstruction(instruction.asThrow()); - break; - default: - continuation = applyUnhandledInstruction(); - break; - } - if (continuation.isAbort()) { - return continuation; - } - if (continuation.isBreak()) { - break; - } - } - return AnalysisContinuation.CONTINUE; - } - - // TODO(b/339210038): When adding support for non-linear control flow, we need to implement - // backtracking of this (i.e., we should remove the out value from the object aliases again). - private AnalysisContinuation applyAssumeInstruction(Assume assume) { - if (isMaybeInstance(assume.src())) { - addInstanceAlias(assume.outValue()); - } - return AnalysisContinuation.CONTINUE; - } - - // TODO(b/339210038): When adding support for non-linear control flow, we need to implement - // backtracking of this (i.e., we should remove the out value from the object aliases again). - private AnalysisContinuation applyCheckCastInstruction(CheckCast checkCast) { - if (isMaybeInstance(checkCast.object())) { - addInstanceAlias(checkCast.outValue()); - } - // If the instance has escaped to the heap and this check-cast instruction throws, then it is - // possible that the instance is retrieved from the heap and all fields are read. - return markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped(); - } - - private AnalysisContinuation applyConstNumber(ConstNumber unusedConstNumber) { - return AnalysisContinuation.CONTINUE; - } - - private AnalysisContinuation applyConstString(ConstString unusedConstString) { - return AnalysisContinuation.CONTINUE; - } - - private AnalysisContinuation applyGotoInstruction(Goto gotoInstruction) { - BasicBlock targetBlock = gotoInstruction.getTarget(); - if (isBlockOnStack(targetBlock)) { - // Bail out in case of cycles. - return markRemainingFieldsAsMaybeReadBeforeWritten(); - } else { - // Continue exploration into the successor block. - worklist.addIgnoringSeenSet(new ProcessBlockWorkItem(targetBlock)); - return AnalysisContinuation.CONTINUE; - } - } - - private AnalysisContinuation applyInstancePut(InstancePut instancePut) { - // If the instance has escaped and this instance-put instruction can throw, then the program - // can get the instance from the heap and read any field. Give up in this case. - if (isEscaped() && instancePut.instructionInstanceCanThrow(appView, code.context())) { - return markRemainingFieldsAsMaybeReadBeforeWritten(); - } - - // Record if this is a definite write to one of the fields of interest. - if (isDefinitelyInstance(instancePut.object())) { - ProgramField resolvedField = - instancePut.resolveField(appView, code.context()).getProgramField(); - if (resolvedField != null && fields.contains(resolvedField)) { - addWrittenBeforeRead(resolvedField); - } - - // If all fields of interest are written before read, then stop the exploration of the - // current program path (but continue to explore any program paths from previous unexplored - // branches). - if (fields.allMatch(self::isWrittenBeforeRead)) { - return AnalysisContinuation.BREAK; - } - } - - // Record if the instance has escaped as a result of this instance-put. - if (!isEscaped() && isMaybeInstance(instancePut.value())) { - setEscaped(instancePut); - } - return AnalysisContinuation.CONTINUE; - } - - private AnalysisContinuation applyInvokeMethodInstruction(InvokeMethod invoke) { - // Allow calls to java.lang.Object.<init>(). - // TODO(b/339210038): Generalize this to other constructors. - if (invoke.isInvokeConstructor(dexItemFactory) - && isDefinitelyInstance(invoke.getFirstArgument())) { - DexClassAndMethod resolvedMethod = - invoke.resolveMethod(appView, code.context()).getResolutionPair(); - if (resolvedMethod != null - && resolvedMethod - .getReference() - .isIdenticalTo(dexItemFactory.objectMembers.constructor)) { - return AnalysisContinuation.CONTINUE; - } - } - - // Conservatively treat calls as reading any field if the receiver has escaped or is escaping. - if (!isEscaped() - && invoke.hasInValueThatMatches(self::isMaybeInstance) - && invoke.instructionMayHaveSideEffects(appView, code.context())) { - setEscaped(invoke); - } - - if (isEscaped()) { - return markRemainingFieldsAsMaybeReadBeforeWritten(); - } - - // Otherwise, this is a call to a method where none of the arguments is an alias of the - // instance, and the instance has not escaped. Therefore, this call cannot read any of fields - // from the instance. - return AnalysisContinuation.CONTINUE; - } - - private AnalysisContinuation applyReturnInstruction(Return unusedReturnInstruction) { - return markRemainingFieldsAsMaybeReadBeforeWritten(); - } - - private AnalysisContinuation applyThrowInstruction(Throw unusedThrowInstruction) { - return markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped(); - } - - private AnalysisContinuation applyUnhandledInstruction() { - return markRemainingFieldsAsMaybeReadBeforeWritten(); - } - - AnalysisContinuation markRemainingFieldsAsMaybeReadBeforeWritten() { - for (ProgramField field : fields) { - if (!isWrittenBeforeRead(field)) { - AnalysisContinuation continuation = acceptFieldMaybeReadBeforeWrite(field); - assert continuation.isAbortOrContinue(); - if (continuation.isAbort()) { - return continuation; - } - } - } - // At this point we could also CONTINUE, but we check if the fields of interest have become - // empty as a result of concurrent modification. - return AnalysisContinuation.abortIf(fields.isEmpty()); - } - - AnalysisContinuation markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped() { - if (isEscaped()) { - return markRemainingFieldsAsMaybeReadBeforeWritten(); - } - return AnalysisContinuation.CONTINUE; - } - } - - class InitialWorkItem extends WorkItem { - - @Override - TraversalContinuation<?, ?> process() { - // We start the analysis from the unique constructor invoke instead of from the NewInstance - // instruction, since no instructions before the constructor call can read any fields from the - // uninitialized this. - // TODO(b/339210038): In principle it may be possible for the NewInstance value to flow into a - // phi before the unique constructor invoke. If this happens we would not record the phi as - // an alias when starting the analysis from the invoke-direct. - InvokeDirect uniqueConstructorInvoke = - getNewInstance().getUniqueConstructorInvoke(dexItemFactory); - if (uniqueConstructorInvoke == null) { - return markRemainingFieldsAsMaybeReadBeforeWritten().toTraversalContinuation(); - } - BasicBlock block = uniqueConstructorInvoke.getBlock(); - // TODO(b/339210038): Maybe allow exceptional control flow. - if (block.hasCatchHandlers()) { - return markRemainingFieldsAsMaybeReadBeforeWritten().toTraversalContinuation(); - } - addBlockToStack(block); - addInstanceAlias(getNewInstance().outValue()); - BasicBlockInstructionIterator instructionIterator = block.iterator(uniqueConstructorInvoke); - // Start the analysis from the invoke-direct instruction. This is important if we can tell - // that the constructor definitely writes some fields. - instructionIterator.previous(); - return applyInstructions(instructionIterator).toTraversalContinuation(); - } - } - - class ProcessBlockWorkItem extends WorkItem { - - private final BasicBlock block; - - ProcessBlockWorkItem(BasicBlock block) { - this.block = block; - } - - @Override - TraversalContinuation<?, ?> process() { - // TODO(b/339210038): Maybe allow exceptional control flow. - if (block.hasCatchHandlers()) { - return TraversalContinuation.breakIf( - markRemainingFieldsAsMaybeReadBeforeWritten().isAbort()); - } - addBlockToStack(block); - applyPhis(block); - AnalysisContinuation continuation = applyInstructions(block.iterator()); - assert continuation.isAbortOrContinue(); - return TraversalContinuation.breakIf(continuation.isAbort()); - } - } -}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java deleted file mode 100644 index 7e24c03..0000000 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java +++ /dev/null
@@ -1,84 +0,0 @@ -// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8.optimize.argumentpropagation.propagation; - -import com.android.tools.r8.graph.ProgramField; -import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.NewInstance; -import com.android.tools.r8.ir.code.Value; -import com.android.tools.r8.utils.collections.ProgramFieldSet; -import com.google.common.collect.Sets; -import java.util.Set; - -/** - * The state we track during the field-maybe-read-before-write/field-never-read-before-written - * analysis. - */ -public class FieldReadBeforeWriteDfsAnalysisState { - - // The current allocation we are analyzing. - private final NewInstance newInstance; - - // The first instruction on the current program path starting from the `newInstance` instruction - // from which the `newInstance` value escapes. - private Instruction escape = null; - - // The set of values that *may* be aliases of the `newInstance` value. - private final Set<Value> instanceAliases = Sets.newIdentityHashSet(); - - // The set of blocks on the current program path. - private final Set<BasicBlock> stack = Sets.newIdentityHashSet(); - - // The set of fields that are guaranteed to be written before they are read on the current program - // path. - private final ProgramFieldSet writtenBeforeRead = ProgramFieldSet.create(); - - FieldReadBeforeWriteDfsAnalysisState(NewInstance newInstance) { - this.newInstance = newInstance; - } - - void addInstanceAlias(Value instanceAlias) { - boolean changed = instanceAliases.add(instanceAlias); - assert changed; - } - - void addBlockToStack(BasicBlock block) { - boolean changed = stack.add(block); - assert changed; - } - - void addWrittenBeforeRead(ProgramField field) { - writtenBeforeRead.add(field); - } - - NewInstance getNewInstance() { - return newInstance; - } - - boolean isBlockOnStack(BasicBlock block) { - return stack.contains(block); - } - - boolean isEscaped() { - return escape != null; - } - - boolean isDefinitelyInstance(Value value) { - return value.getAliasedValue() == newInstance.outValue(); - } - - boolean isMaybeInstance(Value value) { - return instanceAliases.contains(value); - } - - boolean isWrittenBeforeRead(ProgramField field) { - return writtenBeforeRead.contains(field); - } - - void setEscaped(Instruction escape) { - assert !isEscaped(); - this.escape = escape; - } -}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java index 81e3424..d62328d 100644 --- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java +++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -46,7 +46,7 @@ final AppView<AppInfoWithLiveness> appView; final Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers; final IRConverter converter; - protected final FieldStateCollection fieldStates; + final FieldStateCollection fieldStates; final MethodStateCollectionByReference methodStates; public InFlowPropagator( @@ -113,15 +113,12 @@ private Map<FlowGraph, Deque<FlowGraphNode>> includeDefaultValuesInFieldStates( List<FlowGraph> flowGraphs, ExecutorService executorService) throws ExecutionException { - DefaultFieldValueJoiner joiner = createDefaultFieldValueJoiner(flowGraphs); + DefaultFieldValueJoiner joiner = + new DefaultFieldValueJoiner( + appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs); return joiner.joinDefaultFieldValuesForFieldsWithReadBeforeWrite(executorService); } - protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(List<FlowGraph> flowGraphs) { - return new DefaultFieldValueJoiner( - appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs); - } - private void processFlowGraphs(List<FlowGraph> flowGraphs, ExecutorService executorService) throws ExecutionException { ThreadUtils.processItems(
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java index 22ed377..8d7498c 100644 --- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java +++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -9,7 +9,6 @@ import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramField; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation; @@ -34,8 +33,6 @@ import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState; import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState; -import com.android.tools.r8.optimize.argumentpropagation.propagation.DefaultFieldValueJoiner; -import com.android.tools.r8.optimize.argumentpropagation.propagation.FlowGraph; import com.android.tools.r8.optimize.argumentpropagation.propagation.InFlowPropagator; import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.android.tools.r8.utils.IterableUtils; @@ -45,8 +42,6 @@ import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -97,22 +92,7 @@ InFlowPropagator inFlowPropagator = new InFlowPropagator( - appView, null, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates()) { - - @Override - protected DefaultFieldValueJoiner createDefaultFieldValueJoiner( - List<FlowGraph> flowGraphs) { - return new DefaultFieldValueJoiner(appView, null, fieldStates, flowGraphs) { - - @Override - protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() { - // We do not rely on the optimization of any fields in the Composable optimization - // pass. - return Collections.emptyMap(); - } - }; - } - }; + appView, null, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates()); inFlowPropagator.run(executorService); ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java index fa0bdfd..205bd4b 100644 --- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java +++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.lightir.LirInstructionView; import com.android.tools.r8.lightir.LirOpcodeUtils; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.AndroidApiLevelUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ObjectUtils; import com.android.tools.r8.utils.ThreadUtils; @@ -47,7 +48,8 @@ singleCallerMethodCandidates.removeIf( (callee, caller) -> callee.getDefinition().isLibraryMethodOverride().isPossiblyTrue() - || !appView.getKeepInfo(callee).isSingleCallerInliningAllowed(options)); + || !appView.getKeepInfo(callee).isSingleCallerInliningAllowed(options) + || !AndroidApiLevelUtils.isApiSafeForInlining(caller, callee, appView.options())); return traceInstructions(singleCallerMethodCandidates, executorService); }
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java index bd53f9a..b996483 100644 --- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java +++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.shaking; import com.android.tools.r8.androidapi.AndroidApiLevelCompute; +import com.android.tools.r8.androidapi.ApiReferenceStubber; import com.android.tools.r8.androidapi.ComputedApiLevel; import com.android.tools.r8.dex.code.CfOrDexInstruction; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; @@ -131,7 +132,7 @@ @Override public void registerTypeReference(DexType type) { - // Type references are OK as long as we do not have a use on them + // Type references are OK as long as we do not have a use on them. } @Override @@ -141,7 +142,9 @@ @Override public void registerExceptionGuard(DexType guard) { - setMaxApiReferenceLevel(guard); + // Type references as exception guard are OK for stubbed exception guards as they are always + // present at runtime. + setMaxApiReferenceLevelForGuard(guard); } @Override @@ -170,6 +173,17 @@ } } + private void setMaxApiReferenceLevelForGuard(DexType type) { + if (isEnabled) { + ComputedApiLevel computedApiLevel = apiLevelCompute.computeApiLevelForLibraryReference(type); + if (ApiReferenceStubber.isNeverStubbedType(type, appInfoWithClassHierarchy.dexItemFactory()) + || appInfoWithClassHierarchy.options().canHaveDalvikCatchHandlerVerificationBug() + || computedApiLevel.isUnknownApiLevel()) { + maxApiReferenceLevel = maxApiReferenceLevel.max(computedApiLevel); + } + } + } + @SuppressWarnings("HidingField") public ComputedApiLevel getMaxApiReferenceLevel() { return maxApiReferenceLevel;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java index c48ceb0..d7369fbd 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -22,7 +22,7 @@ public static EnqueuerDeferredTracing create( AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer, Mode mode) { InternalOptions options = appView.options(); - if (!options.isShrinking() || !options.enableEnqueuerDeferredTracing) { + if (!options.isShrinking()) { return empty(); } if (!options.isOptimizedResourceShrinking()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java index 8eac490..1c3889d 100644 --- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java +++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -82,6 +82,11 @@ ProgramMethod context, FieldAccessKind accessKind, FieldAccessMetadata metadata) { + if (!fieldReference.getType().isPrimitiveType() + && !options.getTestingOptions().enableEnqueuerDeferredTracingForReferenceFields) { + return false; + } + ProgramField field = resolutionResult.getSingleProgramField(); if (field == null) { return false; @@ -97,7 +102,7 @@ } // If the access is from a reachability sensitive method, then bail out. - if (context.getHolder().getOrComputeReachabilitySensitive(appView)) { + if (context.getHolder().isReachabilitySensitive()) { return enqueueDeferredEnqueuerActions(field); }
diff --git a/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java index 7eb81ce..30e9735 100644 --- a/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java +++ b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
@@ -67,8 +67,12 @@ .resolveMethodOnLegacy( clazz, appView.dexItemFactory().objectMembers.finalize) .asSingleResolution(); - if (resolution != null && resolution.getResolvedHolder().isProgramClass()) { - return TraversalContinuation.doBreak(); + if (resolution != null) { + DexType resolvedType = resolution.getResolvedHolder().getType(); + if (resolvedType.isNotIdenticalTo(appView.dexItemFactory().objectType) + && resolvedType.isNotIdenticalTo(appView.dexItemFactory().enumType)) { + return TraversalContinuation.doBreak(); + } } } return TraversalContinuation.doContinue();
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java index e33a5fa..3354e65 100644 --- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java +++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java
@@ -32,6 +32,7 @@ import com.android.tools.r8.graph.NestHostClassAttribute; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import com.google.common.collect.ImmutableList; import java.util.Collections; @@ -58,7 +59,8 @@ MethodCollectionFactory.fromMethods( createDirectMethods(dexItemFactory), createVirtualMethods(dexItemFactory)), dexItemFactory.getSkipNameValidationForTesting(), - DexProgramClass::invalidChecksumRequest); + DexProgramClass::invalidChecksumRequest, + ReachabilitySensitiveValue.DISABLED); } private static DexEncodedField[] createInstanceFields() {
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java index f6d1bf4..a70f457 100644 --- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java +++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
@@ -51,6 +51,7 @@ import com.android.tools.r8.ir.code.MonitorType; import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap; import java.util.ArrayDeque; @@ -80,7 +81,8 @@ MethodCollectionFactory.fromMethods( createDirectMethods(dexItemFactory), createVirtualMethods(dexItemFactory)), dexItemFactory.getSkipNameValidationForTesting(), - DexProgramClass::invalidChecksumRequest); + DexProgramClass::invalidChecksumRequest, + ReachabilitySensitiveValue.DISABLED); } private static DexEncodedField[] createInstanceFields(DexItemFactory dexItemFactory) {
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 068a1a7..3340e46 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -25,6 +25,7 @@ import com.android.tools.r8.graph.RecordComponentInfo; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -214,7 +215,8 @@ DexEncodedMethod.EMPTY_ARRAY, factory.getSkipNameValidationForTesting(), c -> checksum, - null); + null, + ReachabilitySensitiveValue.DISABLED); if (useSortedMethodBacking) { clazz.getMethodCollection().useSortedBacking(); }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java index c5cb640..f6c7269 100644 --- a/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java +++ b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java
@@ -29,6 +29,11 @@ .accept(new Object[] {androidPlatformBuild}); } + static void setIsolatedSplits(Object builder, boolean isolatedSplits) { + getReflectiveBuilderMethod(builder, "setEnableIsolatedSplits", boolean.class) + .accept(new Object[] {isolatedSplits}); + } + static void addArtProfilesForRewriting(Object builder, Map<Path, Path> artProfileFiles) { try { Class<?> artProfileProviderClass =
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java index 6760583..cd01544 100644 --- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java +++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -35,6 +35,8 @@ */ public class CompileDumpCompatR8 extends CompileDumpBase { + private static final String ISOLATED_SPLITS_FLAG = "--isolated-splits"; + private static final List<String> VALID_OPTIONS = Arrays.asList( "--classfile", @@ -42,7 +44,8 @@ "--debug", "--release", "--enable-missing-library-api-modeling", - "--android-platform-build"); + "--android-platform-build", + ISOLATED_SPLITS_FLAG); private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND = Arrays.asList( @@ -92,6 +95,7 @@ int threads = -1; boolean enableMissingLibraryApiModeling = false; boolean androidPlatformBuild = false; + boolean isolatedSplits = false; for (int i = 0; i < args.length; i++) { String option = args[i]; if (VALID_OPTIONS.contains(option)) { @@ -122,6 +126,9 @@ case "--android-platform-build": androidPlatformBuild = true; break; + case ISOLATED_SPLITS_FLAG: + isolatedSplits = true; + break; default: throw new IllegalArgumentException("Unimplemented option: " + option); } @@ -225,6 +232,7 @@ addArtProfilesForRewriting(commandBuilder, artProfileFiles); addStartupProfileProviders(commandBuilder, startupProfileFiles); setAndroidPlatformBuild(commandBuilder, androidPlatformBuild); + setIsolatedSplits(commandBuilder, isolatedSplits); setEnableExperimentalMissingLibraryApiModeling(commandBuilder, enableMissingLibraryApiModeling); if (desugaredLibJson != null) { commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
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 bbfe77c..d68f123 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -737,9 +737,6 @@ // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes. public boolean processCovariantReturnTypeAnnotations = true; - public boolean enableEnqueuerDeferredTracing = - System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null; - public boolean loadAllClassDefinitions = false; // Whether or not to check for valid multi-dex builds. @@ -924,12 +921,12 @@ public boolean debug = false; - public boolean shouldCompileMethodInDebugMode(AppView<?> appView, ProgramMethod method) { - return debug || method.getOrComputeReachabilitySensitive(appView); + public boolean shouldCompileMethodInDebugMode(ProgramMethod method) { + return debug || method.isReachabilitySensitive(); } public boolean shouldCompileMethodInReleaseMode(AppView<?> appView, ProgramMethod method) { - return !shouldCompileMethodInDebugMode(appView, method); + return !shouldCompileMethodInDebugMode(method); } private final AccessModifierOptions accessModifierOptions = new AccessModifierOptions(this); @@ -2367,6 +2364,8 @@ public boolean enableBridgeHoistingToSharedSyntheticSuperclass = false; public boolean enableCheckCastAndInstanceOfRemoval = true; public boolean enableDeadSwitchCaseElimination = true; + public boolean enableEnqueuerDeferredTracingForReferenceFields = + System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null; public boolean enableInvokeSuperToInvokeVirtualRewriting = true; public boolean enableLegacyClassDefOrdering = System.getProperty("com.android.tools.r8.enableLegacyClassDefOrdering") != null;
diff --git a/src/main/java/com/android/tools/r8/utils/ReachabilitySensitiveValue.java b/src/main/java/com/android/tools/r8/utils/ReachabilitySensitiveValue.java new file mode 100644 index 0000000..efdddc8 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/ReachabilitySensitiveValue.java
@@ -0,0 +1,18 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.utils; + +public enum ReachabilitySensitiveValue { + DISABLED, + ENABLED; + + public static ReachabilitySensitiveValue fromBoolean(boolean enabled) { + return enabled ? ENABLED : DISABLED; + } + + public boolean isEnabled() { + return this == ENABLED; + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java index 891593a..c778df3 100644 --- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java +++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
@@ -8,15 +8,14 @@ import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.ProgramField; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import java.util.stream.Stream; public class ProgramFieldSet implements Iterable<ProgramField> { @@ -55,10 +54,6 @@ backing.putAll(fields.backing); } - public boolean allMatch(Predicate<? super ProgramField> predicate) { - return Iterables.all(this, predicate); - } - public boolean createAndAdd(DexProgramClass clazz, DexEncodedField definition) { return add(new ProgramField(clazz, definition)); }
diff --git a/src/test/examplesJava11/com/android/tools/r8/AlwaysInline.java b/src/test/examplesJava11/com/android/tools/r8/AlwaysInline.java new file mode 100644 index 0000000..f76ecf0 --- /dev/null +++ b/src/test/examplesJava11/com/android/tools/r8/AlwaysInline.java
@@ -0,0 +1,10 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +public @interface AlwaysInline {}
diff --git a/src/test/examplesJava11/com/android/tools/r8/NeverInline.java b/src/test/examplesJava11/com/android/tools/r8/NeverInline.java index 3c69ab7..f41f718 100644 --- a/src/test/examplesJava11/com/android/tools/r8/NeverInline.java +++ b/src/test/examplesJava11/com/android/tools/r8/NeverInline.java
@@ -1,11 +1,14 @@ -// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8; import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +@Retention(RetentionPolicy.CLASS) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java b/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java similarity index 77% rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java rename to src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java index e4847bd..52960eb 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java +++ b/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java
@@ -2,10 +2,8 @@ // 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; +package nesthostexample; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.PACKAGE_NAME; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotNull; @@ -33,20 +31,19 @@ @RunWith(Parameterized.class) public class NestAttributesUpdateTest extends TestBase { + private static final String PACKAGE_NAME = "nesthostexample."; + + private static final Class<?> MERGING_OUTER_CLASS = BasicNestHostClassMerging.class; + private static final Class<?> PRUNING_OUTER_CLASS = BasicNestHostTreePruning.class; + private static final String MERGING_EXPECTED_RESULT = StringUtils.lines("OuterMiddleInner"); + private static final String PRUNING_EXPECTED_RESULT = StringUtils.lines("NotPruned"); @Parameter(0) public TestParameters parameters; - private final String MERGING_OUTER_CLASS = "BasicNestHostClassMerging"; - private final String PRUNING_OUTER_CLASS = "BasicNestHostTreePruning"; - private final String MERGING_EXPECTED_RESULT = StringUtils.lines("OuterMiddleInner"); - private final String PRUNING_EXPECTED_RESULT = StringUtils.lines("NotPruned"); - @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters() - .withCfRuntimesStartingFromIncluding(CfVm.JDK11) - .build(); + return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build(); } @Test @@ -62,18 +59,17 @@ @Test public void testClassMergingNestHostRemoval() throws Exception { testNestAttributesCorrect( - MERGING_OUTER_CLASS + "$MiddleOuter", + BasicNestHostClassMerging.MiddleOuter.class, MERGING_OUTER_CLASS, MERGING_EXPECTED_RESULT, 2, - builder -> { - builder.addOptionsModification( - internalOptions -> { - // The test makes an invoke to StringConcatFactory which is not known to DEX and - // we therefore fail to merge the classes. - internalOptions.apiModelingOptions().enableApiCallerIdentification = false; - }); - }); + builder -> + builder.addOptionsModification( + internalOptions -> { + // The test makes an invoke to StringConcatFactory which is not known to DEX and + // we therefore fail to merge the classes. + internalOptions.apiModelingOptions().enableApiCallerIdentification = false; + })); } @Test @@ -89,7 +85,7 @@ @Test public void testTreePruningNestHostRemoval() throws Exception { testNestAttributesCorrect( - PRUNING_OUTER_CLASS + "$Pruned", + BasicNestHostTreePruning.Pruned.class, PRUNING_OUTER_CLASS, PRUNING_EXPECTED_RESULT, 1, @@ -97,22 +93,22 @@ } public void testNestAttributesCorrect( - String mainClassName, - String outerNestName, + Class<?> mainClass, + Class<?> outerNestClass, String expectedResult, int expectedNumClassesLeft, ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer) throws Exception { testNestAttributesCorrect( - mainClassName, - outerNestName, + mainClass, + outerNestClass, expectedResult, true, expectedNumClassesLeft, testBuilderConsumer); testNestAttributesCorrect( - mainClassName, - outerNestName, + mainClass, + outerNestClass, expectedResult, false, expectedNumClassesLeft, @@ -120,23 +116,22 @@ } public void testNestAttributesCorrect( - String mainClassName, - String outerNestName, + Class<?> mainClass, + Class<?> outerNestClass, String expectedResult, boolean minification, int expectedNumClassesLeft, ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer) throws Exception { - String actualMainClassName = PACKAGE_NAME + mainClassName; testForR8(parameters.getBackend()) - .addKeepMainRule(actualMainClassName) + .addKeepMainRule(mainClass) .minification(minification) .addOptionsModification( options -> { // Disable optimizations else additional classes are removed since they become unused. options.enableClassInlining = false; }) - .addProgramFiles(classesMatching(outerNestName)) + .addProgramClassesAndInnerClasses(outerNestClass) .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp)) .addKeepPackageNamesRule("nesthostexample") .addInliningAnnotations() @@ -147,7 +142,7 @@ assertEquals(expectedNumClassesLeft, inspector.allClasses().size()); assertNestAttributesCorrect(inspector); }) - .run(parameters.getRuntime(), actualMainClassName) + .run(parameters.getRuntime(), mainClass) .assertSuccessWithOutput(expectedResult); }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java b/src/test/examplesJava11/nesthostexample/NestClassMergingTest.java similarity index 65% rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java rename to src/test/examplesJava11/nesthostexample/NestClassMergingTest.java index 322fb9b..273b7ec 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java +++ b/src/test/examplesJava11/nesthostexample/NestClassMergingTest.java
@@ -1,17 +1,16 @@ -package com.android.tools.r8.desugar.nestaccesscontrol; +// 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. -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.PACKAGE_NAME; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching; +package nesthostexample; import com.android.tools.r8.Jdk9TestUtils; -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.TestRuntime.CfVm; import com.android.tools.r8.utils.StringUtils; -import java.nio.file.Path; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -25,10 +24,10 @@ @Parameter(0) public TestParameters parameters; - private final String NEST_MAIN_CLASS = PACKAGE_NAME + "NestHostInlining"; - private final String NEST_SUBCLASS_MAIN_CLASS = PACKAGE_NAME + "NestHostInliningSubclasses"; - private final String OUTSIDE_WITH_ACCESS_MAIN_CLASS = PACKAGE_NAME + "OutsideInliningWithAccess"; - private final String OUTSIDE_NO_ACCESS_MAIN_CLASS = PACKAGE_NAME + "OutsideInliningNoAccess"; + private final Class<?> NEST_MAIN_CLASS = NestHostInlining.class; + private final Class<?> NEST_SUBCLASS_MAIN_CLASS = NestHostInliningSubclasses.class; + private final Class<?> OUTSIDE_WITH_ACCESS_MAIN_CLASS = OutsideInliningWithAccess.class; + private final Class<?> OUTSIDE_NO_ACCESS_MAIN_CLASS = OutsideInliningNoAccess.class; private final String NEST_MAIN_EXPECTED_RESULT = StringUtils.lines("inlining", "InnerNoPrivAccess"); private final String NEST_SUBCLASS_MAIN_EXPECTED_RESULT = @@ -40,20 +39,19 @@ @Parameters(name = "{0}") public static TestParametersCollection data() { - return getTestParameters() - .withCfRuntimesStartingFromIncluding(CfVm.JDK11) - .build(); + return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build(); } @Test public void testClassMergeAcrossTwoNests() throws Exception { // Potentially merge classes from one nest with classes from another nest. testClassMergeAcrossNest( - new String[] {NEST_MAIN_CLASS}, new String[] {NEST_MAIN_EXPECTED_RESULT}); + new Class<?>[] {NEST_MAIN_CLASS}, new String[] {NEST_MAIN_EXPECTED_RESULT}); testClassMergeAcrossNest( - new String[] {NEST_SUBCLASS_MAIN_CLASS}, new String[] {NEST_SUBCLASS_MAIN_EXPECTED_RESULT}); + new Class<?>[] {NEST_SUBCLASS_MAIN_CLASS}, + new String[] {NEST_SUBCLASS_MAIN_EXPECTED_RESULT}); testClassMergeAcrossNest( - new String[] {NEST_MAIN_CLASS, NEST_SUBCLASS_MAIN_CLASS}, + new Class<?>[] {NEST_MAIN_CLASS, NEST_SUBCLASS_MAIN_CLASS}, new String[] {NEST_MAIN_EXPECTED_RESULT, NEST_SUBCLASS_MAIN_EXPECTED_RESULT}); } @@ -61,7 +59,7 @@ public void testClassMergeAcrossNestAndNonNest() throws Exception { // Potentially merge classes from a nest with non nest classes. testClassMergeAcrossNest( - new String[] { + new Class<?>[] { NEST_MAIN_CLASS, OUTSIDE_NO_ACCESS_MAIN_CLASS, OUTSIDE_WITH_ACCESS_MAIN_CLASS }, new String[] { @@ -70,22 +68,23 @@ OUTSIDE_WITH_ACCESS_MAIN_EXPECTED_RESULT }); testClassMergeAcrossNest( - new String[] {OUTSIDE_NO_ACCESS_MAIN_CLASS}, + new Class<?>[] {OUTSIDE_NO_ACCESS_MAIN_CLASS}, new String[] {OUTSIDE_NO_ACCESS_MAIN_EXPECTED_RESULT}); testClassMergeAcrossNest( - new String[] {OUTSIDE_WITH_ACCESS_MAIN_CLASS}, + new Class<?>[] {OUTSIDE_WITH_ACCESS_MAIN_CLASS}, new String[] {OUTSIDE_WITH_ACCESS_MAIN_EXPECTED_RESULT}); } - public void testClassMergeAcrossNest(String[] mainClasses, String[] expectedResults) + public void testClassMergeAcrossNest(Class<?>[] mainClasses, String[] expectedResults) throws Exception { - List<Path> bothNestsAndOutsideClassToCompile = classesMatching("Inlining"); - R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend()); - for (String clazz : mainClasses) { - r8FullTestBuilder.addKeepMainRule(clazz); - } R8TestCompileResult compileResult = - r8FullTestBuilder + testForR8(parameters.getBackend()) + .apply( + b -> { + for (Class<?> clazz : mainClasses) { + b.addKeepMainRule(clazz); + } + }) .addOptionsModification( options -> { // Disable optimizations else additional classes are removed since they become @@ -94,7 +93,12 @@ options.enableNestReduction = false; }) .enableInliningAnnotations() - .addProgramFiles(bothNestsAndOutsideClassToCompile) + .addProgramClassesAndInnerClasses( + List.of( + NEST_MAIN_CLASS, + NEST_SUBCLASS_MAIN_CLASS, + OUTSIDE_WITH_ACCESS_MAIN_CLASS, + OUTSIDE_NO_ACCESS_MAIN_CLASS)) .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp)) .addKeepPackageNamesRule("nesthostexample") .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java b/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java similarity index 65% rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java rename to src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java index 5b59d1d..70c0120 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java +++ b/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java
@@ -2,18 +2,15 @@ // 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; +package nesthostexample; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesOfNest; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass; +import static nesthostexample.BasicNestHostWithInnerClassConstructors.getExpectedResult; import com.android.tools.r8.Jdk9TestUtils; 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.ToolHelper.DexVm; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -22,6 +19,8 @@ @RunWith(Parameterized.class) public class NestConstructorRemovedArgTest extends TestBase { + private static final Class<?> MAIN_CLASS = BasicNestHostWithInnerClassConstructors.class; + public NestConstructorRemovedArgTest(TestParameters parameters) { this.parameters = parameters; } @@ -32,8 +31,7 @@ public static TestParametersCollection data() { return getTestParameters() .withCfRuntimesStartingFromIncluding(CfVm.JDK11) - .withDexRuntime(DexVm.Version.first()) - .withDexRuntime(DexVm.Version.last()) + .withDexRuntimes() .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport()) .enableApiLevelsForCf() .build(); @@ -42,33 +40,31 @@ @Test public void testRemoveArgConstructorNestsR8() throws Exception { parameters.assumeR8TestParameters(); - String nestID = "constructors"; testForR8(parameters.getBackend()) - .addKeepMainRule(getMainClass(nestID)) + .addKeepMainRule(MAIN_CLASS) .addDontObfuscate() .setMinApi(parameters) .addOptionsModification(options -> options.enableClassInlining = false) - .addProgramFiles(classesOfNest(nestID)) + .addProgramClassesAndInnerClasses(MAIN_CLASS) .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp)) .compile() - .run(parameters.getRuntime(), getMainClass(nestID)) - .assertSuccessWithOutput(getExpectedResult(nestID)); + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutputLines(getExpectedResult()); } @Test public void testRemoveArgConstructorNestsR8NoTreeShaking() throws Exception { parameters.assumeR8TestParameters(); - String nestID = "constructors"; testForR8(parameters.getBackend()) .noTreeShaking() - .addKeepMainRule(getMainClass(nestID)) + .addKeepMainRule(MAIN_CLASS) .addDontObfuscate() .setMinApi(parameters) .addOptionsModification(options -> options.enableClassInlining = false) - .addProgramFiles(classesOfNest(nestID)) + .addProgramClassesAndInnerClasses(MAIN_CLASS) .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp)) .compile() - .run(parameters.getRuntime(), getMainClass(nestID)) - .assertSuccessWithOutput(getExpectedResult(nestID)); + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutputLines(getExpectedResult()); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMemberPropagatedTest.java b/src/test/examplesJava11/nesthostexample/NestMemberPropagatedTest.java similarity index 63% rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMemberPropagatedTest.java rename to src/test/examplesJava11/nesthostexample/NestMemberPropagatedTest.java index 3e86e4f..dc1a1d3 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMemberPropagatedTest.java +++ b/src/test/examplesJava11/nesthostexample/NestMemberPropagatedTest.java
@@ -2,11 +2,8 @@ // 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; +package nesthostexample; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass; import static junit.framework.TestCase.assertEquals; import com.android.tools.r8.TestBase; @@ -15,8 +12,6 @@ import com.android.tools.r8.TestRuntime.CfVm; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundClassSubject; -import java.nio.file.Path; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -25,6 +20,8 @@ @RunWith(Parameterized.class) public class NestMemberPropagatedTest extends TestBase { + private static final Class<?> MAIN_CLASS = NestPvtFieldPropagated.class; + public NestMemberPropagatedTest(TestParameters parameters) { this.parameters = parameters; } @@ -38,19 +35,15 @@ @Test public void testPvtMemberPropagated() throws Exception { - List<Path> toCompile = classesMatching("NestPvtFieldPropagated"); testForR8(parameters.getBackend()) - .addKeepMainRule(getMainClass("memberPropagated")) + .addKeepMainRule(MAIN_CLASS) .addDontObfuscate() - .addOptionsModification( - options -> { - options.enableClassInlining = false; - }) - .addProgramFiles(toCompile) + .addOptionsModification(options -> options.enableClassInlining = false) + .addProgramClassesAndInnerClasses(MAIN_CLASS) .compile() .inspect(this::assertMemberPropagated) - .run(parameters.getRuntime(), getMainClass("memberPropagated")) - .assertSuccessWithOutput(getExpectedResult("memberPropagated")); + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutputLines("toPropagateStatic"); } private void assertMemberPropagated(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/examplesJava11/nesthostexample/NestMethodInlinedTest.java similarity index 77% rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java rename to src/test/examplesJava11/nesthostexample/NestMethodInlinedTest.java index 48d4ca1..ef93446 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java +++ b/src/test/examplesJava11/nesthostexample/NestMethodInlinedTest.java
@@ -2,11 +2,8 @@ // 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; +package nesthostexample; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; @@ -14,12 +11,11 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.android.tools.r8.utils.codeinspector.MethodSubject; -import java.nio.file.Path; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -28,6 +24,19 @@ @RunWith(Parameterized.class) public class NestMethodInlinedTest extends TestBase { + private static final Class<?> MAIN_CLASS = NestPvtMethodCallInlined.class; + + private static final String EXPECTED_RESULT = + StringUtils.lines( + "nestPvtCallToInlineInner", + "nestPvtCallToInlineInnerInterface", + "notInlinedPvtCallInner", + "notInlinedPvtCallInnerInterface", + "notInlinedPvtCallInnerSub", + "notInlinedPvtCallInnerInterface", + "nestPvtCallToInlineInnerSub", + "nestPvtCallToInlineInner"); + public NestMethodInlinedTest(TestParameters parameters) { this.parameters = parameters; } @@ -45,17 +54,16 @@ @Test public void testReference() throws Exception { testForRuntime(parameters) - .addProgramFiles(classesMatching("NestPvtMethodCallInlined")) - .run(parameters.getRuntime(), getMainClass("pvtCallInlined")) - .assertSuccessWithOutput(getExpectedResult("pvtCallInlined")); + .addProgramClassesAndInnerClasses(MAIN_CLASS) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_RESULT); } @Test public void testPvtMethodCallInlined() throws Exception { parameters.assumeR8TestParameters(); - List<Path> toCompile = classesMatching("NestPvtMethodCallInlined"); testForR8(parameters.getBackend()) - .addKeepMainRule(getMainClass("pvtCallInlined")) + .addKeepMainRule(MAIN_CLASS) .addDontObfuscate() .addOptionsModification( options -> { @@ -63,13 +71,14 @@ options.getVerticalClassMergerOptions().disable(); }) .enableInliningAnnotations() + .enableAlwaysInliningAnnotations() .enableMemberValuePropagationAnnotations() - .addProgramFiles(toCompile) + .addProgramClassesAndInnerClasses(MAIN_CLASS) .compile() .inspect(this::assertMethodsInlined) .inspect(NestAttributesUpdateTest::assertNestAttributesCorrect) - .run(parameters.getRuntime(), getMainClass("pvtCallInlined")) - .assertSuccessWithOutput(getExpectedResult("pvtCallInlined")); + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_RESULT); } private void assertMethodsInlined(CodeInspector inspector) {
diff --git a/src/test/examplesJava11/nesthostexample/NestOnProgramOnClassPathTest.java b/src/test/examplesJava11/nesthostexample/NestOnProgramOnClassPathTest.java new file mode 100644 index 0000000..d2e3d0c --- /dev/null +++ b/src/test/examplesJava11/nesthostexample/NestOnProgramOnClassPathTest.java
@@ -0,0 +1,114 @@ +// 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 nesthostexample; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.D8TestCompileResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FoundClassSubject; +import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; +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 NestOnProgramOnClassPathTest extends TestBase { + + public NestOnProgramOnClassPathTest(TestParameters parameters) { + this.parameters = parameters; + } + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + + @Test + public void testD8MethodBridgesPresent() throws Exception { + parameters.assumeDexRuntime(); + Class<?> nestHost = BasicNestHostWithInnerClassMethods.class; + // 1 inner class. + D8TestCompileResult singleInner = + compileClassesWithD8ProgramClasses( + nestHost, BasicNestHostWithInnerClassMethods.BasicNestedClass.class); + singleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 2)); + // Outer class. + D8TestCompileResult host = compileClassesWithD8ProgramClasses(nestHost, nestHost); + host.inspect(inspector -> assertThisNumberOfBridges(inspector, 2)); + // 2 inner classes. + D8TestCompileResult multipleInner = + compileClassesWithD8ProgramClasses( + NestHostExample.class, + NestHostExample.StaticNestMemberInner.class, + NestHostExample.StaticNestMemberInner.StaticNestMemberInnerInner.class); + multipleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 5)); + } + + @Test + public void testD8ConstructorBridgesPresent() throws Exception { + parameters.assumeDexRuntime(); + Class<?> nestHost = BasicNestHostWithInnerClassConstructors.class; + D8TestCompileResult inner = + compileClassesWithD8ProgramClasses( + nestHost, BasicNestHostWithInnerClassConstructors.BasicNestedClass.class); + inner.inspect( + inspector -> { + assertThisNumberOfBridges(inspector, 3); + assertNestConstructor(inspector); + }); + D8TestCompileResult host = compileClassesWithD8ProgramClasses(nestHost, nestHost); + host.inspect( + inspector -> { + assertThisNumberOfBridges(inspector, 1); + assertNestConstructor(inspector); + }); + } + + @Test + public void testD8ConstructorNestMergeCorrect() throws Exception { + // Multiple Nest Constructor classes have to be merged here. + parameters.assumeDexRuntime(); + Class<?> nestHost = BasicNestHostWithInnerClassConstructors.class; + D8TestCompileResult inner = + compileClassesWithD8ProgramClasses( + nestHost, BasicNestHostWithInnerClassConstructors.BasicNestedClass.class); + D8TestCompileResult host = compileClassesWithD8ProgramClasses(nestHost, nestHost); + testForD8() + .addProgramFiles(inner.writeToZip(), host.writeToZip()) + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), nestHost) + .assertSuccessWithOutputLines(BasicNestHostWithInnerClassConstructors.getExpectedResult()); + } + + private D8TestCompileResult compileClassesWithD8ProgramClasses( + Class<?> nestHost, Class<?>... classes) throws Exception { + return testForD8() + .setMinApi(parameters) + .addProgramClasses(classes) + .addClasspathClasses(nestHost.getNestMembers()) + .compile(); + } + + private static void assertNestConstructor(CodeInspector inspector) { + assertTrue(inspector.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)); + } + + private static void assertThisNumberOfBridges(CodeInspector inspector, int numBridges) { + for (FoundClassSubject clazz : inspector.allClasses()) { + if (!clazz.isSynthetic()) { + assertEquals(numBridges, clazz.allMethods(FoundMethodSubject::isSynthetic).size()); + } + } + } +}
diff --git a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java index 1e36fe1..9670e69 100644 --- a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java +++ b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
@@ -4,6 +4,7 @@ package nesthostexample; +import com.android.tools.r8.AlwaysInline; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverPropagateValue; @@ -11,6 +12,7 @@ public static class Inner { + @AlwaysInline public String methodWithPvtCallToInline() { return notInlinedPvtCall(); } @@ -21,6 +23,7 @@ return "notInlinedPvtCallInner"; } + @AlwaysInline private String nestPvtCallToInline() { return "nestPvtCallToInlineInner"; } @@ -28,6 +31,7 @@ public interface InnerInterface { + @AlwaysInline default String methodWithPvtCallToInline() { return notInlinedPvtCall(); } @@ -38,6 +42,7 @@ return "notInlinedPvtCallInnerInterface"; } + @AlwaysInline private String nestPvtCallToInline() { return "nestPvtCallToInlineInnerInterface"; } @@ -84,22 +89,22 @@ InnerInterface impl = new InnerInterfaceImpl(); // Inlining through nest access (invoke virtual/interface). - System.out.println(i.nestPvtCallToInline()); - System.out.println(impl.nestPvtCallToInline()); + System.out.println((Object) i.nestPvtCallToInline()); + System.out.println((Object) impl.nestPvtCallToInline()); // Inlining transformations. // Invoke direct -> invoke virtual. - System.out.println(i.methodWithPvtCallToInline()); + System.out.println((Object) i.methodWithPvtCallToInline()); // Invoke interface -> invoke virtual. - System.out.println(impl.methodWithPvtCallToInline()); + System.out.println((Object) impl.methodWithPvtCallToInline()); // Invoke virtual -> invoke direct. - System.out.println(iSub.dispatchInlining(impl)); + System.out.println((Object) iSub.dispatchInlining(impl)); // Invoke interface -> invoke direct. - System.out.println(impl.dispatchInlining(iSub)); + System.out.println((Object) impl.dispatchInlining(iSub)); // Inheritance + invoke virtual and nest access. // This may mess up lookup logic. - System.out.println(iSub.nestPvtCallToInline()); - System.out.println(((Inner) iSub).nestPvtCallToInline()); + System.out.println((Object) iSub.nestPvtCallToInline()); + System.out.println((Object) ((Inner) iSub).nestPvtCallToInline()); } }
diff --git a/src/test/java/com/android/tools/r8/B344363462Test.java b/src/test/java/com/android/tools/r8/B344363462Test.java deleted file mode 100644 index bba41ed..0000000 --- a/src/test/java/com/android/tools/r8/B344363462Test.java +++ /dev/null
@@ -1,78 +0,0 @@ -// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -package com.android.tools.r8; - -import com.android.tools.r8.utils.StringUtils; -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 B344363462Test extends TestBase { - - @Parameter() public TestParameters parameters; - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withAllRuntimesAndApiLevels().build(); - } - - private static final String EXPECTED_OUTPUT = StringUtils.lines("-225"); - private static final String UNEXPECTED_OUTPUT = StringUtils.lines("0"); - - @Test - public void testD8() throws Exception { - parameters.assumeDexRuntime(); - testForD8(parameters.getBackend()) - .addInnerClasses(getClass()) - .setMinApi(parameters) - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutput(EXPECTED_OUTPUT); - } - - @Test - public void testD8Release() throws Exception { - parameters.assumeDexRuntime(); - testForD8(parameters.getBackend()) - .addInnerClasses(getClass()) - .setMinApi(parameters) - .release() - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutput(UNEXPECTED_OUTPUT); - } - - @Test - public void testR8() throws Exception { - testForR8(parameters.getBackend()) - .addInnerClasses(getClass()) - .addKeepMainRule(TestClass.class) - .setMinApi(parameters) - .run(parameters.getRuntime(), TestClass.class) - .assertSuccessWithOutput(UNEXPECTED_OUTPUT); - } - - static class TestClass { - - int iFld1; - - void t() { - int i5 = 61127, i7 = 42011; - long[] lArr = new long[10]; - try { - iFld1 -= 225L; - lArr[i5] = i7; - iFld1 = i7; - } catch (ArrayIndexOutOfBoundsException err) { - } - System.out.println(iFld1); - } - - public static void main(String[] strArr) { - TestClass test = new TestClass(); - test.t(); - } - } -}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java index a45b22e..e212a7a 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java
@@ -160,12 +160,11 @@ testForR8(parameters.getBackend()) .apply(this::setupTestBuilder) .addKeepMainRule(Main.class) - .addDontObfuscate() .compile() .applyIf( exceptionPresentAtRuntime(), b -> b.addBootClasspathClasses(LibraryException.class)) .run(parameters.getRuntime(), Main.class) - .apply(this::checkUnexpectedOutput) + .apply(this::checkOutput) .inspect(this::inspect); } @@ -181,21 +180,6 @@ } } - private void checkUnexpectedOutput(SingleTestRunResult<?> runResult) { - if (exceptionPresentAtRuntime()) { - if (parameters.isCfRuntime() - || parameters.getApiLevel().isGreaterThanOrEqualTo(mockExceptionLevel)) { - runResult.assertSuccessWithOutputLines("Valid behaviour"); - } else { - // TODO(b/342961827): This should not happen. - runResult.assertSuccessWithOutputLines( - LibraryException.class.getTypeName() + ": Failed, true"); - } - } else { - runResult.assertSuccessWithOutputLines("java.lang.NoClassDefFoundError, false"); - } - } - // Only present from api level P. public static class LibraryException extends Exception { public LibraryException(String message) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java index a9ca217..6074c6e 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java
@@ -24,7 +24,7 @@ @RunWith(Parameterized.class) public class ApiModelNoInliningOfTryCatchReferenceTest extends TestBase { - private final AndroidApiLevel exceptionApiLevel = AndroidApiLevel.L_MR1; + private final AndroidApiLevel exceptionApiLevel = AndroidApiLevel.M; @Parameter() public TestParameters parameters; @@ -51,8 +51,9 @@ .enableInliningAnnotations() .addHorizontallyMergedClassesInspector( horizontallyMergedClassesInspector -> { + // Dalvik verifier error present up to and not including L. if (parameters.isDexRuntime() - && parameters.getApiLevel().isGreaterThanOrEqualTo(exceptionApiLevel)) { + && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) { horizontallyMergedClassesInspector.assertIsCompleteMergeGroup( TestClass.class, Caller.class); } else { @@ -63,11 +64,14 @@ ApiModelingTestHelper.addTracedApiReferenceLevelCallBack( (reference, apiLevel) -> { if (reference.equals(Reference.methodFromMethod(tryCatch))) { + // Dalvik verifier error present up to and not including L. assertEquals( - exceptionApiLevel.max( - parameters.isCfRuntime() - ? AndroidApiLevel.B - : parameters.getApiLevel()), + parameters.isDexRuntime() + && parameters + .getApiLevel() + .isGreaterThanOrEqualTo(AndroidApiLevel.L) + ? parameters.getApiLevel() + : exceptionApiLevel, apiLevel); } }))
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java index 1cb50ba..917c268 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java +++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java
@@ -40,7 +40,7 @@ config.run(new BenchmarkEnvironment(config, temp, false)); } - public static BenchmarkRunner runner(BenchmarkConfig config) { - return BenchmarkRunner.runner(config); + public static BenchmarkRunner runner(BenchmarkEnvironment environment) { + return BenchmarkRunner.runner(environment); } }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java index fe36c52..e8e5641 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java +++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java
@@ -19,6 +19,10 @@ this.isGolem = isGolem; } + public boolean failOnCodeSizeDifferences() { + return System.getProperty("BENCHMARK_IGNORE_CODE_SIZE_DIFFERENCES") == null; + } + public BenchmarkConfig getConfig() { return config; } @@ -36,4 +40,20 @@ public Path getGolemDependencyRoot() { return Paths.get("benchmarks", config.getDependencyDirectoryName()); } + + public boolean hasBenchmarkIterationsOverride() { + return System.getProperty("BENCHMARK_ITERATIONS") != null; + } + + public int getBenchmarkIterationsOverride() { + return Integer.parseInt(System.getProperty("BENCHMARK_ITERATIONS")); + } + + public boolean hasOutputPath() { + return System.getProperty("BENCHMARK_OUTPUT") != null; + } + + public Path getOutputPath() { + return Paths.get(System.getProperty("BENCHMARK_OUTPUT")); + } }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java index 14b10f4..da3f3d2 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java +++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
@@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.benchmarks; +import com.android.tools.r8.errors.Unimplemented; +import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -46,12 +48,17 @@ } @Override - public void printResults(ResultMode mode) { + public void printResults(ResultMode mode, boolean failOnCodeSizeDifferences) { List<String> sorted = new ArrayList<>(results.keySet()); sorted.sort(String::compareTo); for (String name : sorted) { BenchmarkResultsSingle singleResults = results.get(name); - singleResults.printResults(mode); + singleResults.printResults(mode, failOnCodeSizeDifferences); } } + + @Override + public void writeResults(PrintStream out) { + throw new Unimplemented(); + } }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java index e0bb9f193..e15dffb 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java +++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
@@ -3,8 +3,11 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.benchmarks; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; +import java.io.PrintStream; import java.util.Set; public class BenchmarkResultsSingle implements BenchmarkResults { @@ -19,6 +22,18 @@ this.metrics = metrics; } + public String getName() { + return name; + } + + public LongList getCodeSizeResults() { + return codeSizeResults; + } + + public LongList getRuntimeResults() { + return runtimeResults; + } + @Override public void addRuntimeResult(long result) { verifyMetric(BenchmarkMetric.RunTimeRaw, metrics.contains(BenchmarkMetric.RunTimeRaw), true); @@ -75,7 +90,7 @@ } @Override - public void printResults(ResultMode mode) { + public void printResults(ResultMode mode, boolean failOnCodeSizeDifferences) { verifyConfigAndResults(); if (!runtimeResults.isEmpty()) { long sum = runtimeResults.stream().mapToLong(l -> l).sum(); @@ -84,13 +99,24 @@ } if (!codeSizeResults.isEmpty()) { long size = codeSizeResults.getLong(0); - for (int i = 1; i < codeSizeResults.size(); i++) { - if (size != codeSizeResults.getLong(i)) { - throw new RuntimeException( - "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i)); + if (failOnCodeSizeDifferences) { + for (int i = 1; i < codeSizeResults.size(); i++) { + if (size != codeSizeResults.getLong(i)) { + throw new RuntimeException( + "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i)); + } } } printCodeSize(size); } } + + @Override + public void writeResults(PrintStream out) { + Gson gson = + new GsonBuilder() + .registerTypeAdapter(BenchmarkResultsSingle.class, new BenchmarkResultsSingleAdapter()) + .create(); + out.print(gson.toJson(this)); + } }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java new file mode 100644 index 0000000..55936d7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
@@ -0,0 +1,34 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.benchmarks; + +import com.android.tools.r8.utils.ListUtils; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; + +public class BenchmarkResultsSingleAdapter implements JsonSerializer<BenchmarkResultsSingle> { + + @Override + public JsonElement serialize( + BenchmarkResultsSingle result, Type type, JsonSerializationContext jsonSerializationContext) { + JsonArray resultsArray = new JsonArray(); + ListUtils.forEachWithIndex( + result.getCodeSizeResults(), + (codeSizeResult, iteration) -> { + JsonObject resultObject = new JsonObject(); + resultObject.addProperty("code_size", codeSizeResult); + resultObject.addProperty("runtime", result.getRuntimeResults().getLong(iteration)); + resultsArray.add(resultObject); + }); + + JsonObject benchmarkObject = new JsonObject(); + benchmarkObject.addProperty("benchmark_name", result.getName()); + benchmarkObject.add("results", resultsArray); + return benchmarkObject; + } +}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java index 2ac97a6..1a4e33c 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java +++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
@@ -3,8 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.benchmarks; +import com.android.tools.r8.errors.Unimplemented; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; +import java.io.PrintStream; public class BenchmarkResultsWarmup implements BenchmarkResults { @@ -51,7 +53,7 @@ } @Override - public void printResults(ResultMode mode) { + public void printResults(ResultMode mode, boolean failOnCodeSizeDifferences) { if (runtimeResults.isEmpty()) { throw new BenchmarkConfigError("Expected runtime results for warmup run"); } @@ -61,4 +63,9 @@ BenchmarkResults.prettyMetric( name, BenchmarkMetric.StartupTime, BenchmarkResults.prettyTime(result))); } + + @Override + public void writeResults(PrintStream out) { + throw new Unimplemented(); + } }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java index 2fed6dc..78441e3 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java +++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
@@ -4,6 +4,9 @@ package com.android.tools.r8.benchmarks; import com.android.tools.r8.benchmarks.BenchmarkResults.ResultMode; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; public class BenchmarkRunner { @@ -11,17 +14,17 @@ void run(BenchmarkResults results) throws Exception; } - private final BenchmarkConfig config; + private final BenchmarkEnvironment environment; private int warmups = 0; private int iterations = 1; private ResultMode resultMode = BenchmarkResults.ResultMode.AVERAGE; - private BenchmarkRunner(BenchmarkConfig config) { - this.config = config; + private BenchmarkRunner(BenchmarkEnvironment environment) { + this.environment = environment; } - public static BenchmarkRunner runner(BenchmarkConfig config) { - return new BenchmarkRunner(config); + public static BenchmarkRunner runner(BenchmarkEnvironment environment) { + return new BenchmarkRunner(environment); } public BenchmarkRunner setWarmupIterations(int iterations) { @@ -29,6 +32,12 @@ return this; } + public int getBenchmarkIterations() { + return environment.hasBenchmarkIterationsOverride() + ? environment.getBenchmarkIterationsOverride() + : iterations; + } + public BenchmarkRunner setBenchmarkIterations(int iterations) { this.iterations = iterations; return this; @@ -46,6 +55,7 @@ public void run(BenchmarkRunnerFunction fn) throws Exception { long warmupTotalTime = 0; + BenchmarkConfig config = environment.getConfig(); BenchmarkResults warmupResults = new BenchmarkResultsWarmup(config.getName()); if (warmups > 0) { long start = System.nanoTime(); @@ -59,7 +69,7 @@ ? new BenchmarkResultsSingle(config.getName(), config.getMetrics()) : new BenchmarkResultsCollection(config.getSubBenchmarks()); long start = System.nanoTime(); - for (int i = 0; i < iterations; i++) { + for (int i = 0; i < getBenchmarkIterations(); i++) { fn.run(results); } long benchmarkTotalTime = System.nanoTime() - start; @@ -71,11 +81,14 @@ if (warmups > 0) { printMetaInfo("warmup", warmups, warmupTotalTime); if (config.hasTimeWarmupRuns()) { - warmupResults.printResults(resultMode); + warmupResults.printResults(resultMode, environment.failOnCodeSizeDifferences()); } } - printMetaInfo("benchmark", iterations, benchmarkTotalTime); - results.printResults(resultMode); + printMetaInfo("benchmark", getBenchmarkIterations(), benchmarkTotalTime); + results.printResults(resultMode, environment.failOnCodeSizeDifferences()); + if (environment.hasOutputPath()) { + writeResults(results); + } System.out.println(); } @@ -84,4 +97,11 @@ System.out.println(" " + kind + " iterations: " + iterations); System.out.println(" " + kind + " total time: " + BenchmarkResults.prettyTime(totalTime)); } + + private void writeResults(BenchmarkResults results) throws IOException { + try (PrintStream printStream = + new PrintStream(Files.newOutputStream(environment.getOutputPath()))) { + results.writeResults(printStream); + } + } }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java index 7e2a677..ac4f229 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java +++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -203,7 +203,7 @@ private static BenchmarkMethod internalRunR8( AppDumpBenchmarkBuilder builder, boolean enableResourceShrinking) { return environment -> - BenchmarkBase.runner(environment.getConfig()) + BenchmarkBase.runner(environment) .setWarmupIterations(1) .run( results -> { @@ -244,7 +244,7 @@ private static BenchmarkMethod runBatchD8(AppDumpBenchmarkBuilder builder) { return environment -> - BenchmarkBase.runner(environment.getConfig()) + BenchmarkBase.runner(environment) .setWarmupIterations(1) .run( results -> { @@ -262,7 +262,7 @@ private static BenchmarkMethod runIncrementalD8(AppDumpBenchmarkBuilder builder) { return environment -> - BenchmarkBase.runner(environment.getConfig()) + BenchmarkBase.runner(environment) .setWarmupIterations(1) .reportResultSum() .run(
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java index 3f12fc8..adfa5f8 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java +++ b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
@@ -65,7 +65,7 @@ ImmutableSet.of(ANDROID_JAR.getRoot(environment).resolve("android.jar")), LibraryDesugaringSpecification.JDK11_DESCRIPTOR, ""); - runner(environment.getConfig()) + runner(environment) .setWarmupIterations(1) .setBenchmarkIterations(10) .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java index 6b5e4f9..2d50a3d 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java +++ b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java
@@ -54,7 +54,7 @@ } public static void run(BenchmarkEnvironment environment) throws Exception { - runner(environment.getConfig()) + runner(environment) .setWarmupIterations(1) .setBenchmarkIterations(10) .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java index cb43a1d..4c8c31c 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java +++ b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
@@ -101,7 +101,7 @@ public static BenchmarkMethod benchmarkD8(Options options) { return environment -> - runner(environment.getConfig()) + runner(environment) .setWarmupIterations(1) .setBenchmarkIterations(100) .reportResultSum() @@ -119,7 +119,7 @@ public static BenchmarkMethod benchmarkR8(Options options) { return environment -> - runner(environment.getConfig()) + runner(environment) .setWarmupIterations(1) .setBenchmarkIterations(4) .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java index 28e1e83..c3cd9d0 100644 --- a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java +++ b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
@@ -59,7 +59,7 @@ public static BenchmarkMethod benchmarkRetrace() { return environment -> - runner(environment.getConfig()) + runner(environment) .setWarmupIterations(1) .setBenchmarkIterations(4) .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java index fa46f4d..0d912b3 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.classmerging.horizontal; -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; @@ -36,9 +35,7 @@ .assertSuccessWithOutputLines("foo", "hello", "5", "foobar") .inspect( codeInspector -> { - assertThat( - codeInspector.clazz(C.class), - isAbsentIf(parameters.canInitNewInstanceUsingSuperclassConstructor())); + assertThat(codeInspector.clazz(C.class), isPresent()); assertThat(codeInspector.clazz(D.class), isPresent()); assertThat(codeInspector.clazz(E.class), isPresent()); });
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java index 8f73003..cba6309 100644 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java +++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java
@@ -4,13 +4,9 @@ package com.android.tools.r8.desugar.nestaccesscontrol; -import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; -import static java.util.stream.Collectors.toList; -import static org.hamcrest.core.StringContains.containsString; import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.nio.file.Path; @@ -23,7 +19,6 @@ Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR).resolve("nesthostexample" + JAR_EXTENSION); public static final Path CLASSES_PATH = Paths.get(ToolHelper.getExamplesJava11BuildDir()).resolve("nesthostexample/"); - public static final String PACKAGE_NAME = "nesthostexample."; public static final List<String> CLASS_NAMES = ImmutableList.of( @@ -68,13 +63,6 @@ "NestHostExample$StaticNestMemberInner$StaticNestMemberInnerInner", "NestHostExample$StaticNestInterfaceInner", "NestHostExample$ExampleEnumCompilation"); - public static final int NUMBER_OF_TEST_CLASSES = CLASS_NAMES.size(); - - // The following map use ids, i.e., strings which represents respectively - // a nest with only field, method, constructor, anonymous class and - // all at once nest based private accesses. - public static final ImmutableList<String> NEST_IDS = - ImmutableList.of("fields", "methods", "constructors", "anonymous", "all"); public static final ImmutableMap<String, String> MAIN_CLASSES = ImmutableMap.<String, String>builder() .put("fields", "BasicNestHostWithInnerClassFields") @@ -89,94 +77,10 @@ .put("pvtCallInlined", "NestPvtMethodCallInlined") .put("memberPropagated", "NestPvtFieldPropagated") .build(); - public static final String ALL_RESULT_LINE = - String.join( - ", ", - new String[] { - "field", - "staticField", - "staticField", - "hostMethod", - "staticHostMethod", - "staticHostMethod", - "nest1SField", - "staticNest1SField", - "staticNest1SField", - "nest1SMethod", - "staticNest1SMethod", - "staticNest1SMethod", - "nest2SField", - "staticNest2SField", - "staticNest2SField", - "nest2SMethod", - "staticNest2SMethod", - "staticNest2SMethod", - "nest1Field", - "nest1Method", - "nest2Field", - "nest2Method" - }); - public static final ImmutableMap<String, String> EXPECTED_RESULTS = - ImmutableMap.<String, String>builder() - .put( - "fields", - StringUtils.lines( - "RWnestFieldRWRWnestFieldRWRWnestFieldnoBridge", "RWfieldRWRWfieldRWRWnestField")) - .put( - "methods", - StringUtils.lines( - "nestMethodstaticNestMethodstaticNestMethodnoBridge", - "hostMethodstaticHostMethodstaticNestMethod")) - .put( - "constructors", - StringUtils.lines( - "field", "nest1SField", "1", "innerFieldUnusedConstructor", "nothing")) - .put( - "anonymous", - StringUtils.lines( - "fieldstaticFieldstaticFieldhostMethodstaticHostMethodstaticHostMethod")) - .put( - "all", - StringUtils.lines( - ALL_RESULT_LINE, - ALL_RESULT_LINE, - ALL_RESULT_LINE, - ALL_RESULT_LINE, - "staticInterfaceMethodstaticStaticInterfaceMethod", - "staticInterfaceMethodstaticStaticInterfaceMethod", - "staticInterfaceMethodstaticStaticInterfaceMethod", - "staticInterfaceMethodstaticStaticInterfaceMethod", - "3")) - .put( - "pvtCallInlined", - StringUtils.lines( - "nestPvtCallToInlineInner", - "nestPvtCallToInlineInnerInterface", - "notInlinedPvtCallInner", - "notInlinedPvtCallInnerInterface", - "notInlinedPvtCallInnerSub", - "notInlinedPvtCallInnerInterface", - "nestPvtCallToInlineInnerSub", - "nestPvtCallToInlineInner")) - .put("memberPropagated", StringUtils.lines("toPropagateStatic")) - .build(); public static String getMainClass(String id) { - return PACKAGE_NAME + MAIN_CLASSES.get(id); + return "nesthostexample." + MAIN_CLASSES.get(id); } - public static String getExpectedResult(String id) { - return EXPECTED_RESULTS.get(id); - } - public static List<Path> classesOfNest(String nestID) { - return classesMatching(MAIN_CLASSES.get(nestID)); - } - - public static List<Path> classesMatching(String matcher) { - return CLASS_NAMES.stream() - .filter(name -> containsString(matcher).matches(name)) - .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION)) - .collect(toList()); - } }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java deleted file mode 100644 index 464e2c1..0000000 --- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java +++ /dev/null
@@ -1,131 +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.desugar.nestaccesscontrol; - -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.JAR; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult; -import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass; -import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; -import static java.util.stream.Collectors.toList; -import static org.hamcrest.core.StringContains.containsString; -import static org.hamcrest.core.StringEndsWith.endsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.android.tools.r8.D8TestCompileResult; -import com.android.tools.r8.TestBase; -import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; -import com.android.tools.r8.utils.codeinspector.CodeInspector; -import com.android.tools.r8.utils.codeinspector.FoundClassSubject; -import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; -import java.nio.file.Path; -import java.util.List; -import org.hamcrest.Matcher; -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 NestOnProgramAndClassPathTest extends TestBase { - - public NestOnProgramAndClassPathTest(TestParameters parameters) { - this.parameters = parameters; - } - - private final TestParameters parameters; - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withDexRuntimes().withAllApiLevels().build(); - } - - @Test - public void testD8MethodBridgesPresent() throws Exception { - parameters.assumeDexRuntime(); - // 1 inner class. - D8TestCompileResult singleInner = - compileClassesWithD8ProgramClassesMatching( - containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass")); - singleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 2)); - // Outer class. - D8TestCompileResult host = - compileClassesWithD8ProgramClassesMatching(endsWith("BasicNestHostWithInnerClassMethods")); - host.inspect(inspector -> assertThisNumberOfBridges(inspector, 2)); - // 2 inner classes. - D8TestCompileResult multipleInner = - compileClassesWithD8ProgramClassesMatching( - containsString("NestHostExample$StaticNestMemberInner")); - multipleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 5)); - } - - @Test - public void testD8ConstructorBridgesPresent() throws Exception { - parameters.assumeDexRuntime(); - D8TestCompileResult inner = - compileClassesWithD8ProgramClassesMatching( - containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass")); - inner.inspect( - inspector -> { - assertThisNumberOfBridges(inspector, 3); - assertNestConstructor(inspector); - }); - D8TestCompileResult host = - compileClassesWithD8ProgramClassesMatching( - endsWith("BasicNestHostWithInnerClassConstructors")); - host.inspect( - inspector -> { - assertThisNumberOfBridges(inspector, 1); - assertNestConstructor(inspector); - }); - } - - @Test - public void testD8ConstructorNestMergeCorrect() throws Exception { - // Multiple Nest Constructor classes have to be merged here. - parameters.assumeDexRuntime(); - D8TestCompileResult inner = - compileClassesWithD8ProgramClassesMatching( - containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass")); - D8TestCompileResult host = - compileClassesWithD8ProgramClassesMatching( - endsWith("BasicNestHostWithInnerClassConstructors")); - testForD8() - .addProgramFiles(inner.writeToZip(), host.writeToZip()) - .setMinApi(parameters) - .compile() - .run(parameters.getRuntime(), getMainClass("constructors")) - .assertSuccessWithOutput(getExpectedResult("constructors")); - } - - private D8TestCompileResult compileClassesWithD8ProgramClassesMatching(Matcher<String> matcher) - throws Exception { - List<Path> matchingClasses = - CLASS_NAMES.stream() - .filter(matcher::matches) - .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION)) - .collect(toList()); - return testForD8() - .setMinApi(parameters) - .addProgramFiles(matchingClasses) - .addClasspathFiles(JAR) - .compile(); - } - - private static void assertNestConstructor(CodeInspector inspector) { - assertTrue(inspector.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)); - } - - private static void assertThisNumberOfBridges(CodeInspector inspector, int numBridges) { - for (FoundClassSubject clazz : inspector.allClasses()) { - if (!clazz.isSynthetic()) { - assertEquals(numBridges, clazz.allMethods(FoundMethodSubject::isSynthetic).size()); - } - } - } -}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java index c7c00b8..6499f3d 100644 --- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java +++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.conversion.callgraph.Node; import com.android.tools.r8.origin.SynthesizedOrigin; +import com.android.tools.r8.utils.ReachabilitySensitiveValue; import java.util.Collections; class CallGraphTestBase extends TestBase { @@ -44,7 +45,8 @@ DexEncodedField.EMPTY_ARRAY, MethodCollectionFactory.empty(), false, - DexProgramClass::invalidChecksumRequest); + DexProgramClass::invalidChecksumRequest, + ReachabilitySensitiveValue.DISABLED); Node createNode(String methodName) { DexMethod signature =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270ConstClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270ConstClassTest.java new file mode 100644 index 0000000..684f3a4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270ConstClassTest.java
@@ -0,0 +1,80 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization; + +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.effectivelytrivialphioptimization.b345248270.I; +import com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270.PublicAccessor; +import com.android.tools.r8.utils.StringUtils; +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 Regress345248270ConstClassTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private static final String EXPECTED_OUTPUT = StringUtils.lines("true"); + + @Test + public void testD8() throws Exception { + parameters.assumeDexRuntime(); + testForD8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addProgramClasses( + I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass()) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addProgramClasses( + I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .enableNeverClassInliningAnnotations() + .enableNoAccessModificationAnnotationsForMembers() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + static class TestClass { + public static Class<?> test() { + Class<?> r = null; + if (System.currentTimeMillis() > 0) { + r = PublicAccessor.getPackagePrivateImplementationClass(); + } else { + System.out.println("Do something"); + r = PublicAccessor.getPackagePrivateImplementationClass(); + } + return r; + } + + public static Class<?> test2() { + return System.currentTimeMillis() > 0 + ? PublicAccessor.getPackagePrivateImplementationClass() + : null; + } + + public static void main(String[] args) { + Class<?> c = test(); + System.out.println(c == test2()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270Test.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270Test.java new file mode 100644 index 0000000..0b0fcb3 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270Test.java
@@ -0,0 +1,113 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; +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.ir.optimize.effectivelytrivialphioptimization.b345248270.I; +import com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270.PublicAccessor; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.Matchers; +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 Regress345248270Test extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private static final String EXPECTED_OUTPUT = StringUtils.lines("true"); + + @Test + public void testD8() throws Exception { + parameters.assumeDexRuntime(); + testForD8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addProgramClasses( + I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass()) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addProgramClasses( + I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .enableNeverClassInliningAnnotations() + .enableNoAccessModificationAnnotationsForMembers() + .run(parameters.getRuntime(), TestClass.class) + .inspect( + inspector -> { + assertThat(inspector.clazz(TestClass.class), isPresent()); + // test is inlined into main. + assertThat( + inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("test"), + Matchers.isAbsent()); + ClassSubject packagePrivateImplementation = + inspector.clazz(PublicAccessor.getPackagePrivateImplementationClass()); + assertThat(packagePrivateImplementation, isPresent()); + // No direct field access of the package private field. + assertTrue( + inspector + .clazz(TestClass.class) + .mainMethod() + .streamInstructions() + .filter(InstructionSubject::isFieldAccess) + .map(InstructionSubject::getField) + .noneMatch( + f -> + f.getType() + .isIdenticalTo( + packagePrivateImplementation + .getDexProgramClass() + .getType()))); + }) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + static class TestClass { + public static I test() { + I r = null; + if (System.currentTimeMillis() > 0) { + r = PublicAccessor.getPackagePrivateImplementation(); + } else { + System.out.println("Do something"); + r = PublicAccessor.getPackagePrivateImplementation(); + } + return r; + } + + public static I test2() { + return System.currentTimeMillis() > 0 + ? PublicAccessor.getPackagePrivateImplementation() + : null; + } + + public static void main(String[] args) { + I i = test(); + System.out.println(i == test2()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/I.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/I.java new file mode 100644 index 0000000..ea07f32 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/I.java
@@ -0,0 +1,6 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270; + +public interface I {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PackagePrivateImplementation.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PackagePrivateImplementation.java new file mode 100644 index 0000000..7d615db --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PackagePrivateImplementation.java
@@ -0,0 +1,12 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NoAccessModification; + +@NeverClassInline +class PackagePrivateImplementation implements I { + @NoAccessModification static final I NULL = new PackagePrivateImplementation(); +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PublicAccessor.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PublicAccessor.java new file mode 100644 index 0000000..52ab243 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PublicAccessor.java
@@ -0,0 +1,14 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270; + +public class PublicAccessor { + public static I getPackagePrivateImplementation() { + return PackagePrivateImplementation.NULL; + } + + public static Class<?> getPackagePrivateImplementationClass() { + return PackagePrivateImplementation.class; + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java index ea19697..4ff3f4f 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
@@ -4,8 +4,8 @@ package com.android.tools.r8.ir.optimize.inliner; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; import com.android.tools.r8.R8TestRunResult; @@ -87,10 +87,16 @@ .transform(); } - private boolean compilationTargetIsMissingExceptionType() { + private boolean isStubbed() { + // Note: this is a simplified version of ApiReferenceStubber.isAlwaysStubbedType only testing + // the types relevant for this test. + return !exception.startsWith("java.") && !exception.startsWith("javax."); + } + + private boolean isPresentInRuntime() { // A CF target could target any API in the end. - return parameters.isCfRuntime() - || parameters.getApiLevel().getLevel() < EXCEPTIONS.get(exception); + return parameters.isDexRuntime() + && parameters.getApiLevel().getLevel() >= EXCEPTIONS.get(exception); } @Test @@ -101,7 +107,7 @@ .addProgramClassFileData(getClassWithCatchHandler()) .addKeepMainRule(TestClass.class) .setMinApi(parameters) - // Use the latest library so that all of the exceptions are defined. + // Use the latest library so that all the exceptions are defined. .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) .compile() .inspect(this::checkInlined) @@ -111,7 +117,7 @@ private void checkResult(R8TestRunResult runResult) { // The bootclasspath for our build of 4.4.4 does not contain various bits. Allow verify error. - if (!compilationTargetIsMissingExceptionType() + if (isPresentInRuntime() && parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V4_4_4) && (exception.startsWith("android.media") || exception.startsWith("android.view"))) { runResult.assertFailureWithErrorThatThrows(VerifyError.class); @@ -126,10 +132,15 @@ boolean mainHasInlinedCatchHandler = Streams.stream(classSubject.mainMethod().iterateTryCatches()) .anyMatch(tryCatch -> tryCatch.isCatching(exception)); - if (compilationTargetIsMissingExceptionType()) { + if (parameters.isCfRuntime()) { assertFalse(mainHasInlinedCatchHandler); } else { - assertTrue(mainHasInlinedCatchHandler); + assertEquals( + // Dalvik verifier error present up to and not including L. + parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.L) + ? parameters.getApiLevel().getLevel() >= EXCEPTIONS.get(exception) + : isPresentInRuntime() || isStubbed(), + mainHasInlinedCatchHandler); } } @@ -137,7 +148,7 @@ public static void main(String[] args) { if (args.length == 200) { - // Never called + // Never called. ClassWithCatchHandler.methodWithCatch(); } System.out.println("Done...");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java index 142d565..f952fa9 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java
@@ -3,10 +3,10 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.optimize.membervaluepropagation; -import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.NeverClassInline; import com.android.tools.r8.TestBase; @@ -42,11 +42,17 @@ .inspect( inspector -> { ClassSubject aClassSubject = inspector.clazz(A.class); - assertThat(aClassSubject, isAbsent()); + // TODO(b/339210038): Should always be absent. + assertThat( + aClassSubject, + isPresentIf(parameters.canInitNewInstanceUsingSuperclassConstructor())); MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod(); assertThat(mainMethodSubject, isPresent()); - assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstNumber(42))); + // TODO(b/339210038): Should always contain 42. + assertEquals( + parameters.canInitNewInstanceUsingSuperclassConstructor(), + mainMethodSubject.streamInstructions().noneMatch(i -> i.isConstNumber(42))); }) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("42");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java index 862fbc9..fca4473 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
@@ -4,8 +4,8 @@ package com.android.tools.r8.ir.optimize.membervaluepropagation.fields; -import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverClassInline; @@ -18,20 +18,21 @@ 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 FieldInitializedByConstantArgumentMultipleConstructorsTest extends TestBase { - @Parameter(0) - public TestParameters parameters; + private final TestParameters parameters; - @Parameters(name = "{0}") + @Parameterized.Parameters(name = "{0}") public static TestParametersCollection data() { return getTestParameters().withAllRuntimesAndApiLevels().build(); } + public FieldInitializedByConstantArgumentMultipleConstructorsTest(TestParameters parameters) { + this.parameters = parameters; + } + @Test public void test() throws Exception { testForR8(parameters.getBackend()) @@ -50,7 +51,10 @@ ClassSubject testClassSubject = inspector.clazz(TestClass.class); assertThat(testClassSubject, isPresent()); assertThat(testClassSubject.uniqueMethodWithOriginalName("live"), isPresent()); - assertThat(testClassSubject.uniqueMethodWithOriginalName("dead"), isAbsent()); + // TODO(b/280275115): Constructor inlining regresses instance field value analysis. + assertThat( + testClassSubject.uniqueMethodWithOriginalName("dead"), + isPresentIf(parameters.canInitNewInstanceUsingSuperclassConstructor())); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java new file mode 100644 index 0000000..de5ba07 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java
@@ -0,0 +1,278 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.ir.optimize.redundantarraygetelimination; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FieldSubject; +import com.android.tools.r8.utils.codeinspector.InstructionSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class RedundantArrayGetEliminationWithThrowingArrayAccessTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + private static final String EXPECTED_OUTPUT = + StringUtils.lines("-1", "-3", "-6", "-10", "42011", "9", "42010", "8", "13"); + + @Test + public void testD8() throws Exception { + parameters.assumeDexRuntime(); + testForD8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + private void countInstanceGetOfField(int count, MethodSubject method, FieldSubject field) { + assertThat(method, isPresentAndRenamed()); + assertThat(field, isPresentAndRenamed()); + assertEquals( + count, + method + .streamInstructions() + .filter(InstructionSubject::isInstancePut) + .map(InstructionSubject::getField) + .filter(f -> f.isIdenticalTo(field.getDexField())) + .count()); + } + + private void zeroArrayGet(MethodSubject method) { + countArrayGet(0, method); + } + + private void oneArrayGet(MethodSubject method) { + countArrayGet(1, method); + } + + private void countArrayGet(int count, MethodSubject method) { + assertThat(method, isPresentAndRenamed()); + assertEquals(count, method.streamInstructions().filter(InstructionSubject::isArrayGet).count()); + } + + private void oneInstanceGetOfField(MethodSubject method, FieldSubject field) { + countInstanceGetOfField(1, method, field); + } + + private void twoInstanceGetOfField(MethodSubject method, FieldSubject field) { + countInstanceGetOfField(2, method, field); + } + + private void inspect(CodeInspector inspector) { + ClassSubject testClass = inspector.clazz(TestClass.class); + assertThat(testClass, isPresentAndNotRenamed()); + FieldSubject iFld1 = testClass.uniqueFieldWithOriginalName("iFld1"); + twoInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutThrows"), iFld1); + zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutThrows")); + twoInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetThrows"), iFld1); + oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetThrows")); + twoInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutThrows"), iFld1); + zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutThrows")); + twoInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetThrows"), iFld1); + oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetThrows")); + oneInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutSucceeds"), iFld1); + zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutSucceeds")); + oneInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetSucceeds"), iFld1); + zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetSucceeds")); + twoInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutSucceeds"), iFld1); + zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutSucceeds")); + twoInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetSucceeds"), iFld1); + oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetSucceeds")); + twoInstanceGetOfField( + testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutAndGetSucceeds"), iFld1); + zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutAndGetSucceeds")); + } + + @Test + public void testD8Release() throws Exception { + parameters.assumeDexRuntime(); + testForD8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters) + .release() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters) + .enableInliningAnnotations() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT) + .inspect(this::inspect); + } + + static class TestClass { + + int iFld1; + + long[] array() { + return new long[System.currentTimeMillis() > 0 ? 10 : 20]; + } + + @NeverInline + void arrayOfKnownSizePutThrows() { + int i5 = 61127, i7 = 42011; + long[] lArr = new long[10]; + try { + iFld1 -= 1L; + lArr[i5] = i7; + iFld1 = i7; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfKnownSizeGetThrows() { + int i5 = 61127, i7 = 42011; + long[] lArr = new long[10]; + try { + iFld1 -= 2L; + long x = lArr[i5]; + iFld1 = i7; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfUnknownSizePutThrows() { + int i5 = 61127, i7 = 42011; + long[] lArr = array(); + try { + iFld1 -= 3L; + lArr[i5] = i7; + iFld1 = i7; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfUnknownSizeGetThrows() { + int i5 = 61127, i7 = 42011; + long[] lArr = array(); + try { + iFld1 -= 4L; + long x = lArr[i5]; + iFld1 = i7; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfKnownSizePutSucceeds() { + int i5 = 9, i7 = 42011; + long[] lArr = new long[10]; + try { + iFld1 -= 5L; + lArr[i5] = i7; + iFld1 = i7; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfKnownSizeGetSucceeds() { + int i5 = 9, i7 = 42011; + long[] lArr = new long[10]; + try { + iFld1 -= 6L; + long x = lArr[i5]; + iFld1 = i5; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfUnknownSizePutSucceeds() { + int i5 = 9, i7 = 42010; + long[] lArr = array(); + try { + iFld1 -= 225L; + lArr[i5] = i7; + iFld1 = i7; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfUnknownSizeGetSucceeds() { + int i5 = 8, i7 = 42011; + long[] lArr = array(); + try { + iFld1 -= 225L; + long x = lArr[i5]; + iFld1 = i5; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + @NeverInline + void arrayOfUnknownSizePutAndGetSucceeds() { + int i5 = 0, i7 = 13; + long[] lArr = array(); + try { + iFld1 -= 5L; + lArr[i5] = i7; + iFld1 = (int) lArr[i5]; + } catch (ArrayIndexOutOfBoundsException err) { + } + System.out.println(iFld1); + } + + public static void main(String[] strArr) { + TestClass test = new TestClass(); + test.arrayOfKnownSizePutThrows(); + test.arrayOfKnownSizeGetThrows(); + test.arrayOfUnknownSizePutThrows(); + test.arrayOfUnknownSizeGetThrows(); + test.arrayOfKnownSizePutSucceeds(); + test.arrayOfKnownSizeGetSucceeds(); + test.arrayOfUnknownSizePutSucceeds(); + test.arrayOfUnknownSizeGetSucceeds(); + test.arrayOfUnknownSizePutAndGetSucceeds(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java index 02195c5..43cf4a7 100644 --- a/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java +++ b/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java
@@ -80,7 +80,7 @@ @TypePattern( classNamePattern = @ClassNamePattern( - simpleNamePattern = + unqualifiedNamePattern = @StringPattern(startsWith = "ClassNameStringPatternsTest$Foo"))) }), @KeepTarget( @@ -89,7 +89,7 @@ methodParameterTypePatterns = { @TypePattern( classNamePattern = - @ClassNamePattern(simpleNamePattern = @StringPattern(endsWith = "Bar"))) + @ClassNamePattern(unqualifiedNamePattern = @StringPattern(endsWith = "Bar"))) }), }) public void foo() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java index 52850da..a024909 100644 --- a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java +++ b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
@@ -121,10 +121,11 @@ classConstant = OnMethods.class, kind = KeepItemKind.CLASS_AND_METHODS, methodAnnotatedByClassNamePattern = - @ClassNamePattern(simpleName = "MembersAnnotatedByPatternsTest$C"), + @ClassNamePattern(unqualifiedName = "MembersAnnotatedByPatternsTest$C"), constrainAnnotations = @AnnotationPattern( - namePattern = @ClassNamePattern(simpleName = "MembersAnnotatedByPatternsTest$C"))) + namePattern = + @ClassNamePattern(unqualifiedName = "MembersAnnotatedByPatternsTest$C"))) }) public void foo(Class<?> clazz) throws Exception { for (Field field : clazz.getDeclaredFields()) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java index 699ddd6..d6b97d1 100644 --- a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java +++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
@@ -195,7 +195,7 @@ @UsesReflection({ @KeepTarget( kind = KeepItemKind.CLASS_AND_METHODS, - classNamePattern = @ClassNamePattern(simpleName = "B"), + classNamePattern = @ClassNamePattern(unqualifiedName = "B"), methodName = "foo", methodReturnTypeConstant = String.class) }) @@ -216,13 +216,13 @@ kind = KeepItemKind.CLASS_AND_METHODS, classNamePattern = @ClassNamePattern( - simpleName = "A", + unqualifiedName = "A", packageName = "com.android.tools.r8.keepanno.classpatterns.pkg2"), methodName = "foo", methodReturnTypePattern = @TypePattern( classNamePattern = - @ClassNamePattern(packageName = "java.lang", simpleName = "String")))) + @ClassNamePattern(packageName = "java.lang", unqualifiedName = "String")))) public void foo() throws Exception { Util.lookupClassesAndInvokeMethods(); } @@ -240,11 +240,11 @@ kind = KeepItemKind.CLASS_AND_METHODS, classNamePattern = @ClassNamePattern( - simpleName = "A", + unqualifiedName = "A", packageName = "com.android.tools.r8.keepanno.classpatterns.pkg2"), methodName = "foo", methodReturnTypePattern = - @TypePattern(classNamePattern = @ClassNamePattern(simpleName = "String")))) + @TypePattern(classNamePattern = @ClassNamePattern(unqualifiedName = "String")))) public void foo() throws Exception { Util.lookupClassesAndInvokeMethods(); }
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java index f79c36e..5a45f17 100644 --- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java +++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
@@ -25,8 +25,8 @@ import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.USED_BY_NATIVE; import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.USED_BY_REFLECTION; import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.USES_REFLECTION; +import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.getUnqualifiedName; import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.quote; -import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.simpleName; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.keepanno.doctests.ForApiDocumentationTest; @@ -116,7 +116,7 @@ } private static String getPrefix(ClassReference annoType) { - return "`@" + simpleName(annoType); + return "`@" + getUnqualifiedName(annoType); } private static String getSuffix() { @@ -196,24 +196,24 @@ } private String getMdAnnotationLink(ClassReference clazz) { - return "[@" + simpleName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")"; + return "[@" + getUnqualifiedName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")"; } private String getMdAnnotationPropertyLink(ClassReference clazz, GroupMember method) { String methodName = method.name; String url = getClassJavaDocUrl(clazz) + "#" + methodName + "()"; - return "[@" + simpleName(clazz) + "." + methodName + "](" + url + ")"; + return "[@" + getUnqualifiedName(clazz) + "." + methodName + "](" + url + ")"; } private String getMdEnumLink(ClassReference clazz) { - return "[" + simpleName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")"; + return "[" + getUnqualifiedName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")"; } private String getMdEnumMemberLink(EnumReference enumMember) { ClassReference clazz = enumMember.enumClass; String enumName = enumMember.name(); String url = getClassJavaDocUrl(clazz) + "#" + enumName; - return "[" + simpleName(clazz) + "." + enumName + "](" + url + ")"; + return "[" + getUnqualifiedName(clazz) + "." + enumName + "](" + url + ")"; } private void println() {
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 index c2a658d..b6be4dc 100644 --- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java +++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -199,20 +199,20 @@ ImmutableList.of(FIELD_ACCESS_VOLATILE, FIELD_ACCESS_TRANSIENT); private static final String DEFAULT_INVALID_STRING_PATTERN = - "@" + simpleName(STRING_PATTERN) + "(exact = \"\")"; + "@" + getUnqualifiedName(STRING_PATTERN) + "(exact = \"\")"; private static final String DEFAULT_INVALID_TYPE_PATTERN = - "@" + simpleName(TYPE_PATTERN) + "(name = \"\")"; + "@" + getUnqualifiedName(TYPE_PATTERN) + "(name = \"\")"; private static final String DEFAULT_INVALID_CLASS_NAME_PATTERN = - "@" + simpleName(CLASS_NAME_PATTERN) + "(simpleName = \"\")"; + "@" + getUnqualifiedName(CLASS_NAME_PATTERN) + "(unqualifiedName = \"\")"; private static final String DEFAULT_ANY_INSTANCE_OF_PATTERN = - "@" + simpleName(INSTANCE_OF_PATTERN) + "()"; + "@" + getUnqualifiedName(INSTANCE_OF_PATTERN) + "()"; - private static ClassReference astClass(String simpleName) { - return classFromTypeName(AST_PKG + simpleName); + private static ClassReference astClass(String unqualifiedName) { + return classFromTypeName(AST_PKG + unqualifiedName); } - private static ClassReference annoClass(String simpleName) { - return classFromTypeName(ANNO_PKG + simpleName); + private static ClassReference annoClass(String unqualifiedName) { + return classFromTypeName(ANNO_PKG + unqualifiedName); } private static EnumReference enumRef(ClassReference enumClass, String valueName) { @@ -223,7 +223,7 @@ return "\"" + str + "\""; } - public static String simpleName(ClassReference clazz) { + public static String getUnqualifiedName(ClassReference clazz) { String binaryName = clazz.getBinaryName(); return binaryName.substring(binaryName.lastIndexOf('/') + 1); } @@ -271,12 +271,12 @@ public GroupMember requiredValue(ClassReference type) { assert valueDefault == null; - return setType(simpleName(type)); + return setType(getUnqualifiedName(type)); } public GroupMember requiredArrayValue(ClassReference type) { assert valueDefault == null; - return setType(simpleName(type) + "[]"); + return setType(getUnqualifiedName(type) + "[]"); } public GroupMember requiredStringValue() { @@ -289,12 +289,12 @@ } public GroupMember defaultValue(ClassReference type, String value) { - setType(simpleName(type)); + setType(getUnqualifiedName(type)); return setValue(value); } public GroupMember defaultArrayValue(ClassReference type, String value) { - setType(simpleName(type) + "[]"); + setType(getUnqualifiedName(type) + "[]"); return setValue("{" + value + "}"); } @@ -640,21 +640,23 @@ .defaultObjectClass()); } - private Group classNamePatternSimpleNameGroup() { - return new Group("class-simple-name") + private Group classNamePatternUnqualifiedNameGroup() { + return new Group("class-unqualified-name") .addMember( - new GroupMember("simpleName") - .setDocTitle("Exact simple name of the class or interface.") + new GroupMember("unqualifiedName") + .setDocTitle("Exact and unqualified name of the class or interface.") .addParagraph( - "For example, the simple name of {@code com.example.MyClass} is {@code" - + " MyClass}.") - .addParagraph("The default matches any simple name.") + "For example, the unqualified name of {@code com.example.MyClass} is {@code" + + " MyClass}.", + "Note that for inner classes a `$` will appear in the unqualified name," + + "such as, {@code MyClass$MyInnerClass}.") + .addParagraph("The default matches any unqualified name.") .defaultEmptyString()) .addMember( - new GroupMember("simpleNamePattern") - .setDocTitle("Define the simple-name pattern by a string pattern.") - .addParagraph("The default matches any simple name.") - .setDocReturn("The string pattern of the class simple name.") + new GroupMember("unqualifiedNamePattern") + .setDocTitle("Define the unqualified class-name pattern by a string pattern.") + .setDocReturn("The string pattern of the unqualified class name.") + .addParagraph("The default matches any unqualified name.") .defaultValue(STRING_PATTERN, DEFAULT_INVALID_STRING_PATTERN)); } @@ -1246,7 +1248,7 @@ .printDoc(this::println); println("@Target(ElementType.ANNOTATION_TYPE)"); println("@Retention(RetentionPolicy.CLASS)"); - println("public @interface " + simpleName(STRING_PATTERN) + " {"); + println("public @interface " + getUnqualifiedName(STRING_PATTERN) + " {"); println(); withIndent( () -> { @@ -1275,7 +1277,7 @@ .printDoc(this::println); println("@Target(ElementType.ANNOTATION_TYPE)"); println("@Retention(RetentionPolicy.CLASS)"); - println("public @interface " + simpleName(TYPE_PATTERN) + " {"); + println("public @interface " + getUnqualifiedName(TYPE_PATTERN) + " {"); println(); withIndent(() -> typePatternGroup().generate(this)); println(); @@ -1294,18 +1296,18 @@ .printDoc(this::println); println("@Target(ElementType.ANNOTATION_TYPE)"); println("@Retention(RetentionPolicy.CLASS)"); - println("public @interface " + simpleName(CLASS_NAME_PATTERN) + " {"); + println("public @interface " + getUnqualifiedName(CLASS_NAME_PATTERN) + " {"); println(); withIndent( () -> { Group exactNameGroup = classNamePatternFullNameGroup(); - Group simpleNameGroup = classNamePatternSimpleNameGroup(); + Group unqualifiedNameGroup = classNamePatternUnqualifiedNameGroup(); Group packageGroup = classNamePatternPackageGroup(); - exactNameGroup.addMutuallyExclusiveGroups(simpleNameGroup, packageGroup); + exactNameGroup.addMutuallyExclusiveGroups(unqualifiedNameGroup, packageGroup); exactNameGroup.generate(this); println(); - simpleNameGroup.generate(this); + unqualifiedNameGroup.generate(this); println(); packageGroup.generate(this); }); @@ -1323,7 +1325,7 @@ .printDoc(this::println); println("@Target(ElementType.ANNOTATION_TYPE)"); println("@Retention(RetentionPolicy.CLASS)"); - println("public @interface " + simpleName(INSTANCE_OF_PATTERN) + " {"); + println("public @interface " + getUnqualifiedName(INSTANCE_OF_PATTERN) + " {"); println(); withIndent( () -> { @@ -1347,7 +1349,7 @@ .printDoc(this::println); println("@Target(ElementType.ANNOTATION_TYPE)"); println("@Retention(RetentionPolicy.CLASS)"); - println("public @interface " + simpleName(ANNOTATION_PATTERN) + " {"); + println("public @interface " + getUnqualifiedName(ANNOTATION_PATTERN) + " {"); println(); withIndent( () -> { @@ -1522,10 +1524,10 @@ "Assume the item of the annotation is denoted by 'CTX' and referred to as its" + " context.") .addCodeBlock( - annoSimpleName(USES_REFLECTION) + annoUnqualifiedName(USES_REFLECTION) + "(value = targets, [additionalPreconditions = preconditions])", "==>", - annoSimpleName(KEEP_EDGE) + "(", + annoUnqualifiedName(KEEP_EDGE) + "(", " consequences = targets,", " preconditions = {createConditionFromContext(CTX)} + preconditions", ")", @@ -1555,7 +1557,7 @@ "@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD," + " ElementType.CONSTRUCTOR})"); println("@Retention(RetentionPolicy.CLASS)"); - println("public @interface " + simpleName(USES_REFLECTION) + " {"); + println("public @interface " + getUnqualifiedName(USES_REFLECTION) + " {"); println(); withIndent( () -> { @@ -1642,12 +1644,12 @@ println("}"); } - private static String annoSimpleName(ClassReference clazz) { - return "@" + simpleName(clazz); + private static String annoUnqualifiedName(ClassReference clazz) { + return "@" + getUnqualifiedName(clazz); } private static String docLink(ClassReference clazz) { - return "{@link " + simpleName(clazz) + "}"; + return "{@link " + getUnqualifiedName(clazz) + "}"; } private static String docLink(GroupMember member) { @@ -1655,7 +1657,7 @@ } private static String docEnumLink(EnumReference enumRef) { - return "{@link " + simpleName(enumRef.enumClass) + "#" + enumRef.enumValue + "}"; + return "{@link " + getUnqualifiedName(enumRef.enumClass) + "#" + enumRef.enumValue + "}"; } private static String docEnumLinkList(EnumReference... values) { @@ -1835,7 +1837,7 @@ withIndent( () -> { generateAnnotationConstants(USED_BY_NATIVE); - println("// Content is the same as " + simpleName(USED_BY_REFLECTION) + "."); + println("// Content is the same as " + getUnqualifiedName(USED_BY_REFLECTION) + "."); }); println("}"); println(); @@ -2088,7 +2090,7 @@ () -> { generateAnnotationConstants(CLASS_NAME_PATTERN); classNamePatternFullNameGroup().generateConstants(this); - classNamePatternSimpleNameGroup().generateConstants(this); + classNamePatternUnqualifiedNameGroup().generateConstants(this); classNamePatternPackageGroup().generateConstants(this); }); println("}");
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringVerticallyMergedClassTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringVerticallyMergedClassTest.java new file mode 100644 index 0000000..022d375 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringVerticallyMergedClassTest.java
@@ -0,0 +1,72 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.naming; + +import com.android.tools.r8.NeverPropagateValue; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import java.util.HashMap; +import java.util.Map; +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 IdentifierNameStringVerticallyMergedClassTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addKeepClassAndMembersRulesWithAllowObfuscation(A.class) + .addKeepRules("-identifiernamestring class " + Main.class.getTypeName() + " { <fields>; }") + .addVerticallyMergedClassesInspector( + inspector -> inspector.assertMergedIntoSubtype(I.class).assertNoOtherClassesMerged()) + .enableMemberValuePropagationAnnotations() + .setMinApi(parameters) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A"); + } + + static class Main { + + @NeverPropagateValue + static String s = "com.android.tools.r8.naming.IdentifierNameStringVerticallyMergedClassTest$I"; + + public static void main(String[] args) throws Exception { + String key = System.currentTimeMillis() > 0 ? s : args[0]; + A a = (A) createInstanceMap().get(key); + System.out.println(a); + } + + static Map<String, Object> createInstanceMap() { + Map<String, Object> map = new HashMap<>(); + map.put(I.class.getName(), new A()); + return map; + } + } + + interface I {} + + public static class A implements I { + + @Override + public String toString() { + return "A"; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress339371242.java b/src/test/java/com/android/tools/r8/regress/Regress339371242.java new file mode 100644 index 0000000..2201a1a --- /dev/null +++ b/src/test/java/com/android/tools/r8/regress/Regress339371242.java
@@ -0,0 +1,73 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.regress; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class Regress339371242 extends TestBase { + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultRuntimes().withAllApiLevels().build(); + } + + public Regress339371242(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class, WithLibraryFinalizer.class) + .addLibraryClasses(LibraryClassWithFinalizer.class) + .addDefaultRuntimeLibrary(parameters) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters) + .compile() + .inspect( + codeInspector -> { + ClassSubject clazz = codeInspector.clazz(TestClass.class); + assertThat(clazz, isPresent()); + assertThat(clazz.uniqueFieldWithOriginalName("handler"), isPresent()); + }); + } + + public static class LibraryClassWithFinalizer { + + @Override + protected void finalize() throws Throwable { + super.finalize(); + } + } + + public static class WithLibraryFinalizer extends LibraryClassWithFinalizer {} + + public static class TestClass { + private final WithLibraryFinalizer handler; + + public static void main(String[] args) { + new TestClass().foo(); + } + + public TestClass() { + handler = new WithLibraryFinalizer(); + } + + public void foo() { + System.out.println("ab"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java new file mode 100644 index 0000000..a81e419 --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java
@@ -0,0 +1,80 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.resolution; + +import com.android.tools.r8.NeverInline; +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 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; + +// Regression test for b/342802978 after R8 class inlining. +@RunWith(Parameterized.class) +public class ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return TestParameters.builder() + .withAllRuntimes() + .withApiLevel(apiLevelWithDefaultInterfaceMethodsSupport()) + .build(); + } + + @Parameter public TestParameters parameters; + + @Test + public void testReference() throws Exception { + testForRuntime(parameters) + .addProgramClasses(I.class, A.class, B.class, TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .apply(this::checkOutput); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .enableInliningAnnotations() + .setMinApi(parameters) + .addKeepMainRule(TestClass.class) + .addKeepClassAndMembersRules(I.class) + .addProgramClasses(I.class, A.class, B.class, TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .apply(this::checkOutput); + } + + private void checkOutput(SingleTestRunResult<?> r) { + r.assertSuccessWithOutputLines("0"); + } + + interface I { + + default String[] myClone(String[] strings) { + return new B().myClone(strings); + } + } + + static class A implements I {} + + static class B { + + @NeverInline + public String[] myClone(String[] strings) { + return strings.clone(); + } + } + + static class TestClass { + + public static void main(String[] args) { + I i = System.nanoTime() > 0 ? new A() : null; + System.out.println(i.myClone(args).length); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java new file mode 100644 index 0000000..edae17d --- /dev/null +++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java
@@ -0,0 +1,86 @@ +// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.resolution; + +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 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; + +// Regression test for b/342802978, but with R8 giving rise to the issue after inlining. +@RunWith(Parameterized.class) +public class ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return TestParameters.builder() + .withAllRuntimes() + .withApiLevel(apiLevelWithDefaultInterfaceMethodsSupport()) + .build(); + } + + @Parameter public TestParameters parameters; + + @Test + public void testReference() throws Exception { + testForRuntime(parameters) + .addProgramClasses(I.class, A.class, TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("0"); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .setMinApi(parameters) + .addKeepMainRule(TestClass.class) + .addProgramClasses(I.class, A.class, TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .apply(this::checkOutput); + } + + private void checkOutput(SingleTestRunResult<?> r) { + r.assertSuccessWithOutputLines("0"); + } + + interface I { + + static String[] myClone(String[] strings) { + try { + return A.inlinedClone(strings); + } catch (RuntimeException e) { + // Extra code to avoid simple inlining. + System.out.println("Unexpected exception: " + e); + throw e; + } + } + } + + static class A { + + public static String[] inlinedClone(String[] strings) { + return strings.clone(); + } + } + + static class TestClass { + + public static void main(String[] args) { + int count = 0; + // Repeated calls to avoid single-caller inlining of the interface method. + count += I.myClone(args).length; + count += I.myClone(args).length; + count += I.myClone(args).length; + count += I.myClone(args).length; + count += I.myClone(args).length; + System.out.println(count); + } + } +}
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java index cb49489..2061f8f 100644 --- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.TestRuntime.CfRuntime; import com.android.tools.r8.ToolHelper.DexVm.Kind; import com.android.tools.r8.benchmarks.BenchmarkResults; +import com.android.tools.r8.cf.CfVersion; import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion; import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.dex.Marker.Tool; @@ -26,6 +27,7 @@ import com.android.tools.r8.graph.AssemblyWriter; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DirectMappedDexApplication; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.Position; @@ -1601,6 +1603,11 @@ "Unexpected configuration with identical test and test-base path" + resolveTestPath); } if (foundTestPath && foundBasePath) { + if (identicalClassesIgnoringClassFileVersion(resolveTestPath, resolveBasePath)) { + System.out.println( + "Ambiguous yet identical test file: " + resolveTestPath + " and " + resolveBasePath); + return resolveBasePath; + } throw new RuntimeException( "Ambiguous test file: " + resolveTestPath + " and " + resolveBasePath); } @@ -1617,6 +1624,27 @@ + resolveBasePath); } + private static boolean identicalClassesIgnoringClassFileVersion( + Path resolveBasePath, Path resolveTestPath) { + InternalOptions options = new InternalOptions(); + + try { + AndroidApp input1 = AndroidApp.builder().addProgramFiles(resolveBasePath).build(); + DexApplication app1 = new ApplicationReader(input1, options, Timing.empty()).read(); + DexProgramClass clazz1 = app1.classes().iterator().next(); + clazz1.downgradeInitialClassFileVersion(CfVersion.V1_8); + + AndroidApp input2 = AndroidApp.builder().addProgramFiles(resolveTestPath).build(); + DexApplication app2 = new ApplicationReader(input2, options, Timing.empty()).read(); + DexProgramClass clazz2 = app2.classes().iterator().next(); + clazz2.downgradeInitialClassFileVersion(CfVersion.V1_8); + + return clazz1.compareTo(clazz2) == 0; + } catch (Exception e) { + return false; + } + } + public static Collection<Path> getClassFilesForInnerClasses(Collection<Class<?>> classes) throws IOException { return getClassFilesForInnerClasses(computeLegacyOrGradleSpecifiedLocation(), classes);
diff --git a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java index 3e52ba7..9dcfa97 100644 --- a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java +++ b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.benchmarks; import com.android.tools.r8.utils.StringUtils; +import java.io.PrintStream; public interface BenchmarkResults { // Append a runtime result. This may be summed or averaged depending on the benchmark set up. @@ -19,7 +20,9 @@ // This will throw if called on a benchmark without sub-benchmarks. BenchmarkResults getSubResults(String name); - void printResults(ResultMode resultMode); + void printResults(ResultMode resultMode, boolean failOnCodeSizeDifferences); + + void writeResults(PrintStream out); static String prettyTime(long nanoTime) { return "" + (nanoTime / 1000000) + " ms";
diff --git a/tools/dex2oat.py b/tools/dex2oat.py index 8df8f86..58b2418 100755 --- a/tools/dex2oat.py +++ b/tools/dex2oat.py
@@ -152,9 +152,10 @@ append_dex2oat_verbose_flags(options, cmd) utils.PrintCmd(cmd) subprocess.check_call(cmd) - cmd = adb_cmd(serial, 'logcat', '-d', '-s', 'dex2oat') + cmd = adb_cmd(serial, 'logcat', '-d', '-s', 'dex2oat', 'dex2oat_original') utils.PrintCmd(cmd) subprocess.check_call(cmd) + return 0
diff --git a/tools/historic_run.py b/tools/historic_run.py index 37c3dae..9822648 100755 --- a/tools/historic_run.py +++ b/tools/historic_run.py
@@ -34,6 +34,10 @@ result.add_option('--output', default='build', help='Directory where to output results') + result.add_option('--timeout', + default=1000, + help='Timeout in seconds (-1 for no timeout)', + type=int) return result.parse_args(argv) @@ -52,16 +56,30 @@ def __repr__(self): return self.__str__() + def hash(self): + return self.git_hash + + def title(self): + result = subprocess.check_output( + ['git', 'show-branch', '--no-name', self.git_hash]).decode('utf-8') + return result.strip() + + def author_name(self): + result = subprocess.check_output([ + 'git', 'show', '--no-notes', '--no-patch', '--pretty=%an', + self.git_hash + ]).decode('utf-8') + return result.strip() + + def committer_timestamp(self): + return self.timestamp + def git_commit_from_hash(hash): - commit_timestamp = subprocess.check_output([ - 'git', - 'show', - '--no-patch', - '--no-notes', - '--pretty=\'%ct\'', - hash - ]).decode().strip().strip('\'') + commit_timestamp_str = subprocess.check_output( + ['git', 'show', '--no-patch', '--no-notes', '--pretty=%ct', + hash]).decode('utf-8').strip() + commit_timestamp = int(commit_timestamp_str) destination_dir = '%s/%s/' % (MASTER_COMMITS, hash) destination = '%s%s' % (destination_dir, 'r8.jar') commit = GitCommit(hash, destination_dir, destination, commit_timestamp) @@ -70,9 +88,12 @@ def enumerate_git_commits(top, bottom): if bottom is None: - output = subprocess.check_output(['git', 'rev-list', '--first-parent', '-n', 1000, top]) + output = subprocess.check_output( + ['git', 'rev-list', '--first-parent', '-n', 1000, top]) else: - output = subprocess.check_output(['git', 'rev-list', '--first-parent', '%s^..%s' % (bottom, top)]) + output = subprocess.check_output( + ['git', 'rev-list', '--first-parent', + '%s^..%s' % (bottom, top)]) commits = [] for c in output.decode().splitlines(): commit_hash = c.strip() @@ -81,8 +102,8 @@ def get_available_commits(commits): - cloud_commits = subprocess.check_output( - ['gsutil.py', 'ls', MASTER_COMMITS]).decode().splitlines() + cloud_commits = subprocess.check_output(['gsutil.py', 'ls', MASTER_COMMITS + ]).decode().splitlines() available_commits = [] for commit in commits: if commit.destination_dir in cloud_commits: @@ -129,7 +150,7 @@ count = 0 for index in commit_permutations: count += 1 - print('Running commit %s out of %s' % (count, len(commits))) + print('\nRunning commit %s out of %s' % (count, len(commits))) commit = commits[index] if not utils.cloud_storage_exists(commit.destination): # We may have a directory, but no r8.jar @@ -164,11 +185,13 @@ def run_cmd(options, commit): - cmd = [options.cmd, commit.git_hash] + cmd = options.cmd.split(' ') + cmd.append(commit.git_hash) output_path = options.output or 'build' time_commit = '%s_%s' % (commit.timestamp, commit.git_hash) time_commit_path = os.path.join(output_path, time_commit) print(' '.join(cmd)) + status = None if not options.dry_run: if not os.path.exists(time_commit_path): os.makedirs(time_commit_path) @@ -177,15 +200,20 @@ with open(stdout_path, 'w') as stdout: with open(stderr_path, 'w') as stderr: process = subprocess.Popen(cmd, stdout=stdout, stderr=stderr) - timeout = 1000 - while process.poll() is None and timeout > 0: + timeout = options.timeout + while process.poll() is None and timeout != 0: time.sleep(1) timeout -= 1 if process.poll() is None: process.kill() print("Task timed out") stderr.write("timeout\n") - print('Wrote outputs to: %s' % time_commit_path) + status = 'TIMED OUT' + else: + returncode = process.returncode + status = 'SUCCESS' if returncode == 0 else f'FAILED ({returncode})' + print(f'Wrote outputs to: {time_commit_path}') + print(status) def main(argv):
diff --git a/tools/perf.py b/tools/perf.py index 3762c5d..0186435 100755 --- a/tools/perf.py +++ b/tools/perf.py
@@ -4,14 +4,24 @@ # BSD-style license that can be found in the LICENSE file. import argparse +import compiledump +import json import os +import shutil import subprocess import sys +import upload_benchmark_data_to_google_storage import utils -DEFAULT_ITERATIONS = 10 -BUCKET = "r8-test-results" +BUCKET = "r8-perf-results" +SAMPLE_BENCHMARK_RESULT_JSON = { + 'benchmark_name': '<benchmark_name>', + 'results': [{ + 'code_size': 0, + 'runtime': 0 + }] +} # Result structure on cloud storage # gs://bucket/benchmark_results/APP/TARGET/GIT_HASH/results @@ -19,68 +29,174 @@ # where results simply contains the result lines and # meta contains information about the execution (machine) + def ParseOptions(): result = argparse.ArgumentParser() result.add_argument('--app', - help='Specific app to measure.', - default='NowInAndroidApp') + help='Specific app(s) to measure.', + action='append') + result.add_argument('--iterations', + help='How many times run_benchmark is run.', + type=int, + default=1) + result.add_argument('--iterations-inner', + help='How many iterations to run inside run_benchmark.', + type=int, + default=10) + result.add_argument('--outdir', + help='Output directory for running locally.') + result.add_argument('--skip-if-output-exists', + help='Skip if output exists.', + action='store_true', + default=False) result.add_argument('--target', help='Specific target to run on.', default='r8-full', choices=['d8', 'r8-full', 'r8-force', 'r8-compat']) - result.add_argument('--iterations', - help='How many iterations to run.', - type=int, - default=DEFAULT_ITERATIONS) - return result.parse_known_args() + result.add_argument('--verbose', + help='To enable verbose logging.', + action='store_true', + default=False) + result.add_argument('--version', + '-v', + help='Use R8 hash for the run (default local build)', + default=None) + options, args = result.parse_known_args() + options.apps = options.app or ['NowInAndroidApp', 'TiviApp'] + options.quiet = not options.verbose + del options.app + return options, args -def ParseOutput(output, options, log_array): - for line in output.decode('utf-8').splitlines(): - print(" -- " + line) - # Output lines look like: - # Benchmark results for NowInAndroidApp on target r8-full - # warmup reporting mode: average - # warmup iterations: 1 - # warmup total time: 58580 ms - # benchmark reporting mode: average - # benchmark iterations: 1 - # benchmark total time: 39613 ms - # NowInAndroidApp(RunTimeRaw): 39154 ms - # NowInAndroidApp(CodeSize): 5102196 - if line.startswith(options.app + "("): - log_array.append(line) +def MergeBenchmarkResultJsonFiles(benchmark_result_json_files): + merged_benchmark_result_json = None + for benchmark_result_json_file in benchmark_result_json_files: + benchmark_result_json = ParseBenchmarkResultJsonFile( + benchmark_result_json_file) + if merged_benchmark_result_json is None: + merged_benchmark_result_json = benchmark_result_json + else: + MergeBenchmarkResultJsonFile(merged_benchmark_result_json, + benchmark_result_json) + return merged_benchmark_result_json -def GetGSLocation(app, target, filename): - return "gs://%s/%s/%s/%s/%s" % (BUCKET, app, target, - utils.get_HEAD_sha1(), filename) +def MergeBenchmarkResultJsonFile(merged_benchmark_result_json, + benchmark_result_json): + assert benchmark_result_json.keys() == SAMPLE_BENCHMARK_RESULT_JSON.keys() + assert merged_benchmark_result_json[ + 'benchmark_name'] == benchmark_result_json['benchmark_name'] + merged_benchmark_result_json['results'].extend( + benchmark_result_json['results']) + +def ParseBenchmarkResultJsonFile(result_json_file): + with open(result_json_file, 'r') as f: + lines = f.readlines() + return json.loads(''.join(lines)) + + +def GetArtifactLocation(app, target, version, filename): + version_or_head = version or utils.get_HEAD_sha1() + return f'{app}/{target}/{version_or_head}/{filename}' + + +def GetGSLocation(filename, bucket=BUCKET): + return f'gs://{bucket}/{filename}' + + +def ArchiveOutputFile(file, dest, bucket=BUCKET, header=None, outdir=None): + if outdir: + dest_in_outdir = os.path.join(outdir, dest) + os.makedirs(os.path.dirname(dest_in_outdir), exist_ok=True) + shutil.copyfile(file, dest_in_outdir) + else: + utils.upload_file_to_cloud_storage(file, + GetGSLocation(dest, bucket=bucket), + header=header) + + +# Usage with historic_run.py: +# ./tools/historic_run.py +# --cmd "perf.py --skip-if-output-exists --version" +# --timeout -1 +# --top 3373fd18453835bf49bff9f02523a507a2ebf317 +# --bottom 7486f01e0622cb5935b77a92b59ddf1ca8dbd2e2 def main(): - (options, args) = ParseOptions() - cmd = ['tools/run_benchmark.py', '--target', options.target, - '--benchmark', options.app] - log_array = [] - # Build and warmup - output = subprocess.check_output(cmd) - ParseOutput(output, options, log_array) - cmd.append('--no-build') - for i in range(options.iterations): - output = subprocess.check_output(cmd) - ParseOutput(output, options, log_array) + options, args = ParseOptions() with utils.TempDir() as temp: - result_file = os.path.join(temp, "result_file") - with open(result_file, 'w') as f: - for l in log_array: - f.write(l + "\n") - utils.upload_file_to_cloud_storage(result_file, - GetGSLocation(options.app, options.target, 'results')) - if os.environ.get('SWARMING_BOT_ID'): - meta_file = os.path.join(temp, "meta") - with open(meta_file, 'w') as f: - f.write("Produced by: " + os.environ.get('SWARMING_BOT_ID')) - utils.upload_file_to_cloud_storage(meta_file, - GetGSLocation(options.app, options.target, 'meta')) + if options.version: + # Download r8.jar once instead of once per run_benchmark.py invocation. + download_options = argparse.Namespace(no_build=True, nolib=True) + r8jar = compiledump.download_distribution(options.version, + download_options, temp) + + for app in options.apps: + if options.skip_if_output_exists: + if options.outdir: + raise NotImplementedError + output = GetGSLocation( + GetArtifactLocation(app, options.target, options.version, + 'result.json')) + if utils.cloud_storage_exists(output): + print(f'Skipping run, {output} already exists.') + continue + + base_cmd = [ + 'tools/run_benchmark.py', '--benchmark', app, '--target', + options.target + ] + if options.verbose: + base_cmd.append('--verbose') + if options.version: + base_cmd.extend([ + '--version', options.version, '--version-jar', r8jar, + '--nolib' + ]) + + # Build + utils.Print(f'Preparing {app}', quiet=options.quiet) + build_cmd = base_cmd + ['--iterations', '0'] + subprocess.check_output(build_cmd) + + # Run benchmark. + benchmark_result_json_files = [] + for i in range(options.iterations): + utils.Print(f'Benchmarking {app} ({i+1}/{options.iterations})', + quiet=options.quiet) + benchhmark_result_file = os.path.join(temp, f'result_file_{i}') + iteration_cmd = base_cmd + [ + '--iterations', + str(options.iterations_inner), '--output', + benchhmark_result_file, '--no-build' + ] + subprocess.check_output(iteration_cmd) + benchmark_result_json_files.append(benchhmark_result_file) + + # Merge results and write output. + result_file = os.path.join(temp, 'result_file') + with open(result_file, 'w') as f: + json.dump( + MergeBenchmarkResultJsonFiles(benchmark_result_json_files), + f) + ArchiveOutputFile(result_file, + GetArtifactLocation(app, options.target, + options.version, + 'result.json'), + outdir=options.outdir) + + # Write metadata. + if utils.is_bot(): + meta_file = os.path.join(temp, "meta") + with open(meta_file, 'w') as f: + f.write("Produced by: " + os.environ.get('SWARMING_BOT_ID')) + ArchiveOutputFile(meta_file, + GetArtifactLocation(app, options.target, + options.version, 'meta'), + outdir=options.outdir) + + if utils.is_bot(): + upload_benchmark_data_to_google_storage.run() if __name__ == '__main__':
diff --git a/tools/perf/benchmark_data.json b/tools/perf/benchmark_data.json new file mode 100644 index 0000000..9d18466 --- /dev/null +++ b/tools/perf/benchmark_data.json
@@ -0,0 +1,24 @@ +[{ + "author": "Christoffer Adamsen", + "hash": "e6a33325a4a4bac599ffce1f75cab8505a35d02a", + "submitted": "Thu Jun 06 13:07:44 2024 +0200", + "title": "Allow compiling CompileDumpCompatR8 in isolation", + "benchmarks": { + "NowInAndroidApp": { + "benchmark_name": "NowInAndroidApp", + "results": [ + { "code_size": 42, "runtime": 1 }, + { "code_size": 42, "runtime": 2 }, + { "code_size": 42, "runtime": 3 } + ] + }, + "TiviApp": { + "benchmark_name": "TiviApp", + "results": [ + { "code_size": 84, "runtime": 4 }, + { "code_size": 84, "runtime": 5 }, + { "code_size": 84, "runtime": 6 } + ] + } + } +}] \ No newline at end of file
diff --git a/tools/perf/index.html b/tools/perf/index.html new file mode 100644 index 0000000..8d3089c --- /dev/null +++ b/tools/perf/index.html
@@ -0,0 +1,337 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>R8 perf</title> +</head> +<body> + <select id="benchmark-selector"></select> + <div> + <canvas id="myChart"></canvas> + </div> + <div> + <div style="float: left; width: 50%"> + <button type="button" id="show-more-left" disabled>⇐</button> + <button type="button" id="show-less-left">⇒</button> + </div> + <div style="float: left; text-align: right; width: 50%"> + <button type="button" id="show-less-right">⇐</button> + <button type="button" id="show-more-right" disabled>⇒</button> + </div> + </div> + <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script> + <script type="module"> + // Utility methods. + Array.prototype.any = function(predicate) { + for (const element of this.values()) { + if (predicate(element)) { + return true; + } + } + return false; + }; + Array.prototype.first = function() { + return this[0]; + }; + Array.prototype.avg = function() { + return this.reduce(function(x, y) { return x + y; }, 0) / this.length; + }; + Array.prototype.min = function() { + return this.reduce(function(x, y) { return x === null ? y : Math.min(x, y); }, null); + }; + Array.prototype.reverseInPlace = function() { + for (var i = 0; i < Math.floor(this.length / 2); i++) { + var temp = this[i]; + this[i] = this[this.length - i - 1]; + this[this.length - i - 1] = temp; + } + }; + Number.prototype.ns_to_s = function() { + const seconds = this/10E8; + const seconds_with_one_decimal = Math.round(seconds*10)/10; + return seconds; + }; + + // Import and reverse commits so that newest are last. + import commits from "./benchmark_data.json" with { type: "json" }; + commits.reverseInPlace() + + // Amend the commits with their unique index. + for (var i = 0; i < commits.length; i++) { + commits[i].index = i; + } + + // DOM references. + const benchmarkSelector = document.getElementById('benchmark-selector') + const canvas = document.getElementById('myChart'); + const showMoreLeft = document.getElementById('show-more-left'); + const showLessLeft = document.getElementById('show-less-left'); + const showLessRight = document.getElementById('show-less-right'); + const showMoreRight = document.getElementById('show-more-right'); + + // Initialize benchmark selector. + const benchmarks = new Set(); + for (const commit of commits.values()) { + for (const benchmark in commit.benchmarks) { + benchmarks.add(benchmark) + } + } + var selectedBenchmark = window.location.hash.substring(1) + if (!benchmarks.has(selectedBenchmark)) { + selectedBenchmark = benchmarks.values().next().value; + } + for (const benchmark of benchmarks.values()) { + const opt = document.createElement('option'); + opt.value = benchmark; + opt.innerHTML = benchmark; + benchmarkSelector.appendChild(opt); + if (benchmark == selectedBenchmark) { + benchmarkSelector.selectedIndex = benchmarkSelector.options.length - 1 + } + } + benchmarkSelector.onchange = function(event) { + selectedBenchmark = + benchmarkSelector.options[benchmarkSelector.selectedIndex].value; + updateChart(); + window.location.hash = selectedBenchmark; + }; + + // Chart data provider. + function getData(start = 0, end = commits.length) { + const filteredCommits = + commits + .slice(start, end); + //.filter( + // commit => + // selectedBenchmark in commit.benchmarks + // && commit.benchmarks[selectedBenchmark].results.length > 0); + const labels = filteredCommits.map((c, i) => i); + const codeSizeData = + filteredCommits.map( + (c, i) => + selectedBenchmark in filteredCommits[i].benchmarks + ? filteredCommits[i] + .benchmarks[selectedBenchmark] + .results + .first() + .code_size + : NaN); + const codeSizeScatterData = []; + for (const commit of filteredCommits.values()) { + if (!(selectedBenchmark in commit.benchmarks)) { + continue; + } + const codeSizes = + commit.benchmarks[selectedBenchmark].results.map(result => result.code_size) + const expectedCodeSize = codeSizes.first(); + if (codeSizes.any(codeSize => codeSize != expectedCodeSize)) { + const seen = new Set(); + seen.add(expectedCodeSize); + for (const codeSize of codeSizes.values()) { + if (!seen.has(codeSize)) { + codeSizeScatterData.push({ x: commit.index, y: codeSize }); + seen.add(codeSize); + } + } + } + } + const runtimeData = + filteredCommits.map( + (c, i) => + selectedBenchmark in filteredCommits[i].benchmarks + ? filteredCommits[i] + .benchmarks[selectedBenchmark] + .results + .map(result => result.runtime) + .min() + .ns_to_s() + : NaN); + const runtimeScatterData = []; + for (const commit of filteredCommits.values()) { + if (!(selectedBenchmark in commit.benchmarks)) { + continue; + } + const runtimes = + commit.benchmarks[selectedBenchmark].results.map(result => result.runtime) + for (const runtime of runtimes.values()) { + runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() }); + } + } + + const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined; + return { + labels: labels, + datasets: [ + { + type: 'line', + label: 'Code size', + data: codeSizeData, + tension: 0.1, + segment: { + borderColor: ctx => + skipped(ctx, 'rgba(54, 162, 235, 0.5)'), + borderDash: ctx => skipped(ctx, [6, 6]), + }, + spanGaps: true + }, + { + type: 'scatter', + label: 'Nondeterminism', + data: codeSizeScatterData, + pointBackgroundColor: 'red' + }, + { + type: 'line', + label: 'Runtime', + data: runtimeData, + tension: 0.1, + yAxisID: 'y2', + segment: { + borderColor: ctx => + skipped(ctx, 'rgba(255, 160, 64, 0.5)'), + borderDash: ctx => skipped(ctx, [6, 6]), + }, + spanGaps: true + }, + { + type: 'scatter', + label: 'Runtime variance', + data: runtimeScatterData, + yAxisID: 'y2' + } + ], + }; + } + + // Chart options. + const options = { + onHover: (event, chartElement) => + event.native.target.style.cursor = + chartElement[0] ? 'pointer' : 'default', + plugins: { + tooltip: { + callbacks: { + title: (context) => { + const elementInfo = context[0]; + var commit; + if (elementInfo.dataset.type == 'line') { + commit = commits[elementInfo.dataIndex]; + } else { + console.assert(elementInfo.dataset.type == 'scatter'); + commit = commits[elementInfo.raw.x]; + } + return commit.title; + }, + footer: (context) => { + const elementInfo = context[0]; + var commit; + if (elementInfo.dataset.type == 'line') { + commit = commits[elementInfo.dataIndex]; + } else { + console.assert(elementInfo.dataset.type == 'scatter'); + commit = commits[elementInfo.raw.x]; + } + return `Author: ${commit.author}\n` + + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n` + + `Hash: ${commit.hash}`; + } + } + } + }, + responsive: true, + scales: { + x: {}, + y: { + position: 'left', + title: { + display: true, + text: 'Code size (bytes)' + } + }, + y2: { + position: 'right', + title: { + display: true, + text: 'Runtime (seconds)' + } + } + } + }; + + // Create chart. + const myChart = new Chart(canvas, { + data: getData(), + options: options + }); + + // Setup click handler. + canvas.onclick = function (event) { + const points = + myChart.getElementsAtEventForMode( + event, 'nearest', { intersect: true }, true); + if (points.length > 0) { + const point = points[0]; + const commit = commits[point.index]; + window.open('https://r8.googlesource.com/r8/+/' + commit.hash, '_blank'); + } + }; + + // Setup chart navigation. + var left = 0; + var right = commits.length; + + showMoreLeft.onclick = function (event) { + if (left == 0) { + return; + } + const currentSize = right - left; + left = left - currentSize; + if (left < 0) { + left = 0; + } + updateChart(); + }; + + showLessLeft.onclick = function (event) { + const currentSize = right - left; + left = left + Math.floor(currentSize / 2); + if (left >= right) { + left = right - 1; + } + updateChart(); + }; + + showLessRight.onclick = function (event) { + if (right == 0) { + return; + } + const currentSize = right - left; + right = right - Math.floor(currentSize / 2); + if (right < left) { + right = left; + } + updateChart(); + }; + + showMoreRight.onclick = function (event) { + const currentSize = right - left; + right = right + currentSize; + if (right > commits.length) { + right = commits.length; + } + updateChart(); + }; + + function updateChart() { + console.assert(left <= right); + const newData = getData(left, right); + Object.assign(myChart.data, newData); + myChart.update(); + showMoreLeft.disabled = left == 0; + showLessLeft.disabled = left == right - 1; + showLessRight.disabled = left == right - 1; + showMoreRight.disabled = right == commits.length; + } + </script> +</body> +</html> \ No newline at end of file
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py index 3a1efbf..6bf0882 100755 --- a/tools/run_benchmark.py +++ b/tools/run_benchmark.py
@@ -63,6 +63,12 @@ help='Enable assertions when running', default=False, action='store_true') + result.add_argument('--iterations', + '-i', + help='Number of iterations to run', + type=int) + result.add_argument('--output', + help='Output path where to write the result') result.add_argument('--print-times', help='Print timing information from r8', default=False, @@ -72,17 +78,35 @@ '-v', help='Use R8 version/hash for the run (default local build)', default=None) + result.add_argument( + '--version-jar', + help='The r8.jar corresponding to the version given at --version.', + default=None) result.add_argument('--temp', help='A directory to use for temporaries and outputs.', default=None) - return result.parse_known_args(argv) + result.add_argument('--verbose', + help='To enable verbose logging.', + action='store_true', + default=False) + options, args = result.parse_known_args(argv) + options.quiet = not options.verbose + # We must download the non-lib distribution when running with a specific + # version, since BenchmarkMainEntryRunner is using R8 internals. + # TODO(b/346477461): Look into removing this limitation. + assert options.version is None or options.nolib + return options, args def main(argv, temp): - (options, args) = parse_options(argv) + options, args = parse_options(argv) + + if options.output: + options.output = os.path.abspath(options.output) if options.temp: temp = options.temp + os.makedirs(temp, exist_ok=True) if options.golem: options.no_build = True @@ -92,11 +116,14 @@ if options.nolib: testBuildTargets = [ - utils.GRADLE_TASK_TEST_JAR, utils.GRADLE_TASK_TEST_DEPS_JAR + utils.GRADLE_TASK_TEST_JAR, utils.GRADLE_TASK_TEST_DEPS_JAR, + utils.GRADLE_TASK_TEST_UNZIP_TESTBASE ] buildTargets = [utils.GRADLE_TASK_R8] + testBuildTargets r8jar = utils.R8_JAR - testjars = [utils.R8_TESTS_JAR, utils.R8_TESTS_DEPS_JAR, utils.R8_TESTBASE_JAR] + testjars = [ + utils.R8_TESTS_JAR, utils.R8_TESTS_DEPS_JAR, utils.R8_TESTBASE_JAR + ] else: testBuildTargets = GOLEM_BUILD_TARGETS_TESTS buildTargets = GOLEM_BUILD_TARGETS @@ -107,11 +134,11 @@ os.path.join(utils.R8LIB_TESTBASE_JAR) ] - if options.version: + if options.version or options.version_jar: # r8 is downloaded so only test jar needs to be built. buildTargets = testBuildTargets - r8jar = compiledump.download_distribution(options.version, - options.nolib, temp) + r8jar = options.version_jar or compiledump.download_distribution( + options.version, options, temp) if not options.no_build: gradle.RunGradle(buildTargets + ['-Pno_internal']) @@ -127,7 +154,11 @@ def run(options, r8jar, testjars): jdkhome = get_jdk_home(options, options.benchmark) - cmd = [jdk.GetJavaExecutable(jdkhome)] + cmd = [ + jdk.GetJavaExecutable(jdkhome), '-Xms8g', '-Xmx8g', + '-XX:+TieredCompilation', '-XX:TieredStopAtLevel=4', + '-DBENCHMARK_IGNORE_CODE_SIZE_DIFFERENCES' + ] if options.enable_assertions: cmd.append('-ea') if options.print_times: @@ -137,6 +168,12 @@ f'-DTEST_DATA_LOCATION={utils.REPO_ROOT}/d8_r8/test_modules/tests_java_8/build/classes/java/test', f'-DTESTBASE_DATA_LOCATION={utils.REPO_ROOT}/d8_r8/test_modules/testbase/build/classes/java/main', ]) + if options.iterations is not None: + if options.iterations == 0: + return + cmd.append(f'-DBENCHMARK_ITERATIONS={options.iterations}') + if options.output: + cmd.append(f'-DBENCHMARK_OUTPUT={options.output}') cmd.extend(['-cp', ':'.join([r8jar] + testjars)]) cmd.extend([ 'com.android.tools.r8.benchmarks.BenchmarkMainEntryRunner', @@ -146,6 +183,7 @@ # repository root as an argument. The runner can then setup dependencies. 'golem' if options.golem else utils.REPO_ROOT, ]) + utils.PrintCmd(cmd, quiet=options.quiet) return subprocess.check_call(cmd)
diff --git a/tools/upload_benchmark_data_to_google_storage.py b/tools/upload_benchmark_data_to_google_storage.py new file mode 100755 index 0000000..4fb9444 --- /dev/null +++ b/tools/upload_benchmark_data_to_google_storage.py
@@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +import historic_run +import json +import os +import perf +import time +import utils + +import sys + +APPS = ['NowInAndroidApp', 'TiviApp'] +TARGETS = ['r8-full'] +NUM_COMMITS = 250 + +INDEX_HTML = os.path.join(utils.TOOLS_DIR, 'perf/index.html') + + +def DownloadCloudBucket(dest): + os.makedirs(dest) + utils.download_file_from_cloud_storage(perf.GetGSLocation('*'), + dest, + concurrent=True, + flags=['-R']) + + +def ParseJsonFromCloudStorage(filename, local_bucket): + abs_path = os.path.join(local_bucket, filename) + if not os.path.exists(abs_path): + return None + with open(abs_path, 'r') as f: + lines = f.readlines() + content = ''.join(lines) + try: + return json.loads(content) + except: + return None + + +def run(): + # Get the N most recent commits sorted by newest first. + top = utils.get_sha1_from_revision('origin/main') + bottom = utils.get_nth_sha1_from_revision(NUM_COMMITS - 1, 'origin/main') + commits = historic_run.enumerate_git_commits(top, bottom) + assert len(commits) == NUM_COMMITS + + # Download all benchmark data from the cloud bucket to a temp folder. + with utils.TempDir() as temp: + local_bucket = os.path.join(temp, perf.BUCKET) + DownloadCloudBucket(local_bucket) + + # Aggregate all the result.json files into a single benchmark_data.json file + # that has the same format as tools/perf/benchmark_data.json. + benchmark_data = [] + for commit in commits: + benchmarks = {} + for app in APPS: + for target in TARGETS: + filename = perf.GetArtifactLocation(app, target, + commit.hash(), + 'result.json') + app_benchmark_data = ParseJsonFromCloudStorage( + filename, local_bucket) + if app_benchmark_data: + benchmarks[app] = app_benchmark_data + if benchmarks or benchmark_data: + benchmark_data.append({ + 'author': commit.author_name(), + 'hash': commit.hash(), + 'submitted': commit.committer_timestamp(), + 'title': commit.title(), + 'benchmarks': benchmarks + }) + + # Trim data. + new_benchmark_data_len = len(benchmark_data) + while new_benchmark_data_len > 0: + candidate_len = new_benchmark_data_len - 1 + if not benchmark_data[candidate_len]['benchmarks']: + new_benchmark_data_len = candidate_len + else: + break + benchmark_data = benchmark_data[0:new_benchmark_data_len] + + # Serialize JSON to temp file. + benchmark_data_file = os.path.join(temp, 'benchmark_data.json') + with open(benchmark_data_file, 'w') as f: + json.dump(benchmark_data, f) + + # Write output files to public bucket. + perf.ArchiveOutputFile(benchmark_data_file, + 'perf/benchmark_data.json', + header='Cache-Control:no-store') + perf.ArchiveOutputFile(INDEX_HTML, 'perf/index.html') + + +def main(): + run() + + +if __name__ == '__main__': + sys.exit(main())
diff --git a/tools/utils.py b/tools/utils.py index 6faecc8..2b77ed5 100644 --- a/tools/utils.py +++ b/tools/utils.py
@@ -56,6 +56,7 @@ GRADLE_TASK_TESTBASE_WITH_APPLY_MAPPING_JAR = ':test:rewriteTestBaseForR8LibWithRelocatedDeps' GRADLE_TASK_TEST_DEPS_JAR = ':test:packageTestDeps' GRADLE_TASK_TEST_JAR = ':test:relocateTestsForR8LibWithRelocatedDeps' +GRADLE_TASK_TEST_UNZIP_TESTBASE = ':test:unzipTestBase' R8 = 'r8' R8LIB = 'r8lib' @@ -67,8 +68,10 @@ R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar') R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar') R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar') -THREADING_MODULE_BLOCKING_JAR = os.path.join(LIBS, 'threading-module-blocking.jar') -THREADING_MODULE_SINGLE_THREADED_JAR = os.path.join(LIBS, 'threading-module-single-threaded.jar') +THREADING_MODULE_BLOCKING_JAR = os.path.join(LIBS, + 'threading-module-blocking.jar') +THREADING_MODULE_SINGLE_THREADED_JAR = os.path.join( + LIBS, 'threading-module-single-threaded.jar') R8_TESTS_JAR = os.path.join(LIBS, 'r8tests.jar') R8_TESTBASE_JAR = os.path.join(LIBS, 'r8test_base.jar') R8LIB_TESTBASE_JAR = os.path.join(LIBS, 'r8libtestbase-cf.jar') @@ -81,7 +84,8 @@ LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join( CUSTOM_CONVERSION_DIR, 'library_desugar_conversions.jar') KEEPANNO_ANNOTATIONS_JAR = os.path.join(LIBS, 'keepanno-annotations.jar') -KEEPANNO_ANNOTATIONS_DOC = os.path.join('d8_r8', 'keepanno', 'build', 'docs', 'javadoc') +KEEPANNO_ANNOTATIONS_DOC = os.path.join('d8_r8', 'keepanno', 'build', 'docs', + 'javadoc') DESUGAR_CONFIGURATION = os.path.join('src', 'library_desugar', 'desugar_jdk_libs.json') @@ -346,6 +350,14 @@ subprocess.check_output(cmd) +def get_nth_sha1_from_revision(n, revision): + result = subprocess.check_output([ + 'git', 'log', revision, f'--skip={n}', '--max-count=1', + '--pretty=format:%H' + ]).decode('utf-8') + return result.strip() + + def get_sha1(filename): sha1 = hashlib.sha1() with open(filename, 'rb') as f: @@ -357,6 +369,13 @@ return sha1.hexdigest() +def get_sha1_from_revision(revision): + cmd = ['git', 'rev-parse', revision] + PrintCmd(cmd) + with ChangedWorkingDirectory(REPO_ROOT): + return subprocess.check_output(cmd).decode('utf-8').strip() + + def get_HEAD_branch(): result = subprocess.check_output( ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode('utf-8') @@ -364,20 +383,13 @@ def get_HEAD_sha1(): - return get_HEAD_sha1_for_checkout(REPO_ROOT) + return get_sha1_from_revision('HEAD') def get_HEAD_diff_stat(): return subprocess.check_output(['git', 'diff', '--stat']).decode('utf-8') -def get_HEAD_sha1_for_checkout(checkout): - cmd = ['git', 'rev-parse', 'HEAD'] - PrintCmd(cmd) - with ChangedWorkingDirectory(checkout): - return subprocess.check_output(cmd).decode('utf-8').strip() - - def makedirs_if_needed(path): try: os.makedirs(path) @@ -390,12 +402,15 @@ return 'gsutil.py' if os.name != 'nt' else 'gsutil.py.bat' -def upload_file_to_cloud_storage(source, destination): - cmd = [get_gsutil(), 'cp'] - cmd += [source, destination] +def upload_file_to_cloud_storage(source, destination, header=None): + cmd = [get_gsutil()] + if header: + cmd.extend(['-h', header]) + cmd.extend(['cp', source, destination]) PrintCmd(cmd) subprocess.check_call(cmd) + def check_dir_args(source, destination): # We require that the dirname of the paths coincide, e.g., src/dirname and dst/dirname # The target is then stripped so the upload command will be: cp -R src/dirname dst/ @@ -406,10 +421,11 @@ f'{source} and {destination}') if len(destination_parent.strip()) == 0: raise Exception( - 'Attempt to upload directory to empty destination directory: ' - + destination) + 'Attempt to upload directory to empty destination directory: ' + + destination) return destination_parent + def upload_directory_to_cloud_storage(source, destination, parallel=True): destination_parent = check_dir_args(source, destination) cmd = [get_gsutil()] @@ -420,6 +436,7 @@ PrintCmd(cmd) subprocess.check_call(cmd) + def rsync_directory_to_cloud_storage(source, destination, parallel=True): check_dir_args(source, destination) cmd = [get_gsutil()] @@ -430,6 +447,7 @@ PrintCmd(cmd) subprocess.check_call(cmd) + def delete_file_from_cloud_storage(destination): cmd = [get_gsutil(), 'rm', destination] PrintCmd(cmd) @@ -460,8 +478,18 @@ return subprocess.call(cmd) == 0 -def download_file_from_cloud_storage(source, destination, quiet=False): - cmd = [get_gsutil(), 'cp', source, destination] +def download_file_from_cloud_storage(source, + destination, + concurrent=False, + flags=None, + quiet=False): + cmd = [get_gsutil()] + if concurrent: + cmd.append('-m') + cmd.append('cp') + if flags: + cmd.extend(flags) + cmd.extend([source, destination]) PrintCmd(cmd, quiet=quiet) subprocess.check_call(cmd)