Replace reflection-based compiler arguments copying with generated code
Using of Kotlin reflection for simple operations like bean management is very slow First time initialization time: 261 ms for `copyBean(K2JVMCompilerArguments())` Subsequent calls of `copyBean(K2JVMCompilerArguments())` take 1.7 ms per call Unfortunately compiler argument handling is also used in Kotlin IntelliJ plugin to parse facet settings. Big projects may have thousands of Kotlin facets The same `ArgumentUtilsKt.copyProperties` frame is seen across various freezes: IDEA-252440 2-3 minutes freeze on Kotlin project reimporting in last 203 eap IDEA-253107 A lot of short freezes (1-3 sec) during Kotlin project development KTIJ-23501 Make main run configuration detection lighter KTIJ-22435 Unresponsive UI with 100% cpu Reflection issue: KT-56358 KClasses.getMemberProperties takes too much time This commit replaces all reflection stuff with a simple code generation Now `K2JVMCompilerArguments().clone()` goes to hard-to-measure time
This commit is contained in:
@@ -78,6 +78,7 @@ dependencies {
|
||||
testImplementation(projectTests(":compiler:tests-common-new"))
|
||||
testImplementation(projectTests(":js:js.tests"))
|
||||
testImplementation(project(":kotlin-gradle-compiler-types"))
|
||||
testImplementation(project(":jps:jps-common"))
|
||||
testApiJUnit5()
|
||||
|
||||
if (Ide.IJ()) {
|
||||
@@ -91,6 +92,8 @@ projectTest(parallel = true) {
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
val generateCompilerArgumentsCopy by generator("org.jetbrains.kotlin.generators.arguments.GenerateCompilerArgumentsCopyKt")
|
||||
|
||||
val generateTests by generator("org.jetbrains.kotlin.generators.tests.GenerateTestsKt") {
|
||||
dependsOn(":generators:analysis-api-generator:generateFrontendApiTests")
|
||||
}
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.generators.arguments
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.arguments.*
|
||||
import org.jetbrains.kotlin.config.CompilerSettings
|
||||
import org.jetbrains.kotlin.config.JpsPluginSettings
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KClassifier
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.full.superclasses
|
||||
|
||||
private val CLASSES_TO_PROCESS: List<KClass<*>> = listOf(
|
||||
JpsPluginSettings::class,
|
||||
CompilerSettings::class,
|
||||
K2MetadataCompilerArguments::class,
|
||||
K2NativeCompilerArguments::class,
|
||||
K2JSDceArguments::class,
|
||||
K2JSCompilerArguments::class,
|
||||
K2JVMCompilerArguments::class,
|
||||
)
|
||||
|
||||
private val PACKAGE_TO_DIR_MAPPING: Map<Package, File> = mapOf(
|
||||
K2JVMCompilerArguments::class.java.`package` to File("compiler/cli/cli-common/gen"),
|
||||
JpsPluginSettings::class.java.`package` to File("jps/jps-common/gen"),
|
||||
)
|
||||
|
||||
fun generateCompilerArgumentsCopy(withPrinterToFile: (targetFile: File, Printer.() -> Unit) -> Unit) {
|
||||
val processed = mutableSetOf<KClass<*>>()
|
||||
for (klass in CLASSES_TO_PROCESS) {
|
||||
generateRec(klass, withPrinterToFile, processed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRec(
|
||||
klass: KClass<*>,
|
||||
withPrinterToFile: (targetFile: File, Printer.() -> Unit) -> Unit,
|
||||
processed: MutableSet<KClass<*>>,
|
||||
) {
|
||||
if (!processed.add(klass)) return
|
||||
|
||||
val klassName = klass.simpleName!!
|
||||
val fqn = klass.qualifiedName!!
|
||||
val `package` = klass.java.`package`
|
||||
val destDir = PACKAGE_TO_DIR_MAPPING[`package`]!!.resolve(`package`.name.replace('.', '/'))
|
||||
withPrinterToFile(destDir.resolve(klassName + "CopyGenerated.kt")) {
|
||||
println(
|
||||
"""
|
||||
@file:Suppress("unused", "DuplicatedCode")
|
||||
|
||||
// DO NOT EDIT MANUALLY!
|
||||
// Generated by generators/tests/org/jetbrains/kotlin/generators/arguments/GenerateCompilerArgumentsCopy.kt
|
||||
// To regenerate run 'generateCompilerArgumentsCopy' task
|
||||
|
||||
package ${`package`.name}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
fun isSupportedImmutable(type: KType): Boolean {
|
||||
val classifier: KClassifier = type.classifier!!
|
||||
return when {
|
||||
classifier is KClass<*> && classifier == List::class -> isSupportedImmutable(type.arguments.single().type!!)
|
||||
classifier == InternalArgument::class -> true
|
||||
classifier == Boolean::class -> true
|
||||
classifier == Int::class -> true
|
||||
classifier == String::class -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
println("@OptIn(org.jetbrains.kotlin.utils.IDEAPluginsCompatibilityAPI::class)")
|
||||
println("fun copy$klassName(from: $klassName, to: $klassName): $klassName {")
|
||||
withIndent {
|
||||
val superClasses: List<KClass<*>> = klass.superclasses.filterNot { it.java.isInterface }
|
||||
check(superClasses.size < 2) {
|
||||
"too many super classes in $klass: ${superClasses.joinToString()}"
|
||||
}
|
||||
|
||||
val superKlass = superClasses.singleOrNull()
|
||||
if (superKlass != null && superKlass != Freezable::class) {
|
||||
generateRec(superKlass, withPrinterToFile, processed)
|
||||
if (superKlass.java.`package` != `package`) {
|
||||
print("${superKlass.java.`package`.name}.")
|
||||
}
|
||||
println("copy${superKlass.simpleName}(from, to)")
|
||||
println()
|
||||
}
|
||||
|
||||
val properties = collectProperties(klass, false)
|
||||
|
||||
for (property in properties.filter { klass.declaredMemberProperties.contains(it) }) {
|
||||
val type = property.returnType
|
||||
val classifier: KClassifier = type.classifier!!
|
||||
when {
|
||||
// Please add cases on the go
|
||||
// Please add a test to GenerateCompilerArgumentsCopyTest if the change is not trivial
|
||||
|
||||
classifier is KClass<*> && classifier.java.isArray -> {
|
||||
val arrayElementType = type.arguments.single().type!!
|
||||
val nullableMarker = if (type.isMarkedNullable) "?" else ""
|
||||
when (arrayElementType.classifier) {
|
||||
String::class -> println("to.${property.name} = from.${property.name}${nullableMarker}.copyOf()")
|
||||
else -> error("Unsupported array element type $arrayElementType (member '${property.name}' of $fqn)")
|
||||
}
|
||||
}
|
||||
|
||||
isSupportedImmutable(type) -> println("to.${property.name} = from.${property.name}")
|
||||
|
||||
else -> error("Unsupported type to copy: $type (member '${property.name}' of $fqn)")
|
||||
}
|
||||
}
|
||||
|
||||
println()
|
||||
println("return to")
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
generateCompilerArgumentsCopy(::getPrinterToFile)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.generators.arguments
|
||||
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.cli.common.arguments.*
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.test.assertContentEquals
|
||||
|
||||
class GenerateCompilerArgumentsCopyTest : TestCase() {
|
||||
fun testCompilerArgumentsCopyFunctionsAreUpToDate() {
|
||||
generateCompilerArgumentsCopy(::getPrinterForTests)
|
||||
}
|
||||
|
||||
fun testCopyDoesNotCopyTransientFields() {
|
||||
val a = K2JVMCompilerArguments()
|
||||
a.errors = ArgumentParseErrors()
|
||||
a.moduleName = "my module name"
|
||||
|
||||
val b = K2JVMCompilerArguments()
|
||||
assertNull(b.errors)
|
||||
assertNull(b.moduleName)
|
||||
|
||||
copyK2JVMCompilerArguments(a, b)
|
||||
assertNull(b.errors)
|
||||
assertEquals("my module name", b.moduleName)
|
||||
}
|
||||
|
||||
fun testCopyDuplicatesArray() {
|
||||
val a = K2JVMCompilerArguments()
|
||||
a.additionalJavaModules = arrayOf("xxx")
|
||||
|
||||
val b = K2JVMCompilerArguments()
|
||||
copyK2JVMCompilerArguments(a, b)
|
||||
|
||||
assertContentEquals(a.additionalJavaModules, b.additionalJavaModules)
|
||||
assertNotSame(a.additionalJavaModules, b.additionalJavaModules)
|
||||
|
||||
b.additionalJavaModules!![0] = "yyy"
|
||||
assertEquals("xxx", a.additionalJavaModules!![0])
|
||||
}
|
||||
|
||||
fun testCollectPropertiesDoesNotReturnTransient() {
|
||||
val errorProperty = CommonToolArguments::errors
|
||||
assertTrue(Modifier.isTransient(errorProperty.javaField!!.modifiers))
|
||||
|
||||
val properties = collectProperties(CommonToolArguments::class, false)
|
||||
assertFalse(properties.any { it.name == errorProperty.name })
|
||||
}
|
||||
}
|
||||
+1
-13
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.generators.arguments
|
||||
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
|
||||
fun generateGradleCompilerTypes(withPrinterToFile: (targetFile: File, Printer.() -> Unit) -> Unit) {
|
||||
val destDir = File("libraries/tools/kotlin-gradle-compiler-types/src/generated/kotlin")
|
||||
@@ -27,16 +26,5 @@ fun generateGradleCompilerTypes(withPrinterToFile: (targetFile: File, Printer.()
|
||||
}
|
||||
|
||||
fun main() {
|
||||
fun getPrinter(file: File, fn: Printer.() -> Unit) {
|
||||
if (!file.exists()) {
|
||||
file.parentFile.mkdirs()
|
||||
file.createNewFile()
|
||||
}
|
||||
PrintStream(file.outputStream().buffered()).use {
|
||||
val printer = Printer(it)
|
||||
printer.fn()
|
||||
}
|
||||
}
|
||||
|
||||
generateGradleCompilerTypes(::getPrinter)
|
||||
generateGradleCompilerTypes(::getPrinterToFile)
|
||||
}
|
||||
|
||||
+1
-13
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty1
|
||||
@@ -145,18 +144,7 @@ fun generateKotlinGradleOptions(withPrinterToFile: (targetFile: File, Printer.()
|
||||
}
|
||||
|
||||
fun main() {
|
||||
fun getPrinter(file: File, fn: Printer.() -> Unit) {
|
||||
if (!file.exists()) {
|
||||
file.parentFile.mkdirs()
|
||||
file.createNewFile()
|
||||
}
|
||||
PrintStream(file.outputStream()).use {
|
||||
val printer = Printer(it)
|
||||
printer.fn()
|
||||
}
|
||||
}
|
||||
|
||||
generateKotlinGradleOptions(::getPrinter)
|
||||
generateKotlinGradleOptions(::getPrinterToFile)
|
||||
}
|
||||
|
||||
private fun generateKotlinCommonToolOptions(
|
||||
|
||||
+3
-31
@@ -9,42 +9,14 @@ package org.jetbrains.kotlin.generators.arguments.test
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.generators.arguments.generateGradleCompilerTypes
|
||||
import org.jetbrains.kotlin.generators.arguments.generateKotlinGradleOptions
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import org.jetbrains.kotlin.generators.arguments.getPrinterForTests
|
||||
|
||||
class GenerateKotlinGradleOptionsTest : TestCase() {
|
||||
fun testKotlinGradleOptionsAreUpToDate() {
|
||||
fun getPrinter(file: File, fn: Printer.() -> Unit) {
|
||||
val bytesOut = ByteArrayOutputStream()
|
||||
|
||||
PrintStream(bytesOut).use {
|
||||
val printer = Printer(it)
|
||||
printer.fn()
|
||||
}
|
||||
|
||||
val upToDateContent = bytesOut.toString()
|
||||
KtUsefulTestCase.assertSameLinesWithFile(file.absolutePath, upToDateContent)
|
||||
}
|
||||
|
||||
generateKotlinGradleOptions(::getPrinter)
|
||||
generateKotlinGradleOptions(::getPrinterForTests)
|
||||
}
|
||||
|
||||
fun testKotlinGradleTypesAreUpToDate() {
|
||||
fun getPrinter(file: File, fn: Printer.() -> Unit) {
|
||||
val bytesOut = ByteArrayOutputStream()
|
||||
|
||||
PrintStream(bytesOut).use {
|
||||
val printer = Printer(it)
|
||||
printer.fn()
|
||||
}
|
||||
|
||||
val upToDateContent = bytesOut.toString()
|
||||
KtUsefulTestCase.assertSameLinesWithFile(file.absolutePath, upToDateContent)
|
||||
}
|
||||
|
||||
generateGradleCompilerTypes(::getPrinter)
|
||||
generateGradleCompilerTypes(::getPrinterForTests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.generators.arguments
|
||||
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
private fun printToString(fn: Printer.() -> Unit): String {
|
||||
val builder = StringBuilder()
|
||||
Printer(builder).fn()
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
fun getPrinterForTests(file: File, fn: Printer.() -> Unit) {
|
||||
KtUsefulTestCase.assertSameLinesWithFile(file.absolutePath, printToString(fn))
|
||||
}
|
||||
|
||||
fun getPrinterToFile(file: File, fn: Printer.() -> Unit) {
|
||||
Files.createDirectories(file.toPath().parent)
|
||||
file.writeText(printToString(fn))
|
||||
}
|
||||
Reference in New Issue
Block a user