Merge commit 'a848fa8fca4e37f7fb6fae4f2475ed559ec1b8f3' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index a329c0f..9b4bb2a 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -5,6 +5,10 @@
from os import path
import datetime
from subprocess import check_output, Popen, PIPE, STDOUT
+import sys
+import inspect
+sys.path.append(path.dirname(inspect.getfile(lambda: None)))
+from tools.utils import EnsureDepFromGoogleCloudStorage
FMT_CMD = path.join(
'third_party',
@@ -16,7 +20,10 @@
'google-java-format-diff.py')
FMT_CMD_JDK17 = path.join('tools','google-java-format-diff.py')
-
+FMT_SHA1 = path.join(
+ 'third_party', 'google', 'google-java-format', '1.14.0.tar.gz.sha1')
+FMT_TGZ = path.join(
+ 'third_party', 'google', 'google-java-format', '1.14.0.tar.gz')
def CheckDoNotMerge(input_api, output_api):
for l in input_api.change.FullDescriptionText().splitlines():
@@ -26,6 +33,7 @@
return []
def CheckFormatting(input_api, output_api, branch):
+ EnsureDepFromGoogleCloudStorage(FMT_CMD, FMT_TGZ, FMT_SHA1, 'google-format')
results = []
for f in input_api.AffectedFiles():
path = f.LocalPath()
diff --git a/build.gradle b/build.gradle
index 332b74a..834e3d6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -341,8 +341,6 @@
"jsr223-api-1.0",
"rhino-1.7.10",
"rhino-android-1.1.1",
- "kotlin/kotlin-compiler-1.3.11",
- "kotlin/kotlin-compiler-1.3.41",
"kotlin/kotlin-compiler-1.3.72",
"kotlin/kotlin-compiler-1.4.20",
"kotlin/kotlin-compiler-1.5.0",
@@ -1578,28 +1576,26 @@
task buildKotlinR8TestResources {
def examplesDir = file("src/test/kotlinR8TestResources")
examplesDir.eachDir { dir ->
- kotlin.Kotlinc.KotlinTargetVersion.values().each { kotlinTargetVersion ->
- def name = dir.getName()
- def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
- def javaOutput = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}/java"
- def javaOutputJarName = "${name}.java.jar"
- def javaOutputJarDir = "build/test/kotlinR8TestResources/${kotlinTargetVersion}"
- task "${taskName}Java"(type: JavaCompile) {
- source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.java')
- destinationDir = file(javaOutput)
- classpath = sourceSets.main.compileClasspath
- sourceCompatibility = JavaVersion.VERSION_1_6
- targetCompatibility = JavaVersion.VERSION_1_6
- options.compilerArgs += ["-g", "-Xlint:-options"]
- }
- task "${taskName}JavaJar"(type: Jar, dependsOn: "${taskName}Java") {
- archiveName = javaOutputJarName
- destinationDir = file(javaOutputJarDir)
- from javaOutput
- include "**/*.class"
- }
- dependsOn "${taskName}JavaJar"
+ def name = dir.getName()
+ def taskName = "jar_kotlinR8TestResources_${name}"
+ def javaOutput = "build/test/kotlinR8TestResources/${name}/java"
+ def javaOutputJarName = "${name}.jar"
+ def javaOutputJarDir = "build/test/kotlinR8TestResources"
+ task "${taskName}Java"(type: JavaCompile) {
+ source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.java')
+ destinationDir = file(javaOutput)
+ classpath = sourceSets.main.compileClasspath
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ options.compilerArgs += ["-g", "-Xlint:-options"]
}
+ task "${taskName}JavaJar"(type: Jar, dependsOn: "${taskName}Java") {
+ archiveName = javaOutputJarName
+ destinationDir = file(javaOutputJarDir)
+ from javaOutput
+ include "**/*.class"
+ }
+ dependsOn "${taskName}JavaJar"
}
}
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index ab4a518..8af5714 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -11,6 +11,7 @@
import org.gradle.api.Task
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSet
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.register
@@ -96,7 +97,7 @@
}
/**
- * Builds a jar for each subfolder in an examples test source set.
+ * Builds a jar for each subfolder in an test source set.
*
* <p> As an example, src/test/examplesJava9 contains subfolders: backport, collectionof, ..., .
* These are compiled to individual jars and placed in <repo-root>/build/test/examplesJava9/ as:
@@ -105,37 +106,58 @@
* Calling this from a project will amend the task graph with the task named
* getExamplesJarsTaskName(examplesName) such that it can be referenced from the test runners.
*/
-fun Project.buildJavaExamplesJars(examplesName : String) : Task {
+fun Project.buildExampleJars(name : String) : Task {
val outputFiles : MutableList<File> = mutableListOf()
val jarTasks : MutableList<Task> = mutableListOf()
- var testSourceSet = extensions
+ val testSourceSet = extensions
.getByType(JavaPluginExtension::class.java)
.sourceSets
// The TEST_SOURCE_SET_NAME is the source set defined by writing java { sourcesets.test { ... }}
.getByName(SourceSet.TEST_SOURCE_SET_NAME)
+ val destinationDir = getRoot().resolveAll("build", "test", name)
+ val classesOutput = destinationDir.resolve("classes")
+ testSourceSet.java.destinationDirectory.set(classesOutput)
+ testSourceSet.resources.destinationDirectory.set(destinationDir)
testSourceSet
.java
.sourceDirectories
.files
.forEach { srcDir ->
srcDir.listFiles(File::isDirectory)?.forEach { exampleDir ->
- jarTasks.add(tasks.register<Jar>("jar-examples$examplesName-${exampleDir.name}") {
+ var generationTask : Task? = null
+ if (exampleDir.resolve("TestGenerator.java").isFile) {
+ generationTask = tasks.register<JavaExec>(
+ "generate-$name-${exampleDir.name}") {
+ dependsOn("compileTestJava")
+ mainClass.set("${exampleDir.name}.TestGenerator")
+ classpath = files(
+ classesOutput,
+ testSourceSet.compileClasspath)
+ args(classesOutput.toString())
+ }.get()
+ }
+ jarTasks.add(tasks.register<Jar>(
+ "jar-$name-${exampleDir.name}") {
dependsOn("compileTestJava")
+ if (generationTask != null) {
+ dependsOn(generationTask)
+ }
archiveFileName.set("${exampleDir.name}.jar")
- destinationDirectory.set(getRoot().resolveAll("build", "test", "examples$examplesName"))
- from(testSourceSet.output.classesDirs.files.map{ it.resolve(exampleDir.name) }) {
- include("**/*.class")
+ destinationDirectory.set(destinationDir)
+ from(classesOutput) {
+ include("${exampleDir.name}/**/*.class")
+ exclude("**/TestGenerator*")
}
}.get())
}
}
- return tasks.register(getExamplesJarsTaskName(examplesName)) {
- dependsOn(jarTasks)
+ return tasks.register(getExampleJarsTaskName(name)) {
+ dependsOn(jarTasks.toTypedArray())
outputs.files(outputFiles)
}.get()
}
-fun Project.getExamplesJarsTaskName(name: String) : String {
+fun Project.getExampleJarsTaskName(name: String) : String {
return "build-example-jars-$name"
}
@@ -275,6 +297,8 @@
}
object ThirdPartyDeps {
+ val androidJars = getThirdPartyAndroidJars()
+ val androidVMs = getThirdPartyAndroidVms()
val apiDatabase = ThirdPartyDependency(
"apiDatabase",
Paths.get(
@@ -284,26 +308,42 @@
"resources",
"new_api_database.ser").toFile(),
Paths.get("third_party", "api_database", "api_database.tar.gz.sha1").toFile())
+ val compilerApi = ThirdPartyDependency(
+ "compiler-api",
+ Paths.get(
+ "third_party",
+ "binary_compatibility_tests",
+ "compiler_api_tests",
+ "tests.jar").toFile(),
+ Paths.get(
+ "third_party",
+ "binary_compatibility_tests",
+ "compiler_api_tests.tar.gz.sha1").toFile())
val ddmLib = ThirdPartyDependency(
"ddmlib",
Paths.get("third_party", "ddmlib", "ddmlib.jar").toFile(),
Paths.get("third_party", "ddmlib.tar.gz.sha1").toFile())
+ val jacoco = ThirdPartyDependency(
+ "jacoco",
+ Paths.get("third_party", "jacoco", "0.8.6", "lib", "jacocoagent.jar").toFile(),
+ Paths.get("third_party", "jacoco", "0.8.6.tar.gz.sha1").toFile()
+ )
val jasmin = ThirdPartyDependency(
"jasmin",
Paths.get("third_party", "jasmin", "jasmin-2.4.jar").toFile(),
Paths.get("third_party", "jasmin.tar.gz.sha1").toFile())
- val jdwpTests = ThirdPartyDependency(
- "jdwp-tests",
- Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar").toFile(),
- Paths.get("third_party", "jdwp-tests.tar.gz.sha1").toFile())
- val androidJars : List<ThirdPartyDependency> = getThirdPartyAndroidJars()
val java8Runtime = ThirdPartyDependency(
"openjdk-rt-1.8",
Paths.get("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar").toFile(),
Paths.get("third_party", "openjdk", "openjdk-rt-1.8.tar.gz.sha1").toFile()
)
- val androidVMs : List<ThirdPartyDependency> = getThirdPartyAndroidVms()
- val jdks : List<ThirdPartyDependency> = getJdks()
+ val jdks = getJdks()
+ val jdwpTests = ThirdPartyDependency(
+ "jdwp-tests",
+ Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar").toFile(),
+ Paths.get("third_party", "jdwp-tests.tar.gz.sha1").toFile())
+ val kotlinCompilers = getThirdPartyKotlinCompilers()
+ val proguards = getThirdPartyProguards()
}
fun getThirdPartyAndroidJars() : List<ThirdPartyDependency> {
@@ -373,4 +413,39 @@
} else {
return Jdk.values().filter{ !it.isJdk8() }.map{ it.getThirdPartyDependency()}
}
+}
+
+fun getThirdPartyProguards() : List<ThirdPartyDependency> {
+ val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
+ return listOf("proguard5.2.1", "proguard6.0.1", "proguard-7.0.0")
+ .map { ThirdPartyDependency(
+ it,
+ Paths.get(
+ "third_party",
+ "proguard",
+ it,
+ "bin",
+ if (os.isWindows) "proguard.bat" else "proguard.sh").toFile(),
+ Paths.get("third_party", "proguard", "${it}.tar.gz.sha1").toFile())}
+}
+
+fun getThirdPartyKotlinCompilers() : List<ThirdPartyDependency> {
+ return listOf(
+ "kotlin-compiler-1.3.72",
+ "kotlin-compiler-1.4.20",
+ "kotlin-compiler-1.5.0",
+ "kotlin-compiler-1.6.0",
+ "kotlin-compiler-1.7.0",
+ "kotlin-compiler-1.8.0",
+ "kotlin-compiler-dev")
+ .map { ThirdPartyDependency(
+ it,
+ Paths.get(
+ "third_party",
+ "kotlin",
+ it,
+ "kotlinc",
+ "lib",
+ "kotlin-stdlib.jar").toFile(),
+ Paths.get("third_party", "kotlin", "${it}.tar.gz.sha1").toFile())}
}
\ No newline at end of file
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DownloadDependencyTask.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DownloadDependencyTask.kt
index 24185c6..4d43b40 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DownloadDependencyTask.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DownloadDependencyTask.kt
@@ -40,7 +40,7 @@
option = "dependency",
description = "Sets the dependency information for a cloud stored file")
fun setDependency(
- dependencyName : String, sha1File: File, outputDir : File, dependencyType: DependencyType) {
+ dependencyName: String, sha1File: File, outputDir: File, dependencyType: DependencyType) {
_outputDir = outputDir
_sha1File = sha1File
_tarGzFile = sha1File.resolveSibling(sha1File.name.replace(".sha1", ""))
@@ -70,12 +70,11 @@
getWorkerExecutor()!!
.noIsolation()
.submit(RunDownload::class.java) {
- this.type.set(dependencyType)
+ type.set(dependencyType)
this.sha1File.set(sha1File)
}
}
-
interface RunDownloadParameters : WorkParameters {
val type : Property<DependencyType>
val sha1File : RegularFileProperty
diff --git a/d8_r8/settings.gradle.kts b/d8_r8/settings.gradle.kts
index 402137e..9907334 100644
--- a/d8_r8/settings.gradle.kts
+++ b/d8_r8/settings.gradle.kts
@@ -23,20 +23,20 @@
"download_from_google_storage.py",
"--extract",
"--bucket",
- "${dependencies_bucket}",
+ dependencies_bucket,
"--sha1_file",
"${sha1File}"
)
println("Executing command: ${cmd.joinToString(" ")}")
- var process = ProcessBuilder().command(cmd).start()
+ val process = ProcessBuilder().command(cmd).start()
process.waitFor()
if (process.exitValue() != 0) {
throw GradleException(
"Bootstrapping dependencies_new download failed:\n"
+ "${String(process.getErrorStream().readAllBytes(),
java.nio.charset.StandardCharsets.UTF_8)}\n"
- + "${String(process.getInputStream().readAllBytes(),
- java.nio.charset.StandardCharsets.UTF_8)}")
+ + String(process.getInputStream().readAllBytes(),
+ java.nio.charset.StandardCharsets.UTF_8))
}
}
@@ -44,9 +44,11 @@
downloadFromGoogleStorage(thirdParty.resolve("dependencies.tar.gz.sha1"))
downloadFromGoogleStorage(thirdParty.resolve("dependencies_new.tar.gz.sha1"))
+pluginManagement {
+ includeBuild(rootProject.projectDir.resolve("commonBuildSrc"))
+}
// This project is temporarily located in d8_r8. When moved to root, the parent
// folder should just be removed.
-includeBuild(root.resolve("commonBuildSrc"))
includeBuild(root.resolve("keepanno"))
// We need to include src/main as a composite-build otherwise our test-modules
diff --git a/d8_r8/test/settings.gradle.kts b/d8_r8/test/settings.gradle.kts
index ed1f4ea..cec4093 100644
--- a/d8_r8/test/settings.gradle.kts
+++ b/d8_r8/test/settings.gradle.kts
@@ -6,13 +6,14 @@
val root = rootProject.projectDir.parentFile
includeBuild(root.resolve("main"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examples"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidN"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidP"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidO"))
includeBuild(root.resolve("test_modules").resolve("tests_java_8"))
includeBuild(root.resolve("test_modules").resolve("tests_java_9"))
includeBuild(root.resolve("test_modules").resolve("tests_java_10"))
includeBuild(root.resolve("test_modules").resolve("tests_java_11"))
includeBuild(root.resolve("test_modules").resolve("tests_java_17"))
includeBuild(root.resolve("test_modules").resolve("tests_java_20"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examples"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidN"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidP"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidO"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_kotlinR8TestResources"))
diff --git a/d8_r8/test_modules/tests_java_10/build.gradle.kts b/d8_r8/test_modules/tests_java_10/build.gradle.kts
index f24610d..aec1b33 100644
--- a/d8_r8/test_modules/tests_java_10/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_10/build.gradle.kts
@@ -24,7 +24,7 @@
dependencies { }
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("Java10")
+val buildExampleJars = buildExampleJars("examplesJava10")
tasks {
withType<JavaCompile> {
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 1ff3ea8..bbf8d9c 100644
--- a/d8_r8/test_modules/tests_java_11/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_11/build.gradle.kts
@@ -24,7 +24,7 @@
dependencies { }
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("Java11")
+val buildExampleJars = buildExampleJars("examplesJava11")
tasks {
withType<JavaCompile> {
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 9e0f2b7..e68b356 100644
--- a/d8_r8/test_modules/tests_java_17/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_17/build.gradle.kts
@@ -24,7 +24,7 @@
dependencies { }
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("Java17")
+val buildExampleJars = buildExampleJars("examplesJava17")
tasks {
withType<JavaCompile> {
diff --git a/d8_r8/test_modules/tests_java_20/build.gradle.kts b/d8_r8/test_modules/tests_java_20/build.gradle.kts
index 85cd393..5c348ed 100644
--- a/d8_r8/test_modules/tests_java_20/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_20/build.gradle.kts
@@ -24,7 +24,7 @@
dependencies { }
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("Java20")
+val buildExampleJars = buildExampleJars("examplesJava20")
tasks {
withType<JavaCompile> {
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 3493286..f7e52e5 100644
--- a/d8_r8/test_modules/tests_java_8/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_8/build.gradle.kts
@@ -55,21 +55,25 @@
val thirdPartyRuntimeDependenciesTask = ensureThirdPartyDependencies(
"runtimeDeps",
- listOf(ThirdPartyDeps.java8Runtime)
+ listOf(ThirdPartyDeps.compilerApi, ThirdPartyDeps.jacoco, ThirdPartyDeps.java8Runtime)
+ ThirdPartyDeps.androidJars
+ ThirdPartyDeps.androidVMs
- + ThirdPartyDeps.jdks)
+ + ThirdPartyDeps.jdks
+ + ThirdPartyDeps.kotlinCompilers
+ + ThirdPartyDeps.proguards)
val sourceSetDependenciesTasks = arrayOf(
- projectTask("tests_java_examples", getExamplesJarsTaskName("")),
- projectTask("tests_java_examplesAndroidN", getExamplesJarsTaskName("AndroidN")),
- projectTask("tests_java_examplesAndroidO", getExamplesJarsTaskName("AndroidO")),
- projectTask("tests_java_examplesAndroidP", getExamplesJarsTaskName("AndroidP")),
- projectTask("tests_java_9", getExamplesJarsTaskName("Java9")),
- projectTask("tests_java_10", getExamplesJarsTaskName("Java10")),
- projectTask("tests_java_11", getExamplesJarsTaskName("Java11")),
- projectTask("tests_java_17", getExamplesJarsTaskName("Java17")),
- projectTask("tests_java_20", getExamplesJarsTaskName("Java20")))
+ projectTask("tests_java_examples", getExampleJarsTaskName("examples")),
+ projectTask("tests_java_9", getExampleJarsTaskName("examplesJava9")),
+ projectTask("tests_java_10", getExampleJarsTaskName("examplesJava10")),
+ projectTask("tests_java_11", getExampleJarsTaskName("examplesJava11")),
+ projectTask("tests_java_17", getExampleJarsTaskName("examplesJava17")),
+ projectTask("tests_java_20", getExampleJarsTaskName("examplesJava20")),
+ projectTask("tests_java_examplesAndroidN", getExampleJarsTaskName("examplesAndroidN")),
+ projectTask("tests_java_examplesAndroidO", getExampleJarsTaskName("examplesAndroidO")),
+ projectTask("tests_java_examplesAndroidP", getExampleJarsTaskName("examplesAndroidP")),
+ projectTask("tests_java_kotlinR8TestResources", getExampleJarsTaskName("kotlinR8TestResources")),
+)
fun testDependencies() : FileCollection {
return sourceSets
diff --git a/d8_r8/test_modules/tests_java_8/settings.gradle.kts b/d8_r8/test_modules/tests_java_8/settings.gradle.kts
index 73f5d73..4935653 100644
--- a/d8_r8/test_modules/tests_java_8/settings.gradle.kts
+++ b/d8_r8/test_modules/tests_java_8/settings.gradle.kts
@@ -11,13 +11,14 @@
// will compete with the test to compile the source files.
includeBuild(root.resolve("main"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examples"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidN"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidO"))
-includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidP"))
includeBuild(root.resolve("test_modules").resolve("tests_java_9"))
includeBuild(root.resolve("test_modules").resolve("tests_java_10"))
includeBuild(root.resolve("test_modules").resolve("tests_java_11"))
includeBuild(root.resolve("test_modules").resolve("tests_java_17"))
includeBuild(root.resolve("test_modules").resolve("tests_java_20"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examples"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidN"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidO"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_examplesAndroidP"))
+includeBuild(root.resolve("test_modules").resolve("tests_java_kotlinR8TestResources"))
diff --git a/d8_r8/test_modules/tests_java_9/build.gradle.kts b/d8_r8/test_modules/tests_java_9/build.gradle.kts
index ac5923f..e7a55e1 100644
--- a/d8_r8/test_modules/tests_java_9/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_9/build.gradle.kts
@@ -24,7 +24,7 @@
dependencies { }
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("Java9")
+val buildExampleJars = buildExampleJars("examplesJava9")
tasks {
withType<JavaCompile> {
diff --git a/d8_r8/test_modules/tests_java_examples/build.gradle.kts b/d8_r8/test_modules/tests_java_examples/build.gradle.kts
index 05798be..16fec67 100644
--- a/d8_r8/test_modules/tests_java_examples/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_examples/build.gradle.kts
@@ -27,7 +27,7 @@
}
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("")
+val buildExampleJars = buildExampleJars("examples")
tasks {
withType<JavaCompile> {
diff --git a/d8_r8/test_modules/tests_java_examplesAndroidN/build.gradle.kts b/d8_r8/test_modules/tests_java_examplesAndroidN/build.gradle.kts
index 8cfe7d2..54b5a45 100644
--- a/d8_r8/test_modules/tests_java_examplesAndroidN/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_examplesAndroidN/build.gradle.kts
@@ -27,7 +27,7 @@
}
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("AndroidN")
+val buildExampleJars = buildExampleJars("examplesAndroidN")
tasks {
withType<JavaCompile> {
diff --git a/d8_r8/test_modules/tests_java_examplesAndroidO/build.gradle.kts b/d8_r8/test_modules/tests_java_examplesAndroidO/build.gradle.kts
index 44ed59d..43020e3 100644
--- a/d8_r8/test_modules/tests_java_examplesAndroidO/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_examplesAndroidO/build.gradle.kts
@@ -27,11 +27,13 @@
}
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("AndroidO")
+val buildExampleJars = buildExampleJars("examplesAndroidO")
tasks {
withType<JavaCompile> {
options.setFork(true)
+ options.compilerArgs.add("-Xlint:-options")
+ options.compilerArgs.add("-parameters")
options.forkOptions.memoryMaximumSize = "3g"
options.forkOptions.jvmArgs = listOf(
"-Xss256m",
diff --git a/d8_r8/test_modules/tests_java_examplesAndroidP/build.gradle.kts b/d8_r8/test_modules/tests_java_examplesAndroidP/build.gradle.kts
index 7e58f3b..13c2530 100644
--- a/d8_r8/test_modules/tests_java_examplesAndroidP/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_examplesAndroidP/build.gradle.kts
@@ -27,7 +27,7 @@
}
// We just need to register the examples jars for it to be referenced by other modules.
-val buildExampleJars = buildJavaExamplesJars("AndroidP")
+val buildExampleJars = buildExampleJars("examplesAndroidP")
tasks {
withType<JavaCompile> {
diff --git a/d8_r8/test_modules/tests_java_kotlinR8TestResources/build.gradle.kts b/d8_r8/test_modules/tests_java_kotlinR8TestResources/build.gradle.kts
new file mode 100644
index 0000000..353d621
--- /dev/null
+++ b/d8_r8/test_modules/tests_java_kotlinR8TestResources/build.gradle.kts
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import org.gradle.api.JavaVersion
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ `kotlin-dsl`
+ `java-library`
+ id("dependencies-plugin")
+}
+
+val root = getRoot()
+
+java {
+ sourceSets.test.configure {
+ java.srcDirs.clear()
+ java.srcDir(root.resolveAll("src", "test", "kotlinR8TestResources"))
+ }
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+ testCompileOnly(Deps.asm)
+}
+
+// We just need to register the examples jars for it to be referenced by other modules.
+val buildExampleJars = buildExampleJars("kotlinR8TestResources")
+
+tasks {
+ withType<JavaCompile> {
+ options.setFork(true)
+ options.forkOptions.memoryMaximumSize = "3g"
+ options.forkOptions.jvmArgs = listOf(
+ "-Xss256m",
+ // Set the bootclass path so compilation is consistent with 1.8 target compatibility.
+ "-Xbootclasspath/a:third_party/openjdk/openjdk-rt-1.8/rt.jar")
+ }
+ withType<KotlinCompile> {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ }
+}
diff --git a/d8_r8/test_modules/tests_java_kotlinR8TestResources/gradle.properties b/d8_r8/test_modules/tests_java_kotlinR8TestResources/gradle.properties
new file mode 100644
index 0000000..1de43f9
--- /dev/null
+++ b/d8_r8/test_modules/tests_java_kotlinR8TestResources/gradle.properties
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true
+systemProp.file.encoding=UTF-8
+
+# Enable new incremental compilation
+kotlin.incremental.useClasspathSnapshot=true
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# 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
diff --git a/d8_r8/test_modules/tests_java_kotlinR8TestResources/settings.gradle.kts b/d8_r8/test_modules/tests_java_kotlinR8TestResources/settings.gradle.kts
new file mode 100644
index 0000000..58a9232
--- /dev/null
+++ b/d8_r8/test_modules/tests_java_kotlinR8TestResources/settings.gradle.kts
@@ -0,0 +1,5 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+rootProject.name = "tests_java_kotlinR8TestResources"
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index 03691dd3..91a12e2 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -41,7 +41,7 @@
triggering_policy {
kind: GREEDY_BATCHING
max_concurrent_invocations: 1
- max_batch_size: 1
+ max_batch_size: 42
}
buildbucket {
server: "cr-buildbucket.appspot.com"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 358def3..dd14d77 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -307,7 +307,7 @@
dimensions = get_dimensions(),
triggering_policy = scheduler.policy(
kind = scheduler.GREEDY_BATCHING_KIND,
- max_batch_size = 1,
+ max_batch_size = 42,
max_concurrent_invocations = 1
),
priority = 25,
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index e3a2cbb..be74e08 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -68,6 +68,7 @@
* The GlobalSyntheticsGenerator, a tool for generating a dex file for all possible global
* synthetics.
*/
+@Keep
public class GlobalSyntheticsGenerator {
private static boolean ensureAllGlobalSyntheticsModeled(SyntheticNaming naming) {
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
index 4ae00bd..1d85164 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
@@ -25,6 +25,7 @@
/**
* Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler.
*/
+@Keep
public final class GlobalSyntheticsGeneratorCommand {
private final ProgramConsumer programConsumer;
@@ -151,6 +152,7 @@
*
* <p>A builder is obtained by calling {@link GlobalSyntheticsGeneratorCommand#builder}.
*/
+ @Keep
public static class Builder {
private ProgramConsumer programConsumer = null;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 86b5b5f..080447c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -505,6 +505,9 @@
.appInfo()
.notifyHorizontalClassMergerFinished(HorizontalClassMerger.Mode.INITIAL);
+ // TODO(b/225838009): Horizontal merging currently assumes pre-phase CF conversion.
+ appView.testing().enterLirSupportedPhase();
+
new ProtoNormalizer(appViewWithLiveness).run(executorService, timing);
// Clear traced methods roots to not hold on to the main dex live method set.
@@ -541,6 +544,9 @@
appView.setGraphLens(new AppliedGraphLens(appView));
timing.end();
+ // TODO(b/225838009): Support tracing and building LIR in Enqueuer.
+ PrimaryR8IRConverter.finalizeLirToOutputFormat(appView, timing, executorService);
+
if (options.shouldRerunEnqueuer()) {
timing.begin("Post optimization code stripping");
try {
diff --git a/src/main/java/com/android/tools/r8/dump/CompilerDump.java b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
index eaf9e53..7655a6e 100644
--- a/src/main/java/com/android/tools/r8/dump/CompilerDump.java
+++ b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
@@ -45,6 +45,10 @@
return directory.resolve("proguard.config");
}
+ public Path getDesugaredLibraryFile() {
+ return directory.resolve("desugared-library.json");
+ }
+
public void sanitizeProguardConfig(ProguardConfigSanitizer sanitizer) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(getProguardConfigFile())) {
String next = reader.readLine();
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 5a20e3d..06deed3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -26,6 +26,8 @@
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueFieldJoiner;
+import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueParameterJoiner;
import com.android.tools.r8.ir.desugar.TypeRewriter;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
@@ -100,6 +102,8 @@
private KeepInfoCollection keepInfo = null;
private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
+ private final AbstractValueFieldJoiner abstractValueFieldJoiner;
+ private final AbstractValueParameterJoiner abstractValueParameterJoiner;
private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
new InstanceFieldInitializationInfoFactory();
private final SimpleInliningConstraintFactory simpleInliningConstraintFactory =
@@ -170,13 +174,20 @@
this.context =
timing.time(
"Compilation context", () -> CompilationContext.createInitialContext(options()));
+ this.wholeProgramOptimizations = wholeProgramOptimizations;
+ if (enableWholeProgramOptimizations()) {
+ abstractValueFieldJoiner = new AbstractValueFieldJoiner(withClassHierarchy());
+ abstractValueParameterJoiner = new AbstractValueParameterJoiner(withClassHierarchy());
+ } else {
+ abstractValueFieldJoiner = null;
+ abstractValueParameterJoiner = null;
+ }
this.artProfileCollection = artProfileCollection;
this.startupProfile = startupProfile;
this.dontWarnConfiguration =
timing.time(
"Dont warn config",
() -> DontWarnConfiguration.create(options().getProguardConfiguration()));
- this.wholeProgramOptimizations = wholeProgramOptimizations;
this.initClassLens = timing.time("Init class lens", InitClassLens::getThrowingInstance);
this.typeRewriter = mapper;
timing.begin("Create argument propagator");
@@ -314,6 +325,14 @@
return abstractValueFactory;
}
+ public AbstractValueFieldJoiner getAbstractValueFieldJoiner() {
+ return abstractValueFieldJoiner;
+ }
+
+ public AbstractValueParameterJoiner getAbstractValueParameterJoiner() {
+ return abstractValueParameterJoiner;
+ }
+
public InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory() {
return instanceFieldInitializationInfoFactory;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index cc8e00f..a87febd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -1043,6 +1043,10 @@
permittedSubclasses.clear();
}
+ public void removePermittedSubclassAttribute(Predicate<PermittedSubclassAttribute> predicate) {
+ permittedSubclasses.removeIf(predicate);
+ }
+
public boolean isLocalClass() {
InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
// The corresponding enclosing-method attribute might be not available, e.g., CF version 50.
@@ -1139,6 +1143,10 @@
return permittedSubclasses;
}
+ public void setPermittedSubclassAttributes(List<PermittedSubclassAttribute> permittedSubclasses) {
+ this.permittedSubclasses = permittedSubclasses;
+ }
+
public List<RecordComponentInfo> getRecordComponents() {
return recordComponents;
}
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 1aa866f..fb50d0e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -415,6 +415,18 @@
.traverse((field, value) -> fn.apply(field.asProgramField(), value), initialValue);
}
+ public TraversalContinuation<?, ?> traverseProgramInstanceFields(
+ Function<? super ProgramField, TraversalContinuation<?, ?>> fn) {
+ return getFieldCollection().traverseInstanceFields(field -> fn.apply(field.asProgramField()));
+ }
+
+ public <BT, CT> TraversalContinuation<BT, CT> traverseProgramInstanceFields(
+ BiFunction<? super ProgramField, CT, TraversalContinuation<BT, CT>> fn, CT initialValue) {
+ return getFieldCollection()
+ .traverseInstanceFields(
+ (field, value) -> fn.apply(field.asProgramField(), value), initialValue);
+ }
+
public TraversalContinuation<?, ?> traverseProgramMethods(
Function<? super ProgramMethod, TraversalContinuation<?, ?>> fn) {
return getMethodCollection().traverse(method -> fn.apply(new ProgramMethod(this, method)));
diff --git a/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java b/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
index dee7446..ec0758a 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
@@ -72,15 +72,61 @@
@Override
<BT, CT> TraversalContinuation<BT, CT> traverse(
DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
- TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
- for (DexEncodedField definition : staticFields) {
- DexClassAndField field = DexClassAndField.create(holder, definition);
- traversalContinuation = fn.apply(field);
- if (traversalContinuation.shouldBreak()) {
- return traversalContinuation;
- }
+ TraversalContinuation<BT, CT> traversalContinuation = traverseStaticFields(holder, fn);
+ if (traversalContinuation.shouldBreak()) {
+ return traversalContinuation;
}
- for (DexEncodedField definition : instanceFields) {
+ return traverseInstanceFields(holder, fn);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverse(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ TraversalContinuation<BT, CT> traversalContinuation =
+ traverseStaticFields(holder, fn, initialValue);
+ if (traversalContinuation.shouldBreak()) {
+ return traversalContinuation;
+ }
+ return traverseInstanceFields(
+ holder, fn, traversalContinuation.asContinue().getValueOrDefault(null));
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+ return traverseInstanceOrStaticFields(holder, instanceFields, fn);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ return traverseInstanceOrStaticFields(holder, instanceFields, fn, initialValue);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+ return traverseInstanceOrStaticFields(holder, staticFields, fn);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ return traverseInstanceOrStaticFields(holder, staticFields, fn, initialValue);
+ }
+
+ private static <BT, CT> TraversalContinuation<BT, CT> traverseInstanceOrStaticFields(
+ DexClass holder,
+ DexEncodedField[] fields,
+ Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+ TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
+ for (DexEncodedField definition : fields) {
DexClassAndField field = DexClassAndField.create(holder, definition);
traversalContinuation = fn.apply(field);
if (traversalContinuation.shouldBreak()) {
@@ -90,21 +136,14 @@
return traversalContinuation;
}
- @Override
- <BT, CT> TraversalContinuation<BT, CT> traverse(
+ private static <BT, CT> TraversalContinuation<BT, CT> traverseInstanceOrStaticFields(
DexClass holder,
+ DexEncodedField[] fields,
BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
TraversalContinuation<BT, CT> traversalContinuation =
TraversalContinuation.doContinue(initialValue);
- for (DexEncodedField definition : staticFields) {
- DexClassAndField field = DexClassAndField.create(holder, definition);
- traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
- if (traversalContinuation.shouldBreak()) {
- return traversalContinuation;
- }
- }
- for (DexEncodedField definition : instanceFields) {
+ for (DexEncodedField definition : fields) {
DexClassAndField field = DexClassAndField.create(holder, definition);
traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
if (traversalContinuation.shouldBreak()) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldCollection.java b/src/main/java/com/android/tools/r8/graph/FieldCollection.java
index 729b238..9b3c09b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldCollection.java
@@ -73,6 +73,28 @@
return backing.traverse(holder, fn, initialValue);
}
+ public <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+ return backing.traverseInstanceFields(holder, fn);
+ }
+
+ public <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ return backing.traverseInstanceFields(holder, fn, initialValue);
+ }
+
+ public <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+ return backing.traverseStaticFields(holder, fn);
+ }
+
+ public <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ return backing.traverseStaticFields(holder, fn, initialValue);
+ }
+
public boolean verify() {
forEachField(
field -> {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
index 522748e..cce38fd 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
@@ -34,6 +34,22 @@
BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue);
+ abstract <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn);
+
+ abstract <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue);
+
+ abstract <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn);
+
+ abstract <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue);
+
// Collection methods.
abstract int size();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java b/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
index 6b74673..a6d9b57 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.google.common.base.Predicates.alwaysTrue;
+
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
@@ -49,15 +51,7 @@
@Override
<BT, CT> TraversalContinuation<BT, CT> traverse(
DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
- TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
- for (DexEncodedField definition : fieldMap.values()) {
- DexClassAndField field = DexClassAndField.create(holder, definition);
- traversalContinuation = fn.apply(field);
- if (traversalContinuation.shouldBreak()) {
- return traversalContinuation;
- }
- }
- return traversalContinuation;
+ return traverseFieldsThatMatches(holder, fn, alwaysTrue());
}
@Override
@@ -65,13 +59,68 @@
DexClass holder,
BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
CT initialValue) {
+ return traverseFieldsThatMatches(holder, fn, alwaysTrue(), initialValue);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+ return traverseFieldsThatMatches(holder, fn, DexEncodedField::isInstance);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseInstanceFields(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ return traverseFieldsThatMatches(holder, fn, DexEncodedField::isInstance, initialValue);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+ return traverseFieldsThatMatches(holder, fn, DexEncodedField::isStatic);
+ }
+
+ @Override
+ <BT, CT> TraversalContinuation<BT, CT> traverseStaticFields(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ return traverseFieldsThatMatches(holder, fn, DexEncodedField::isStatic, initialValue);
+ }
+
+ private <BT, CT> TraversalContinuation<BT, CT> traverseFieldsThatMatches(
+ DexClass holder,
+ Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn,
+ Predicate<? super DexEncodedField> predicate) {
+ TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
+ for (DexEncodedField definition : fieldMap.values()) {
+ if (predicate.test(definition)) {
+ DexClassAndField field = DexClassAndField.create(holder, definition);
+ traversalContinuation = fn.apply(field);
+ if (traversalContinuation.shouldBreak()) {
+ return traversalContinuation;
+ }
+ }
+ }
+ return traversalContinuation;
+ }
+
+ private <BT, CT> TraversalContinuation<BT, CT> traverseFieldsThatMatches(
+ DexClass holder,
+ BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+ Predicate<? super DexEncodedField> predicate,
+ CT initialValue) {
TraversalContinuation<BT, CT> traversalContinuation =
TraversalContinuation.doContinue(initialValue);
for (DexEncodedField definition : fieldMap.values()) {
- DexClassAndField field = DexClassAndField.create(holder, definition);
- traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
- if (traversalContinuation.shouldBreak()) {
- return traversalContinuation;
+ if (predicate.test(definition)) {
+ DexClassAndField field = DexClassAndField.create(holder, definition);
+ traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
+ if (traversalContinuation.shouldBreak()) {
+ return traversalContinuation;
+ }
}
}
return traversalContinuation;
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 a84af1a..035ab26 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
@@ -28,6 +28,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -297,15 +298,23 @@
return permittedSubclassAttributes;
}
boolean changed = false;
- List<PermittedSubclassAttribute> newPermittedSubclassAttributes =
- new ArrayList<>(permittedSubclassAttributes.size());
+ LinkedHashSet<DexType> newPermittedSubclassTypes =
+ new LinkedHashSet<>(permittedSubclassAttributes.size());
for (PermittedSubclassAttribute permittedSubclassAttribute : permittedSubclassAttributes) {
- DexType permittedSubclassType = permittedSubclassAttribute.getPermittedSubclass();
- DexType newPermittedSubclassType = fixupType(permittedSubclassType);
- newPermittedSubclassAttributes.add(new PermittedSubclassAttribute(newPermittedSubclassType));
- changed |= newPermittedSubclassType != permittedSubclassType;
+ DexType permittedSubClassType = permittedSubclassAttribute.getPermittedSubclass();
+ DexType newPermittedSubClassType = fixupType(permittedSubClassType);
+ newPermittedSubclassTypes.add(newPermittedSubClassType);
+ changed |= newPermittedSubClassType != permittedSubClassType;
}
- return changed ? newPermittedSubclassAttributes : permittedSubclassAttributes;
+ if (!changed) {
+ return permittedSubclassAttributes;
+ }
+ List<PermittedSubclassAttribute> newPermittedSubclassAttributes =
+ new ArrayList<>(newPermittedSubclassTypes.size());
+ for (DexType newPermittedSubclassType : newPermittedSubclassTypes) {
+ newPermittedSubclassAttributes.add(new PermittedSubclassAttribute(newPermittedSubclassType));
+ }
+ return newPermittedSubclassAttributes;
}
protected List<RecordComponentInfo> fixupRecordComponents(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
index bd83a89..6eecb7a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
@@ -7,9 +7,14 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramField;
public class HorizontalClassMergerUtils {
+ public static boolean isClassIdField(AppView<?> appView, ProgramField field) {
+ return isClassIdField(appView, field.getDefinition());
+ }
+
public static boolean isClassIdField(AppView<?> appView, DexEncodedField field) {
DexField classIdField = appView.dexItemFactory().objectMembers.classIdField;
if (field.isD8R8Synthesized() && field.getType().isIntType()) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 804b505..ee56cc7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -157,6 +157,8 @@
clazz.setInnerClasses(fixupInnerClassAttributes(clazz.getInnerClasses()));
clazz.setNestHostAttribute(fixupNestHost(clazz.getNestHostClassAttribute()));
clazz.setNestMemberAttributes(fixupNestMemberAttributes(clazz.getNestMembersClassAttributes()));
+ clazz.setPermittedSubclassAttributes(
+ fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()));
}
private void fixupProgramClassSuperTypes(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index ec226d9..9676db7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.utils.BooleanBox;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.BitSet;
@@ -36,24 +37,8 @@
*/
public class SparseConditionalConstantPropagation extends CodeRewriterPass<AppInfo> {
- private final IRCode code;
- private final Map<Value, LatticeElement> mapping = new HashMap<>();
- // TODO(b/270398965): Replace LinkedList.
- @SuppressWarnings("JdkObsolete")
- private final Deque<Value> ssaEdges = new LinkedList<>();
- // TODO(b/270398965): Replace LinkedList.
- @SuppressWarnings("JdkObsolete")
- private final Deque<BasicBlock> flowEdges = new LinkedList<>();
-
- private final BitSet[] executableFlowEdges;
- private final BitSet visitedBlocks;
-
- public SparseConditionalConstantPropagation(AppView<?> appView, IRCode code) {
+ public SparseConditionalConstantPropagation(AppView<?> appView) {
super(appView);
- this.code = code;
- int maxBlockNumber = code.getCurrentBlockNumber() + 1;
- executableFlowEdges = new BitSet[maxBlockNumber];
- visitedBlocks = new BitSet(maxBlockNumber);
}
@Override
@@ -68,220 +53,255 @@
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
- BasicBlock firstBlock = code.entryBlock();
- visitInstructions(firstBlock);
+ return new SparseConditionalConstantPropagationOnCode(code).run();
+ }
- while (!flowEdges.isEmpty() || !ssaEdges.isEmpty()) {
- while (!flowEdges.isEmpty()) {
- BasicBlock block = flowEdges.poll();
- for (Phi phi : block.getPhis()) {
- visitPhi(phi);
+ private class SparseConditionalConstantPropagationOnCode {
+
+ private final IRCode code;
+ private final Map<Value, LatticeElement> mapping = new HashMap<>();
+
+ // TODO(b/270398965): Replace LinkedList.
+ @SuppressWarnings("JdkObsolete")
+ private final Deque<Value> ssaEdges = new LinkedList<>();
+
+ // TODO(b/270398965): Replace LinkedList.
+ @SuppressWarnings("JdkObsolete")
+ private final Deque<BasicBlock> flowEdges = new LinkedList<>();
+
+ private final BitSet[] executableFlowEdges;
+ private final BitSet visitedBlocks;
+
+ private SparseConditionalConstantPropagationOnCode(IRCode code) {
+ this.code = code;
+ int maxBlockNumber = code.getCurrentBlockNumber() + 1;
+ executableFlowEdges = new BitSet[maxBlockNumber];
+ visitedBlocks = new BitSet(maxBlockNumber);
+ }
+
+ protected CodeRewriterResult run() {
+ BasicBlock firstBlock = code.entryBlock();
+ visitInstructions(firstBlock);
+
+ while (!flowEdges.isEmpty() || !ssaEdges.isEmpty()) {
+ while (!flowEdges.isEmpty()) {
+ BasicBlock block = flowEdges.poll();
+ for (Phi phi : block.getPhis()) {
+ visitPhi(phi);
+ }
+ if (!visitedBlocks.get(block.getNumber())) {
+ visitInstructions(block);
+ }
}
- if (!visitedBlocks.get(block.getNumber())) {
- visitInstructions(block);
- }
- }
- while (!ssaEdges.isEmpty()) {
- Value value = ssaEdges.poll();
- for (Phi phi : value.uniquePhiUsers()) {
- visitPhi(phi);
- }
- for (Instruction user : value.uniqueUsers()) {
- BasicBlock userBlock = user.getBlock();
- if (visitedBlocks.get(userBlock.getNumber())) {
- visitInstruction(user);
+ while (!ssaEdges.isEmpty()) {
+ Value value = ssaEdges.poll();
+ for (Phi phi : value.uniquePhiUsers()) {
+ visitPhi(phi);
+ }
+ for (Instruction user : value.uniqueUsers()) {
+ BasicBlock userBlock = user.getBlock();
+ if (visitedBlocks.get(userBlock.getNumber())) {
+ visitInstruction(user);
+ }
}
}
}
+ boolean hasChanged = rewriteConstants();
+ assert code.isConsistentSSA(appView);
+ return CodeRewriterResult.hasChanged(hasChanged);
}
- rewriteConstants();
- assert code.isConsistentSSA(appView);
- return CodeRewriterResult.NONE;
- }
- private void rewriteConstants() {
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- List<BasicBlock> blockToAnalyze = new ArrayList<>();
- mapping.entrySet().stream()
- .filter(entry -> entry.getValue().isConst())
- .forEach(
- entry -> {
- Value value = entry.getKey();
- ConstNumber evaluatedConst = entry.getValue().asConst().getConstNumber();
- if (value.definition != evaluatedConst) {
- value.addAffectedValuesTo(affectedValues);
- if (value.isPhi()) {
- // D8 relies on dead code removal to get rid of the dead phi itself.
- if (value.hasAnyUsers()) {
- BasicBlock block = value.asPhi().getBlock();
- blockToAnalyze.add(block);
- // Create a new constant, because it can be an existing constant that flow
- // directly
- // into the phi.
- ConstNumber newConst = ConstNumber.copyOf(code, evaluatedConst);
- InstructionListIterator iterator = block.listIterator(code);
- Instruction inst = iterator.nextUntil(i -> !i.isMoveException());
- newConst.setPosition(inst.getPosition());
- if (!inst.isDebugPosition()) {
- iterator.previous();
+ private boolean rewriteConstants() {
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ List<BasicBlock> blockToAnalyze = new ArrayList<>();
+ BooleanBox hasChanged = new BooleanBox(false);
+ mapping.entrySet().stream()
+ .filter(entry -> entry.getValue().isConst())
+ .forEach(
+ entry -> {
+ Value value = entry.getKey();
+ ConstNumber evaluatedConst = entry.getValue().asConst().getConstNumber();
+ if (value.definition != evaluatedConst) {
+ value.addAffectedValuesTo(affectedValues);
+ if (value.isPhi()) {
+ // D8 relies on dead code removal to get rid of the dead phi itself.
+ if (value.hasAnyUsers()) {
+ BasicBlock block = value.asPhi().getBlock();
+ blockToAnalyze.add(block);
+ // Create a new constant, because it can be an existing constant that flow
+ // directly
+ // into the phi.
+ ConstNumber newConst = ConstNumber.copyOf(code, evaluatedConst);
+ InstructionListIterator iterator = block.listIterator(code);
+ Instruction inst = iterator.nextUntil(i -> !i.isMoveException());
+ newConst.setPosition(inst.getPosition());
+ if (!inst.isDebugPosition()) {
+ iterator.previous();
+ }
+ iterator.add(newConst);
+ value.replaceUsers(newConst.outValue());
+ hasChanged.set();
}
- iterator.add(newConst);
- value.replaceUsers(newConst.outValue());
+ } else {
+ BasicBlock block = value.definition.getBlock();
+ InstructionListIterator iterator = block.listIterator(code);
+ iterator.nextUntil(i -> i == value.definition);
+ iterator.replaceCurrentInstruction(evaluatedConst);
+ hasChanged.set();
}
- } else {
- BasicBlock block = value.definition.getBlock();
- InstructionListIterator iterator = block.listIterator(code);
- iterator.nextUntil(i -> i == value.definition);
- iterator.replaceCurrentInstruction(evaluatedConst);
}
- }
- });
- for (BasicBlock block : blockToAnalyze) {
- block.deduplicatePhis();
+ });
+ for (BasicBlock block : blockToAnalyze) {
+ block.deduplicatePhis();
+ }
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
+ boolean changed = hasChanged.get();
+ if (changed) {
+ code.removeAllDeadAndTrivialPhis();
+ code.removeRedundantBlocks();
+ }
+ return changed;
}
- if (!affectedValues.isEmpty()) {
- new TypeAnalysis(appView).narrowing(affectedValues);
+
+ private LatticeElement getLatticeElement(Value value) {
+ return mapping.getOrDefault(value, Top.getInstance());
}
- code.removeAllDeadAndTrivialPhis();
- code.removeRedundantBlocks();
- }
- private LatticeElement getLatticeElement(Value value) {
- return mapping.getOrDefault(value, Top.getInstance());
- }
+ private void setLatticeElement(Value value, LatticeElement element) {
+ mapping.put(value, element);
+ }
- private void setLatticeElement(Value value, LatticeElement element) {
- mapping.put(value, element);
- }
-
- private void visitPhi(Phi phi) {
- BasicBlock phiBlock = phi.getBlock();
- int phiBlockNumber = phiBlock.getNumber();
- LatticeElement element = Top.getInstance();
- List<BasicBlock> predecessors = phiBlock.getPredecessors();
- int size = predecessors.size();
- for (int i = 0; i < size; i++) {
- BasicBlock predecessor = predecessors.get(i);
- if (isExecutableEdge(predecessor.getNumber(), phiBlockNumber)) {
- element = element.meet(getLatticeElement(phi.getOperand(i)));
- // bottom lattice can no longer be changed, thus no need to continue
- if (element.isBottom()) {
- break;
+ private void visitPhi(Phi phi) {
+ BasicBlock phiBlock = phi.getBlock();
+ int phiBlockNumber = phiBlock.getNumber();
+ LatticeElement element = Top.getInstance();
+ List<BasicBlock> predecessors = phiBlock.getPredecessors();
+ int size = predecessors.size();
+ for (int i = 0; i < size; i++) {
+ BasicBlock predecessor = predecessors.get(i);
+ if (isExecutableEdge(predecessor.getNumber(), phiBlockNumber)) {
+ element = element.meet(getLatticeElement(phi.getOperand(i)));
+ // bottom lattice can no longer be changed, thus no need to continue
+ if (element.isBottom()) {
+ break;
+ }
+ }
+ }
+ if (!element.isTop()) {
+ LatticeElement currentPhiElement = getLatticeElement(phi);
+ if (currentPhiElement.meet(element) != currentPhiElement) {
+ ssaEdges.add(phi);
+ setLatticeElement(phi, element);
}
}
}
- if (!element.isTop()) {
- LatticeElement currentPhiElement = getLatticeElement(phi);
- if (currentPhiElement.meet(element) != currentPhiElement) {
- ssaEdges.add(phi);
- setLatticeElement(phi, element);
+
+ private void visitInstructions(BasicBlock block) {
+ for (Instruction instruction : block.getInstructions()) {
+ visitInstruction(instruction);
+ }
+ visitedBlocks.set(block.getNumber());
+ }
+
+ private void visitInstruction(Instruction instruction) {
+ if (instruction.outValue() != null && !instruction.isDebugLocalUninitialized()) {
+ LatticeElement element = instruction.evaluate(code, this::getLatticeElement);
+ LatticeElement currentLattice = getLatticeElement(instruction.outValue());
+ if (currentLattice.meet(element) != currentLattice) {
+ setLatticeElement(instruction.outValue(), element);
+ ssaEdges.add(instruction.outValue());
+ }
+ }
+ if (instruction.isJumpInstruction()) {
+ addFlowEdgesForJumpInstruction(instruction.asJumpInstruction());
}
}
- }
- private void visitInstructions(BasicBlock block) {
- for (Instruction instruction : block.getInstructions()) {
- visitInstruction(instruction);
- }
- visitedBlocks.set(block.getNumber());
- }
-
- private void visitInstruction(Instruction instruction) {
- if (instruction.outValue() != null && !instruction.isDebugLocalUninitialized()) {
- LatticeElement element = instruction.evaluate(code, this::getLatticeElement);
- LatticeElement currentLattice = getLatticeElement(instruction.outValue());
- if (currentLattice.meet(element) != currentLattice) {
- setLatticeElement(instruction.outValue(), element);
- ssaEdges.add(instruction.outValue());
- }
- }
- if (instruction.isJumpInstruction()) {
- addFlowEdgesForJumpInstruction(instruction.asJumpInstruction());
- }
- }
-
- private void addFlowEdgesForJumpInstruction(JumpInstruction jumpInstruction) {
- BasicBlock jumpInstBlock = jumpInstruction.getBlock();
- int jumpInstBlockNumber = jumpInstBlock.getNumber();
- if (jumpInstruction.isIf()) {
- If theIf = jumpInstruction.asIf();
- if (theIf.isZeroTest()) {
- LatticeElement element = getLatticeElement(theIf.inValues().get(0));
- if (element.isConst()) {
- BasicBlock target = theIf.targetFromCondition(element.asConst().getConstNumber());
- if (!isExecutableEdge(jumpInstBlockNumber, target.getNumber())) {
- setExecutableEdge(jumpInstBlockNumber, target.getNumber());
- flowEdges.add(target);
+ private void addFlowEdgesForJumpInstruction(JumpInstruction jumpInstruction) {
+ BasicBlock jumpInstBlock = jumpInstruction.getBlock();
+ int jumpInstBlockNumber = jumpInstBlock.getNumber();
+ if (jumpInstruction.isIf()) {
+ If theIf = jumpInstruction.asIf();
+ if (theIf.isZeroTest()) {
+ LatticeElement element = getLatticeElement(theIf.inValues().get(0));
+ if (element.isConst()) {
+ BasicBlock target = theIf.targetFromCondition(element.asConst().getConstNumber());
+ if (!isExecutableEdge(jumpInstBlockNumber, target.getNumber())) {
+ setExecutableEdge(jumpInstBlockNumber, target.getNumber());
+ flowEdges.add(target);
+ }
+ return;
}
+ } else {
+ LatticeElement leftElement = getLatticeElement(theIf.inValues().get(0));
+ LatticeElement rightElement = getLatticeElement(theIf.inValues().get(1));
+ if (leftElement.isConst() && rightElement.isConst()) {
+ ConstNumber leftNumber = leftElement.asConst().getConstNumber();
+ ConstNumber rightNumber = rightElement.asConst().getConstNumber();
+ BasicBlock target = theIf.targetFromCondition(leftNumber, rightNumber);
+ if (!isExecutableEdge(jumpInstBlockNumber, target.getNumber())) {
+ setExecutableEdge(jumpInstBlockNumber, target.getNumber());
+ flowEdges.add(target);
+ }
+ return;
+ }
+ assert !leftElement.isTop();
+ assert !rightElement.isTop();
+ }
+ } else if (jumpInstruction.isIntSwitch()) {
+ IntSwitch switchInst = jumpInstruction.asIntSwitch();
+ LatticeElement switchElement = getLatticeElement(switchInst.value());
+ if (switchElement.isConst()) {
+ BasicBlock target =
+ switchInst.getKeyToTargetMap().get(switchElement.asConst().getIntValue());
+ if (target == null) {
+ target = switchInst.fallthroughBlock();
+ }
+ assert target != null;
+ setExecutableEdge(jumpInstBlockNumber, target.getNumber());
+ flowEdges.add(target);
+ return;
+ }
+ } else if (jumpInstruction.isStringSwitch()) {
+ StringSwitch switchInst = jumpInstruction.asStringSwitch();
+ LatticeElement switchElement = getLatticeElement(switchInst.value());
+ if (switchElement.isConst()) {
+ // There is currently no constant propagation for strings, so it must be null.
+ assert switchElement.asConst().getConstNumber().isZero();
+ BasicBlock target = switchInst.fallthroughBlock();
+ setExecutableEdge(jumpInstBlockNumber, target.getNumber());
+ flowEdges.add(target);
return;
}
} else {
- LatticeElement leftElement = getLatticeElement(theIf.inValues().get(0));
- LatticeElement rightElement = getLatticeElement(theIf.inValues().get(1));
- if (leftElement.isConst() && rightElement.isConst()) {
- ConstNumber leftNumber = leftElement.asConst().getConstNumber();
- ConstNumber rightNumber = rightElement.asConst().getConstNumber();
- BasicBlock target = theIf.targetFromCondition(leftNumber, rightNumber);
- if (!isExecutableEdge(jumpInstBlockNumber, target.getNumber())) {
- setExecutableEdge(jumpInstBlockNumber, target.getNumber());
- flowEdges.add(target);
- }
- return;
+ assert jumpInstruction.isGoto() || jumpInstruction.isReturn() || jumpInstruction.isThrow();
+ }
+
+ for (BasicBlock dst : jumpInstBlock.getSuccessors()) {
+ if (!isExecutableEdge(jumpInstBlockNumber, dst.getNumber())) {
+ setExecutableEdge(jumpInstBlockNumber, dst.getNumber());
+ flowEdges.add(dst);
}
- assert !leftElement.isTop();
- assert !rightElement.isTop();
}
- } else if (jumpInstruction.isIntSwitch()) {
- IntSwitch switchInst = jumpInstruction.asIntSwitch();
- LatticeElement switchElement = getLatticeElement(switchInst.value());
- if (switchElement.isConst()) {
- BasicBlock target = switchInst.getKeyToTargetMap()
- .get(switchElement.asConst().getIntValue());
- if (target == null) {
- target = switchInst.fallthroughBlock();
- }
- assert target != null;
- setExecutableEdge(jumpInstBlockNumber, target.getNumber());
- flowEdges.add(target);
- return;
- }
- } else if (jumpInstruction.isStringSwitch()) {
- StringSwitch switchInst = jumpInstruction.asStringSwitch();
- LatticeElement switchElement = getLatticeElement(switchInst.value());
- if (switchElement.isConst()) {
- // There is currently no constant propagation for strings, so it must be null.
- assert switchElement.asConst().getConstNumber().isZero();
- BasicBlock target = switchInst.fallthroughBlock();
- setExecutableEdge(jumpInstBlockNumber, target.getNumber());
- flowEdges.add(target);
- return;
- }
- } else {
- assert jumpInstruction.isGoto() || jumpInstruction.isReturn() || jumpInstruction.isThrow();
}
- for (BasicBlock dst : jumpInstBlock.getSuccessors()) {
- if (!isExecutableEdge(jumpInstBlockNumber, dst.getNumber())) {
- setExecutableEdge(jumpInstBlockNumber, dst.getNumber());
- flowEdges.add(dst);
+ private void setExecutableEdge(int from, int to) {
+ BitSet previousExecutable = executableFlowEdges[to];
+ if (previousExecutable == null) {
+ previousExecutable = new BitSet(executableFlowEdges.length);
+ executableFlowEdges[to] = previousExecutable;
}
+ previousExecutable.set(from);
}
- }
- private void setExecutableEdge(int from, int to) {
- BitSet previousExecutable = executableFlowEdges[to];
- if (previousExecutable == null) {
- previousExecutable = new BitSet(executableFlowEdges.length);
- executableFlowEdges[to] = previousExecutable;
+ private boolean isExecutableEdge(int from, int to) {
+ BitSet previousExecutable = executableFlowEdges[to];
+ if (previousExecutable == null) {
+ return false;
+ }
+ return previousExecutable.get(from);
}
- previousExecutable.set(from);
- }
-
- private boolean isExecutableEdge(int from, int to) {
- BitSet previousExecutable = executableFlowEdges[to];
- if (previousExecutable == null) {
- return false;
- }
- return previousExecutable.get(from);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index d871616..e6ac132 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -85,7 +85,7 @@
appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField();
if (field != null) {
if (fieldAssignmentTracker != null) {
- fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field, code.context());
+ fieldAssignmentTracker.recordFieldAccess(fieldInstruction, field);
}
if (fieldBitAccessAnalysis != null) {
fieldBitAccessAnalysis.recordFieldAccess(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 0781787..0f47d7e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -45,12 +45,13 @@
import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepFieldInfo;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.ProgramFieldMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.IdentityHashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -76,7 +77,7 @@
// has been seen to the field.
private final Map<DexEncodedField, FieldState> fieldStates = new ConcurrentHashMap<>();
- private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>>
+ private final Map<DexProgramClass, ProgramFieldMap<AbstractValue>>
abstractFinalInstanceFieldValues = new ConcurrentHashMap<>();
FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
@@ -116,15 +117,14 @@
// No instance fields to track.
return;
}
- Map<DexEncodedField, AbstractValue> abstractFinalInstanceFieldValuesForClass =
- new IdentityHashMap<>();
+ ProgramFieldMap<AbstractValue> abstractFinalInstanceFieldValuesForClass =
+ ProgramFieldMap.create();
clazz.forEachProgramInstanceField(
field -> {
if (field.isFinalOrEffectivelyFinal(appView)) {
FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.getReference());
if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
- abstractFinalInstanceFieldValuesForClass.put(
- field.getDefinition(), BottomValue.getInstance());
+ abstractFinalInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
}
}
});
@@ -185,13 +185,13 @@
});
}
- void recordFieldAccess(FieldInstruction instruction, ProgramField field, ProgramMethod context) {
+ void recordFieldAccess(FieldInstruction instruction, ProgramField field) {
if (instruction.isFieldPut()) {
- recordFieldPut(field, instruction.value(), context);
+ recordFieldPut(field, instruction.value());
}
}
- private void recordFieldPut(ProgramField field, Value value, ProgramMethod context) {
+ private void recordFieldPut(ProgramField field, Value value) {
// For now only attempt to prove that fields are definitely null. In order to prove a single
// value for fields that are not definitely null, we need to prove that the given field is never
// read before it is written.
@@ -225,12 +225,12 @@
if (fieldState.isArray()) {
ConcreteArrayTypeFieldState arrayFieldState = fieldState.asArray();
- return arrayFieldState.mutableJoin(appView, abstractValue);
+ return arrayFieldState.mutableJoin(appView, field, abstractValue);
}
if (fieldState.isPrimitive()) {
ConcretePrimitiveTypeFieldState primitiveFieldState = fieldState.asPrimitive();
- return primitiveFieldState.mutableJoin(abstractValue, abstractValueFactory);
+ return primitiveFieldState.mutableJoin(appView, field, abstractValue);
}
assert fieldState.isClass();
@@ -242,7 +242,7 @@
}
void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
- Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+ ProgramFieldMap<AbstractValue> abstractInstanceFieldValuesForClass =
abstractFinalInstanceFieldValues.get(clazz);
if (abstractInstanceFieldValuesForClass == null) {
// We are not tracking the value of any of clazz' instance fields.
@@ -273,75 +273,59 @@
// Synchronize on the lattice element (abstractInstanceFieldValuesForClass) in case we process
// another allocation site of `clazz` concurrently.
synchronized (abstractInstanceFieldValuesForClass) {
- Iterator<Map.Entry<DexEncodedField, AbstractValue>> iterator =
- abstractInstanceFieldValuesForClass.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next();
- DexEncodedField field = entry.getKey();
- AbstractValue abstractValue = entry.getValue();
-
- // The power set lattice is an expensive abstraction, so use it with caution.
- boolean isClassIdField = HorizontalClassMergerUtils.isClassIdField(appView, field);
-
- InstanceFieldInitializationInfo initializationInfo =
- initializationInfoCollection.get(field);
- if (initializationInfo.isArgumentInitializationInfo()) {
- InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
- initializationInfo.asArgumentInitializationInfo();
- Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
- AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context);
- abstractValue =
- abstractValue.join(
- argumentAbstractValue,
- appView.abstractValueFactory(),
- field.getType().isReferenceType(),
- isClassIdField);
- assert !abstractValue.isBottom();
- } else if (initializationInfo.isSingleValue()) {
- SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
- abstractValue =
- abstractValue.join(
- singleValueInitializationInfo,
- appView.abstractValueFactory(),
- field.getType().isReferenceType(),
- isClassIdField);
- } else if (initializationInfo.isTypeInitializationInfo()) {
- // TODO(b/149732532): Not handled, for now.
- abstractValue = UnknownValue.getInstance();
- } else {
- assert initializationInfo.isUnknown();
- abstractValue = UnknownValue.getInstance();
- }
-
- assert !abstractValue.isBottom();
-
- // When approximating the possible values for the $r8$classId fields from horizontal class
- // merging, give up if the set of possible values equals the size of the merge group. In
- // this case, the information is useless.
- if (isClassIdField && abstractValue.isNonConstantNumberValue()) {
- NonConstantNumberValue initialAbstractValue =
- field.getOptimizationInfo().getAbstractValue().asNonConstantNumberValue();
- if (initialAbstractValue != null) {
- if (abstractValue.asNonConstantNumberValue().getAbstractionSize()
- >= initialAbstractValue.getAbstractionSize()) {
+ abstractInstanceFieldValuesForClass.removeIf(
+ (field, abstractValue, entry) -> {
+ InstanceFieldInitializationInfo initializationInfo =
+ initializationInfoCollection.get(field);
+ if (initializationInfo.isArgumentInitializationInfo()) {
+ InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
+ initializationInfo.asArgumentInitializationInfo();
+ Value argument =
+ invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
+ AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context);
+ abstractValue =
+ appView
+ .getAbstractValueFieldJoiner()
+ .join(abstractValue, argumentAbstractValue, field);
+ } else if (initializationInfo.isSingleValue()) {
+ SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
+ abstractValue =
+ appView
+ .getAbstractValueFieldJoiner()
+ .join(abstractValue, singleValueInitializationInfo, field);
+ } else if (initializationInfo.isTypeInitializationInfo()) {
+ // TODO(b/149732532): Not handled, for now.
+ abstractValue = UnknownValue.getInstance();
+ } else {
+ assert initializationInfo.isUnknown();
abstractValue = UnknownValue.getInstance();
}
- }
- }
- if (!abstractValue.isUnknown()) {
- entry.setValue(abstractValue);
- continue;
- }
+ assert !abstractValue.isBottom();
- // We just lost track for this field.
- iterator.remove();
- }
+ // When approximating the possible values for the $r8$classId fields from horizontal
+ // class
+ // merging, give up if the set of possible values equals the size of the merge group. In
+ // this case, the information is useless.
+ if (abstractValue.isNonConstantNumberValue()) {
+ assert HorizontalClassMergerUtils.isClassIdField(appView, field);
+ NonConstantNumberValue initialAbstractValue =
+ field.getOptimizationInfo().getAbstractValue().asNonConstantNumberValue();
+ if (initialAbstractValue != null
+ && abstractValue.asNonConstantNumberValue().getAbstractionSize()
+ >= initialAbstractValue.getAbstractionSize()) {
+ abstractValue = UnknownValue.getInstance();
+ }
+ }
+
+ entry.setValue(abstractValue);
+ return abstractValue.isUnknown();
+ });
}
}
private void recordAllFieldPutsProcessed(
- ProgramField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
+ ProgramField field, OptimizationFeedbackDelayed feedback) {
FieldState fieldState = fieldStates.getOrDefault(field.getDefinition(), FieldState.bottom());
AbstractValue abstractValue = fieldState.getAbstractValue(appView.abstractValueFactory());
if (abstractValue.isNonTrivial()) {
@@ -384,10 +368,9 @@
.get(field);
if (fieldInitializationInfo.isSingleValue()) {
abstractValue =
- abstractValue.join(
- fieldInitializationInfo.asSingleValue(),
- appView.abstractValueFactory(),
- field.getType());
+ appView
+ .getAbstractValueFieldJoiner()
+ .join(abstractValue, fieldInitializationInfo.asSingleValue(), field);
if (abstractValue.isUnknown()) {
break;
}
@@ -413,24 +396,25 @@
private void recordAllAllocationsSitesProcessed(
DexProgramClass clazz, OptimizationFeedbackDelayed feedback) {
- Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+ ProgramFieldMap<AbstractValue> abstractInstanceFieldValuesForClass =
abstractFinalInstanceFieldValues.get(clazz);
if (abstractInstanceFieldValuesForClass == null) {
return;
}
- for (DexEncodedField field : clazz.instanceFields()) {
- AbstractValue abstractValue =
- abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance());
- if (abstractValue.isBottom()) {
- feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz));
- break;
- }
- if (abstractValue.isUnknown()) {
- continue;
- }
- feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
- }
+ clazz.traverseProgramInstanceFields(
+ field -> {
+ AbstractValue abstractValue =
+ abstractInstanceFieldValuesForClass.getOrDefault(field, UnknownValue.getInstance());
+ if (abstractValue.isBottom()) {
+ feedback.modifyAppInfoWithLiveness(modifier -> modifier.removeInstantiatedType(clazz));
+ return TraversalContinuation.doBreak();
+ }
+ if (abstractValue.isNonTrivial()) {
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+ }
+ return TraversalContinuation.doContinue();
+ });
}
public void waveDone(ProgramMethodSet wave, OptimizationFeedbackDelayed feedback) {
@@ -438,8 +422,7 @@
// therefore important that the optimization info has been flushed in advance.
assert feedback.noUpdatesLeft();
for (ProgramMethod method : wave) {
- fieldAccessGraph.markProcessed(
- method, field -> recordAllFieldPutsProcessed(field, method, feedback));
+ fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
objectAllocationGraph.markProcessed(
method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
index 1b82558..fef8935 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.analysis.fieldaccess.state;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -36,9 +37,10 @@
return this;
}
- public FieldState mutableJoin(AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
+ public FieldState mutableJoin(
+ AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue abstractValue) {
this.abstractValue =
- this.abstractValue.joinReference(abstractValue, appView.abstractValueFactory());
+ appView.getAbstractValueFieldJoiner().join(this.abstractValue, abstractValue, field);
return isEffectivelyUnknown() ? unknown() : this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
index 3f51c25..a323ba6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
@@ -48,7 +48,7 @@
ProgramField field) {
assert field.getType().isClassType();
this.abstractValue =
- this.abstractValue.joinReference(abstractValue, appView.abstractValueFactory());
+ appView.getAbstractValueFieldJoiner().join(this.abstractValue, abstractValue, field);
this.dynamicType =
WideningUtils.widenDynamicNonReceiverType(
appView, this.dynamicType.join(appView, dynamicType), field.getType());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
index c2eb778..1f329f5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.analysis.fieldaccess.state;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
/** The information that we track for fields whose type is a primitive type. */
public class ConcretePrimitiveTypeFieldState extends ConcreteFieldState {
@@ -38,11 +41,12 @@
}
public FieldState mutableJoin(
- AbstractValue abstractValue, AbstractValueFactory abstractValueFactory) {
+ AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue abstractValue) {
if (abstractValue.isUnknown()) {
return FieldState.unknown();
}
- this.abstractValue = this.abstractValue.joinPrimitive(abstractValue, abstractValueFactory);
+ this.abstractValue =
+ appView.getAbstractValueFieldJoiner().join(this.abstractValue, abstractValue, field);
return isEffectivelyUnknown() ? unknown() : this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index c4e0a83..f657c65 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.analysis.value;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -75,6 +74,10 @@
return null;
}
+ public boolean hasDefinitelySetAndUnsetBitsInformation() {
+ return false;
+ }
+
public boolean hasKnownArrayLength() {
return false;
}
@@ -176,71 +179,12 @@
return null;
}
- public AbstractValue join(AbstractValue other, AbstractValueFactory factory, DexType type) {
- return join(other, factory, type.isReferenceType(), false);
+ public boolean isDefiniteBitsNumberValue() {
+ return false;
}
- public AbstractValue joinPrimitive(AbstractValue other, AbstractValueFactory factory) {
- return join(other, factory, false, false);
- }
-
- public AbstractValue joinReference(AbstractValue other, AbstractValueFactory factory) {
- return join(other, factory, true, false);
- }
-
- // TODO(b/196321452): Clean this up, in particular, replace the "allow" parameters by a
- // configuration object.
- public AbstractValue join(
- AbstractValue other,
- AbstractValueFactory factory,
- boolean allowNullOrAbstractValue,
- boolean allowNonConstantNumbers) {
- if (isBottom() || other.isUnknown()) {
- return other;
- }
- if (isUnknown() || other.isBottom()) {
- return this;
- }
- if (equals(other)) {
- return this;
- }
- if (allowNullOrAbstractValue) {
- if (isNull()) {
- return NullOrAbstractValue.create(other);
- }
- if (other.isNull()) {
- return NullOrAbstractValue.create(this);
- }
- if (isNullOrAbstractValue() && asNullOrAbstractValue().getNonNullValue().equals(other)) {
- return this;
- }
- if (other.isNullOrAbstractValue()
- && other.asNullOrAbstractValue().getNonNullValue().equals(this)) {
- return other;
- }
- return unknown();
- }
- assert !isNullOrAbstractValue();
- assert !other.isNullOrAbstractValue();
- if (allowNonConstantNumbers
- && isConstantOrNonConstantNumberValue()
- && other.isConstantOrNonConstantNumberValue()) {
- NumberFromSetValue.Builder numberFromSetValueBuilder;
- if (isSingleNumberValue()) {
- numberFromSetValueBuilder = NumberFromSetValue.builder(asSingleNumberValue());
- } else {
- assert isNumberFromSetValue();
- numberFromSetValueBuilder = asNumberFromSetValue().instanceBuilder();
- }
- if (other.isSingleNumberValue()) {
- numberFromSetValueBuilder.addInt(other.asSingleNumberValue().getIntValue());
- } else {
- assert other.isNumberFromSetValue();
- numberFromSetValueBuilder.addInts(other.asNumberFromSetValue());
- }
- return numberFromSetValueBuilder.build(factory);
- }
- return unknown();
+ public DefiniteBitsNumberValue asDefiniteBitsNumberValue() {
+ return null;
}
public abstract AbstractValue rewrittenWithLens(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index bab5e75..bc50262 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -23,6 +23,14 @@
private ConcurrentHashMap<Integer, KnownLengthArrayState> knownArrayLengthStates =
new ConcurrentHashMap<>();
+ public AbstractValue createDefiniteBitsNumberValue(
+ int definitelySetBits, int definitelyUnsetBits) {
+ if (definitelySetBits != 0 && definitelyUnsetBits != 0) {
+ return new DefiniteBitsNumberValue(definitelySetBits, definitelyUnsetBits);
+ }
+ return AbstractValue.unknown();
+ }
+
public SingleConstClassValue createSingleConstClassValue(DexType type) {
return singleConstClassValues.computeIfAbsent(type, SingleConstClassValue::new);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java
new file mode 100644
index 0000000..0c49fca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueJoiner.java
@@ -0,0 +1,217 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import static com.android.tools.r8.ir.analysis.value.AbstractValue.unknown;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
+
+public abstract class AbstractValueJoiner {
+
+ protected final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+ private AbstractValueJoiner(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this.appView = appView;
+ }
+
+ private AbstractValueFactory factory() {
+ return appView.abstractValueFactory();
+ }
+
+ final AbstractValue internalJoin(
+ AbstractValue abstractValue,
+ AbstractValue otherAbstractValue,
+ AbstractValueJoinerConfig config,
+ DexType type) {
+ if (abstractValue.isBottom() || otherAbstractValue.isUnknown()) {
+ return otherAbstractValue;
+ }
+ if (abstractValue.isUnknown()
+ || otherAbstractValue.isBottom()
+ || abstractValue.equals(otherAbstractValue)) {
+ return abstractValue;
+ }
+ return type.isReferenceType()
+ ? joinReference(abstractValue, otherAbstractValue)
+ : joinPrimitive(abstractValue, otherAbstractValue, config, type);
+ }
+
+ private AbstractValue joinPrimitive(
+ AbstractValue abstractValue,
+ AbstractValue otherAbstractValue,
+ AbstractValueJoinerConfig config,
+ DexType type) {
+ assert !abstractValue.isNullOrAbstractValue();
+ assert !otherAbstractValue.isNullOrAbstractValue();
+
+ if (config.canUseNumberIntervalAndNumberSetAbstraction()
+ && abstractValue.isConstantOrNonConstantNumberValue()
+ && otherAbstractValue.isConstantOrNonConstantNumberValue()) {
+ NumberFromSetValue.Builder numberFromSetValueBuilder;
+ if (abstractValue.isSingleNumberValue()) {
+ numberFromSetValueBuilder = NumberFromSetValue.builder(abstractValue.asSingleNumberValue());
+ } else {
+ assert abstractValue.isNumberFromSetValue();
+ numberFromSetValueBuilder = abstractValue.asNumberFromSetValue().instanceBuilder();
+ }
+ if (otherAbstractValue.isSingleNumberValue()) {
+ numberFromSetValueBuilder.addInt(otherAbstractValue.asSingleNumberValue().getIntValue());
+ } else {
+ assert otherAbstractValue.isNumberFromSetValue();
+ numberFromSetValueBuilder.addInts(otherAbstractValue.asNumberFromSetValue());
+ }
+ return numberFromSetValueBuilder.build(factory());
+ }
+
+ if (config.canUseDefiniteBitsAbstraction()) {
+ return joinPrimitiveToDefiniteBitsNumberValue(abstractValue, otherAbstractValue, type);
+ }
+
+ return unknown();
+ }
+
+ private AbstractValue joinPrimitiveToDefiniteBitsNumberValue(
+ AbstractValue abstractValue, AbstractValue otherAbstractValue, DexType type) {
+ assert type.isIntType();
+ if (!abstractValue.hasDefinitelySetAndUnsetBitsInformation()
+ || !otherAbstractValue.hasDefinitelySetAndUnsetBitsInformation()) {
+ return unknown();
+ }
+ // Normalize order.
+ if (!abstractValue.isSingleNumberValue() && otherAbstractValue.isSingleNumberValue()) {
+ AbstractValue tmp = abstractValue;
+ abstractValue = otherAbstractValue;
+ otherAbstractValue = tmp;
+ }
+ if (abstractValue.isSingleNumberValue()) {
+ SingleNumberValue singleNumberValue = abstractValue.asSingleNumberValue();
+ if (otherAbstractValue.isSingleNumberValue()) {
+ SingleNumberValue otherSingleNumberValue = otherAbstractValue.asSingleNumberValue();
+ return factory()
+ .createDefiniteBitsNumberValue(
+ singleNumberValue.getDefinitelySetIntBits()
+ & otherSingleNumberValue.getDefinitelySetIntBits(),
+ singleNumberValue.getDefinitelyUnsetIntBits()
+ & otherSingleNumberValue.getDefinitelyUnsetIntBits());
+ } else {
+ assert otherAbstractValue.isDefiniteBitsNumberValue();
+ DefiniteBitsNumberValue otherDefiniteBitsNumberValue =
+ otherAbstractValue.asDefiniteBitsNumberValue();
+ return otherDefiniteBitsNumberValue.join(factory(), singleNumberValue);
+ }
+ } else {
+ // Both are guaranteed to be non-const due to normalization.
+ assert abstractValue.isDefiniteBitsNumberValue();
+ assert otherAbstractValue.isDefiniteBitsNumberValue();
+ DefiniteBitsNumberValue definiteBitsNumberValue = abstractValue.asDefiniteBitsNumberValue();
+ DefiniteBitsNumberValue otherDefiniteBitsNumberValue =
+ otherAbstractValue.asDefiniteBitsNumberValue();
+ return definiteBitsNumberValue.join(factory(), otherDefiniteBitsNumberValue);
+ }
+ }
+
+ private AbstractValue joinReference(
+ AbstractValue abstractValue, AbstractValue otherAbstractValue) {
+ if (abstractValue.isNull()) {
+ return NullOrAbstractValue.create(otherAbstractValue);
+ }
+ if (otherAbstractValue.isNull()) {
+ return NullOrAbstractValue.create(abstractValue);
+ }
+ if (abstractValue.isNullOrAbstractValue()
+ && abstractValue.asNullOrAbstractValue().getNonNullValue().equals(otherAbstractValue)) {
+ return abstractValue;
+ }
+ if (otherAbstractValue.isNullOrAbstractValue()
+ && otherAbstractValue.asNullOrAbstractValue().getNonNullValue().equals(abstractValue)) {
+ return otherAbstractValue;
+ }
+ return unknown();
+ }
+
+ public static class AbstractValueFieldJoiner extends AbstractValueJoiner {
+
+ public AbstractValueFieldJoiner(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ super(appView);
+ }
+
+ public AbstractValue join(
+ AbstractValue abstractValue, AbstractValue otherAbstractValue, ProgramField field) {
+ AbstractValueJoinerConfig config = getConfig(field);
+ AbstractValue result =
+ internalJoin(abstractValue, otherAbstractValue, config, field.getType());
+ assert result.equals(
+ internalJoin(otherAbstractValue, abstractValue, config, field.getType()));
+ return result;
+ }
+
+ private AbstractValueJoinerConfig getConfig(ProgramField field) {
+ if (HorizontalClassMergerUtils.isClassIdField(appView, field)) {
+ return AbstractValueJoinerConfig.getClassIdFieldConfig();
+ }
+ return AbstractValueJoinerConfig.getDefaultConfig();
+ }
+ }
+
+ public static class AbstractValueParameterJoiner extends AbstractValueJoiner {
+
+ public AbstractValueParameterJoiner(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ super(appView);
+ }
+
+ public AbstractValue join(
+ AbstractValue abstractValue, AbstractValue otherAbstractValue, DexType type) {
+ // TODO(b/196017578): Use a config that allows the definite bits abstraction for parameters
+ // used in bitwise operations.
+ AbstractValueJoinerConfig config = AbstractValueJoinerConfig.getDefaultConfig();
+ AbstractValue result = internalJoin(abstractValue, otherAbstractValue, config, type);
+ assert result.equals(internalJoin(otherAbstractValue, abstractValue, config, type));
+ return result;
+ }
+ }
+
+ private static class AbstractValueJoinerConfig {
+
+ // The power set lattice is an expensive abstraction, so use it with caution.
+ private static final AbstractValueJoinerConfig CLASS_ID_FIELD_CONFIG =
+ new AbstractValueJoinerConfig().setCanUseNumberIntervalAndNumberSetAbstraction();
+
+ private static final AbstractValueJoinerConfig DEFAULT_CONFIG = new AbstractValueJoinerConfig();
+
+ public static AbstractValueJoinerConfig getClassIdFieldConfig() {
+ return CLASS_ID_FIELD_CONFIG;
+ }
+
+ public static AbstractValueJoinerConfig getDefaultConfig() {
+ return DEFAULT_CONFIG;
+ }
+
+ private boolean canUseDefiniteBitsAbstraction;
+ private boolean canUseNumberIntervalAndNumberSetAbstraction;
+
+ boolean canUseDefiniteBitsAbstraction() {
+ return canUseDefiniteBitsAbstraction;
+ }
+
+ @SuppressWarnings("UnusedMethod")
+ AbstractValueJoinerConfig setCanUseDefiniteBitsAbstraction() {
+ canUseDefiniteBitsAbstraction = true;
+ return this;
+ }
+
+ boolean canUseNumberIntervalAndNumberSetAbstraction() {
+ return canUseNumberIntervalAndNumberSetAbstraction;
+ }
+
+ AbstractValueJoinerConfig setCanUseNumberIntervalAndNumberSetAbstraction() {
+ canUseNumberIntervalAndNumberSetAbstraction = true;
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
new file mode 100644
index 0000000..b081050
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/DefiniteBitsNumberValue.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.Objects;
+
+public class DefiniteBitsNumberValue extends NonConstantNumberValue {
+
+ private final int definitelySetBits;
+ private final int definitelyUnsetBits;
+
+ public DefiniteBitsNumberValue(int definitelySetBits, int definitelyUnsetBits) {
+ assert (definitelySetBits & definitelyUnsetBits) == 0;
+ this.definitelySetBits = definitelySetBits;
+ this.definitelyUnsetBits = definitelyUnsetBits;
+ }
+
+ @Override
+ public boolean containsInt(int value) {
+ return false;
+ }
+
+ @Override
+ public long getAbstractionSize() {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public boolean hasDefinitelySetAndUnsetBitsInformation() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefiniteBitsNumberValue() {
+ return true;
+ }
+
+ @Override
+ public DefiniteBitsNumberValue asDefiniteBitsNumberValue() {
+ return this;
+ }
+
+ @Override
+ public boolean isNonTrivial() {
+ return true;
+ }
+
+ @Override
+ public OptionalBool isSubsetOf(int[] values) {
+ return OptionalBool.unknown();
+ }
+
+ public AbstractValue join(
+ AbstractValueFactory abstractValueFactory, DefiniteBitsNumberValue definiteBitsNumberValue) {
+ return join(
+ abstractValueFactory,
+ definiteBitsNumberValue.definitelySetBits,
+ definiteBitsNumberValue.definitelyUnsetBits);
+ }
+
+ public AbstractValue join(
+ AbstractValueFactory abstractValueFactory, SingleNumberValue singleNumberValue) {
+ return join(
+ abstractValueFactory,
+ singleNumberValue.getDefinitelySetIntBits(),
+ singleNumberValue.getDefinitelyUnsetIntBits());
+ }
+
+ public AbstractValue join(
+ AbstractValueFactory abstractValueFactory,
+ int otherDefinitelySetBits,
+ int otherDefinitelyUnsetBits) {
+ if (definitelySetBits == otherDefinitelySetBits
+ && definitelyUnsetBits == otherDefinitelyUnsetBits) {
+ return this;
+ }
+ return abstractValueFactory.createDefiniteBitsNumberValue(
+ definitelySetBits & otherDefinitelySetBits, definitelyUnsetBits & otherDefinitelyUnsetBits);
+ }
+
+ @Override
+ public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+ return true;
+ }
+
+ @Override
+ public AbstractValue rewrittenWithLens(
+ AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || o.getClass() != getClass()) {
+ return false;
+ }
+ DefiniteBitsNumberValue definiteBitsNumberValue = (DefiniteBitsNumberValue) o;
+ return definitelySetBits == definiteBitsNumberValue.definitelySetBits
+ && definitelyUnsetBits == definiteBitsNumberValue.definitelyUnsetBits;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 31 * (31 * (31 + definitelySetBits) + definitelyUnsetBits);
+ assert hash == Objects.hash(definitelySetBits, definitelyUnsetBits);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return "DefiniteBitsNumberValue(set: "
+ + Integer.toBinaryString(definitelySetBits)
+ + "; unset: "
+ + Integer.toBinaryString(definitelyUnsetBits)
+ + ")";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 5327427..e29e468 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -37,6 +37,11 @@
}
@Override
+ public boolean hasDefinitelySetAndUnsetBitsInformation() {
+ return true;
+ }
+
+ @Override
public OptionalBool isSubsetOf(int[] values) {
return OptionalBool.of(ArrayUtils.containsInt(values, getIntValue()));
}
@@ -81,6 +86,14 @@
return value != 0;
}
+ public int getDefinitelySetIntBits() {
+ return getIntValue();
+ }
+
+ public int getDefinitelyUnsetIntBits() {
+ return ~getDefinitelySetIntBits();
+ }
+
public double getDoubleValue() {
return Double.longBitsToDouble(value);
}
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 0a5ebca..3afb79b 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
@@ -439,13 +439,14 @@
public static IRBuilder create(
ProgramMethod method, AppView<?> appView, SourceCode source, Origin origin) {
+ GraphLens codeLens = method.getDefinition().getCode().getCodeLens(appView);
return new IRBuilder(
method,
appView,
- method.getDefinition().getCode().getCodeLens(appView),
+ codeLens,
source,
origin,
- lookupPrototypeChanges(appView, method),
+ lookupPrototypeChanges(appView, method, codeLens),
new NumberGenerator());
}
@@ -462,8 +463,10 @@
}
public static RewrittenPrototypeDescription lookupPrototypeChanges(
- AppView<?> appView, ProgramMethod method) {
- return appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
+ AppView<?> appView, ProgramMethod method, GraphLens codeLens) {
+ return appView
+ .graphLens()
+ .lookupPrototypeChangesForMethodDefinition(method.getReference(), codeLens);
}
private IRBuilder(
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 5b22d0e..cd7af3b 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
@@ -756,7 +756,7 @@
new ArrayConstructionSimplifier(appView).run(code, timing);
new MoveResultRewriter(appView).run(code, timing);
new StringBuilderAppendOptimizer(appView).run(code, timing);
- new SparseConditionalConstantPropagation(appView, code).run(code, timing);
+ new SparseConditionalConstantPropagation(appView).run(code, timing);
new ThrowCatchOptimizer(appView).run(code, timing);
if (new BranchSimplifier(appView)
.run(code, timing)
@@ -767,7 +767,7 @@
}
new SplitBranch(appView).run(code, timing);
new RedundantConstNumberRemover(appView).run(code, timing);
- new RedundantFieldLoadAndStoreElimination(appView, code).run(code, timing);
+ new RedundantFieldLoadAndStoreElimination(appView).run(code, timing);
new BinopRewriter(appView).run(code, timing);
timing.begin("Optimize class initializers");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 729a2e5..93ce2a2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
@@ -55,7 +56,6 @@
private DexApplication internalOptimize(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
throws ExecutionException {
- appView.testing().enterLirSupportedPhase();
// Desugaring happens in the enqueuer.
assert instructionDesugaring.isEmpty();
@@ -217,30 +217,37 @@
// Assure that no more optimization feedback left after post processing.
assert feedback.noUpdatesLeft();
- finalizeLirToOutputFormat(timing, executorService);
return appView.appInfo().app();
}
- private void finalizeLirToOutputFormat(Timing timing, ExecutorService executorService)
+ public static void finalizeLirToOutputFormat(
+ AppView<?> appView, Timing timing, ExecutorService executorService)
throws ExecutionException {
appView.testing().exitLirSupportedPhase();
- if (!options.testing.canUseLir(appView)) {
+ if (!appView.testing().canUseLir(appView)) {
return;
}
- String output = options.isGeneratingClassFiles() ? "CF" : "DEX";
+ DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
+ String output = appView.options().isGeneratingClassFiles() ? "CF" : "DEX";
timing.begin("LIR->IR->" + output);
ThreadUtils.processItems(
appView.appInfo().classes(),
- clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat),
+ clazz ->
+ clazz.forEachProgramMethod(
+ m -> finalizeLirMethodToOutputFormat(m, deadCodeRemover, appView)),
executorService);
appView
.getSyntheticItems()
.getPendingSyntheticClasses()
- .forEach(clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat));
+ .forEach(
+ clazz ->
+ clazz.forEachProgramMethod(
+ m -> finalizeLirMethodToOutputFormat(m, deadCodeRemover, appView)));
timing.end();
}
- void finalizeLirMethodToOutputFormat(ProgramMethod method) {
+ private static void finalizeLirMethodToOutputFormat(
+ ProgramMethod method, DeadCodeRemover deadCodeRemover, AppView<?> appView) {
Code code = method.getDefinition().getCode();
if (!(code instanceof LirCode)) {
return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
index 3176d29..b8a54fb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -94,14 +94,16 @@
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
+ boolean hasChanged = false;
WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
while (worklist.hasNext()) {
BasicBlock block = worklist.next();
- simplifyArrayConstructionBlock(block, worklist, code, appView.options());
+ hasChanged |= simplifyArrayConstructionBlock(block, worklist, code, appView.options());
}
- // Do only when the rewriter pass has changed something.
- code.removeRedundantBlocks();
- return CodeRewriterResult.NONE;
+ if (hasChanged) {
+ code.removeRedundantBlocks();
+ }
+ return CodeRewriterResult.hasChanged(hasChanged);
}
@Override
@@ -109,8 +111,9 @@
return appView.options().isGeneratingDex();
}
- private void simplifyArrayConstructionBlock(
+ private boolean simplifyArrayConstructionBlock(
BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
+ boolean hasChanged = false;
RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
@@ -199,7 +202,9 @@
// The above has invalidated the block iterator so reset it and continue.
it = block.listIterator(code, instructionAfterCandidate);
+ hasChanged = true;
}
+ return hasChanged;
}
private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
index 70c64f1..aed22c2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -252,6 +252,7 @@
@Override
public CodeRewriterResult rewriteCode(IRCode code) {
+ boolean hasChanged = false;
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
Instruction next = iterator.next();
@@ -262,39 +263,42 @@
BinopDescriptor binopDescriptor = descriptors.get(binop.getClass());
assert binopDescriptor != null;
if (identityAbsorbingSimplification(iterator, binop, binopDescriptor)) {
+ hasChanged = true;
continue;
}
- successiveSimplification(iterator, binop, binopDescriptor, code);
+ hasChanged |= successiveSimplification(iterator, binop, binopDescriptor, code);
}
}
}
- code.removeAllDeadAndTrivialPhis();
- code.removeRedundantBlocks();
+ if (hasChanged) {
+ code.removeAllDeadAndTrivialPhis();
+ code.removeRedundantBlocks();
+ }
assert code.isConsistentSSA(appView);
- return CodeRewriterResult.NONE;
+ return CodeRewriterResult.hasChanged(hasChanged);
}
- private void successiveSimplification(
+ private boolean successiveSimplification(
InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
if (binop.outValue().hasDebugUsers()) {
- return;
+ return false;
}
ConstNumber constBLeft = getConstNumber(binop.leftValue());
ConstNumber constBRight = getConstNumber(binop.rightValue());
if ((constBLeft != null && constBRight != null)
|| (constBLeft == null && constBRight == null)) {
- return;
+ return false;
}
Value otherValue = constBLeft == null ? binop.leftValue() : binop.rightValue();
if (otherValue.isPhi() || !otherValue.getDefinition().isBinop()) {
- return;
+ return false;
}
Binop prevBinop = otherValue.getDefinition().asBinop();
ConstNumber constALeft = getConstNumber(prevBinop.leftValue());
ConstNumber constARight = getConstNumber(prevBinop.rightValue());
if ((constALeft != null && constARight != null)
|| (constALeft == null && constARight == null)) {
- return;
+ return false;
}
ConstNumber constB = constBLeft == null ? constBRight : constBLeft;
ConstNumber constA = constALeft == null ? constARight : constALeft;
@@ -306,11 +310,13 @@
assert binop.isCommutative();
Value newConst = addNewConstNumber(code, iterator, constB, constA, binopDescriptor);
replaceBinop(iterator, code, input, newConst, binopDescriptor);
+ return true;
} else if (binopDescriptor.isShift()) {
// x shift: a shift: b => x shift: (a + b) where a + b is a constant.
if (constBRight != null && constARight != null) {
Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
replaceBinop(iterator, code, input, newConst, binopDescriptor);
+ return true;
}
} else if (binop.isSub() && constBRight != null) {
// a - x - b => (a - b) - x where (a - b) is a constant.
@@ -319,9 +325,11 @@
if (constARight == null) {
Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+ return true;
} else {
Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+ return true;
}
}
} else {
@@ -331,18 +339,22 @@
// We ignore b - (x + a) and b - (a + x) with constBRight != null.
Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
replaceBinop(iterator, code, newConst, input, BinopDescriptor.ADD);
+ return true;
} else if (binop.isAdd() && prevBinop.isSub()) {
// x - a + b => x - (a - b) where (a - b) is a constant.
// a - x + b => (a + b) - x where (a + b) is a constant.
if (constALeft == null) {
Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+ return true;
} else {
Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+ return true;
}
}
}
+ return false;
}
private void replaceBinop(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
index 7057abc..33ec433 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
@@ -41,6 +41,7 @@
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
+ boolean hasChanged = false;
int noCandidate = code.reserveMarkingColor();
if (hasCSECandidate(code, noCandidate)) {
final ListMultimap<Wrapper<Instruction>, Value> instructionToValue =
@@ -65,6 +66,7 @@
instruction.outValue().replaceUsers(candidate);
candidate.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
eliminated = true;
+ hasChanged = true;
iterator.removeOrReplaceByDebugLocalRead();
break; // Don't try any more candidates.
}
@@ -78,9 +80,11 @@
}
}
code.returnMarkingColor(noCandidate);
- code.removeRedundantBlocks();
+ if (hasChanged) {
+ code.removeRedundantBlocks();
+ }
assert code.isConsistentSSA(appView);
- return CodeRewriterResult.NONE;
+ return CodeRewriterResult.hasChanged(hasChanged);
}
private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
index 96b52dc..2666be2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
@@ -53,7 +53,7 @@
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
}
- return CodeRewriterResult.NONE;
+ return CodeRewriterResult.hasChanged(loopRemoved);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
index 70402cc..99caca6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
@@ -52,22 +52,16 @@
@Override
protected boolean shouldRewriteCode(IRCode code) {
+ if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
+ && !appView.options().testing.forceRedundantConstNumberRemoval) {
+ // See also b/124152497.
+ return false;
+ }
return options.enableRedundantConstNumberOptimization && code.metadata().mayHaveConstNumber();
}
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
- redundantConstNumberRemoval(code);
- return CodeRewriterResult.NONE;
- }
-
- public void redundantConstNumberRemoval(IRCode code) {
- if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
- && !appView.options().testing.forceRedundantConstNumberRemoval) {
- // See also b/124152497.
- return;
- }
-
LazyBox<Long2ReferenceMap<List<ConstNumber>>> constantsByValue =
new LazyBox<>(() -> getConstantsByValue(code));
LazyBox<DominatorTree> dominatorTree = new LazyBox<>(() -> new DominatorTree(code));
@@ -169,6 +163,7 @@
code.removeAllDeadAndTrivialPhis();
}
assert code.isConsistentSSA(appView);
+ return CodeRewriterResult.hasChanged(changed);
}
private static Long2ReferenceMap<List<ConstNumber>> getConstantsByValue(IRCode code) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
index 2defd76..6ceb56d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
@@ -56,11 +56,11 @@
protected CodeRewriterResult rewriteCode(IRCode code) {
List<BasicBlock> candidates = computeCandidates(code);
if (candidates.isEmpty()) {
- return CodeRewriterResult.NONE;
+ return CodeRewriterResult.NO_CHANGE;
}
Map<Goto, BasicBlock> newTargets = findGotosToRetarget(candidates);
if (newTargets.isEmpty()) {
- return CodeRewriterResult.NONE;
+ return CodeRewriterResult.NO_CHANGE;
}
retargetGotos(newTargets);
Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -74,7 +74,7 @@
}
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
- return CodeRewriterResult.NONE;
+ return CodeRewriterResult.HAS_CHANGED;
}
private void retargetGotos(Map<Goto, BasicBlock> newTargets) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
index fd79613..920417a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
@@ -59,15 +59,15 @@
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
- optimizeAlwaysThrowingInstructions(code);
+ boolean hasChanged = optimizeAlwaysThrowingInstructions(code);
if (!isDebugMode(code.context())) {
- rewriteThrowNullPointerException(code);
+ hasChanged |= rewriteThrowNullPointerException(code);
}
- return CodeRewriterResult.NONE;
+ return CodeRewriterResult.hasChanged(hasChanged);
}
// Rewrite 'throw new NullPointerException()' to 'throw null'.
- private void rewriteThrowNullPointerException(IRCode code) {
+ private boolean rewriteThrowNullPointerException(IRCode code) {
boolean hasChanged = false;
boolean shouldRemoveUnreachableBlocks = false;
for (BasicBlock block : code.blocks) {
@@ -209,17 +209,19 @@
code.removeRedundantBlocks();
}
assert code.isConsistentSSA(appView);
+ return hasChanged;
}
// Find all instructions that always throw, split the block after each such instruction and follow
// it with a block throwing a null value (which should result in NPE). Note that this throw is not
// expected to be ever reached, but is intended to satisfy verifier.
- private void optimizeAlwaysThrowingInstructions(IRCode code) {
+ private boolean optimizeAlwaysThrowingInstructions(IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
ProgramMethod context = code.context();
boolean hasUnlinkedCatchHandlers = false;
+ boolean hasChanged = false;
// For cyclic phis we sometimes do not propagate the dynamic upper type after rewritings.
// The inValue.isAlwaysNull(appView) check below will not recompute the dynamic type of phi's
// so we recompute all phis here if they are always null.
@@ -287,6 +289,7 @@
}
instructionIterator.replaceCurrentInstructionWithThrowNull(
appView, code, blockIterator, blocksToRemove, affectedValues);
+ hasChanged = true;
continue;
}
}
@@ -324,6 +327,7 @@
instructionIterator.replaceCurrentInstructionWithThrowNull(
appView, code, blockIterator, blocksToRemove, affectedValues);
instructionIterator.unsetInsertionPosition();
+ hasChanged = true;
}
}
}
@@ -335,8 +339,11 @@
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
- code.removeRedundantBlocks();
+ if (hasChanged) {
+ code.removeRedundantBlocks();
+ }
assert code.isConsistentSSA(appView);
+ return hasChanged;
}
// Find any case where we have a catch followed immediately and only by a rethrow. This is extra
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 5b45fd5..b2b0782 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
@@ -44,6 +44,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination.RedundantFieldLoadAndStoreEliminationOnCode.ExistingValue;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -71,29 +72,8 @@
private static final int MAX_CAPACITY = 10000;
private static final int MIN_CAPACITY_PER_BLOCK = 50;
- private final ProgramMethod method;
- private final IRCode code;
- private final int maxCapacityPerBlock;
- private final boolean release;
-
- // Values that may require type propagation.
- private final Set<Value> affectedValues = Sets.newIdentityHashSet();
-
- // Maps keeping track of fields that have an already loaded value at basic block entry.
- private final BlockStates activeStates = new BlockStates();
-
- // Maps keeping track of fields with already loaded values for the current block during
- // elimination.
- private BlockState activeState;
-
- private final Map<BasicBlock, Set<Instruction>> instructionsToRemove = new IdentityHashMap<>();
-
- public RedundantFieldLoadAndStoreElimination(AppView<?> appView, IRCode code) {
+ public RedundantFieldLoadAndStoreElimination(AppView<?> appView) {
super(appView);
- this.method = code.context();
- this.code = code;
- this.maxCapacityPerBlock = Math.max(MIN_CAPACITY_PER_BLOCK, MAX_CAPACITY / code.blocks.size());
- this.release = !appView.options().debug;
}
@Override
@@ -111,8 +91,7 @@
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
- run();
- return CodeRewriterResult.NONE;
+ return new RedundantFieldLoadAndStoreEliminationOnCode(code).run();
}
private interface FieldValue {
@@ -126,80 +105,6 @@
TypeElement getType(AppView<?> appView, TypeElement outType);
}
- private class ExistingValue implements FieldValue {
-
- private final Value value;
-
- private ExistingValue(Value value) {
- this.value = value;
- }
-
- @Override
- public ExistingValue asExistingValue() {
- return this;
- }
-
- @Override
- public void eliminateRedundantRead(InstructionListIterator it, Instruction redundant) {
- affectedValues.addAll(redundant.outValue().affectedValues());
- redundant.outValue().replaceUsers(value);
- it.removeOrReplaceByDebugLocalRead();
- value.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
- }
-
- @Override
- public TypeElement getType(AppView<?> appView, TypeElement outType) {
- return value.getType();
- }
-
- public Value getValue() {
- return value;
- }
-
- @Override
- public String toString() {
- return "ExistingValue(v" + value.getNumber() + ")";
- }
- }
-
- private class MaterializableValue implements FieldValue {
-
- private final SingleValue value;
-
- private MaterializableValue(SingleValue value) {
- assert value.isMaterializableInContext(appView.withLiveness(), method);
- this.value = value;
- }
-
- @Override
- public void eliminateRedundantRead(InstructionListIterator it, Instruction redundant) {
- affectedValues.addAll(redundant.outValue().affectedValues());
- it.replaceCurrentInstruction(
- value.createMaterializingInstruction(appView.withClassHierarchy(), code, redundant));
- }
-
- @Override
- public TypeElement getType(AppView<?> appView, TypeElement outType) {
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- if (value.isSingleStringValue() || value.isSingleDexItemBasedStringValue()) {
- return dexItemFactory.stringType.toTypeElement(
- RedundantFieldLoadAndStoreElimination.this.appView, Nullability.definitelyNotNull());
- }
- if (value.isSingleFieldValue()) {
- return value.asSingleFieldValue().getField().getTypeElement(appView);
- }
- // For numbers (and null), we don't encode the type along with the value. Therefore, we
- // fallback to the existing out type in this case.
- assert value.isSingleNumberValue();
- if (outType.isReferenceType()) {
- assert value.isNull();
- return TypeElement.getNull();
- }
- assert outType.isPrimitiveType();
- return outType;
- }
- }
-
private abstract static class ArraySlot {
protected final Value array;
@@ -294,6 +199,7 @@
}
private static class FieldAndObject {
+
private final DexField field;
private final Value object;
@@ -318,578 +224,693 @@
}
}
- public boolean isFinal(DexClassAndField field) {
- if (field.isProgramField()) {
- // Treat this field as being final if it is declared final or we have determined a constant
- // value for it.
- return field.getDefinition().isFinal()
- || field.getDefinition().getOptimizationInfo().getAbstractValue().isSingleValue();
- }
- return appView.libraryMethodOptimizer().isFinalLibraryField(field.getDefinition());
- }
+ class RedundantFieldLoadAndStoreEliminationOnCode {
- private DexClassAndField resolveField(DexField field) {
- if (appView.enableWholeProgramOptimizations()) {
- SingleFieldResolutionResult resolutionResult =
- appView.appInfo().withLiveness().resolveField(field).asSingleFieldResolutionResult();
- return resolutionResult != null ? resolutionResult.getResolutionPair() : null;
- }
- if (field.getHolderType() == method.getHolderType()) {
- return method.getHolder().lookupProgramField(field);
- }
- return null;
- }
+ private final ProgramMethod method;
+ private final IRCode code;
+ private final int maxCapacityPerBlock;
+ private final boolean release;
- public void run() {
- Reference2IntMap<BasicBlock> pendingNormalSuccessors = new Reference2IntOpenHashMap<>();
- for (BasicBlock block : code.blocks) {
- if (!block.hasUniqueNormalSuccessor()) {
- pendingNormalSuccessors.put(block, block.numberOfNormalSuccessors());
+ // Values that may require type propagation.
+ private final Set<Value> affectedValues = Sets.newIdentityHashSet();
+
+ // Maps keeping track of fields that have an already loaded value at basic block entry.
+ private final BlockStates activeStates = new BlockStates();
+
+ // Maps keeping track of fields with already loaded values for the current block during
+ // elimination.
+ private BlockState activeState;
+
+ private final Map<BasicBlock, Set<Instruction>> instructionsToRemove = new IdentityHashMap<>();
+
+ private boolean hasChanged = false;
+
+ private RedundantFieldLoadAndStoreEliminationOnCode(IRCode code) {
+ this.method = code.context();
+ this.code = code;
+ this.maxCapacityPerBlock =
+ Math.max(MIN_CAPACITY_PER_BLOCK, MAX_CAPACITY / code.blocks.size());
+ this.release = !appView.options().debug;
+ }
+
+ class ExistingValue implements FieldValue {
+
+ private final Value value;
+
+ private ExistingValue(Value value) {
+ this.value = value;
+ }
+
+ @Override
+ public ExistingValue asExistingValue() {
+ return this;
+ }
+
+ @Override
+ public void eliminateRedundantRead(InstructionListIterator it, Instruction redundant) {
+ affectedValues.addAll(redundant.outValue().affectedValues());
+ redundant.outValue().replaceUsers(value);
+ it.removeOrReplaceByDebugLocalRead();
+ value.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+ hasChanged = true;
+ }
+
+ @Override
+ public TypeElement getType(AppView<?> appView, TypeElement outType) {
+ return value.getType();
+ }
+
+ public Value getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "ExistingValue(v" + value.getNumber() + ")";
}
}
- AssumeRemover assumeRemover = new AssumeRemover(appView, code, affectedValues);
- for (BasicBlock head : code.topologicallySortedBlocks()) {
- if (head.hasUniquePredecessor() && head.getUniquePredecessor().hasUniqueNormalSuccessor()) {
- // Already visited.
- continue;
+ private class MaterializableValue implements FieldValue {
+
+ private final SingleValue value;
+
+ private MaterializableValue(SingleValue value) {
+ assert value.isMaterializableInContext(appView.withLiveness(), method);
+ this.value = value;
}
- activeState = activeStates.computeActiveStateOnBlockEntry(head, maxCapacityPerBlock);
- activeStates.removeDeadBlockExitStates(head, pendingNormalSuccessors);
- BasicBlock block = head;
- BasicBlock end = null;
- do {
- InstructionListIterator it = block.listIterator(code);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.isArrayAccess()) {
- if (instruction.isArrayGet()) {
- handleArrayGet(it, instruction.asArrayGet());
+
+ @Override
+ public void eliminateRedundantRead(InstructionListIterator it, Instruction redundant) {
+ affectedValues.addAll(redundant.outValue().affectedValues());
+ it.replaceCurrentInstruction(
+ value.createMaterializingInstruction(appView.withClassHierarchy(), code, redundant));
+ hasChanged = true;
+ }
+
+ @Override
+ public TypeElement getType(AppView<?> appView, TypeElement outType) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ if (value.isSingleStringValue() || value.isSingleDexItemBasedStringValue()) {
+ return dexItemFactory.stringType.toTypeElement(
+ RedundantFieldLoadAndStoreElimination.this.appView, Nullability.definitelyNotNull());
+ }
+ if (value.isSingleFieldValue()) {
+ return value.asSingleFieldValue().getField().getTypeElement(appView);
+ }
+ // For numbers (and null), we don't encode the type along with the value. Therefore, we
+ // fallback to the existing out type in this case.
+ assert value.isSingleNumberValue();
+ if (outType.isReferenceType()) {
+ assert value.isNull();
+ return TypeElement.getNull();
+ }
+ assert outType.isPrimitiveType();
+ return outType;
+ }
+ }
+
+ public boolean isFinal(DexClassAndField field) {
+ if (field.isProgramField()) {
+ // Treat this field as being final if it is declared final or we have determined a constant
+ // value for it.
+ return field.getDefinition().isFinal()
+ || field.getDefinition().getOptimizationInfo().getAbstractValue().isSingleValue();
+ }
+ return appView.libraryMethodOptimizer().isFinalLibraryField(field.getDefinition());
+ }
+
+ private DexClassAndField resolveField(DexField field) {
+ if (appView.enableWholeProgramOptimizations()) {
+ SingleFieldResolutionResult resolutionResult =
+ appView.appInfo().withLiveness().resolveField(field).asSingleFieldResolutionResult();
+ return resolutionResult != null ? resolutionResult.getResolutionPair() : null;
+ }
+ if (field.getHolderType() == method.getHolderType()) {
+ return method.getHolder().lookupProgramField(field);
+ }
+ return null;
+ }
+
+ public CodeRewriterResult run() {
+ Reference2IntMap<BasicBlock> pendingNormalSuccessors = new Reference2IntOpenHashMap<>();
+ for (BasicBlock block : code.blocks) {
+ if (!block.hasUniqueNormalSuccessor()) {
+ pendingNormalSuccessors.put(block, block.numberOfNormalSuccessors());
+ }
+ }
+
+ AssumeRemover assumeRemover = new AssumeRemover(appView, code, affectedValues);
+ for (BasicBlock head : code.topologicallySortedBlocks()) {
+ if (head.hasUniquePredecessor() && head.getUniquePredecessor().hasUniqueNormalSuccessor()) {
+ // Already visited.
+ continue;
+ }
+ activeState = activeStates.computeActiveStateOnBlockEntry(head, maxCapacityPerBlock);
+ activeStates.removeDeadBlockExitStates(head, pendingNormalSuccessors);
+ BasicBlock block = head;
+ BasicBlock end = null;
+ do {
+ InstructionListIterator it = block.listIterator(code);
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isArrayAccess()) {
+ if (instruction.isArrayGet()) {
+ handleArrayGet(it, instruction.asArrayGet());
+ } else {
+ assert instruction.isArrayPut();
+ handleArrayPut(instruction.asArrayPut());
+ }
+ } else if (instruction.isFieldInstruction()) {
+ DexField reference = instruction.asFieldInstruction().getField();
+ DexClassAndField field = resolveField(reference);
+ if (field == null || field.getDefinition().isVolatile()) {
+ killAllNonFinalActiveFields();
+ continue;
+ }
+
+ if (instruction.isInstanceGet()) {
+ handleInstanceGet(it, instruction.asInstanceGet(), field, assumeRemover);
+ } else if (instruction.isInstancePut()) {
+ handleInstancePut(instruction.asInstancePut(), field);
+ } else if (instruction.isStaticGet()) {
+ handleStaticGet(it, instruction.asStaticGet(), field, assumeRemover);
+ } else if (instruction.isStaticPut()) {
+ handleStaticPut(instruction.asStaticPut(), field);
+ }
+ } else if (instruction.isAssume()) {
+ assumeRemover.removeIfMarked(instruction.asAssume(), it);
+ } else if (instruction.isInitClass()) {
+ handleInitClass(it, instruction.asInitClass());
+ } else if (instruction.isMonitor()) {
+ if (instruction.asMonitor().isEnter()) {
+ killAllNonFinalActiveFields();
+ }
+ } else if (instruction.isInvokeDirect()) {
+ handleInvokeDirect(instruction.asInvokeDirect());
+ } else if (instruction.isInvokeStatic()) {
+ handleInvokeStatic(instruction.asInvokeStatic());
+ } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
+ killAllNonFinalActiveFields();
+ } else if (instruction.isNewInstance()) {
+ handleNewInstance(instruction.asNewInstance());
} else {
- assert instruction.isArrayPut();
- handleArrayPut(instruction.asArrayPut());
- }
- } else if (instruction.isFieldInstruction()) {
- DexField reference = instruction.asFieldInstruction().getField();
- DexClassAndField field = resolveField(reference);
- if (field == null || field.getDefinition().isVolatile()) {
- killAllNonFinalActiveFields();
- continue;
- }
+ // If the current instruction could trigger a method invocation, it could also cause
+ // field values to change. In that case, it must be handled above.
+ assert !instruction.instructionMayTriggerMethodInvocation(appView, method);
- if (instruction.isInstanceGet()) {
- handleInstanceGet(it, instruction.asInstanceGet(), field, assumeRemover);
- } else if (instruction.isInstancePut()) {
- handleInstancePut(instruction.asInstancePut(), field);
- } else if (instruction.isStaticGet()) {
- handleStaticGet(it, instruction.asStaticGet(), field, assumeRemover);
- } else if (instruction.isStaticPut()) {
- handleStaticPut(instruction.asStaticPut(), field);
- }
- } else if (instruction.isAssume()) {
- assumeRemover.removeIfMarked(instruction.asAssume(), it);
- } else if (instruction.isInitClass()) {
- handleInitClass(it, instruction.asInitClass());
- } else if (instruction.isMonitor()) {
- if (instruction.asMonitor().isEnter()) {
- killAllNonFinalActiveFields();
- }
- } else if (instruction.isInvokeDirect()) {
- handleInvokeDirect(instruction.asInvokeDirect());
- } else if (instruction.isInvokeStatic()) {
- handleInvokeStatic(instruction.asInvokeStatic());
- } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
- killAllNonFinalActiveFields();
- } else if (instruction.isNewInstance()) {
- handleNewInstance(instruction.asNewInstance());
- } else {
- // If the current instruction could trigger a method invocation, it could also cause
- // field values to change. In that case, it must be handled above.
- assert !instruction.instructionMayTriggerMethodInvocation(appView, method);
+ // Clear the field writes.
+ if (instruction.instructionInstanceCanThrow(appView, method)) {
+ activeState.clearMostRecentFieldWrites();
+ activeState.clearMostRecentInitClass();
+ }
- // Clear the field writes.
- if (instruction.instructionInstanceCanThrow(appView, method)) {
- activeState.clearMostRecentFieldWrites();
- activeState.clearMostRecentInitClass();
+ // If this assertion fails for a new instruction we need to determine if that
+ // 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()
+ || instruction.isConstClass()
+ || instruction.isConstMethodHandle()
+ || instruction.isConstMethodType()
+ || instruction.isConstNumber()
+ || instruction.isConstString()
+ || instruction.isDebugInstruction()
+ || instruction.isDexItemBasedConstString()
+ || instruction.isGoto()
+ || instruction.isIf()
+ || instruction.isInstanceOf()
+ || instruction.isInvokeMultiNewArray()
+ || instruction.isInvokeNewArray()
+ || instruction.isMoveException()
+ || instruction.isNewArrayEmpty()
+ || instruction.isNewArrayFilledData()
+ || instruction.isReturn()
+ || instruction.isSwitch()
+ || instruction.isThrow()
+ || instruction.isUnop()
+ || instruction.isRecordFieldValues()
+ : "Unexpected instruction of type " + instruction.getClass().getTypeName();
}
-
- // If this assertion fails for a new instruction we need to determine if that
- // 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()
- || instruction.isConstClass()
- || instruction.isConstMethodHandle()
- || instruction.isConstMethodType()
- || instruction.isConstNumber()
- || instruction.isConstString()
- || instruction.isDebugInstruction()
- || instruction.isDexItemBasedConstString()
- || instruction.isGoto()
- || instruction.isIf()
- || instruction.isInstanceOf()
- || instruction.isInvokeMultiNewArray()
- || instruction.isInvokeNewArray()
- || instruction.isMoveException()
- || instruction.isNewArrayEmpty()
- || instruction.isNewArrayFilledData()
- || instruction.isReturn()
- || instruction.isSwitch()
- || instruction.isThrow()
- || instruction.isUnop()
- || instruction.isRecordFieldValues()
- : "Unexpected instruction of type " + instruction.getClass().getTypeName();
}
- }
- if (block.hasUniqueNormalSuccessorWithUniquePredecessor()) {
- block = block.getUniqueNormalSuccessor();
- } else {
- end = block;
- block = null;
- }
- } while (block != null);
- assert end != null;
- activeStates.recordActiveStateOnBlockExit(end, activeState);
+ if (block.hasUniqueNormalSuccessorWithUniquePredecessor()) {
+ block = block.getUniqueNormalSuccessor();
+ } else {
+ end = block;
+ block = null;
+ }
+ } while (block != null);
+ assert end != null;
+ activeStates.recordActiveStateOnBlockExit(end, activeState);
+ }
+ processInstructionsToRemove();
+ assumeRemover.removeMarkedInstructions().finish();
+ if (hasChanged) {
+ code.removeRedundantBlocks();
+ }
+ assert code.isConsistentSSA(appView);
+ return CodeRewriterResult.hasChanged(hasChanged);
}
- processInstructionsToRemove();
- assumeRemover.removeMarkedInstructions().finish();
- code.removeRedundantBlocks();
- assert code.isConsistentSSA(appView);
- }
- private void processInstructionsToRemove() {
- instructionsToRemove.forEach(
- (block, instructionsToRemoveInBlock) -> {
- assert instructionsToRemoveInBlock.stream()
- .allMatch(instruction -> instruction.getBlock() == block);
- InstructionListIterator instructionIterator = block.listIterator(code);
- while (instructionIterator.hasNext()) {
- Instruction instruction = instructionIterator.next();
- assert !instruction.isJumpInstruction();
- if (instructionsToRemoveInBlock.contains(instruction)) {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- instructionsToRemoveInBlock.remove(instruction);
- if (instructionsToRemoveInBlock.isEmpty()) {
- return;
+ private void processInstructionsToRemove() {
+ instructionsToRemove.forEach(
+ (block, instructionsToRemoveInBlock) -> {
+ assert instructionsToRemoveInBlock.stream()
+ .allMatch(instruction -> instruction.getBlock() == block);
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ assert !instruction.isJumpInstruction();
+ if (instructionsToRemoveInBlock.contains(instruction)) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ hasChanged = true;
+ instructionsToRemoveInBlock.remove(instruction);
+ if (instructionsToRemoveInBlock.isEmpty()) {
+ return;
+ }
}
}
- }
- });
- }
-
- private boolean verifyWasInstanceInitializer() {
- VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
- assert verticallyMergedClasses != null;
- assert verticallyMergedClasses.isMergeTarget(method.getHolderType())
- || appView.horizontallyMergedClasses().isMergeTarget(method.getHolderType());
- assert appView
- .dexItemFactory()
- .isConstructor(appView.graphLens().getOriginalMethodSignature(method.getReference()));
- assert method.getDefinition().getOptimizationInfo().forceInline();
- return true;
- }
-
- private void handleInvokeDirect(InvokeDirect invoke) {
- if (!appView.hasLiveness()) {
- killAllNonFinalActiveFields();
- return;
+ });
}
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-
- DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method);
- if (singleTarget == null || !singleTarget.getDefinition().isInstanceInitializer()) {
- killAllNonFinalActiveFields();
- return;
+ private boolean verifyWasInstanceInitializer() {
+ VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+ assert verticallyMergedClasses != null;
+ assert verticallyMergedClasses.isMergeTarget(method.getHolderType())
+ || appView.horizontallyMergedClasses().isMergeTarget(method.getHolderType());
+ assert appView
+ .dexItemFactory()
+ .isConstructor(appView.graphLens().getOriginalMethodSignature(method.getReference()));
+ assert method.getDefinition().getOptimizationInfo().forceInline();
+ return true;
}
- InstanceInitializerInfo instanceInitializerInfo =
- singleTarget.getDefinition().getOptimizationInfo().getInstanceInitializerInfo(invoke);
- if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
- killAllNonFinalActiveFields();
- }
+ private void handleInvokeDirect(InvokeDirect invoke) {
+ if (!appView.hasLiveness()) {
+ killAllNonFinalActiveFields();
+ return;
+ }
- InstanceFieldInitializationInfoCollection fieldInitializationInfos =
- instanceInitializerInfo.fieldInitializationInfos();
- fieldInitializationInfos.forEachWithDeterministicOrder(
- appView,
- (field, info) -> {
- if (!appViewWithLiveness
- .appInfo()
- .mayPropagateValueFor(appViewWithLiveness, field.getReference())) {
- return;
- }
- if (info.isArgumentInitializationInfo()) {
- Value value =
- invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
- Value object = invoke.getReceiver().getAliasedValue();
- FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
- if (field.isFinalOrEffectivelyFinal(appViewWithLiveness)) {
- activeState.putFinalOrEffectivelyFinalInstanceField(
- fieldAndObject, new ExistingValue(value));
- } else {
- activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
+ DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method);
+ if (singleTarget == null || !singleTarget.getDefinition().isInstanceInitializer()) {
+ killAllNonFinalActiveFields();
+ return;
+ }
+
+ InstanceInitializerInfo instanceInitializerInfo =
+ singleTarget.getDefinition().getOptimizationInfo().getInstanceInitializerInfo(invoke);
+ if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+ killAllNonFinalActiveFields();
+ }
+
+ InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+ instanceInitializerInfo.fieldInitializationInfos();
+ fieldInitializationInfos.forEachWithDeterministicOrder(
+ appView,
+ (field, info) -> {
+ if (!appViewWithLiveness
+ .appInfo()
+ .mayPropagateValueFor(appViewWithLiveness, field.getReference())) {
+ return;
}
- } else if (info.isSingleValue()) {
- SingleValue value = info.asSingleValue();
- if (value.isMaterializableInContext(appViewWithLiveness, method)) {
+ if (info.isArgumentInitializationInfo()) {
+ Value value =
+ invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
Value object = invoke.getReceiver().getAliasedValue();
FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
if (field.isFinalOrEffectivelyFinal(appViewWithLiveness)) {
activeState.putFinalOrEffectivelyFinalInstanceField(
- fieldAndObject, new MaterializableValue(value));
+ fieldAndObject, new ExistingValue(value));
} else {
- activeState.putNonFinalInstanceField(
- fieldAndObject, new MaterializableValue(value));
+ activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
}
+ } else if (info.isSingleValue()) {
+ SingleValue value = info.asSingleValue();
+ if (value.isMaterializableInContext(appViewWithLiveness, method)) {
+ Value object = invoke.getReceiver().getAliasedValue();
+ FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
+ if (field.isFinalOrEffectivelyFinal(appViewWithLiveness)) {
+ activeState.putFinalOrEffectivelyFinalInstanceField(
+ fieldAndObject, new MaterializableValue(value));
+ } else {
+ activeState.putNonFinalInstanceField(
+ fieldAndObject, new MaterializableValue(value));
+ }
+ }
+ } else {
+ assert info.isTypeInitializationInfo();
}
- } else {
- assert info.isTypeInitializationInfo();
- }
- });
- }
-
- private void handleInvokeStatic(InvokeStatic invoke) {
- if (appView.hasClassHierarchy()) {
- ProgramMethod resolvedMethod =
- appView
- .appInfo()
- .withClassHierarchy()
- .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
- .getResolvedProgramMethod();
- if (resolvedMethod != null) {
- markClassAsInitialized(resolvedMethod.getHolderType());
- markMostRecentInitClassForRemoval(resolvedMethod.getHolderType());
- }
+ });
}
- killAllNonFinalActiveFields();
- }
-
- private void handleInitClass(InstructionListIterator instructionIterator, InitClass initClass) {
- assert !initClass.outValue().hasAnyUsers();
-
- killNonFinalActiveFields(initClass);
-
- // If the instruction can throw, we can't use any previous field stores for store-after-store
- // elimination.
- if (initClass.instructionInstanceCanThrow(appView, method)) {
- activeState.clearMostRecentFieldWrites();
- }
-
- DexType clazz = initClass.getClassValue();
- if (markClassAsInitialized(clazz)) {
- if (release) {
- activeState.setMostRecentInitClass(initClass);
- }
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- }
- }
-
- private boolean markClassAsInitialized(DexType type) {
- return activeState.markClassAsInitialized(type);
- }
-
- private void markMostRecentInitClassForRemoval(DexType initializedType) {
- InitClass mostRecentInitClass = activeState.getMostRecentInitClass();
- if (mostRecentInitClass != null && mostRecentInitClass.getClassValue() == initializedType) {
- instructionsToRemove
- .computeIfAbsent(mostRecentInitClass.getBlock(), ignoreKey(Sets::newIdentityHashSet))
- .add(mostRecentInitClass);
- }
- }
-
- private void handleArrayGet(InstructionListIterator it, ArrayGet arrayGet) {
- 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.
- return;
- }
- if (arrayGet.outValue().hasLocalInfo()) {
- // This local may be modified through the debugger. Therefore subsequent reads of the same
- // array slot may not read this local.
- return;
- }
-
- Value array = arrayGet.array().getAliasedValue();
- Value index = arrayGet.index().getAliasedValue();
- ArraySlot arraySlot = ArraySlot.create(array, index, arrayGet.getMemberType());
- FieldValue replacement = activeState.getArraySlotValue(arraySlot);
- if (replacement != null) {
- TypeElement outType = arrayGet.outValue().getType();
- if (replacement.getType(appView, outType).lessThanOrEqual(outType, appView)) {
- replacement.eliminateRedundantRead(it, arrayGet);
- }
- return;
- }
- activeState.putArraySlotValue(arraySlot, new ExistingValue(arrayGet.outValue()));
- }
-
- private void handleArrayPut(ArrayPut arrayPut) {
- int index = arrayPut.getIndexOrDefault(-1);
- MemberType memberType = arrayPut.getMemberType();
-
- // An array-put instruction can potentially write the given array slot on all arrays because of
- // aliases.
- if (index < 0) {
- activeState.removeArraySlotValues(memberType);
- } else {
- activeState.removeArraySlotValues(memberType, index);
- }
-
- // Update the value of the field to allow redundant load elimination.
- Value array = arrayPut.array().getAliasedValue();
- Value indexValue = arrayPut.index().getAliasedValue();
- ArraySlot arraySlot = ArraySlot.create(array, indexValue, memberType);
- ExistingValue value = new ExistingValue(arrayPut.value());
- activeState.putArraySlotValue(arraySlot, value);
- }
-
- private void handleInstanceGet(
- InstructionListIterator it,
- InstanceGet instanceGet,
- DexClassAndField field,
- AssumeRemover assumeRemover) {
- if (instanceGet.outValue().hasLocalInfo()) {
- clearMostRecentInstanceFieldWrite(instanceGet, field);
- return;
- }
-
- Value object = instanceGet.object().getAliasedValue();
- FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
- FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
- if (replacement != null) {
- if (isRedundantFieldLoadEliminationAllowed(field)) {
- markAssumeDynamicTypeUsersForRemoval(instanceGet, replacement, assumeRemover);
- replacement.eliminateRedundantRead(it, instanceGet);
- }
- return;
- }
-
- activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(instanceGet.value()));
- activeState.clearMostRecentInitClass();
- clearMostRecentInstanceFieldWrite(instanceGet, field);
- }
-
- private boolean isRedundantFieldLoadEliminationAllowed(DexClassAndField field) {
- // Always allowed in D8 since D8 does not support @NoRedundantFieldLoadElimination.
- return !appView.enableWholeProgramOptimizations()
- || !field.isProgramField()
- || appView
- .getKeepInfo(field.asProgramField())
- .isRedundantFieldLoadEliminationAllowed(appView.options());
- }
-
- private void handleNewInstance(NewInstance newInstance) {
- markClassAsInitialized(newInstance.getType());
- markMostRecentInitClassForRemoval(newInstance.getType());
- if (newInstance.getType().classInitializationMayHaveSideEffectsInContext(appView, method)) {
- killAllNonFinalActiveFields();
- }
- }
-
- private void clearMostRecentInstanceFieldWrite(InstanceGet instanceGet, DexClassAndField field) {
- // If the instruction can throw, we need to clear all most-recent-writes, since subsequent field
- // writes (if any) are not guaranteed to be executed.
- if (instanceGet.instructionInstanceCanThrow(appView, method)) {
- activeState.clearMostRecentFieldWrites();
- } else {
- activeState.clearMostRecentInstanceFieldWrite(field.getReference());
- }
- }
-
- private void markAssumeDynamicTypeUsersForRemoval(
- FieldGet fieldGet, FieldValue replacement, AssumeRemover assumeRemover) {
- ExistingValue existingValue = replacement.asExistingValue();
- if (existingValue == null
- || !existingValue
- .getValue()
- .isDefinedByInstructionSatisfying(
- definition ->
- definition.isFieldGet()
- && definition.asFieldGet().getField().getType()
- == fieldGet.getField().getType())) {
- assumeRemover.markAssumeDynamicTypeUsersForRemoval(fieldGet.outValue());
- }
- }
-
- private void handleInstancePut(InstancePut instancePut, DexClassAndField field) {
- // An instance-put instruction can potentially write the given field on all objects because of
- // aliases.
- activeState.removeNonFinalInstanceFields(field.getReference());
-
- // If the instruction can throw, we can't use any previous field stores for store-after-store
- // elimination.
- if (instancePut.instructionInstanceCanThrow(appView, method)) {
- activeState.clearMostRecentFieldWrites();
- }
-
- // Update the value of the field to allow redundant load elimination.
- Value object = instancePut.object().getAliasedValue();
- FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
- ExistingValue value = new ExistingValue(instancePut.value());
- if (field.isFinalOrEffectivelyFinal(appView)) {
- assert !field.getDefinition().isFinal()
- || method.getDefinition().isInstanceInitializer()
- || verifyWasInstanceInitializer();
- activeState.putFinalOrEffectivelyFinalInstanceField(fieldAndObject, value);
- } else {
- activeState.putNonFinalInstanceField(fieldAndObject, value);
- }
-
- // Record that this field is now most recently written by the current instruction.
- if (release) {
- InstancePut mostRecentInstanceFieldWrite =
- activeState.putMostRecentInstanceFieldWrite(fieldAndObject, instancePut);
- if (mostRecentInstanceFieldWrite != null) {
- instructionsToRemove
- .computeIfAbsent(
- mostRecentInstanceFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
- .add(mostRecentInstanceFieldWrite);
- }
- }
-
- activeState.clearMostRecentInitClass();
- }
-
- private void handleStaticGet(
- InstructionListIterator instructionIterator,
- StaticGet staticGet,
- DexClassAndField field,
- AssumeRemover assumeRemover) {
- markClassAsInitialized(field.getHolderType());
-
- if (staticGet.outValue().hasLocalInfo()) {
- killNonFinalActiveFields(staticGet);
- clearMostRecentStaticFieldWrite(staticGet, field);
- return;
- }
-
- FieldValue replacement = activeState.getStaticFieldValue(field.getReference());
- if (replacement != null) {
- markAssumeDynamicTypeUsersForRemoval(staticGet, replacement, assumeRemover);
- replacement.eliminateRedundantRead(instructionIterator, staticGet);
- return;
- }
-
- // A field get on a different class can cause <clinit> to run and change static field values.
- killNonFinalActiveFields(staticGet);
- clearMostRecentStaticFieldWrite(staticGet, field);
-
- FieldValue value = new ExistingValue(staticGet.value());
- if (field.isFinalOrEffectivelyFinal(appView)) {
- activeState.putFinalStaticField(field.getReference(), value);
- } else {
- activeState.putNonFinalStaticField(field.getReference(), value);
- }
-
- if (appView.hasLiveness()) {
- SingleFieldValue singleFieldValue =
- field.getDefinition().getOptimizationInfo().getAbstractValue().asSingleFieldValue();
- if (singleFieldValue != null) {
- applyObjectState(staticGet.outValue(), singleFieldValue.getObjectState());
- }
- }
-
- markMostRecentInitClassForRemoval(field.getHolderType());
- activeState.clearMostRecentInitClass();
- }
-
- private void clearMostRecentStaticFieldWrite(StaticGet staticGet, DexClassAndField field) {
- // If the instruction can throw, we need to clear all most-recent-writes, since subsequent field
- // writes (if any) are not guaranteed to be executed.
- if (staticGet.instructionInstanceCanThrow(appView, method)) {
- activeState.clearMostRecentFieldWrites();
- } else {
- activeState.clearMostRecentStaticFieldWrite(field.getReference());
- }
- }
-
- private void handleStaticPut(StaticPut staticPut, DexClassAndField field) {
- markClassAsInitialized(field.getHolderType());
-
- // A field put on a different class can cause <clinit> to run and change static field values.
- killNonFinalActiveFields(staticPut);
-
- // If the instruction can throw, we can't use any previous field stores for store-after-store
- // elimination.
- if (staticPut.instructionInstanceCanThrow(appView, method)) {
- activeState.clearMostRecentFieldWrites();
- }
-
- ExistingValue value = new ExistingValue(staticPut.value());
- if (field.isFinalOrEffectivelyFinal(appView)) {
- assert appView.checkForTesting(
- () -> !field.getDefinition().isFinal() || method.getDefinition().isClassInitializer());
- activeState.putFinalStaticField(field.getReference(), value);
- } else {
- activeState.putNonFinalStaticField(field.getReference(), value);
-
- if (release) {
- StaticPut mostRecentStaticFieldWrite =
- activeState.putMostRecentStaticFieldWrite(field.getReference(), staticPut);
- if (mostRecentStaticFieldWrite != null) {
- instructionsToRemove
- .computeIfAbsent(
- mostRecentStaticFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
- .add(mostRecentStaticFieldWrite);
+ private void handleInvokeStatic(InvokeStatic invoke) {
+ if (appView.hasClassHierarchy()) {
+ ProgramMethod resolvedMethod =
+ appView
+ .appInfo()
+ .withClassHierarchy()
+ .unsafeResolveMethodDueToDexFormatLegacy(invoke.getInvokedMethod())
+ .getResolvedProgramMethod();
+ if (resolvedMethod != null) {
+ markClassAsInitialized(resolvedMethod.getHolderType());
+ markMostRecentInitClassForRemoval(resolvedMethod.getHolderType());
}
}
+
+ killAllNonFinalActiveFields();
}
- markMostRecentInitClassForRemoval(field.getHolderType());
- activeState.clearMostRecentInitClass();
- }
+ private void handleInitClass(InstructionListIterator instructionIterator, InitClass initClass) {
+ assert !initClass.outValue().hasAnyUsers();
- private void applyObjectState(Value value, ObjectState objectState) {
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- objectState.forEachAbstractFieldValue(
- (field, fieldValue) -> {
- if (appViewWithLiveness.appInfo().mayPropagateValueFor(appViewWithLiveness, field)
- && fieldValue.isSingleValue()) {
- SingleValue singleFieldValue = fieldValue.asSingleValue();
- if (singleFieldValue.isMaterializableInContext(appViewWithLiveness, method)) {
- activeState.putFinalOrEffectivelyFinalInstanceField(
- new FieldAndObject(field, value), new MaterializableValue(singleFieldValue));
- }
- }
- });
- }
+ killNonFinalActiveFields(initClass);
- private void killAllNonFinalActiveFields() {
- activeState.clearArraySlotValues();
- activeState.clearNonFinalInstanceFields();
- activeState.clearNonFinalStaticFields();
- activeState.clearMostRecentFieldWrites();
- activeState.clearMostRecentInitClass();
- }
+ // If the instruction can throw, we can't use any previous field stores for store-after-store
+ // elimination.
+ if (initClass.instructionInstanceCanThrow(appView, method)) {
+ activeState.clearMostRecentFieldWrites();
+ }
- private void killNonFinalActiveFields(Instruction instruction) {
- assert instruction.isInitClass() || instruction.isStaticFieldInstruction();
- if (instruction.isStaticPut()) {
- if (instruction.instructionMayTriggerMethodInvocation(appView, method)) {
- // Accessing a static field on a different object could cause <clinit> to run which
- // could modify any static field on any other object.
- activeState.clearNonFinalStaticFields();
+ DexType clazz = initClass.getClassValue();
+ if (markClassAsInitialized(clazz)) {
+ if (release) {
+ activeState.setMostRecentInitClass(initClass);
+ }
+ } else {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ hasChanged = true;
+ }
+ }
+
+ private boolean markClassAsInitialized(DexType type) {
+ return activeState.markClassAsInitialized(type);
+ }
+
+ private void markMostRecentInitClassForRemoval(DexType initializedType) {
+ InitClass mostRecentInitClass = activeState.getMostRecentInitClass();
+ if (mostRecentInitClass != null && mostRecentInitClass.getClassValue() == initializedType) {
+ instructionsToRemove
+ .computeIfAbsent(mostRecentInitClass.getBlock(), ignoreKey(Sets::newIdentityHashSet))
+ .add(mostRecentInitClass);
+ }
+ }
+
+ private void handleArrayGet(InstructionListIterator it, ArrayGet arrayGet) {
+ 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.
+ return;
+ }
+ if (arrayGet.outValue().hasLocalInfo()) {
+ // This local may be modified through the debugger. Therefore subsequent reads of the same
+ // array slot may not read this local.
+ return;
+ }
+
+ Value array = arrayGet.array().getAliasedValue();
+ Value index = arrayGet.index().getAliasedValue();
+ ArraySlot arraySlot = ArraySlot.create(array, index, arrayGet.getMemberType());
+ FieldValue replacement = activeState.getArraySlotValue(arraySlot);
+ if (replacement != null) {
+ TypeElement outType = arrayGet.outValue().getType();
+ if (replacement.getType(appView, outType).lessThanOrEqual(outType, appView)) {
+ replacement.eliminateRedundantRead(it, arrayGet);
+ }
+ return;
+ }
+ activeState.putArraySlotValue(arraySlot, new ExistingValue(arrayGet.outValue()));
+ }
+
+ private void handleArrayPut(ArrayPut arrayPut) {
+ int index = arrayPut.getIndexOrDefault(-1);
+ MemberType memberType = arrayPut.getMemberType();
+
+ // An array-put instruction can potentially write the given array slot on all arrays because
+ // of
+ // aliases.
+ if (index < 0) {
+ activeState.removeArraySlotValues(memberType);
+ } else {
+ activeState.removeArraySlotValues(memberType, index);
+ }
+
+ // Update the value of the field to allow redundant load elimination.
+ Value array = arrayPut.array().getAliasedValue();
+ Value indexValue = arrayPut.index().getAliasedValue();
+ ArraySlot arraySlot = ArraySlot.create(array, indexValue, memberType);
+ ExistingValue value = new ExistingValue(arrayPut.value());
+ activeState.putArraySlotValue(arraySlot, value);
+ }
+
+ private void handleInstanceGet(
+ InstructionListIterator it,
+ InstanceGet instanceGet,
+ DexClassAndField field,
+ AssumeRemover assumeRemover) {
+ if (instanceGet.outValue().hasLocalInfo()) {
+ clearMostRecentInstanceFieldWrite(instanceGet, field);
+ return;
+ }
+
+ Value object = instanceGet.object().getAliasedValue();
+ FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
+ FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+ if (replacement != null) {
+ if (isRedundantFieldLoadEliminationAllowed(field)) {
+ markAssumeDynamicTypeUsersForRemoval(instanceGet, replacement, assumeRemover);
+ replacement.eliminateRedundantRead(it, instanceGet);
+ }
+ return;
+ }
+
+ activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(instanceGet.value()));
+ activeState.clearMostRecentInitClass();
+ clearMostRecentInstanceFieldWrite(instanceGet, field);
+ }
+
+ private boolean isRedundantFieldLoadEliminationAllowed(DexClassAndField field) {
+ // Always allowed in D8 since D8 does not support @NoRedundantFieldLoadElimination.
+ return !appView.enableWholeProgramOptimizations()
+ || !field.isProgramField()
+ || appView
+ .getKeepInfo(field.asProgramField())
+ .isRedundantFieldLoadEliminationAllowed(appView.options());
+ }
+
+ private void handleNewInstance(NewInstance newInstance) {
+ markClassAsInitialized(newInstance.getType());
+ markMostRecentInitClassForRemoval(newInstance.getType());
+ if (newInstance.getType().classInitializationMayHaveSideEffectsInContext(appView, method)) {
+ killAllNonFinalActiveFields();
+ }
+ }
+
+ private void clearMostRecentInstanceFieldWrite(
+ InstanceGet instanceGet, DexClassAndField field) {
+ // If the instruction can throw, we need to clear all most-recent-writes, since subsequent
+ // field
+ // writes (if any) are not guaranteed to be executed.
+ if (instanceGet.instructionInstanceCanThrow(appView, method)) {
activeState.clearMostRecentFieldWrites();
} else {
- activeState.removeNonFinalStaticField(instruction.asStaticPut().getField());
+ activeState.clearMostRecentInstanceFieldWrite(field.getReference());
}
- } else if (instruction.isInitClass() || instruction.isStaticGet()) {
- if (instruction.instructionMayTriggerMethodInvocation(appView, method)) {
- // Accessing a static field on a different object could cause <clinit> to run which
- // could modify any static field on any other object.
- activeState.clearNonFinalStaticFields();
+ }
+
+ private void markAssumeDynamicTypeUsersForRemoval(
+ FieldGet fieldGet, FieldValue replacement, AssumeRemover assumeRemover) {
+ ExistingValue existingValue = replacement.asExistingValue();
+ if (existingValue == null
+ || !existingValue
+ .getValue()
+ .isDefinedByInstructionSatisfying(
+ definition ->
+ definition.isFieldGet()
+ && definition.asFieldGet().getField().getType()
+ == fieldGet.getField().getType())) {
+ assumeRemover.markAssumeDynamicTypeUsersForRemoval(fieldGet.outValue());
+ }
+ }
+
+ private void handleInstancePut(InstancePut instancePut, DexClassAndField field) {
+ // An instance-put instruction can potentially write the given field on all objects because of
+ // aliases.
+ activeState.removeNonFinalInstanceFields(field.getReference());
+
+ // If the instruction can throw, we can't use any previous field stores for store-after-store
+ // elimination.
+ if (instancePut.instructionInstanceCanThrow(appView, method)) {
activeState.clearMostRecentFieldWrites();
}
- } else if (instruction.isInstanceGet()) {
- throw new Unreachable();
+
+ // Update the value of the field to allow redundant load elimination.
+ Value object = instancePut.object().getAliasedValue();
+ FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
+ ExistingValue value = new ExistingValue(instancePut.value());
+ if (field.isFinalOrEffectivelyFinal(appView)) {
+ assert !field.getDefinition().isFinal()
+ || method.getDefinition().isInstanceInitializer()
+ || verifyWasInstanceInitializer();
+ activeState.putFinalOrEffectivelyFinalInstanceField(fieldAndObject, value);
+ } else {
+ activeState.putNonFinalInstanceField(fieldAndObject, value);
+ }
+
+ // Record that this field is now most recently written by the current instruction.
+ if (release) {
+ InstancePut mostRecentInstanceFieldWrite =
+ activeState.putMostRecentInstanceFieldWrite(fieldAndObject, instancePut);
+ if (mostRecentInstanceFieldWrite != null) {
+ instructionsToRemove
+ .computeIfAbsent(
+ mostRecentInstanceFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
+ .add(mostRecentInstanceFieldWrite);
+ }
+ }
+
+ activeState.clearMostRecentInitClass();
+ }
+
+ private void handleStaticGet(
+ InstructionListIterator instructionIterator,
+ StaticGet staticGet,
+ DexClassAndField field,
+ AssumeRemover assumeRemover) {
+ markClassAsInitialized(field.getHolderType());
+
+ if (staticGet.outValue().hasLocalInfo()) {
+ killNonFinalActiveFields(staticGet);
+ clearMostRecentStaticFieldWrite(staticGet, field);
+ return;
+ }
+
+ FieldValue replacement = activeState.getStaticFieldValue(field.getReference());
+ if (replacement != null) {
+ markAssumeDynamicTypeUsersForRemoval(staticGet, replacement, assumeRemover);
+ replacement.eliminateRedundantRead(instructionIterator, staticGet);
+ return;
+ }
+
+ // A field get on a different class can cause <clinit> to run and change static field values.
+ killNonFinalActiveFields(staticGet);
+ clearMostRecentStaticFieldWrite(staticGet, field);
+
+ FieldValue value = new ExistingValue(staticGet.value());
+ if (field.isFinalOrEffectivelyFinal(appView)) {
+ activeState.putFinalStaticField(field.getReference(), value);
+ } else {
+ activeState.putNonFinalStaticField(field.getReference(), value);
+ }
+
+ if (appView.hasLiveness()) {
+ SingleFieldValue singleFieldValue =
+ field.getDefinition().getOptimizationInfo().getAbstractValue().asSingleFieldValue();
+ if (singleFieldValue != null) {
+ applyObjectState(staticGet.outValue(), singleFieldValue.getObjectState());
+ }
+ }
+
+ markMostRecentInitClassForRemoval(field.getHolderType());
+ activeState.clearMostRecentInitClass();
+ }
+
+ private void clearMostRecentStaticFieldWrite(StaticGet staticGet, DexClassAndField field) {
+ // If the instruction can throw, we need to clear all most-recent-writes, since subsequent
+ // field
+ // writes (if any) are not guaranteed to be executed.
+ if (staticGet.instructionInstanceCanThrow(appView, method)) {
+ activeState.clearMostRecentFieldWrites();
+ } else {
+ activeState.clearMostRecentStaticFieldWrite(field.getReference());
+ }
+ }
+
+ private void handleStaticPut(StaticPut staticPut, DexClassAndField field) {
+ markClassAsInitialized(field.getHolderType());
+
+ // A field put on a different class can cause <clinit> to run and change static field values.
+ killNonFinalActiveFields(staticPut);
+
+ // If the instruction can throw, we can't use any previous field stores for store-after-store
+ // elimination.
+ if (staticPut.instructionInstanceCanThrow(appView, method)) {
+ activeState.clearMostRecentFieldWrites();
+ }
+
+ ExistingValue value = new ExistingValue(staticPut.value());
+ if (field.isFinalOrEffectivelyFinal(appView)) {
+ assert appView.checkForTesting(
+ () -> !field.getDefinition().isFinal() || method.getDefinition().isClassInitializer());
+ activeState.putFinalStaticField(field.getReference(), value);
+ } else {
+ activeState.putNonFinalStaticField(field.getReference(), value);
+
+ if (release) {
+ StaticPut mostRecentStaticFieldWrite =
+ activeState.putMostRecentStaticFieldWrite(field.getReference(), staticPut);
+ if (mostRecentStaticFieldWrite != null) {
+ instructionsToRemove
+ .computeIfAbsent(
+ mostRecentStaticFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
+ .add(mostRecentStaticFieldWrite);
+ }
+ }
+ }
+
+ markMostRecentInitClassForRemoval(field.getHolderType());
+ activeState.clearMostRecentInitClass();
+ }
+
+ private void applyObjectState(Value value, ObjectState objectState) {
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ objectState.forEachAbstractFieldValue(
+ (field, fieldValue) -> {
+ if (appViewWithLiveness.appInfo().mayPropagateValueFor(appViewWithLiveness, field)
+ && fieldValue.isSingleValue()) {
+ SingleValue singleFieldValue = fieldValue.asSingleValue();
+ if (singleFieldValue.isMaterializableInContext(appViewWithLiveness, method)) {
+ activeState.putFinalOrEffectivelyFinalInstanceField(
+ new FieldAndObject(field, value), new MaterializableValue(singleFieldValue));
+ }
+ }
+ });
+ }
+
+ private void killAllNonFinalActiveFields() {
+ activeState.clearArraySlotValues();
+ activeState.clearNonFinalInstanceFields();
+ activeState.clearNonFinalStaticFields();
+ activeState.clearMostRecentFieldWrites();
+ activeState.clearMostRecentInitClass();
+ }
+
+ private void killNonFinalActiveFields(Instruction instruction) {
+ assert instruction.isInitClass() || instruction.isStaticFieldInstruction();
+ if (instruction.isStaticPut()) {
+ if (instruction.instructionMayTriggerMethodInvocation(appView, method)) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeState.clearNonFinalStaticFields();
+ activeState.clearMostRecentFieldWrites();
+ } else {
+ activeState.removeNonFinalStaticField(instruction.asStaticPut().getField());
+ }
+ } else if (instruction.isInitClass() || instruction.isStaticGet()) {
+ if (instruction.instructionMayTriggerMethodInvocation(appView, method)) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeState.clearNonFinalStaticFields();
+ activeState.clearMostRecentFieldWrites();
+ }
+ } else if (instruction.isInstanceGet()) {
+ throw new Unreachable();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 867c2dc..bf25b2d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
@@ -150,7 +151,6 @@
return Sets.newIdentityHashSet();
}
assert code.isConsistentSSABeforeTypesAreCorrect(appView);
- ProgramMethod context = code.context();
EnumUnboxerMethodProcessorEventConsumer eventConsumer = methodProcessor.getEventConsumer();
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
Map<Instruction, DexType> convertedEnums =
@@ -177,256 +177,42 @@
if (enumType != null) {
iterator.removeOrReplaceByDebugLocalRead();
}
- continue;
- }
-
- if (instruction.isIf()) {
- If ifInstruction = instruction.asIf();
- if (!ifInstruction.isZeroTest()) {
- for (int operandIndex = 0; operandIndex < 2; operandIndex++) {
- Value operand = ifInstruction.getOperand(operandIndex);
- DexType enumType = getEnumClassTypeOrNull(operand, convertedEnums);
- if (enumType != null) {
- int otherOperandIndex = 1 - operandIndex;
- Value otherOperand = ifInstruction.getOperand(otherOperandIndex);
- if (otherOperand.getType().isNullType()) {
- iterator.previous();
- ifInstruction.replaceValue(
- otherOperandIndex, iterator.insertConstIntInstruction(code, options, 0));
- iterator.next();
- break;
- }
- }
- }
- }
- }
-
- // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
- // counterpart. The rewriting (== or match) is based on the following:
- // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
- // - equals, hashCode are final and implemented in java.lang.Enum and java.lang.Object,
- // - getClass is final and implemented only in java.lang.Object,
- // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
- // also in the unboxed enum class.
- if (instruction.isInvokeMethodWithReceiver()) {
- InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
- DexType enumType = getEnumClassTypeOrNull(invoke.getReceiver(), convertedEnums);
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (enumType != null) {
- if (invokedMethod == factory.enumMembers.ordinalMethod
- || invokedMethod.match(factory.enumMembers.hashCode)) {
- replaceEnumInvoke(
- iterator,
- invoke,
- getSharedUtilityClass().ensureOrdinalMethod(appView, context, eventConsumer));
- continue;
- } else if (invokedMethod.match(factory.enumMembers.equals)) {
- replaceEnumInvoke(
- iterator,
- invoke,
- getSharedUtilityClass().ensureEqualsMethod(appView, context, eventConsumer));
- continue;
- } else if (invokedMethod == factory.enumMembers.compareTo
- || invokedMethod == factory.enumMembers.compareToWithObject) {
- replaceEnumInvoke(
- iterator,
- invoke,
- getSharedUtilityClass().ensureCompareToMethod(appView, context, eventConsumer));
- continue;
- } else if (invokedMethod == factory.enumMembers.nameMethod) {
- rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
- continue;
- } else if (invokedMethod.match(factory.enumMembers.toString)) {
- DexMethod reboundMethod =
- invokedMethod.withHolder(unboxedEnumsData.representativeType(enumType), factory);
- DexMethod lookupMethod =
- enumUnboxingLens
- .lookupMethod(
- reboundMethod,
- context.getReference(),
- invoke.getType(),
- enumUnboxingLens.getPrevious())
- .getReference();
- // If the SuperEnum had declared a toString() override, then the unboxer moves it to
- // the local utility class method corresponding to that override.
- // If a SubEnum had declared a toString() override, then the unboxer records a
- // synthetic move from SuperEnum.toString() to the dispatch method on the local
- // utility class.
- // When they are the same, then there are no overrides of toString().
- if (lookupMethod == reboundMethod) {
- rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
- } else {
- DexClassAndMethod dexClassAndMethod = appView.definitionFor(lookupMethod);
- assert dexClassAndMethod != null;
- assert dexClassAndMethod.isProgramMethod();
- replaceEnumInvoke(iterator, invoke, dexClassAndMethod.asProgramMethod());
- }
- continue;
- } else if (invokedMethod == factory.objectMembers.getClass) {
- rewriteNullCheck(iterator, invoke, context, eventConsumer);
- continue;
- } else if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
- DexMethod refinedDispatchMethodReference =
- enumUnboxingLens.lookupRefinedDispatchMethod(
- invokedMethod,
- context.getReference(),
- invoke.getType(),
- enumUnboxingLens.getPrevious(),
- invoke.getArgument(0).getAbstractValue(appView, context),
- enumType);
- if (refinedDispatchMethodReference != null) {
- DexClassAndMethod refinedDispatchMethod =
- appView.definitionFor(refinedDispatchMethodReference);
- assert refinedDispatchMethod != null;
- assert refinedDispatchMethod.isProgramMethod();
- replaceEnumInvoke(iterator, invoke, refinedDispatchMethod.asProgramMethod());
- }
- continue;
- }
- } else if (invokedMethod == factory.stringBuilderMethods.appendObject
- || invokedMethod == factory.stringBufferMethods.appendObject) {
- // Rewrites stringBuilder.append(enumInstance) as if it was
- // stringBuilder.append(String.valueOf(unboxedEnumInstance));
- Value enumArg = invoke.getArgument(1);
- DexType enumArgType = getEnumClassTypeOrNull(enumArg, convertedEnums);
- if (enumArgType != null) {
- ProgramMethod stringValueOfMethod =
- getLocalUtilityClass(enumArgType)
- .ensureStringValueOfMethod(appView, context, eventConsumer);
- InvokeStatic toStringInvoke =
- InvokeStatic.builder()
- .setMethod(stringValueOfMethod)
- .setSingleArgument(enumArg)
- .setFreshOutValue(appView, code)
- .setPosition(invoke)
- .build();
- DexMethod newAppendMethod =
- invokedMethod == factory.stringBuilderMethods.appendObject
- ? factory.stringBuilderMethods.appendString
- : factory.stringBufferMethods.appendString;
- List<Value> arguments =
- ImmutableList.of(invoke.getReceiver(), toStringInvoke.outValue());
- InvokeVirtual invokeAppendString =
- new InvokeVirtual(newAppendMethod, invoke.clearOutValue(), arguments);
- invokeAppendString.setPosition(invoke.getPosition());
- iterator.replaceCurrentInstruction(toStringInvoke);
- if (block.hasCatchHandlers()) {
- iterator
- .splitCopyCatchHandlers(code, blocks, appView.options())
- .listIterator(code)
- .add(invokeAppendString);
- } else {
- iterator.add(invokeAppendString);
- }
- continue;
- }
- }
+ } else if (instruction.isIf()) {
+ rewriteIf(code, convertedEnums, iterator, instruction.asIf());
+ } else if (instruction.isInvokeMethodWithReceiver()) {
+ rewriteInvokeMethodWithReceiver(
+ code,
+ eventConsumer,
+ convertedEnums,
+ blocks,
+ block,
+ iterator,
+ instruction.asInvokeMethodWithReceiver());
} else if (instruction.isInvokeStatic()) {
rewriteInvokeStatic(
instruction.asInvokeStatic(),
code,
- context,
convertedEnums,
iterator,
affectedPhis,
eventConsumer);
- }
- if (instruction.isStaticGet()) {
- StaticGet staticGet = instruction.asStaticGet();
- DexField field = staticGet.getField();
- DexType holder = field.holder;
- if (!unboxedEnumsData.isUnboxedEnum(holder)) {
- continue;
- }
- if (staticGet.hasUnusedOutValue()) {
- iterator.removeOrReplaceByDebugLocalRead();
- continue;
- }
- affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
- if (unboxedEnumsData.matchesValuesField(field)) {
- // Load the size of this enum's $VALUES array before the current instruction.
- iterator.previous();
- Value sizeValue =
- iterator.insertConstIntInstruction(
- code, options, unboxedEnumsData.getValuesSize(holder));
- iterator.next();
-
- // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
- InvokeStatic invoke =
- InvokeStatic.builder()
- .setMethod(getSharedUtilityClass().getValuesMethod(context, eventConsumer))
- .setFreshOutValue(appView, code)
- .setSingleArgument(sizeValue)
- .build();
- iterator.replaceCurrentInstruction(invoke);
-
- convertedEnums.put(invoke, holder);
-
- // Check if the call to SharedUtilityClass.values(size) is followed by a call to
- // clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
- // array. This is needed because the javac generated implementation of MyEnum.values()
- // is implemented as `return $VALUES.clone()`.
- removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
- } else if (unboxedEnumsData.hasUnboxedValueFor(field)) {
- // Replace by ordinal + 1 for null check (null is 0).
- ConstNumber intConstant =
- code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
- iterator.replaceCurrentInstruction(intConstant);
- convertedEnums.put(intConstant, holder);
- } else {
- // Nothing to do, handled by lens code rewriting.
- }
- }
-
- if (instruction.isInstanceGet()) {
- InstanceGet instanceGet = instruction.asInstanceGet();
- DexType holder = instanceGet.getField().holder;
- if (unboxedEnumsData.isUnboxedEnum(holder)) {
- ProgramMethod fieldMethod =
- ensureInstanceFieldMethod(instanceGet.getField(), context, eventConsumer);
- Value rewrittenOutValue =
- code.createValue(
- TypeElement.fromDexType(fieldMethod.getReturnType(), maybeNull(), appView));
- Value in = instanceGet.object();
- if (in.getType().isNullType()) {
- iterator.previous();
- in = iterator.insertConstIntInstruction(code, options, 0);
- iterator.next();
- }
- InvokeStatic invoke =
- new InvokeStatic(
- fieldMethod.getReference(), rewrittenOutValue, ImmutableList.of(in));
- iterator.replaceCurrentInstruction(invoke);
- if (unboxedEnumsData.isUnboxedEnum(instanceGet.getField().type)) {
- convertedEnums.put(invoke, instanceGet.getField().type);
- }
- }
- }
-
- // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
- if (instruction.isArrayAccess()) {
- ArrayAccess arrayAccess = instruction.asArrayAccess();
- DexType enumType = getEnumArrayTypeOrNull(arrayAccess, convertedEnums);
- if (enumType != null) {
- if (arrayAccess.hasOutValue()) {
- affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
- }
- arrayAccess = arrayAccess.withMemberType(MemberType.INT);
- iterator.replaceCurrentInstruction(arrayAccess);
- convertedEnums.put(arrayAccess, enumType);
- if (arrayAccess.isArrayPut()) {
- ArrayPut arrayPut = arrayAccess.asArrayPut();
- if (arrayPut.value().getType().isNullType()) {
- iterator.previous();
- arrayPut.replacePutValue(iterator.insertConstIntInstruction(code, options, 0));
- iterator.next();
- }
- }
- }
- assert validateArrayAccess(arrayAccess);
- }
-
- if (instruction.isNewUnboxedEnumInstance()) {
+ } else if (instruction.isStaticGet()) {
+ rewriteStaticGet(
+ code,
+ eventConsumer,
+ affectedPhis,
+ convertedEnums,
+ seenBlocks,
+ instructionsToRemove,
+ iterator,
+ instruction.asStaticGet());
+ } else if (instruction.isInstanceGet()) {
+ rewriteInstanceGet(
+ code, eventConsumer, convertedEnums, iterator, instruction.asInstanceGet());
+ } else if (instruction.isArrayAccess()) {
+ rewriteArrayAccess(
+ code, affectedPhis, convertedEnums, iterator, instruction.asArrayAccess());
+ } else if (instruction.isNewUnboxedEnumInstance()) {
NewUnboxedEnumInstance newUnboxedEnumInstance = instruction.asNewUnboxedEnumInstance();
assert unboxedEnumsData.isUnboxedEnum(newUnboxedEnumInstance.getType());
iterator.replaceCurrentInstruction(
@@ -440,14 +226,267 @@
return affectedPhis;
}
+ private void rewriteArrayAccess(
+ IRCode code,
+ Set<Phi> affectedPhis,
+ Map<Instruction, DexType> convertedEnums,
+ InstructionListIterator iterator,
+ ArrayAccess arrayAccess) {
+ // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
+ DexType enumType = getEnumArrayTypeOrNull(arrayAccess, convertedEnums);
+ if (enumType != null) {
+ if (arrayAccess.hasOutValue()) {
+ affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
+ }
+ arrayAccess = arrayAccess.withMemberType(MemberType.INT);
+ iterator.replaceCurrentInstruction(arrayAccess);
+ convertedEnums.put(arrayAccess, enumType);
+ if (arrayAccess.isArrayPut()) {
+ ArrayPut arrayPut = arrayAccess.asArrayPut();
+ if (arrayPut.value().getType().isNullType()) {
+ iterator.previous();
+ arrayPut.replacePutValue(iterator.insertConstIntInstruction(code, options, 0));
+ iterator.next();
+ }
+ }
+ }
+ assert validateArrayAccess(arrayAccess);
+ }
+
+ private void rewriteIf(
+ IRCode code,
+ Map<Instruction, DexType> convertedEnums,
+ InstructionListIterator iterator,
+ If ifInstruction) {
+ if (!ifInstruction.isZeroTest()) {
+ for (int operandIndex = 0; operandIndex < 2; operandIndex++) {
+ Value operand = ifInstruction.getOperand(operandIndex);
+ DexType enumType = getEnumClassTypeOrNull(operand, convertedEnums);
+ if (enumType != null) {
+ int otherOperandIndex = 1 - operandIndex;
+ Value otherOperand = ifInstruction.getOperand(otherOperandIndex);
+ if (otherOperand.getType().isNullType()) {
+ iterator.previous();
+ ifInstruction.replaceValue(
+ otherOperandIndex, iterator.insertConstIntInstruction(code, options, 0));
+ iterator.next();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void rewriteInstanceGet(
+ IRCode code,
+ EnumUnboxerMethodProcessorEventConsumer eventConsumer,
+ Map<Instruction, DexType> convertedEnums,
+ InstructionListIterator iterator,
+ InstanceGet instanceGet) {
+ DexType holder = instanceGet.getField().holder;
+ if (unboxedEnumsData.isUnboxedEnum(holder)) {
+ ProgramMethod fieldMethod =
+ ensureInstanceFieldMethod(instanceGet.getField(), code.context(), eventConsumer);
+ Value rewrittenOutValue =
+ code.createValue(
+ TypeElement.fromDexType(fieldMethod.getReturnType(), maybeNull(), appView));
+ Value in = instanceGet.object();
+ if (in.getType().isNullType()) {
+ iterator.previous();
+ in = iterator.insertConstIntInstruction(code, options, 0);
+ iterator.next();
+ }
+ InvokeStatic invoke =
+ new InvokeStatic(fieldMethod.getReference(), rewrittenOutValue, ImmutableList.of(in));
+ iterator.replaceCurrentInstruction(invoke);
+ if (unboxedEnumsData.isUnboxedEnum(instanceGet.getField().type)) {
+ convertedEnums.put(invoke, instanceGet.getField().type);
+ }
+ }
+ }
+
+ private void rewriteStaticGet(
+ IRCode code,
+ EnumUnboxerMethodProcessorEventConsumer eventConsumer,
+ Set<Phi> affectedPhis,
+ Map<Instruction, DexType> convertedEnums,
+ Set<BasicBlock> seenBlocks,
+ Set<Instruction> instructionsToRemove,
+ InstructionListIterator iterator,
+ StaticGet staticGet) {
+ DexField field = staticGet.getField();
+ DexType holder = field.holder;
+ if (!unboxedEnumsData.isUnboxedEnum(holder)) {
+ return;
+ }
+ if (staticGet.hasUnusedOutValue()) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ return;
+ }
+ affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+ if (unboxedEnumsData.matchesValuesField(field)) {
+ // Load the size of this enum's $VALUES array before the current instruction.
+ iterator.previous();
+ Value sizeValue =
+ iterator.insertConstIntInstruction(code, options, unboxedEnumsData.getValuesSize(holder));
+ iterator.next();
+
+ // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
+ InvokeStatic invoke =
+ InvokeStatic.builder()
+ .setMethod(getSharedUtilityClass().getValuesMethod(code.context(), eventConsumer))
+ .setFreshOutValue(appView, code)
+ .setSingleArgument(sizeValue)
+ .build();
+ iterator.replaceCurrentInstruction(invoke);
+
+ convertedEnums.put(invoke, holder);
+
+ // Check if the call to SharedUtilityClass.values(size) is followed by a call to
+ // clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
+ // array. This is needed because the javac generated implementation of MyEnum.values()
+ // is implemented as `return $VALUES.clone()`.
+ removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
+ } else if (unboxedEnumsData.hasUnboxedValueFor(field)) {
+ // Replace by ordinal + 1 for null check (null is 0).
+ ConstNumber intConstant = code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
+ iterator.replaceCurrentInstruction(intConstant);
+ convertedEnums.put(intConstant, holder);
+ } else {
+ // Nothing to do, handled by lens code rewriting.
+ }
+ }
+
+ // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
+ // counterpart. The rewriting (== or match) is based on the following:
+ // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
+ // - equals, hashCode are final and implemented in java.lang.Enum and java.lang.Object,
+ // - getClass is final and implemented only in java.lang.Object,
+ // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
+ // also in the unboxed enum class.
+ private void rewriteInvokeMethodWithReceiver(
+ IRCode code,
+ EnumUnboxerMethodProcessorEventConsumer eventConsumer,
+ Map<Instruction, DexType> convertedEnums,
+ BasicBlockIterator blocks,
+ BasicBlock block,
+ InstructionListIterator iterator,
+ InvokeMethodWithReceiver invoke) {
+ ProgramMethod context = code.context();
+ // If the receiver is null, then the invoke is not rewritten even if the receiver is an
+ // unboxed enum, but we end up with null.ordinal() or similar which has the correct behavior.
+ DexType enumType = getEnumClassTypeOrNull(invoke.getReceiver(), convertedEnums);
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (enumType != null) {
+ if (invokedMethod == factory.enumMembers.ordinalMethod
+ || invokedMethod.match(factory.enumMembers.hashCode)) {
+ replaceEnumInvoke(
+ iterator,
+ invoke,
+ getSharedUtilityClass().ensureOrdinalMethod(appView, context, eventConsumer));
+ } else if (invokedMethod.match(factory.enumMembers.equals)) {
+ replaceEnumInvoke(
+ iterator,
+ invoke,
+ getSharedUtilityClass().ensureEqualsMethod(appView, context, eventConsumer));
+ } else if (invokedMethod == factory.enumMembers.compareTo
+ || invokedMethod == factory.enumMembers.compareToWithObject) {
+ replaceEnumInvoke(
+ iterator,
+ invoke,
+ getSharedUtilityClass().ensureCompareToMethod(appView, context, eventConsumer));
+ } else if (invokedMethod == factory.enumMembers.nameMethod) {
+ rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
+ } else if (invokedMethod.match(factory.enumMembers.toString)) {
+ DexMethod reboundMethod =
+ invokedMethod.withHolder(unboxedEnumsData.representativeType(enumType), factory);
+ DexMethod lookupMethod =
+ enumUnboxingLens
+ .lookupMethod(
+ reboundMethod,
+ context.getReference(),
+ invoke.getType(),
+ enumUnboxingLens.getPrevious())
+ .getReference();
+ // If the SuperEnum had declared a toString() override, then the unboxer moves it to
+ // the local utility class method corresponding to that override.
+ // If a SubEnum had declared a toString() override, then the unboxer records a
+ // synthetic move from SuperEnum.toString() to the dispatch method on the local
+ // utility class.
+ // When they are the same, then there are no overrides of toString().
+ if (lookupMethod == reboundMethod) {
+ rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
+ } else {
+ DexClassAndMethod dexClassAndMethod = appView.definitionFor(lookupMethod);
+ assert dexClassAndMethod != null;
+ assert dexClassAndMethod.isProgramMethod();
+ replaceEnumInvoke(iterator, invoke, dexClassAndMethod.asProgramMethod());
+ }
+ } else if (invokedMethod == factory.objectMembers.getClass) {
+ rewriteNullCheck(iterator, invoke, context, eventConsumer);
+ } else if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+ DexMethod refinedDispatchMethodReference =
+ enumUnboxingLens.lookupRefinedDispatchMethod(
+ invokedMethod,
+ context.getReference(),
+ invoke.getType(),
+ enumUnboxingLens.getPrevious(),
+ invoke.getArgument(0).getAbstractValue(appView, context),
+ enumType);
+ if (refinedDispatchMethodReference != null) {
+ DexClassAndMethod refinedDispatchMethod =
+ appView.definitionFor(refinedDispatchMethodReference);
+ assert refinedDispatchMethod != null;
+ assert refinedDispatchMethod.isProgramMethod();
+ replaceEnumInvoke(iterator, invoke, refinedDispatchMethod.asProgramMethod());
+ }
+ }
+ } else if (invokedMethod == factory.stringBuilderMethods.appendObject
+ || invokedMethod == factory.stringBufferMethods.appendObject) {
+ // Rewrites stringBuilder.append(enumInstance) as if it was
+ // stringBuilder.append(String.valueOf(unboxedEnumInstance));
+ Value enumArg = invoke.getArgument(1);
+ DexType enumArgType = getEnumClassTypeOrNull(enumArg, convertedEnums);
+ if (enumArgType != null) {
+ ProgramMethod stringValueOfMethod =
+ getLocalUtilityClass(enumArgType)
+ .ensureStringValueOfMethod(appView, context, eventConsumer);
+ InvokeStatic toStringInvoke =
+ InvokeStatic.builder()
+ .setMethod(stringValueOfMethod)
+ .setSingleArgument(enumArg)
+ .setFreshOutValue(appView, code)
+ .setPosition(invoke)
+ .build();
+ DexMethod newAppendMethod =
+ invokedMethod == factory.stringBuilderMethods.appendObject
+ ? factory.stringBuilderMethods.appendString
+ : factory.stringBufferMethods.appendString;
+ List<Value> arguments = ImmutableList.of(invoke.getReceiver(), toStringInvoke.outValue());
+ InvokeVirtual invokeAppendString =
+ new InvokeVirtual(newAppendMethod, invoke.clearOutValue(), arguments);
+ invokeAppendString.setPosition(invoke.getPosition());
+ iterator.replaceCurrentInstruction(toStringInvoke);
+ if (block.hasCatchHandlers()) {
+ iterator
+ .splitCopyCatchHandlers(code, blocks, appView.options())
+ .listIterator(code)
+ .add(invokeAppendString);
+ } else {
+ iterator.add(invokeAppendString);
+ }
+ }
+ }
+ }
+
private void rewriteInvokeStatic(
InvokeStatic invoke,
IRCode code,
- ProgramMethod context,
Map<Instruction, DexType> convertedEnums,
InstructionListIterator instructionIterator,
Set<Phi> affectedPhis,
EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+ ProgramMethod context = code.context();
DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
if (singleTarget == null) {
return;
@@ -508,13 +547,28 @@
rewriteStringValueOf(invoke, context, convertedEnums, instructionIterator, eventConsumer);
} else if (invokedMethod == factory.objectsMethods.equals) {
assert invoke.arguments().size() == 2;
- Value argument = invoke.getFirstArgument();
- DexType enumType = getEnumClassTypeOrNull(argument, convertedEnums);
- if (enumType != null) {
+ if (Iterables.any(
+ invoke.arguments(), arg -> getEnumClassTypeOrNull(arg, convertedEnums) != null)) {
+ // If any of the input is null, replace it by const 0.
+ // If both inputs are null, no rewriting happen here.
+ List<Value> newArguments = new ArrayList<>(invoke.arguments().size());
+ for (Value arg : invoke.arguments()) {
+ if (arg.getType().isNullType()) {
+ Value constZero = insertConstZero(code);
+ newArguments.add(constZero);
+ } else {
+ assert getEnumClassTypeOrNull(arg, convertedEnums) != null;
+ newArguments.add(arg);
+ }
+ }
replaceEnumInvoke(
instructionIterator,
invoke,
- getSharedUtilityClass().ensureObjectsEqualsMethod(appView, context, eventConsumer));
+ getSharedUtilityClass().ensureObjectsEqualsMethod(appView, context, eventConsumer),
+ newArguments);
+ } else {
+ assert invoke.getArgument(0).getType().isReferenceType();
+ assert invoke.getArgument(1).getType().isReferenceType();
}
}
return;
@@ -687,11 +741,19 @@
private void replaceEnumInvoke(
InstructionListIterator iterator, InvokeMethod invoke, ProgramMethod method) {
+ replaceEnumInvoke(iterator, invoke, method, invoke.arguments());
+ }
+
+ private void replaceEnumInvoke(
+ InstructionListIterator iterator,
+ InvokeMethod invoke,
+ ProgramMethod method,
+ List<Value> arguments) {
InvokeStatic replacement =
new InvokeStatic(
method.getReference(),
invoke.hasUnusedOutValue() ? null : invoke.outValue(),
- invoke.arguments());
+ arguments);
assert !replacement.hasOutValue()
|| !replacement.getInvokedMethod().getReturnType().isVoidType();
iterator.replaceCurrentInstruction(replacement);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 5ed16f7..2bc3346 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -117,6 +117,8 @@
}
}
code.removeAllDeadAndTrivialPhis();
+ code.removeRedundantBlocks();
+ assert code.isConsistentSSA(appView);
return CodeRewriterResult.HAS_CHANGED;
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index 6602826..5abcb38 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfIf;
import com.android.tools.r8.cf.code.CfIfCmp;
import com.android.tools.r8.cf.code.CfInstanceFieldRead;
import com.android.tools.r8.cf.code.CfInstruction;
@@ -153,6 +154,9 @@
@Override
public CfCode generateCfCode() {
// This generates something along the lines of:
+ // if (other == null) {
+ // return false;
+ // }
// if (this.getClass() != other.getClass()) {
// return false;
// }
@@ -160,10 +164,22 @@
// recordInstance.getFieldsAsObjects(),
// ((RecordClass) other).getFieldsAsObjects());
DexItemFactory factory = appView.dexItemFactory();
- List<CfInstruction> instructions = new ArrayList<>();
+ int numberOfInstructions = 22;
+ List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
+ CfLabel notNullLabel = new CfLabel();
CfLabel fieldCmp = new CfLabel();
ValueType recordType = ValueType.fromDexType(getHolder());
ValueType objectType = ValueType.fromDexType(factory.objectType);
+ instructions.add(new CfLoad(objectType, 1));
+ instructions.add(new CfIf(IfType.NE, ValueType.OBJECT, notNullLabel));
+ instructions.add(new CfConstNumber(0, ValueType.INT));
+ instructions.add(new CfReturn(ValueType.INT));
+ instructions.add(notNullLabel);
+ instructions.add(
+ CfFrame.builder()
+ .appendLocal(FrameType.initialized(getHolder()))
+ .appendLocal(FrameType.initialized(appView.dexItemFactory().objectType))
+ .build());
instructions.add(new CfLoad(recordType, 0));
instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
instructions.add(new CfLoad(objectType, 1));
@@ -186,6 +202,7 @@
new CfInvoke(
Opcodes.INVOKESTATIC, factory.javaUtilArraysMethods.equalsObjectArray, false));
instructions.add(new CfReturn(ValueType.INT));
+ assert instructions.size() == numberOfInstructions;
return standardCfCodeFromInstructions(instructions);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
index fab9f6f..43da39b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -105,15 +105,11 @@
DexType parameterType,
Action onChangedAction) {
assert parameterType.isClassType();
- boolean allowNullOrAbstractValue = true;
- boolean allowNonConstantNumbers = false;
AbstractValue oldAbstractValue = abstractValue;
abstractValue =
- abstractValue.join(
- parameterState.getAbstractValue(appView),
- appView.abstractValueFactory(),
- allowNullOrAbstractValue,
- allowNonConstantNumbers);
+ appView
+ .getAbstractValueParameterJoiner()
+ .join(abstractValue, parameterState.getAbstractValue(appView), parameterType);
DynamicType oldDynamicType = dynamicType;
DynamicType joinedDynamicType = dynamicType.join(appView, parameterState.getDynamicType());
DynamicType widenedDynamicType =
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
index 429e514..3b3f3cb 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
@@ -56,15 +56,11 @@
DexType parameterType,
Action onChangedAction) {
assert parameterType.isPrimitiveType();
- boolean allowNullOrAbstractValue = false;
- boolean allowNonConstantNumbers = false;
AbstractValue oldAbstractValue = abstractValue;
abstractValue =
- abstractValue.join(
- parameterState.abstractValue,
- appView.abstractValueFactory(),
- allowNullOrAbstractValue,
- allowNonConstantNumbers);
+ appView
+ .getAbstractValueParameterJoiner()
+ .join(abstractValue, parameterState.abstractValue, parameterType);
if (abstractValue.isUnknown()) {
return unknown();
}
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
index ad329a1..85e7fdd 100644
--- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
@@ -135,31 +135,27 @@
// Collect all redundant bridges to remove.
ProgramMethodSet bridgesToRemove = removeRedundantBridgesConcurrently(executorService);
- if (bridgesToRemove.isEmpty()) {
- timing.end();
- return;
- }
+ if (!bridgesToRemove.isEmpty()) {
+ pruneApp(bridgesToRemove, executorService, timing);
- pruneApp(bridgesToRemove, executorService, timing);
+ if (!lensBuilder.isEmpty()) {
+ appView.setGraphLens(lensBuilder.build(appView));
+ }
- if (!lensBuilder.isEmpty()) {
- appView.setGraphLens(lensBuilder.build(appView));
- }
-
- if (memberRebindingIdentityLens != null) {
- for (ProgramMethod bridgeToRemove : bridgesToRemove) {
- DexClassAndMethod resolvedMethod =
- appView
- .appInfo()
- .resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
- .getResolutionPair();
- memberRebindingIdentityLens.addNonReboundMethodReference(
- bridgeToRemove.getReference(), resolvedMethod.getReference());
+ if (memberRebindingIdentityLens != null) {
+ for (ProgramMethod bridgeToRemove : bridgesToRemove) {
+ DexClassAndMethod resolvedMethod =
+ appView
+ .appInfo()
+ .resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
+ .getResolutionPair();
+ memberRebindingIdentityLens.addNonReboundMethodReference(
+ bridgeToRemove.getReference(), resolvedMethod.getReference());
+ }
}
}
-
appView.notifyOptimizationFinishedForTesting();
- appView.appInfo().notifyRedundantBridgeRemoverFinished(true);
+ appView.appInfo().notifyRedundantBridgeRemoverFinished(memberRebindingIdentityLens == null);
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1ea86b5..4bbc625 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -80,6 +80,7 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramDerivedContext;
import com.android.tools.r8.graph.ProgramField;
@@ -2098,6 +2099,23 @@
}
}
+ // Mark types in permitted-subclasses attributes referenced.
+ List<PermittedSubclassAttribute> permittedSubclassAttributes =
+ clazz.getPermittedSubclassAttributes();
+ if (!permittedSubclassAttributes.isEmpty()) {
+ BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer =
+ options.reportMissingClassesInPermittedSubclassesAttributes
+ ? this::reportMissingClass
+ : this::ignoreMissingClass;
+ for (PermittedSubclassAttribute permittedSubclassAttribute : permittedSubclassAttributes) {
+ recordTypeReference(
+ permittedSubclassAttribute.getPermittedSubclass(),
+ clazz,
+ this::recordNonProgramClass,
+ missingClassConsumer);
+ }
+ }
+
KeepReason reason = KeepReason.reachableFromLiveType(clazz.type);
for (DexType iface : clazz.getInterfaces()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java
index 48312bd..7d25797 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java
@@ -69,6 +69,7 @@
// Rewrite field instructions that reference a pruned field.
Set<Value> affectedValues = Sets.newIdentityHashSet();
BasicBlockIterator blockIterator = code.listIterator();
+ boolean hasChanged = false;
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
InstructionListIterator instructionIterator = block.listIterator(code);
@@ -76,34 +77,38 @@
Instruction instruction = instructionIterator.next();
switch (instruction.opcode()) {
case INSTANCE_GET:
- rewriteInstanceGet(
- code,
- instructionIterator,
- instruction.asInstanceGet(),
- affectedValues,
- prunedFields);
+ hasChanged |=
+ rewriteInstanceGet(
+ code,
+ instructionIterator,
+ instruction.asInstanceGet(),
+ affectedValues,
+ prunedFields);
break;
case INSTANCE_PUT:
- rewriteInstancePut(instructionIterator, instruction.asInstancePut(), prunedFields);
+ hasChanged |=
+ rewriteInstancePut(instructionIterator, instruction.asInstancePut(), prunedFields);
break;
case STATIC_GET:
- rewriteStaticGet(
- code,
- instructionIterator,
- instruction.asStaticGet(),
- affectedValues,
- context,
- initializedClassesWithContexts,
- prunedFields);
+ hasChanged |=
+ rewriteStaticGet(
+ code,
+ instructionIterator,
+ instruction.asStaticGet(),
+ affectedValues,
+ context,
+ initializedClassesWithContexts,
+ prunedFields);
break;
case STATIC_PUT:
- rewriteStaticPut(
- code,
- instructionIterator,
- instruction.asStaticPut(),
- context,
- initializedClassesWithContexts,
- prunedFields);
+ hasChanged |=
+ rewriteStaticPut(
+ code,
+ instructionIterator,
+ instruction.asStaticPut(),
+ context,
+ initializedClassesWithContexts,
+ prunedFields);
break;
default:
break;
@@ -113,9 +118,12 @@
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
+ if (hasChanged) {
+ code.removeRedundantBlocks();
+ }
}
- private void rewriteInstanceGet(
+ private boolean rewriteInstanceGet(
IRCode code,
InstructionListIterator instructionIterator,
InstanceGet instanceGet,
@@ -123,27 +131,29 @@
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(instanceGet.getField());
if (prunedField == null) {
- return;
+ return false;
}
insertDefaultValueForFieldGet(
code, instructionIterator, instanceGet, affectedValues, prunedField);
removeOrReplaceInstanceFieldInstructionWithNullCheck(instructionIterator, instanceGet);
+ return true;
}
- private void rewriteInstancePut(
+ private boolean rewriteInstancePut(
InstructionListIterator instructionIterator,
InstancePut instancePut,
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(instancePut.getField());
if (prunedField == null) {
- return;
+ return false;
}
removeOrReplaceInstanceFieldInstructionWithNullCheck(instructionIterator, instancePut);
+ return true;
}
- private void rewriteStaticGet(
+ private boolean rewriteStaticGet(
IRCode code,
InstructionListIterator instructionIterator,
StaticGet staticGet,
@@ -153,16 +163,17 @@
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(staticGet.getField());
if (prunedField == null) {
- return;
+ return false;
}
insertDefaultValueForFieldGet(
code, instructionIterator, staticGet, affectedValues, prunedField);
removeOrReplaceStaticFieldInstructionByInitClass(
code, instructionIterator, context, initializedClassesWithContexts, prunedField);
+ return true;
}
- private void rewriteStaticPut(
+ private boolean rewriteStaticPut(
IRCode code,
InstructionListIterator instructionIterator,
StaticPut staticPut,
@@ -171,11 +182,12 @@
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(staticPut.getField());
if (prunedField == null) {
- return;
+ return false;
}
removeOrReplaceStaticFieldInstructionByInitClass(
code, instructionIterator, context, initializedClassesWithContexts, prunedField);
+ return true;
}
private void insertDefaultValueForFieldGet(
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index 9898d34..c199247 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -257,6 +257,11 @@
return self();
}
+ public Joiner allowPermittedSubclassesRemoval() {
+ builder.allowPermittedSubclassesRemoval();
+ return self();
+ }
+
@Override
public Joiner asClassJoiner() {
return this;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index bec7ae6..9d98730 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1050,6 +1050,8 @@
builder.getModifiersBuilder().setAllowsAccessModification(true);
} else if (acceptString("repackage")) {
builder.getModifiersBuilder().setAllowsRepackaging(true);
+ } else if (acceptString("permittedsubclassesremoval")) {
+ builder.getModifiersBuilder().setAllowsPermittedSubclassesRemoval(true);
} else if (options.isTestingOptionsEnabled()) {
if (acceptString("annotationremoval")) {
builder.getModifiersBuilder().setAllowsAnnotationRemoval(true);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index ec825c3..6073953 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -15,6 +15,7 @@
private boolean allowsOptimization = false;
private boolean allowsObfuscation = false;
private boolean includeDescriptorClasses = false;
+ private boolean allowsPermittedSubclassesRemoval = false;
private Builder() {}
@@ -61,6 +62,11 @@
return this;
}
+ public Builder setAllowsPermittedSubclassesRemoval(boolean allowsPermittedSubclassesRemoval) {
+ this.allowsPermittedSubclassesRemoval = allowsPermittedSubclassesRemoval;
+ return this;
+ }
+
public void setIncludeDescriptorClasses(boolean includeDescriptorClasses) {
this.includeDescriptorClasses = includeDescriptorClasses;
}
@@ -73,7 +79,8 @@
allowsShrinking,
allowsOptimization,
allowsObfuscation,
- includeDescriptorClasses);
+ includeDescriptorClasses,
+ allowsPermittedSubclassesRemoval);
}
}
@@ -84,6 +91,7 @@
public final boolean allowsOptimization;
public final boolean allowsObfuscation;
public final boolean includeDescriptorClasses;
+ public final boolean allowsPermittedSubclassesRemoval;
private ProguardKeepRuleModifiers(
boolean allowsAccessModification,
@@ -92,7 +100,8 @@
boolean allowsShrinking,
boolean allowsOptimization,
boolean allowsObfuscation,
- boolean includeDescriptorClasses) {
+ boolean includeDescriptorClasses,
+ boolean allowsPermittedSubclassesRemoval) {
this.allowsAccessModification = allowsAccessModification;
this.allowsAnnotationRemoval = allowsAnnotationRemoval;
this.allowsRepackaging = allowsRepackaging;
@@ -100,6 +109,7 @@
this.allowsOptimization = allowsOptimization;
this.allowsObfuscation = allowsObfuscation;
this.includeDescriptorClasses = includeDescriptorClasses;
+ this.allowsPermittedSubclassesRemoval = allowsPermittedSubclassesRemoval;
}
/**
@@ -116,7 +126,8 @@
&& allowsObfuscation
&& allowsOptimization
&& allowsShrinking
- && !includeDescriptorClasses;
+ && !includeDescriptorClasses
+ && allowsPermittedSubclassesRemoval;
}
@Override
@@ -131,7 +142,8 @@
&& allowsShrinking == that.allowsShrinking
&& allowsOptimization == that.allowsOptimization
&& allowsObfuscation == that.allowsObfuscation
- && includeDescriptorClasses == that.includeDescriptorClasses;
+ && includeDescriptorClasses == that.includeDescriptorClasses
+ && allowsPermittedSubclassesRemoval == that.allowsPermittedSubclassesRemoval;
}
@Override
@@ -143,7 +155,8 @@
allowsShrinking,
allowsOptimization,
allowsObfuscation,
- includeDescriptorClasses);
+ includeDescriptorClasses,
+ allowsPermittedSubclassesRemoval);
}
@Override
@@ -156,6 +169,7 @@
appendWithComma(builder, allowsShrinking, "allowshrinking");
appendWithComma(builder, allowsOptimization, "allowoptimization");
appendWithComma(builder, includeDescriptorClasses, "includedescriptorclasses");
+ appendWithComma(builder, allowsPermittedSubclassesRemoval, "allowpermittedsubclassesremoval");
return builder.toString();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 8678717..afe4006 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1665,6 +1665,16 @@
includeDescriptorClasses(item, keepRule, preconditionEvent);
context.markAsUsed();
}
+
+ if (item.isProgramClass()
+ && appView.options().isKeepPermittedSubclassesEnabled()
+ && !modifiers.allowsPermittedSubclassesRemoval) {
+ dependentMinimumKeepInfo
+ .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
+ .asClassJoiner()
+ .disallowPermittedSubclassesRemoval();
+ context.markAsUsed();
+ }
}
private boolean isRepackagingDisallowed(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 3688415..ec15263 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.RecordComponentInfo;
@@ -226,6 +227,7 @@
PredicateUtils.not(isReachableInstanceField(reachableInstanceFields)));
}
}
+ clazz.removePermittedSubclassAttribute(this::isAttributeReferencingPrunedType);
unusedItemsPrinter.visited();
assert verifyNoDeadFields(clazz);
}
@@ -319,6 +321,10 @@
return context == null || isTypeMissing(context) || !isTypeLive(context);
}
+ private boolean isAttributeReferencingPrunedType(PermittedSubclassAttribute attr) {
+ return !isTypeLive(attr.getPermittedSubclass());
+ }
+
private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> int firstUnreachableIndex(
List<D> items, Predicate<D> live) {
for (int i = 0; i < items.size(); i++) {
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 c284670..d49b405 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -141,7 +141,15 @@
public enum LineNumberOptimization {
OFF,
- ON
+ ON;
+
+ public boolean isOff() {
+ return this == OFF;
+ }
+
+ public boolean isOn() {
+ return this == ON;
+ }
}
public enum DesugarState {
@@ -876,6 +884,7 @@
public boolean ignoreMissingClasses = false;
public boolean reportMissingClassesInEnclosingMethodAttribute = false;
public boolean reportMissingClassesInInnerClassAttributes = false;
+ public boolean reportMissingClassesInPermittedSubclassesAttributes = false;
public boolean disableGenericSignatureValidation = false;
public boolean disableInnerClassSeparatorValidationWhenRepackaging = false;
@@ -2585,7 +2594,7 @@
}
public boolean canUseDexPc2PcAsDebugInformation() {
- return isGeneratingDex() && lineNumberOptimization == LineNumberOptimization.ON;
+ return isGeneratingDex() && lineNumberOptimization.isOn();
}
// Debug entries may be dropped only if the source file content allows being omitted from
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
index 2a044e5..bbc27a2 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
@@ -5,8 +5,10 @@
package com.android.tools.r8.utils.collections;
import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.utils.TriPredicate;
import com.google.common.base.Equivalence.Wrapper;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
@@ -77,6 +79,12 @@
.removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue()));
}
+ public boolean removeIf(TriPredicate<K, V, Entry<?, V>> predicate) {
+ return backing
+ .entrySet()
+ .removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue(), entry));
+ }
+
public int size() {
return backing.size();
}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
index a6a2a06..e80a0a7 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -26,7 +26,6 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import java.util.ArrayList;
import java.util.List;
@@ -176,7 +175,7 @@
public DexPositionToNoPcMappedRangeMapper(AppView<?> appView) {
this.appView = appView;
- isIdentityMapping = appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
+ isIdentityMapping = appView.options().lineNumberOptimization.isOff();
}
public List<MappedPosition> optimizeDexCodePositions(
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index 7fe3d32..487ac5f 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -1,4 +1,3 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@@ -45,7 +44,6 @@
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OneShotCollectionConsumer;
import com.android.tools.r8.utils.OriginalSourceFiles;
@@ -56,6 +54,7 @@
import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -79,7 +78,7 @@
PositionRangeAllocator.createCardinalPositionRangeAllocator();
private final NonCardinalPositionRangeAllocator nonCardinalRangeCache =
PositionRangeAllocator.createNonCardinalPositionRangeAllocator();
- private final int maxGap = 1000;
+ private final int maxGap;
private MappedPositionToClassNameMapperBuilder(
AppView<?> appView, OriginalSourceFiles originalSourceFiles) {
@@ -88,6 +87,7 @@
classNameMapperBuilder = ClassNameMapper.builder();
classNameMapperBuilder.setCurrentMapVersion(
appView.options().getMapFileVersion().toMapVersionMappingInformation());
+ maxGap = appView.options().lineNumberOptimization.isOn() ? 1000 : 0;
}
public static String getPrunedInlinedClassObfuscatedPrefix() {
@@ -246,7 +246,7 @@
residualMethod,
originalMethod,
originalType)) {
- assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
+ assert appView.options().lineNumberOptimization.isOff()
|| hasAtMostOnePosition(appView, definition)
|| appView.isCfByteCodePassThrough(definition);
return this;
@@ -290,6 +290,8 @@
methodSpecificMappingInformation.add(OutlineMappingInformation.builder().build());
}
+ mappedPositions.sort(Comparator.comparing(MappedPosition::getObfuscatedLine));
+
// Update memberNaming with the collected positions, merging multiple positions into a
// single region whenever possible.
for (int i = 0; i < mappedPositions.size(); /* updated in body */ ) {
@@ -317,14 +319,7 @@
|| firstMappedPosition.getPosition().getOutlineCallee() != null) {
break;
}
- // The mapped positions are not guaranteed to be in order, so maintain first and last
- // position.
- if (firstMappedPosition.getObfuscatedLine() > currentMappedPosition.getObfuscatedLine()) {
- firstMappedPosition = currentMappedPosition;
- }
- if (lastMappedPosition.getObfuscatedLine() < currentMappedPosition.getObfuscatedLine()) {
- lastMappedPosition = currentMappedPosition;
- }
+ lastMappedPosition = currentMappedPosition;
}
Range obfuscatedRange =
nonCardinalRangeCache.get(
@@ -385,9 +380,7 @@
}
i = j;
}
- // TODO(b/287210793): Enable assertion again.
- assert true
- || mappedPositions.size() <= 1
+ assert mappedPositions.size() <= 1
|| getBuilder().hasNoOverlappingRangesForSignature(residualSignature);
return this;
}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java
index 3f0288f..861c9dc 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.utils.CfLineToMethodMapper;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.Pair;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -34,8 +33,7 @@
static PositionRemapper getPositionRemapper(
AppView<?> appView, CfLineToMethodMapper cfLineToMethodMapper) {
- boolean identityMapping =
- appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
+ boolean identityMapping = appView.options().lineNumberOptimization.isOff();
PositionRemapper positionRemapper =
identityMapping
? new IdentityPositionRemapper()
diff --git a/src/test/examplesJava17/records/SimpleRecord.java b/src/test/examplesJava17/records/SimpleRecord.java
index 1f8fca4..3f908dc 100644
--- a/src/test/examplesJava17/records/SimpleRecord.java
+++ b/src/test/examplesJava17/records/SimpleRecord.java
@@ -14,5 +14,22 @@
System.out.println(janeDoe.age);
System.out.println(janeDoe.name());
System.out.println(janeDoe.age());
+
+ // Test equals with self.
+ System.out.println(janeDoe.equals(janeDoe));
+
+ // Test equals with structurally equals Person.
+ Person otherJaneDoe = new Person("Jane Doe", 42);
+ System.out.println(janeDoe.equals(otherJaneDoe));
+ System.out.println(otherJaneDoe.equals(janeDoe));
+
+ // Test equals with not-structually equals Person.
+ Person johnDoe = new Person("John Doe", 42);
+ System.out.println(janeDoe.equals(johnDoe));
+ System.out.println(johnDoe.equals(janeDoe));
+
+ // Test equals with Object and null.
+ System.out.println(janeDoe.equals(new Object()));
+ System.out.println(janeDoe.equals(null));
}
}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index 35e20e9..f314547 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -91,8 +91,7 @@
}
protected Path getJavaJarFile(String folder) {
- return Paths.get(ToolHelper.TESTS_BUILD_DIR, RSRC,
- targetVersion.getFolderName(), folder + ".java" + FileUtils.JAR_EXTENSION);
+ return Paths.get(ToolHelper.TESTS_BUILD_DIR, RSRC, folder + FileUtils.JAR_EXTENSION);
}
protected Path getMappingfile(String folder, String mappingFileName) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index ec3a345..3bad712 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -80,8 +80,6 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesClass;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -1688,18 +1686,6 @@
return clazz.getTypeName();
}
- public static ClassReference examplesClassReference(Class<? extends ExamplesClass> clazz) {
- return Reference.classFromTypeName(examplesTypeName(clazz));
- }
-
- public static String examplesTypeName(Class<? extends ExamplesClass> clazz) {
- try {
- return ReflectiveBuildPathUtils.resolveClassName(clazz);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
public static AndroidApiLevel apiLevelWithDefaultInterfaceMethodsSupport() {
return AndroidApiLevel.N;
}
diff --git a/src/test/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
index 63b1982..c9740c3 100644
--- a/src/test/java/com/android/tools/r8/TestBaseBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
@@ -10,8 +10,6 @@
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesRootPackage;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
import java.util.Arrays;
@@ -56,12 +54,6 @@
return self();
}
- public T addExamplesProgramFiles(Class<? extends ExamplesRootPackage> rootPackage)
- throws Exception {
- Collection<Path> files = ReflectiveBuildPathUtils.allClassFiles(rootPackage);
- return addProgramFiles(files);
- }
-
public T addLibraryProvider(ClassFileResourceProvider provider) {
builder.addLibraryResourceProvider(provider);
return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index f27b3b8..9b4e4fd 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -314,6 +315,10 @@
return addKeepMainRule(mainClass.getTypeName());
}
+ public T addKeepMainRule(ClassReference mainClass) {
+ return addKeepMainRule(mainClass.getTypeName());
+ }
+
public T addKeepMainRules(Class<?>... mainClasses) {
for (Class<?> mainClass : mainClasses) {
this.addKeepMainRule(mainClass);
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 42b728e..447399f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -119,8 +119,8 @@
public static final String LIBS_DIR = BUILD_DIR + "libs/";
public static final String THIRD_PARTY_DIR = getProjectRoot() + "third_party/";
public static final String TOOLS_DIR = getProjectRoot() + "tools/";
- public static final String TESTS_DIR = "src/test/";
- public static final String TESTS_SOURCE_DIR = "src/test/java";
+ public static final String TESTS_DIR = getProjectRoot() + "src/test/";
+ public static final String TESTS_SOURCE_DIR = TESTS_DIR + "java/";
public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
public static final String EXAMPLES_ANDROID_N_DIR = TESTS_DIR + "examplesAndroidN/";
public static final String EXAMPLES_ANDROID_O_DIR = TESTS_DIR + "examplesAndroidO/";
@@ -140,7 +140,6 @@
public static final String GENERATED_PROTO_BUILD_DIR = GENERATED_TEST_BUILD_DIR + "proto/";
public static final String SMALI_BUILD_DIR = THIRD_PARTY_DIR + "smali/";
public static final String JAVA_CLASSES_DIR = BUILD_DIR + "classes/java/";
- public static final String JDK_11_TESTS_CLASSES_DIR = JAVA_CLASSES_DIR + "jdk11Tests/";
public static final String R8_TEST_BUCKET = "r8-test-results";
@@ -178,7 +177,7 @@
private static final String PROGUARD6_0_1 =
THIRD_PARTY_DIR + "proguard/proguard6.0.1/bin/proguard";
private static final String PROGUARD = PROGUARD5_2_1;
- public static final Path JACOCO_ROOT = Paths.get("third_party", "jacoco", "0.8.6");
+ public static final Path JACOCO_ROOT = Paths.get(THIRD_PARTY_DIR, "jacoco", "0.8.6");
public static final Path JACOCO_AGENT = JACOCO_ROOT.resolve(Paths.get("lib", "jacocoagent.jar"));
public static final Path JACOCO_CLI = JACOCO_ROOT.resolve(Paths.get("lib", "jacococli.jar"));
public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS =
@@ -1322,11 +1321,6 @@
return getClassPathForTests().resolve(Paths.get("", parts.toArray(StringUtils.EMPTY_ARRAY)));
}
- public static String getJarEntryForTestPackage(Package pkg) {
- List<String> parts = getNamePartsForTestPackage(pkg);
- return String.join("/", parts);
- }
-
private static List<String> getNamePartsForTestClass(Class<?> clazz) {
List<String> parts = Lists.newArrayList(clazz.getTypeName().split("\\."));
parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ".class");
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 57d23d8..f69b30f 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
@@ -3,8 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.benchmarks.appdumps;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.benchmarks.BenchmarkBase;
import com.android.tools.r8.benchmarks.BenchmarkConfig;
import com.android.tools.r8.benchmarks.BenchmarkConfigError;
@@ -17,6 +19,7 @@
import com.android.tools.r8.dump.CompilerDump;
import com.android.tools.r8.dump.DumpOptions;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@@ -146,6 +149,15 @@
return CompilerDump.fromArchive(dump, environment.getTemp().newFolder().toPath());
}
+ private static void addDesugaredLibrary(
+ TestCompilerBuilder<?, ?, ?, ?, ?> builder, CompilerDump dump) {
+ Path config = dump.getDesugaredLibraryFile();
+ if (Files.exists(config)) {
+ builder.enableCoreLibraryDesugaring(
+ LibraryDesugaringTestConfiguration.forSpecification(config));
+ }
+ }
+
private static BenchmarkMethod runR8(AppDumpBenchmarkBuilder builder) {
return environment ->
BenchmarkBase.runner(environment.getConfig())
@@ -159,6 +171,7 @@
.addLibraryFiles(dump.getLibraryArchive())
.addKeepRuleFiles(dump.getProguardConfigFile())
.setMinApi(dumpProperties.getMinApi())
+ .apply(b -> addDesugaredLibrary(b, dump))
.allowUnnecessaryDontWarnWildcards()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
@@ -184,6 +197,7 @@
.addProgramFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.setMinApi(dumpProperties.getMinApi())
+ .apply(b -> addDesugaredLibrary(b, dump))
.benchmarkCompile(results)
.benchmarkCodeSize(results);
});
@@ -219,6 +233,7 @@
.addClasspathFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.setMinApi(dumpProperties.getMinApi())
+ .apply(b -> addDesugaredLibrary(b, dump))
.setIntermediate(true)
.benchmarkCompile(results.getSubResults(builder.nameForProgramPart()))
.writeToZip());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
index 30b4939..f3ab716 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
@@ -4,6 +4,14 @@
package com.android.tools.r8.classmerging.horizontal;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.jar;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.nestClassMergingTest;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.nestHostA;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.nestHostA$NestMemberA;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.nestHostA$NestMemberB;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.nestHostB;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.nestHostB$NestMemberA;
+import static com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.HorizontalClassMergingTestSources.nestHostB$NestMemberB;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
@@ -12,17 +20,13 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.R.horizontalclassmerging.NestClassMergingTest;
-import com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.R.horizontalclassmerging.NestHostA;
-import com.android.tools.r8.classmerging.horizontal.NestClassMergingTestRunner.R.horizontalclassmerging.NestHostB;
+import com.android.tools.r8.examples.JavaExampleClassProxy;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesClass;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesJava11RootPackage;
-import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesPackage;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
+import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
@@ -30,21 +34,27 @@
public class NestClassMergingTestRunner extends HorizontalClassMergingTestBase {
- public static class R extends ExamplesJava11RootPackage {
- public static class horizontalclassmerging extends ExamplesPackage {
- public static class NestClassMergingTest extends ExamplesClass {}
+ public static class HorizontalClassMergingTestSources {
- public static class NestHostA extends ExamplesClass {
- public static class NestMemberA extends ExamplesClass {}
+ private static final String EXAMPLE_FILE = "examplesJava11";
- public static class NestMemberB extends ExamplesClass {}
- }
+ public static final JavaExampleClassProxy nestClassMergingTest =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "horizontalclassmerging/NestClassMergingTest");
+ public static final JavaExampleClassProxy nestHostA =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "horizontalclassmerging/NestHostA");
+ public static final JavaExampleClassProxy nestHostA$NestMemberA =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "horizontalclassmerging/NestHostA$NestMemberA");
+ public static final JavaExampleClassProxy nestHostA$NestMemberB =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "horizontalclassmerging/NestHostA$NestMemberB");
+ public static final JavaExampleClassProxy nestHostB =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "horizontalclassmerging/NestHostB");
+ public static final JavaExampleClassProxy nestHostB$NestMemberA =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "horizontalclassmerging/NestHostB$NestMemberA");
+ public static final JavaExampleClassProxy nestHostB$NestMemberB =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "horizontalclassmerging/NestHostB$NestMemberB");
- public static class NestHostB extends ExamplesClass {
- public static class NestMemberA extends ExamplesClass {}
-
- public static class NestMemberB extends ExamplesClass {}
- }
+ public static Path jar() {
+ return JavaExampleClassProxy.examplesJar(EXAMPLE_FILE + "/horizontalclassmerging");
}
}
@@ -70,21 +80,21 @@
if (parameters.canUseNestBasedAccesses()) {
inspector
.assertIsCompleteMergeGroup(
- classRef(NestHostA.class),
- classRef(NestHostA.NestMemberA.class),
- classRef(NestHostA.NestMemberB.class))
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberA.getClassReference(),
+ nestHostA$NestMemberB.getClassReference())
.assertIsCompleteMergeGroup(
- classRef(NestHostB.class),
- classRef(NestHostB.NestMemberA.class),
- classRef(NestHostB.NestMemberB.class));
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberA.getClassReference(),
+ nestHostB$NestMemberB.getClassReference());
} else {
inspector.assertIsCompleteMergeGroup(
- classRef(NestHostA.class),
- classRef(NestHostA.NestMemberA.class),
- classRef(NestHostA.NestMemberB.class),
- classRef(NestHostB.class),
- classRef(NestHostB.NestMemberA.class),
- classRef(NestHostB.NestMemberB.class));
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberA.getClassReference(),
+ nestHostA$NestMemberB.getClassReference(),
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberA.getClassReference(),
+ nestHostB$NestMemberB.getClassReference());
}
}));
}
@@ -99,15 +109,17 @@
inspector ->
inspector
.assertIsCompleteMergeGroup(
- classRef(NestHostA.class), classRef(NestHostA.NestMemberA.class))
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberA.getClassReference())
.assertIsCompleteMergeGroup(
- classRef(NestHostB.class), classRef(NestHostB.NestMemberA.class))
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberA.getClassReference())
.assertClassReferencesNotMerged(
- classRef(NestHostA.NestMemberB.class),
- classRef(NestHostB.NestMemberB.class)))
+ nestHostA$NestMemberB.getClassReference(),
+ nestHostB$NestMemberB.getClassReference()))
.addNoHorizontalClassMergingRule(
- examplesTypeName(NestHostA.NestMemberB.class),
- examplesTypeName(NestHostB.NestMemberB.class))
+ nestHostA$NestMemberB.getClassReference().getTypeName(),
+ nestHostB$NestMemberB.getClassReference().getTypeName())
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
@@ -116,17 +128,17 @@
Streams.stream(canditates)
.map(DexClass::getClassReference)
.collect(Collectors.toSet());
- if (candidateClassReferences.contains(classRef(NestHostA.class))) {
+ if (candidateClassReferences.contains(nestHostA.getClassReference())) {
assertEquals(
ImmutableSet.of(
- classRef(NestHostA.class),
- classRef(NestHostA.NestMemberA.class)),
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberA.getClassReference()),
candidateClassReferences);
} else {
assertEquals(
ImmutableSet.of(
- classRef(NestHostB.class),
- classRef(NestHostB.NestMemberA.class)),
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberA.getClassReference()),
candidateClassReferences);
}
return Iterables.find(
@@ -134,9 +146,9 @@
candidate -> {
ClassReference classReference = candidate.getClassReference();
return classReference.equals(
- classRef(NestHostA.NestMemberA.class))
+ nestHostA$NestMemberA.getClassReference())
|| classReference.equals(
- classRef(NestHostB.NestMemberA.class));
+ nestHostB$NestMemberA.getClassReference());
});
};
}));
@@ -152,15 +164,17 @@
inspector ->
inspector
.assertIsCompleteMergeGroup(
- classRef(NestHostA.class), classRef(NestHostA.NestMemberB.class))
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberB.getClassReference())
.assertIsCompleteMergeGroup(
- classRef(NestHostB.class), classRef(NestHostB.NestMemberB.class))
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberB.getClassReference())
.assertClassReferencesNotMerged(
- classRef(NestHostA.NestMemberA.class),
- classRef(NestHostB.NestMemberA.class)))
+ nestHostA$NestMemberA.getClassReference(),
+ nestHostB$NestMemberA.getClassReference()))
.addNoHorizontalClassMergingRule(
- examplesTypeName(NestHostA.NestMemberA.class),
- examplesTypeName(NestHostB.NestMemberA.class))
+ nestHostA$NestMemberA.getClassReference().getTypeName(),
+ nestHostB$NestMemberA.getClassReference().getTypeName())
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
@@ -169,17 +183,17 @@
Streams.stream(canditates)
.map(DexClass::getClassReference)
.collect(Collectors.toSet());
- if (candidateClassReferences.contains(classRef(NestHostA.class))) {
+ if (candidateClassReferences.contains(nestHostA.getClassReference())) {
assertEquals(
ImmutableSet.of(
- classRef(NestHostA.class),
- classRef(NestHostA.NestMemberB.class)),
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberB.getClassReference()),
candidateClassReferences);
} else {
assertEquals(
ImmutableSet.of(
- classRef(NestHostB.class),
- classRef(NestHostB.NestMemberB.class)),
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberB.getClassReference()),
candidateClassReferences);
}
return Iterables.find(
@@ -187,9 +201,9 @@
candidate -> {
ClassReference classReference = candidate.getClassReference();
return classReference.equals(
- classRef(NestHostA.NestMemberB.class))
+ nestHostA$NestMemberB.getClassReference())
|| classReference.equals(
- classRef(NestHostB.NestMemberB.class));
+ nestHostB$NestMemberB.getClassReference());
});
};
}));
@@ -205,15 +219,17 @@
inspector ->
inspector
.assertIsCompleteMergeGroup(
- classRef(NestHostA.class), classRef(NestHostA.NestMemberA.class))
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberA.getClassReference())
.assertIsCompleteMergeGroup(
- classRef(NestHostB.class), classRef(NestHostB.NestMemberA.class))
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberA.getClassReference())
.assertClassReferencesNotMerged(
- classRef(NestHostA.NestMemberB.class),
- classRef(NestHostB.NestMemberB.class)))
+ nestHostA$NestMemberB.getClassReference(),
+ nestHostB$NestMemberB.getClassReference()))
.addNoHorizontalClassMergingRule(
- examplesTypeName(NestHostA.NestMemberB.class),
- examplesTypeName(NestHostB.NestMemberB.class))
+ nestHostA$NestMemberB.getClassReference().getTypeName(),
+ nestHostB$NestMemberB.getClassReference().getTypeName())
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
@@ -222,25 +238,25 @@
Streams.stream(canditates)
.map(DexClass::getClassReference)
.collect(Collectors.toSet());
- if (candidateClassReferences.contains(classRef(NestHostA.class))) {
+ if (candidateClassReferences.contains(nestHostA.getClassReference())) {
assertEquals(
ImmutableSet.of(
- classRef(NestHostA.class),
- classRef(NestHostA.NestMemberA.class)),
+ nestHostA.getClassReference(),
+ nestHostA$NestMemberA.getClassReference()),
candidateClassReferences);
} else {
assertEquals(
ImmutableSet.of(
- classRef(NestHostB.class),
- classRef(NestHostB.NestMemberA.class)),
+ nestHostB.getClassReference(),
+ nestHostB$NestMemberA.getClassReference()),
candidateClassReferences);
}
return Iterables.find(
canditates,
candidate -> {
ClassReference classReference = candidate.getClassReference();
- return classReference.equals(classRef(NestHostA.class))
- || classReference.equals(classRef(NestHostB.class));
+ return classReference.equals(nestHostA.getClassReference())
+ || classReference.equals(nestHostB.getClassReference());
});
};
}));
@@ -248,22 +264,18 @@
private void runTest(ThrowableConsumer<R8FullTestBuilder> configuration) throws Exception {
testForR8(parameters.getBackend())
- .addKeepMainRule(examplesTypeName(NestClassMergingTest.class))
- .addExamplesProgramFiles(R.class)
+ .addKeepMainRule(nestClassMergingTest.getClassReference())
+ .addProgramFiles(jar())
.apply(configuration)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters)
.compile()
- .run(parameters.getRuntime(), examplesTypeName(NestClassMergingTest.class))
+ .run(parameters.getRuntime(), nestClassMergingTest.getClassReference().getTypeName())
.assertSuccessWithOutputLines(
"NestHostA$NestMemberA",
"NestHostA$NestMemberB",
"NestHostB$NestMemberA",
"NestHostB$NestMemberB");
}
-
- private static ClassReference classRef(Class<? extends ExamplesClass> clazz) {
- return examplesClassReference(clazz);
- }
}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
index 60ca0c6..a6dac7d 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
@@ -93,6 +93,11 @@
.resolve(Paths.get("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar"));
}
+ public Path getAndroidJar() {
+ return getProjectRoot()
+ .resolve(Paths.get("third_party", "android_jar", "lib-v33", "android.jar"));
+ }
+
public List<String> getKeepMainRules(Class<?> clazz) {
return Collections.singletonList(
"-keep class " + clazz.getName() + " { public static void main(java.lang.String[]); }");
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 9535f30..d2096f6 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
import com.android.tools.r8.compilerapi.extractmarker.ExtractMarkerApiTest;
import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
+import com.android.tools.r8.compilerapi.globalsyntheticsgenerator.GlobalSyntheticsGeneratorTest;
import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
import com.android.tools.r8.compilerapi.mockdata.MockClass;
@@ -63,7 +64,8 @@
SyntheticContextsConsumerTest.ApiTest.class,
ExtractMarkerApiTest.ApiTest.class,
PartitionMapCommandTest.ApiTest.class,
- CancelCompilationCheckerTest.ApiTest.class);
+ CancelCompilationCheckerTest.ApiTest.class,
+ GlobalSyntheticsGeneratorTest.ApiTest.class);
private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
ImmutableList.of();
diff --git a/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
new file mode 100644
index 0000000..6cab4d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.globalsyntheticsgenerator;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.GlobalSyntheticsGenerator;
+import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import org.junit.Test;
+
+public class GlobalSyntheticsGeneratorTest extends CompilerApiTestRunner {
+
+ public GlobalSyntheticsGeneratorTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void testGlobalSynthetics() throws Exception {
+ new ApiTest(ApiTest.PARAMETERS)
+ .run(
+ new DexIndexedConsumer.ArchiveConsumer(
+ temp.newFolder().toPath().resolve("output.zip")));
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void run(ProgramConsumer programConsumer) throws Exception {
+ GlobalSyntheticsGenerator.run(
+ GlobalSyntheticsGeneratorCommand.builder()
+ .addLibraryFiles(getAndroidJar())
+ .setMinApiLevel(33)
+ .setProgramConsumer(programConsumer)
+ .build());
+ }
+
+ @Test
+ public void testGlobalSynthetics() throws Exception {
+ run(DexIndexedConsumer.emptyConsumer());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index fb80241..dfa8cd5 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -42,7 +42,18 @@
private static final byte[][] PROGRAM_DATA_2 = RecordTestUtils.getProgramData(RECORD_NAME_2);
private static final String MAIN_TYPE_2 = RecordTestUtils.getMainType(RECORD_NAME_2);
private static final String EXPECTED_RESULT_2 =
- StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
+ StringUtils.lines(
+ "Jane Doe",
+ "42",
+ "Jane Doe",
+ "42",
+ "true",
+ "true",
+ "true",
+ "false",
+ "false",
+ "false",
+ "false");
private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index 7dbeae0..e208949 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -29,7 +29,18 @@
private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT =
- StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
+ StringUtils.lines(
+ "Jane Doe",
+ "42",
+ "Jane Doe",
+ "42",
+ "true",
+ "true",
+ "true",
+ "false",
+ "false",
+ "false",
+ "false");
@Parameter(0)
public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
index 07d4ef7..e00e194 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.sealed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static junit.framework.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assume.assumeTrue;
@@ -18,7 +19,7 @@
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.Matchers;
+import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,14 +71,15 @@
}
private void inspect(CodeInspector inspector) {
- ClassSubject clazz = inspector.clazz("enum_sealed.Enum");
- assertThat(clazz, Matchers.isPresentAndRenamed());
- if (!parameters.isCfRuntime()) {
- return;
- }
+ ClassSubject clazz = inspector.clazz(EnumSealed.Enum.typeName());
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(EnumSealed.EnumB.typeName());
+ assertThat(sub1, isPresentAndRenamed());
assertEquals(
- keepPermittedSubclassesAttribute ? 1 : 0,
- clazz.getFinalPermittedSubclassAttributes().size());
+ parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+ ? ImmutableList.of(sub1.asTypeSubject())
+ : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
}
@Test
@@ -90,6 +92,7 @@
keepPermittedSubclassesAttribute,
TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
.addKeepMainRule(EnumSealed.Main.typeName())
+ .addKeepClassRulesWithAllowObfuscation(EnumSealed.Enum.typeName())
.compile()
.inspect(this::inspect)
.run(parameters.getRuntime(), EnumSealed.Main.typeName())
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsVerticalMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsVerticalMergeTest.java
new file mode 100644
index 0000000..fdd28aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsVerticalMergeTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.google.common.collect.ImmutableList;
+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 SealedClassesExtendsVerticalMergeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ static final String EXPECTED = StringUtils.lines("Success!");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class, Sub1.class, Sub2.class, SubSub.class)
+ .addProgramClassFileData(getTransformedClasses());
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub2 = inspector.clazz(Sub2.class);
+ assertThat(sub2, isPresentAndRenamed());
+ ClassSubject subSub = inspector.clazz(SubSub.class);
+ assertThat(subSub, isPresentAndRenamed());
+ // TODO(b/227160052): Should be both subSub.asTypeSubject() and sub2.asTypeSubject().
+ assertEquals(
+ parameters.isCfRuntime() ? ImmutableList.of(sub2.asTypeSubject()) : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .apply(this::addTestClasses)
+ .setMinApi(parameters)
+ .addKeepAttributePermittedSubclasses()
+ .addKeepPermittedSubclasses(Super.class)
+ .addKeepPermittedSubclasses(Sub2.class)
+ .addKeepMainRule(TestClass.class)
+ .addVerticallyMergedClassesInspector(
+ inspector -> {
+ inspector.assertMergedIntoSubtype(Sub1.class);
+ })
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+ r ->
+ r.assertFailureWithErrorThatMatches(
+ containsString("cannot inherit from sealed class")),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new SubSub();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits Sub1, Sub2 */ {}
+
+ static class Sub1 extends Super {}
+
+ static class Sub2 extends Super {}
+
+ static class SubSub extends Sub1 {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesHorizontalMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesHorizontalMergeTest.java
new file mode 100644
index 0000000..270637d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesHorizontalMergeTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 SealedClassesHorizontalMergeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ static final String EXPECTED = StringUtils.lines("Success!");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class, Sub1.class, Sub2.class)
+ .addProgramClassFileData(getTransformedClasses());
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(Sub1.class);
+ assertThat(sub1, isPresentAndRenamed());
+ assertEquals(
+ parameters.isCfRuntime() ? ImmutableList.of(sub1.asTypeSubject()) : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .apply(this::addTestClasses)
+ .setMinApi(parameters)
+ .addKeepAttributePermittedSubclasses()
+ .addKeepClassRulesWithAllowObfuscation(Super.class)
+ .addKeepMainRule(TestClass.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> {
+ inspector
+ .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
+ .assertNoOtherClassesMerged();
+ })
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ !parameters.isCfRuntime() || parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new Sub1();
+ new Sub2();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits Sub1, Sub2 */ {}
+
+ static class Sub1 extends Super {}
+
+ static class Sub2 extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
new file mode 100644
index 0000000..b420268
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import org.hamcrest.Matcher;
+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 SealedClassesIllegalSubclassMergedTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ static final Matcher<String> EXPECTED = containsString("cannot inherit from sealed class");
+ static final String EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE_OR_FIXED_ATTRIBUTE =
+ StringUtils.lines("Success!");
+
+ @Parameters(name = "{0}, keepPermittedSubclasses = {1}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class, Sub1.class, Sub2.class)
+ .addProgramClassFileData(getTransformedClasses());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17));
+ testForJvm(parameters)
+ .apply(this::addTestClasses)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatMatches(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(Sub1.class);
+ assertThat(sub1, isPresentAndRenamed());
+ assertEquals(
+ parameters.isCfRuntime() ? ImmutableList.of(sub1.asTypeSubject()) : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .apply(this::addTestClasses)
+ .setMinApi(parameters)
+ .addKeepAttributePermittedSubclasses()
+ .addKeepClassRulesWithAllowObfuscation(Super.class)
+ .addKeepMainRule(TestClass.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> {
+ inspector
+ .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
+ .assertNoOtherClassesMerged();
+ })
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ !parameters.isCfRuntime() || parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+ // On JDK 17 the class merging also prevents "cannot inherit from sealed class".
+ r ->
+ r.assertSuccessWithOutput(
+ EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE_OR_FIXED_ATTRIBUTE),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class).setPermittedSubclasses(Super.class, Sub1.class).transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new Sub1();
+ new Sub2();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits Sub1 */ {}
+
+ static class Sub1 extends Super {}
+
+ static class Sub2 extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
index 03d9160..240b7a9 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.desugar.sealed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static junit.framework.Assert.assertEquals;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -20,7 +22,7 @@
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.Matchers;
+import com.google.common.collect.ImmutableList;
import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Test;
@@ -81,14 +83,19 @@
}
private void inspect(CodeInspector inspector) {
- ClassSubject clazz = inspector.clazz(C.class);
- assertThat(clazz, Matchers.isPresentAndRenamed());
- if (!parameters.isCfRuntime()) {
- return;
- }
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(Sub1.class);
+ ClassSubject sub2 = inspector.clazz(Sub2.class);
+ ClassSubject sub3 = inspector.clazz(Sub3.class);
+ assertThat(sub1, isPresentAndNotRenamed());
+ assertThat(sub2, isPresentAndNotRenamed());
+ assertThat(sub3, isPresentAndNotRenamed());
assertEquals(
- keepPermittedSubclassesAttribute ? 2 : 0,
- clazz.getFinalPermittedSubclassAttributes().size());
+ parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+ ? ImmutableList.of(sub1.asTypeSubject(), sub2.asTypeSubject())
+ : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
}
@Test
@@ -101,10 +108,8 @@
.applyIf(
keepPermittedSubclassesAttribute,
TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
- // Keep the sealed class to ensure the PermittedSubclasses attribute stays live.
- .addKeepPermittedSubclasses(C.class)
- // Keep subclasses as the PermittedSubclasses attribute is not rewritten.
- .addKeepRules("-keep class * extends " + C.class.getTypeName())
+ .addKeepPermittedSubclasses(Super.class)
+ .addKeepRules("-keep class * extends " + Super.class.getTypeName())
.addKeepMainRule(TestClass.class)
.compile()
.inspect(this::inspect)
@@ -122,7 +127,9 @@
}
public byte[] getTransformedClasses() throws Exception {
- return transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform();
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+ .transform();
}
static class TestClass {
@@ -135,11 +142,11 @@
}
}
- abstract static class C /* permits Sub1, Sub2 */ {}
+ abstract static class Super /* permits Sub1, Sub2 */ {}
- static class Sub1 extends C {}
+ static class Sub1 extends Super {}
- static class Sub2 extends C {}
+ static class Sub2 extends Super {}
- static class Sub3 extends C {}
+ static class Sub3 extends Super {}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsVerticalMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsVerticalMergeTest.java
new file mode 100644
index 0000000..43f4a41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsVerticalMergeTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SealedClassesImplementsVerticalMergeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ static final String EXPECTED = StringUtils.lines("Success!");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class, Super.class, Sub1.class, Sub2.class, SubSub.class)
+ .addProgramClassFileData(getTransformedClasses());
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject iface1 = inspector.clazz(Iface1.class);
+ assertThat(iface1, isPresentAndRenamed());
+ ClassSubject iface2 = inspector.clazz(Iface2.class);
+ assertThat(iface2, isPresentAndRenamed());
+ ClassSubject sub2 = inspector.clazz(Sub2.class);
+ assertThat(sub2, isPresentAndRenamed());
+ ClassSubject subSub = inspector.clazz(SubSub.class);
+ assertThat(subSub, Matchers.isPresentAndRenamed());
+ for (ClassSubject clazz : ImmutableList.of(iface1, iface2)) {
+ assertEquals(
+ // TODO(b/227160052): Should be both subSub.asTypeSubject() and sub2.asTypeSubject().
+ parameters.isCfRuntime() ? ImmutableList.of(sub2.asTypeSubject()) : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .apply(this::addTestClasses)
+ .setMinApi(parameters)
+ .addKeepAttributePermittedSubclasses()
+ .addKeepPermittedSubclasses(Super.class, Iface1.class, Iface2.class, Sub2.class)
+ .addKeepMainRule(TestClass.class)
+ .addVerticallyMergedClassesInspector(
+ inspector -> {
+ inspector.assertMergedIntoSubtype(Sub1.class);
+ })
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+ r ->
+ r.assertFailureWithErrorThatMatches(
+ containsString("cannot implement sealed interface")),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ public List<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ transformer(Iface1.class)
+ .setPermittedSubclasses(Iface1.class, Sub1.class, Sub2.class)
+ .transform(),
+ transformer(Iface2.class)
+ .setPermittedSubclasses(Iface2.class, Sub1.class, Sub2.class)
+ .transform());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new SubSub();
+ System.out.println("Success!");
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ interface Iface1 /* permits Sub1, Sub2 */ {}
+
+ @NoUnusedInterfaceRemoval
+ interface Iface2 /* permits Sub1, Sub2 */ {}
+
+ abstract static class Super {}
+
+ static class Sub1 extends Super implements Iface1, Iface2 {}
+
+ static class Sub2 extends Super implements Iface1 {}
+
+ static class SubSub extends Sub1 {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
index 2b7d981..c51bb03 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.sealed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static junit.framework.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assume.assumeTrue;
@@ -18,7 +19,7 @@
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.Matchers;
+import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,14 +71,17 @@
}
private void inspect(CodeInspector inspector) {
- ClassSubject clazz = inspector.clazz("sealed.Compiler");
- assertThat(clazz, Matchers.isPresentAndRenamed());
- if (!parameters.isCfRuntime()) {
- return;
- }
+ ClassSubject clazz = inspector.clazz(Sealed.Compiler.typeName());
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(Sealed.R8Compiler.typeName());
+ ClassSubject sub2 = inspector.clazz(Sealed.D8Compiler.typeName());
+ assertThat(sub1, isPresentAndRenamed());
+ assertThat(sub2, isPresentAndRenamed());
assertEquals(
- keepPermittedSubclassesAttribute ? 2 : 0,
- clazz.getFinalPermittedSubclassAttributes().size());
+ parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+ ? ImmutableList.of(sub1.asTypeSubject(), sub2.asTypeSubject())
+ : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
}
@Test
@@ -89,8 +93,8 @@
.applyIf(
keepPermittedSubclassesAttribute,
TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
- // Keep the sealed class to ensure the PermittedSubclasses attribute stays live.
.addKeepPermittedSubclasses(Sealed.Compiler.typeName())
+ .addKeepRules("-keep,allowobfuscation class * extends " + Sealed.Compiler.typeName())
.addKeepMainRule(Sealed.Main.typeName())
.compile()
.inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java
new file mode 100644
index 0000000..bcd774a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 SealedClassesMergeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ static final String EXPECTED = StringUtils.lines("Success!");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class, Sub1.class, Sub2.class)
+ .addProgramClassFileData(getTransformedClasses());
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(Sub1.class);
+ assertThat(sub1, isPresentAndRenamed());
+ assertEquals(
+ parameters.isCfRuntime() ? ImmutableList.of(sub1.asTypeSubject()) : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .apply(this::addTestClasses)
+ .setMinApi(parameters)
+ .addKeepAttributePermittedSubclasses()
+ .addKeepClassRulesWithAllowObfuscation(Super.class)
+ .addKeepMainRule(TestClass.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> {
+ inspector
+ .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
+ .assertNoOtherClassesMerged();
+ })
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ !parameters.isCfRuntime() || parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new Sub1();
+ new Sub2();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits Sub1, Sub2 */ {}
+
+ static class Sub1 extends Super {}
+
+ static class Sub2 extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java
new file mode 100644
index 0000000..1893e3b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 SealedClassesShrinkingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ static final String EXPECTED = StringUtils.lines("Success!");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class, UsedSub.class, UnusedSub.class)
+ .addProgramClassFileData(getTransformedClasses());
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(UsedSub.class);
+ assertThat(sub1, isPresentAndRenamed());
+ assertEquals(
+ parameters.isCfRuntime() ? ImmutableList.of(sub1.asTypeSubject()) : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .apply(this::addTestClasses)
+ .setMinApi(parameters)
+ .addKeepAttributePermittedSubclasses()
+ .addKeepClassRulesWithAllowObfuscation(Super.class, UsedSub.class)
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ !parameters.isCfRuntime() || parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, UsedSub.class, UnusedSub.class)
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new UsedSub();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits UsedSub, UnusedSub */ {}
+
+ static class UsedSub extends Super {}
+
+ static class UnusedSub extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
index 3aa1a38..997e4fb 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.desugar.sealed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assume.assumeTrue;
@@ -18,7 +20,7 @@
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.Matchers;
+import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,12 +37,16 @@
@Parameter(1)
public boolean keepPermittedSubclassesAttribute;
+ @Parameter(2)
+ public boolean repackage;
+
static final String EXPECTED = StringUtils.lines("Success!");
- @Parameters(name = "{0}, keepPermittedSubclasses = {1}")
+ @Parameters(name = "{0}, keepPermittedSubclasses = {1}, repackage = {2}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values(),
BooleanUtils.values());
}
@@ -76,14 +82,22 @@
}
private void inspect(CodeInspector inspector) {
- ClassSubject clazz = inspector.clazz(C.class);
- assertThat(clazz, Matchers.isPresentAndRenamed());
- if (!parameters.isCfRuntime()) {
- return;
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, isPresentAndRenamed());
+ ClassSubject sub1 = inspector.clazz(Sub1.class);
+ ClassSubject sub2 = inspector.clazz(Sub2.class);
+ assertThat(sub1, isPresentAndRenamed());
+ assertThat(sub2, isPresentAndRenamed());
+ if (repackage) {
+ assertEquals(-1, sub1.getFinalName().indexOf('.'));
+ } else {
+ assertTrue(sub1.getFinalName().startsWith(getClass().getPackage().getName()));
}
assertEquals(
- keepPermittedSubclassesAttribute ? 2 : 0,
- clazz.getFinalPermittedSubclassAttributes().size());
+ parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+ ? ImmutableList.of(sub1.asTypeSubject(), sub2.asTypeSubject())
+ : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
}
@Test
@@ -96,8 +110,9 @@
keepPermittedSubclassesAttribute,
TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
// Keep the sealed class to ensure the PermittedSubclasses attribute stays live.
- .addKeepPermittedSubclasses(C.class)
+ .addKeepPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
.addKeepMainRule(TestClass.class)
+ .applyIf(repackage, b -> b.addKeepRules("-repackageclasses"))
.compile()
.inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
@@ -108,7 +123,9 @@
}
public byte[] getTransformedClasses() throws Exception {
- return transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform();
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+ .transform();
}
static class TestClass {
@@ -120,9 +137,9 @@
}
}
- abstract static class C /* permits Sub1, Sub2 */ {}
+ public abstract static class Super /* permits Sub1, Sub2 */ {}
- static class Sub1 extends C {}
+ public static class Sub1 extends Super {}
- static class Sub2 extends C {}
+ public static class Sub2 extends Super {}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTestAllowPermittedSubclassesRemovalTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTestAllowPermittedSubclassesRemovalTest.java
new file mode 100644
index 0000000..c220e9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTestAllowPermittedSubclassesRemovalTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+import com.google.common.collect.ImmutableList;
+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 SealedClassesTestAllowPermittedSubclassesRemovalTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ static final String EXPECTED = StringUtils.lines("Success!");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+ builder
+ .addProgramClasses(TestClass.class, Sub1.class, Sub2.class)
+ .addProgramClassFileData(getTransformedClasses());
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(Super.class);
+ assertThat(clazz, Matchers.isPresentAndNotRenamed());
+ ClassSubject sub1 = inspector.clazz(Sub1.class);
+ ClassSubject sub2 = inspector.clazz(Sub2.class);
+ assertThat(sub1, isPresentAndRenamed());
+ assertThat(sub2, isPresentAndRenamed());
+ assertEquals(
+ parameters.isCfRuntime()
+ ? ImmutableList.of(sub1.asTypeSubject(), sub2.asTypeSubject())
+ : ImmutableList.of(),
+ clazz.getFinalPermittedSubclassAttributes());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .apply(this::addTestClasses)
+ .setMinApi(parameters)
+ .addKeepAttributePermittedSubclasses()
+ .addKeepRules("-keep,allowpermittedsubclassesremoval class " + Super.class.getTypeName())
+ .addKeepClassRulesWithAllowObfuscation(Sub1.class, Sub2.class)
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ !parameters.isCfRuntime() || parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new Sub1();
+ new Sub2();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits Sub1, Sub2 */ {}
+
+ static class Sub1 extends Super {}
+
+ static class Sub2 extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNull2ArgumentTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNull2ArgumentTest.java
new file mode 100644
index 0000000..1130986
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxNull2ArgumentTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.Objects;
+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;
+
+/** This is a regression test for b/287193321. */
+@RunWith(Parameterized.class)
+public class EnumUnboxNull2ArgumentTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(options -> options.testing.disableLir())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("true", "null");
+ }
+
+ public enum MyEnum {
+ FOO("1"),
+ BAR("2");
+
+ final String value;
+
+ MyEnum(String value) {
+ this.value = value;
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ // Delay observing that arguments to bar is null until we've inlined foo() and getEnum().
+ String foo = foo();
+ String[] bar = bar(getEnum(), foo);
+ // To ensure bar(MyEnum,String) is not inlined in the first round we add a few additional
+ // calls that will be stripped during IR-processing of main.
+ if (foo != null) {
+ bar(MyEnum.FOO, foo);
+ bar(MyEnum.BAR, foo);
+ }
+ for (String b : bar) {
+ System.out.println(b);
+ }
+ }
+
+ public static String[] bar(MyEnum myEnum, String foo) {
+ if (System.currentTimeMillis() > 1) {
+ MyEnum e = System.currentTimeMillis() > 1 ? null : MyEnum.FOO;
+ // Ensure that the construction is in a separate block than entry() to have constant
+ // canonicalization align the two null values into one.
+ return new String[] {Objects.toString(Objects.equals(myEnum, e)), foo};
+ }
+ return new String[] {};
+ }
+
+ public static MyEnum getEnum() {
+ return null;
+ }
+
+ public static String foo() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/CrossEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/CrossEnumMergingTest.java
new file mode 100644
index 0000000..5c6518b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/CrossEnumMergingTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.enumunboxing.enummerging;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import java.util.List;
+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 CrossEnumMergingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public CrossEnumMergingTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(CrossEnumMergingTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addEnumUnboxingInspector(
+ inspector -> inspector.assertUnboxed(TestEnumReturn.class, TestEnumArg.class))
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("TEST2", "TEST1", "1", "2", "2", "3");
+ }
+
+ public enum TestEnumReturn {
+ TEST1 {
+ @Override
+ public TestEnumReturn other() {
+ return TEST2;
+ }
+ },
+ TEST2 {
+ @Override
+ public TestEnumReturn other() {
+ return TEST1;
+ }
+ };
+
+ public abstract TestEnumReturn other();
+ }
+
+ public enum TestEnumArg {
+ TEST1 {
+ @Override
+ public void print(TestEnumArg arg) {
+ System.out.println(arg.ordinal() + 1);
+ }
+ },
+ TEST2 {
+ @Override
+ public void print(TestEnumArg arg) {
+ System.out.println(arg.ordinal() + 2);
+ }
+ };
+
+ public abstract void print(TestEnumArg arg);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(TestEnumReturn.TEST1.other().toString());
+ System.out.println(TestEnumReturn.TEST2.other().toString());
+
+ TestEnumArg.TEST1.print(TestEnumArg.TEST1);
+ TestEnumArg.TEST1.print(TestEnumArg.TEST2);
+ TestEnumArg.TEST2.print(TestEnumArg.TEST1);
+ TestEnumArg.TEST2.print(TestEnumArg.TEST2);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 0ff1942..e74dd87 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.kotlin;
-
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
@@ -131,6 +131,16 @@
.addKeepRules(keepClassMethod(mainClassName, testMethodSignature))
.addOptionsModification(o -> o.testing.enableLir())
.addOptionsModification(disableClassInliner))
- .inspect(inspector -> checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName()));
+ .inspect(
+ inspector -> {
+ // This changes depending on when we dead-code eliminate.
+ if (kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_5_0)
+ || kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_6_0)
+ || testParameters.isDexRuntime()) {
+ checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
+ } else {
+ checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
+ }
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromPermittedSubclassesAttributeTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromPermittedSubclassesAttributeTest.java
new file mode 100644
index 0000000..af6954b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromPermittedSubclassesAttributeTest.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.diagnostic.DefinitionContext;
+import com.android.tools.r8.diagnostic.internal.DefinitionClassContextImpl;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class MissingClassReferencedFromPermittedSubclassesAttributeTest
+ extends MissingClassesTestBase {
+
+ private static final DefinitionContext referencedFrom =
+ DefinitionClassContextImpl.builder()
+ .setClassContext(Reference.classFromClass(Super.class))
+ .setOrigin(getOrigin(Super.class))
+ .build();
+
+ @Parameters(name = "{1}, report: {0}")
+ public static List<Object[]> refinedData() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ private final boolean reportMissingClassesInPermittedSubclassesAttributes;
+
+ public MissingClassReferencedFromPermittedSubclassesAttributeTest(
+ boolean reportMissingClassesInPermittedSubclassesAttributes, TestParameters parameters) {
+ super(parameters);
+ this.reportMissingClassesInPermittedSubclassesAttributes =
+ reportMissingClassesInPermittedSubclassesAttributes;
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Missing classes stays in the PermittedSubclasses attribute.
+ assertEquals(
+ parameters.isCfRuntime()
+ ? ImmutableList.of(
+ inspector.clazz(Sub.class).asTypeSubject(),
+ inspector.getTypeSubject(MissingSub.class.getTypeName()))
+ : ImmutableList.of(),
+ inspector.clazz(Super.class).getFinalPermittedSubclassAttributes());
+ }
+
+ @Test()
+ public void testNoRules() throws Exception {
+ assertFailsCompilationIf(
+ reportMissingClassesInPermittedSubclassesAttributes,
+ () ->
+ compileWithExpectedDiagnostics(
+ Main.class,
+ reportMissingClassesInPermittedSubclassesAttributes
+ ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ this::configure));
+ }
+
+ @Test
+ public void testDontWarnSuperClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addDontWarn(Super.class).andThen(this::configure))
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testDontWarnMissingClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addDontWarn(MissingSub.class).andThen(this::configure))
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testIgnoreWarnings() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ reportMissingClassesInPermittedSubclassesAttributes
+ ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ addIgnoreWarnings(reportMissingClassesInPermittedSubclassesAttributes)
+ .andThen(this::configure))
+ .inspect(this::inspect);
+ }
+
+ void configure(R8FullTestBuilder builder) {
+ try {
+ builder
+ .addKeepAttributePermittedSubclasses()
+ .addProgramClasses(Sub.class)
+ .addProgramClassFileData(getTransformedClasses())
+ .addKeepClassRulesWithAllowObfuscation(Super.class, Sub.class)
+ .addOptionsModification(
+ options -> {
+ // We do not report missing classes from permitted subclasses attributes by default.
+ assertFalse(options.reportMissingClassesInPermittedSubclassesAttributes);
+ options.reportMissingClassesInPermittedSubclassesAttributes =
+ reportMissingClassesInPermittedSubclassesAttributes;
+ })
+ .applyIf(
+ !reportMissingClassesInPermittedSubclassesAttributes,
+ // The -dontwarn Main and -dontwarn MissingClass tests will have unused -dontwarn
+ // rules.
+ R8TestBuilder::allowUnusedDontWarnPatterns);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ ClassReference getMissingClassReference() {
+ return Reference.classFromClass(MissingSub.class);
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub.class, MissingSub.class)
+ .transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new Sub();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits Sub, MissingSub */ {}
+
+ static class Sub extends Super {}
+
+ static class MissingSub extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 5cc5fe1..91c8722 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
import com.android.tools.r8.TestDiagnosticMessages;
@@ -75,27 +76,27 @@
compileWithExpectedDiagnostics(mainClass, diagnosticsConsumer, null);
}
- public void compileWithExpectedDiagnostics(
+ public R8TestCompileResult compileWithExpectedDiagnostics(
Class<?> mainClass,
DiagnosticsConsumer diagnosticsConsumer,
ThrowableConsumer<R8FullTestBuilder> configuration)
throws CompilationFailedException {
- internalCompileWithExpectedDiagnostics(
+ return internalCompileWithExpectedDiagnostics(
diagnosticsConsumer,
builder ->
builder.addProgramClasses(mainClass).addKeepMainRule(mainClass).apply(configuration));
}
- public void compileWithExpectedDiagnostics(
+ public R8TestCompileResult compileWithExpectedDiagnostics(
ThrowableConsumer<R8FullTestBuilder> configuration, DiagnosticsConsumer diagnosticsConsumer)
throws CompilationFailedException {
- internalCompileWithExpectedDiagnostics(diagnosticsConsumer, configuration);
+ return internalCompileWithExpectedDiagnostics(diagnosticsConsumer, configuration);
}
- private void internalCompileWithExpectedDiagnostics(
+ private R8TestCompileResult internalCompileWithExpectedDiagnostics(
DiagnosticsConsumer diagnosticsConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
throws CompilationFailedException {
- testForR8(parameters.getBackend())
+ return testForR8(parameters.getBackend())
.apply(configuration)
.setMinApi(parameters)
.compileWithExpectedDiagnostics(diagnosticsConsumer);
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
index 7f536a5..016d4ac 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
@@ -56,7 +56,6 @@
@Test
public void testSourceFileAndLineNumberTable() throws Exception {
- assumeTrue("b/288405478", mode.isRelease());
runTest(
ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -68,7 +67,6 @@
@Test
public void testLineNumberTableOnly() throws Exception {
- assumeTrue("b/288405478", mode.isRelease());
assumeTrue(compat);
assumeTrue(parameters.isDexRuntime());
runTest(
@@ -81,7 +79,6 @@
@Test
public void testNoLineNumberTable() throws Exception {
- assumeTrue("b/288405478", mode.isRelease());
assumeTrue(compat);
assumeTrue(parameters.isDexRuntime());
runTest(
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
index 6d03704..2577312 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -45,7 +45,18 @@
private static final String RECORD_NAME = "SimpleRecord";
private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
private static final String EXPECTED_RESULT =
- StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
+ StringUtils.lines(
+ "Jane Doe",
+ "42",
+ "Jane Doe",
+ "42",
+ "true",
+ "true",
+ "true",
+ "false",
+ "false",
+ "false",
+ "false");
private static final ClassReference MAIN_REFERENCE =
Reference.classFromTypeName(RecordTestUtils.getMainType(RECORD_NAME));
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b390f85..fb55a41 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -168,7 +168,10 @@
.enableProguardTestOptions()
.minification(minify.isMinify())
.setMinApi(parameters)
- .addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
+ .addKeepRuleFiles(
+ ListUtils.map(
+ keepRulesFiles,
+ keepRulesFile -> Paths.get(ToolHelper.getProjectRoot(), keepRulesFile)))
.addLibraryFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"))
.addDefaultRuntimeLibrary(parameters)
.addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/utils/ReflectiveBuildPathUtils.java b/src/test/java/com/android/tools/r8/utils/ReflectiveBuildPathUtils.java
deleted file mode 100644
index ed991f2..0000000
--- a/src/test/java/com/android/tools/r8/utils/ReflectiveBuildPathUtils.java
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright (c) 2020, 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;
-
-import com.android.tools.r8.ToolHelper;
-import com.google.common.collect.Iterables;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-public class ReflectiveBuildPathUtils {
-
- public interface PackageUtils {
- String getPackageName() throws Exception;
-
- Path getPackagePath() throws Exception;
-
- PackageUtils getParentPackageUtils() throws Exception;
-
- Iterable<PackageUtils> getAllPackageUtils();
- }
-
- public interface ClassUtils {
- String getClassName() throws Exception;
-
- String getSimpleClassName() throws Exception;
-
- Path getClassPath() throws Exception;
- }
-
- public abstract static class ExamplesRootPackage implements PackageUtils {
- public abstract Path getPackagePath();
-
- @Override
- public String getPackageName() {
- return "";
- }
-
- @Override
- public PackageUtils getParentPackageUtils() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Iterable<PackageUtils> getAllPackageUtils() {
- return instantiateAllPackageUtils(getClass());
- }
- }
-
- public abstract static class ExamplesJava11RootPackage extends ExamplesRootPackage {
- @Override
- public Path getPackagePath() {
- return Paths.get(ToolHelper.EXAMPLES_JAVA11_BUILD_DIR);
- }
- }
-
- public abstract static class ExamplesPackage implements PackageUtils {
- public List<String> getName() {
- return Collections.singletonList(getClass().getSimpleName());
- }
-
- @Override
- public PackageUtils getParentPackageUtils() throws Exception {
- return (PackageUtils) getClass().getDeclaringClass().getConstructor().newInstance();
- }
-
- @Override
- public Path getPackagePath() throws Exception {
- Path path = getParentPackageUtils().getPackagePath();
- for (String folder : getName()) {
- path = path.resolve(folder);
- }
- return path;
- }
-
- @Override
- public String getPackageName() throws Exception {
- return getParentPackageUtils().getPackageName() + String.join(".", getName()) + ".";
- }
-
- @Override
- public Iterable<PackageUtils> getAllPackageUtils() {
- return instantiateAllPackageUtils(getClass());
- }
- }
-
- public static class ExamplesClass implements PackageUtils, ClassUtils {
- @Override
- public PackageUtils getParentPackageUtils() throws Exception {
- return (PackageUtils) getClass().getDeclaringClass().getConstructor().newInstance();
- }
-
- public String getSimpleClassName() throws Exception {
- Object parent = getClass().getDeclaringClass().getConstructor().newInstance();
- if (parent instanceof ClassUtils) {
- return ((ClassUtils) parent).getSimpleClassName() + "$" + getClass().getSimpleName();
- } else {
- return getClass().getSimpleName();
- }
- }
-
- @Override
- public String getClassName() throws Exception {
- return getParentPackageUtils().getPackageName() + getSimpleClassName();
- }
-
- @Override
- public Path getClassPath() throws Exception {
- return getParentPackageUtils().getPackagePath().resolve(getSimpleClassName() + ".class");
- }
-
- @Override
- public String getPackageName() throws Exception {
- return getParentPackageUtils().getPackageName();
- }
-
- @Override
- public Path getPackagePath() throws Exception {
- return getParentPackageUtils().getPackagePath();
- }
-
- @Override
- public Iterable<PackageUtils> getAllPackageUtils() {
- return instantiateAllPackageUtils(getClass());
- }
- }
-
- public static Iterable<PackageUtils> instantiateAllPackageUtils(Class<?> parentClazz) {
- Collection<PackageUtils> children = instantiatePackageUtils(parentClazz);
- return Iterables.concat(
- children,
- Iterables.concat(Iterables.transform(children, PackageUtils::getAllPackageUtils)));
- }
-
- public static Collection<PackageUtils> instantiatePackageUtils(Class<?> parentClazz) {
- Collection<PackageUtils> packageUtils = new ArrayList<>();
- for (Class<?> clazz : parentClazz.getDeclaredClasses()) {
- try {
- Object obj = clazz.getConstructor().newInstance();
- if (obj instanceof PackageUtils) {
- packageUtils.add((PackageUtils) obj);
- }
- } catch (Exception ex) {
- }
- }
- return packageUtils;
- }
-
- public static String resolveClassName(Class<? extends ExamplesClass> clazz) throws Exception {
- return clazz.getConstructor().newInstance().getClassName();
- }
-
- public static Collection<Path> allClassFiles(Class<? extends ExamplesRootPackage> clazz)
- throws Exception {
- Collection<Path> classFiles = new ArrayList<>();
- for (PackageUtils util : clazz.getConstructor().newInstance().getAllPackageUtils()) {
- if (util instanceof ClassUtils) {
- classFiles.add(((ClassUtils) util).getClassPath());
- }
- }
- return classFiles;
- }
-}
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 2133707..94616ba 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-9d322829ca871ff117d24fb8d7cb30001552a3ed
\ No newline at end of file
+13ccbabcc23194ef50c8c3331bf4ef7cee364b75
\ No newline at end of file
diff --git a/third_party/kotlin/kotlin-compiler-1.3.11.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-1.3.11.tar.gz.sha1
deleted file mode 100644
index 05e8466..0000000
--- a/third_party/kotlin/kotlin-compiler-1.3.11.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b3ce5c6ba25ebbbbf62a49be56aad845eabae860
\ No newline at end of file
diff --git a/third_party/kotlin/kotlin-compiler-1.3.41.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-1.3.41.tar.gz.sha1
deleted file mode 100644
index e9de3b3..0000000
--- a/third_party/kotlin/kotlin-compiler-1.3.41.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-8d2ddeeaaf4366a419627a31ef3276a6f96afe40
\ No newline at end of file
diff --git a/third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz.sha1
deleted file mode 100644
index e0b251e..0000000
--- a/third_party/kotlin/kotlin-compiler-1.5.0-M2.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7368d9ee73d01fc609d7ded1bf0506f72aa3ac66
\ No newline at end of file
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000..bb02b91
--- /dev/null
+++ b/tools/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
diff --git a/tools/check-cherry-picks.py b/tools/check-cherry-picks.py
index adf763c..3d66d3c 100755
--- a/tools/check-cherry-picks.py
+++ b/tools/check-cherry-picks.py
@@ -139,7 +139,7 @@
is_cherry_pick = True
# If the change is in the release mappings check for holes.
if missing_from:
- found_errors = change_error(
+ found_errors |= change_error(
change,
'Error: missing Change-Id %s on branch %s. '
'Is present on %s and again on %s.' % (
@@ -150,7 +150,7 @@
# The change is not in the non-dev part of the branch, so we need to
# check that the fork from main included the change.
if not is_commit_in(commit_on_main, newer_branch):
- found_errors = change_error(
+ found_errors |= change_error(
change,
'Error: missing Change-Id %s on branch %s. '
'Is present on %s and on main as commit %s.' % (