Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Worker API for Benchmark generators #235

Merged
merged 5 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,17 @@ def generatePluginConstants = tasks.register("generatePluginConstants") {
Provider<String> minSupportedGradleVersion = libs.versions.minSupportedGradle
inputs.property("minSupportedGradleVersion", minSupportedGradleVersion)

Provider<String> kotlinCompilerVersion = libs.versions.kotlin
inputs.property("kotlinCompilerVersion", kotlinCompilerVersion)

doLast {
constantsKtFile.write(
"""|package kotlinx.benchmark.gradle.internal
|
|internal object BenchmarksPluginConstants {
| const val BENCHMARK_PLUGIN_VERSION = "${benchmarkPluginVersion.get()}"
| const val MIN_SUPPORTED_GRADLE_VERSION = "${minSupportedGradleVersion.get()}"
| const val DEFAULT_KOTLIN_COMPILER_VERSION = "${kotlinCompilerVersion.get()}"
|}
|""".stripMargin()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kotlinx.benchmark.gradle

import kotlinx.benchmark.gradle.SuiteSourceGenerator.Companion.paramAnnotationFQN
import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.UnsignedTypes
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
Expand All @@ -10,6 +11,7 @@ import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.annotations.argumentValue

@RequiresKotlinCompilerEmbeddable
internal fun validateBenchmarkFunctions(functions: List<FunctionDescriptor>) {
functions.forEach { function ->
if (function.visibility != DescriptorVisibilities.PUBLIC) {
Expand All @@ -30,6 +32,7 @@ internal fun validateBenchmarkFunctions(functions: List<FunctionDescriptor>) {
}
}

@RequiresKotlinCompilerEmbeddable
internal fun validateSetupFunctions(functions: List<FunctionDescriptor>) {
functions.forEach { function ->
if (function.visibility != DescriptorVisibilities.PUBLIC) {
Expand All @@ -44,6 +47,7 @@ internal fun validateSetupFunctions(functions: List<FunctionDescriptor>) {
}
}

@RequiresKotlinCompilerEmbeddable
internal fun validateTeardownFunctions(functions: List<FunctionDescriptor>) {
functions.forEach { function ->
if (function.visibility != DescriptorVisibilities.PUBLIC) {
Expand All @@ -58,6 +62,7 @@ internal fun validateTeardownFunctions(functions: List<FunctionDescriptor>) {
}
}

@RequiresKotlinCompilerEmbeddable
internal fun validateParameterProperties(properties: List<PropertyDescriptor>) {
properties.forEach { property ->
if (!property.isVar) {
Expand All @@ -81,4 +86,4 @@ internal fun validateParameterProperties(properties: List<PropertyDescriptor>) {
error("@Param annotation should have at least one argument. The annotation on property `${property.name}` has no arguments.")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import groovy.lang.Closure
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
import org.gradle.api.*
import org.gradle.api.plugins.*
import org.gradle.api.provider.*
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
Expand All @@ -16,7 +17,7 @@ fun Project.benchmark(configure: Action<BenchmarksExtension>) {
extensions.configure(BenchmarksExtension::class.java, configure)
}

open class BenchmarksExtension
abstract class BenchmarksExtension
@KotlinxBenchmarkPluginInternalApi
constructor(
val project: Project
Expand All @@ -28,6 +29,8 @@ constructor(

val version = BenchmarksPlugin.PLUGIN_VERSION

abstract val kotlinCompilerVersion: Property<String>

fun configurations(configureClosure: Closure<NamedDomainObjectContainer<BenchmarkConfiguration>>): NamedDomainObjectContainer<BenchmarkConfiguration> {
return configurations.configure(configureClosure)
}
Expand All @@ -46,7 +49,8 @@ constructor(

val targets: NamedDomainObjectContainer<BenchmarkTarget> = run {
project.container(BenchmarkTarget::class.java) { name ->
val multiplatformClass = tryGetClass<KotlinMultiplatformExtension>("org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension")
val multiplatformClass =
tryGetClass<KotlinMultiplatformExtension>("org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension")
val multiplatform = multiplatformClass?.let { project.extensions.findByType(it) }
val javaExtension = project.extensions.findByType(JavaPluginExtension::class.java)

Expand Down
41 changes: 37 additions & 4 deletions plugin/main/src/kotlinx/benchmark/gradle/BenchmarksPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package kotlinx.benchmark.gradle

import kotlinx.benchmark.gradle.internal.BenchmarkDependencies
import kotlinx.benchmark.gradle.internal.BenchmarksPluginConstants
import kotlinx.benchmark.gradle.internal.BenchmarksPluginConstants.DEFAULT_KOTLIN_COMPILER_VERSION
import kotlinx.benchmark.gradle.internal.BenchmarksPluginConstants.MIN_SUPPORTED_GRADLE_VERSION
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
import org.gradle.api.*
import org.gradle.api.provider.*
import org.gradle.util.GradleVersion
import javax.inject.Inject

@Suppress("unused")
abstract class BenchmarksPlugin
@KotlinxBenchmarkPluginInternalApi
constructor() : Plugin<Project> {
@Inject
constructor(
private val providers: ProviderFactory,
) : Plugin<Project> {

companion object {
const val PLUGIN_ID = "org.jetbrains.kotlinx.benchmark"
Expand Down Expand Up @@ -50,7 +57,7 @@ constructor() : Plugin<Project> {

override fun apply(project: Project) = project.run {
// DO NOT use properties of an extension immediately, it will not contain any user-specified data
val extension = extensions.create(BENCHMARK_EXTENSION_NAME, BenchmarksExtension::class.java, project)
val extension = createBenchmarksExtension(project)

if (GradleVersion.current() < GradleVersion.version(MIN_SUPPORTED_GRADLE_VERSION)) {
logger.error("JetBrains Gradle Benchmarks plugin requires Gradle version $MIN_SUPPORTED_GRADLE_VERSION or higher")
Expand All @@ -62,19 +69,24 @@ constructor() : Plugin<Project> {
if (!getKotlinVersion(kotlinPlugin.pluginVersion).isAtLeast(1, 9, 20)) {
logger.error("JetBrains Gradle Benchmarks plugin requires Kotlin version 1.9.20 or higher")
}
extension.kotlinCompilerVersion.set(kotlinPlugin.pluginVersion)
}

// Create empty task that will depend on all benchmark building tasks to build all benchmarks in a project
// Create a lifecycle task that will depend on all benchmark building tasks to build all benchmarks in a project
val assembleBenchmarks = task<DefaultTask>(ASSEMBLE_BENCHMARKS_TASKNAME) {
group = BENCHMARKS_TASK_GROUP
description = "Generate and build all benchmarks in a project"
}

val benchmarkDependencies = BenchmarkDependencies(project, extension)

configureBenchmarkTaskConventions(project, benchmarkDependencies)

// TODO: Design configuration avoidance
// I currently don't know how to do it correctly yet, so materialize all tasks after project evaluation.
afterEvaluate {
extension.configurations.forEach {
// Create empty task that will depend on all benchmark execution tasks to run all benchmarks in a project
// Create a lifecycle task that will depend on all benchmark execution tasks to run all benchmarks in a project
task<DefaultTask>(it.prefixName(RUN_BENCHMARKS_TASKNAME)) {
group = BENCHMARKS_TASK_GROUP
description = "Execute all benchmarks in a project"
Expand All @@ -92,6 +104,12 @@ constructor() : Plugin<Project> {
}
}

private fun createBenchmarksExtension(project: Project): BenchmarksExtension {
return project.extensions.create(BENCHMARK_EXTENSION_NAME, BenchmarksExtension::class.java, project).apply {
kotlinCompilerVersion.convention(DEFAULT_KOTLIN_COMPILER_VERSION)
fzhinkin marked this conversation as resolved.
Show resolved Hide resolved
}
}

private fun Project.processConfigurations(extension: BenchmarksExtension) {
// Calling `all` on NDOC causes all items to materialize and be configured
extension.targets.all { config ->
Expand All @@ -104,6 +122,21 @@ constructor() : Plugin<Project> {
}
}
}

private fun configureBenchmarkTaskConventions(
project: Project,
benchmarkDependencies: BenchmarkDependencies,
) {
project.tasks.withType(NativeSourceGeneratorTask::class.java).configureEach {
it.runtimeClasspath.from(benchmarkDependencies.benchmarkGeneratorResolver)
}
project.tasks.withType(WasmSourceGeneratorTask::class.java).configureEach {
it.runtimeClasspath.from(benchmarkDependencies.benchmarkGeneratorResolver)
}
project.tasks.withType(JsSourceGeneratorTask::class.java).configureEach {
it.runtimeClasspath.from(benchmarkDependencies.benchmarkGeneratorResolver)
}
}
}

private fun getKotlinVersion(kotlinVersion: String): KotlinVersion {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,43 @@
package kotlinx.benchmark.gradle

import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.*
import org.gradle.api.logging.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.generators.core.*
import org.openjdk.jmh.generators.reflection.*
import org.openjdk.jmh.util.*
import java.io.*
import java.net.*
import java.util.*
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.generators.core.BenchmarkGenerator
import org.openjdk.jmh.generators.core.FileSystemDestination
import org.openjdk.jmh.generators.reflection.RFGeneratorSource
import org.openjdk.jmh.util.FileUtils
import java.io.File
import java.net.URL
import java.net.URLClassLoader

@KotlinxBenchmarkPluginInternalApi
// TODO Make visibility of JmhBytecodeGeneratorWorker `internal` in version 1.0.
adam-enko marked this conversation as resolved.
Show resolved Hide resolved
// Move to package kotlinx.benchmark.gradle.internal.generator.workers, alongside the other workers.
abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkParameters> {

@KotlinxBenchmarkPluginInternalApi
companion object {
// TODO in version 1.0 replace JmhBytecodeGeneratorWorkParameters with this interface:
//internal interface Parameters : WorkParameters {
// val inputClasses: ConfigurableFileCollection
// val inputClasspath: ConfigurableFileCollection
// val outputSourceDirectory: DirectoryProperty
// val outputResourceDirectory: DirectoryProperty
//}

internal companion object {
private const val classSuffix = ".class"
private val logger = Logging.getLogger(JmhBytecodeGeneratorWorker::class.java)
}

private val outputSourceDirectory: File get() = parameters.outputSourceDirectory.get().asFile
private val outputResourceDirectory: File get() = parameters.outputResourceDirectory.get().asFile

override fun execute() {
cleanup(outputSourceDirectory)
cleanup(outputResourceDirectory)
outputSourceDirectory.deleteRecursively()
outputResourceDirectory.deleteRecursively()

val urls = (parameters.inputClasses + parameters.inputClasspath).map { it.toURI().toURL() }.toTypedArray()

Expand All @@ -58,13 +69,13 @@ abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkP
// inside JMH bytecode gen. This hack seem to work, but we need to understand
val introspectionClassLoader = URLClassLoader(urls, benchmarkAnnotation.classLoader)

/*
println("Original_Parent_ParentCL: ${originalClassLoader.parent.parent}")
println("Original_ParentCL: ${originalClassLoader.parent}")
println("OriginalCL: $originalClassLoader")
println("IntrospectCL: $introspectionClassLoader")
println("BenchmarkCL: ${benchmarkAnnotation.classLoader}")
*/
/*
println("Original_Parent_ParentCL: ${originalClassLoader.parent.parent}")
println("Original_ParentCL: ${originalClassLoader.parent}")
println("OriginalCL: $originalClassLoader")
println("IntrospectCL: $introspectionClassLoader")
println("BenchmarkCL: ${benchmarkAnnotation.classLoader}")
*/

try {
currentThread.contextClassLoader = introspectionClassLoader
Expand Down Expand Up @@ -97,7 +108,7 @@ abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkP
}
}

println("Writing out Java source to $outputSourceDirectory and resources to $outputResourceDirectory")
logger.lifecycle("Writing out Java source to $outputSourceDirectory and resources to $outputResourceDirectory")
val gen = BenchmarkGenerator()
gen.generate(source, destination)
gen.complete(source, destination)
Expand All @@ -109,12 +120,15 @@ abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkP
errCount++
sb.append(" - ").append(e.toString()).append("\n")
}
throw RuntimeException("Generation of JMH bytecode failed with " + errCount + " errors:\n" + sb)
throw RuntimeException("Generation of JMH bytecode failed with $errCount errors:\n$sb")
}
}
}

@KotlinxBenchmarkPluginInternalApi
// TODO In version 1.0:
// - Make internal
// - Move to a nested interface inside of JmhBytecodeGeneratorWorker (like the other workers)
interface JmhBytecodeGeneratorWorkParameters : WorkParameters {
val inputClasses: ConfigurableFileCollection
val inputClasspath: ConfigurableFileCollection
Expand Down
51 changes: 22 additions & 29 deletions plugin/main/src/kotlinx/benchmark/gradle/JsSourceGeneratorTask.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package kotlinx.benchmark.gradle

import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable
import kotlinx.benchmark.gradle.internal.generator.workers.GenerateJsSourceWorker
import org.gradle.api.*
import org.gradle.api.file.*
import org.gradle.api.tasks.*
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.storage.StorageManager
import java.io.File
import javax.inject.Inject

@CacheableTask
open class JsSourceGeneratorTask
abstract class JsSourceGeneratorTask
@KotlinxBenchmarkPluginInternalApi
@Inject
constructor(
Expand All @@ -37,34 +36,28 @@ constructor(
@OutputDirectory
lateinit var outputSourcesDir: File

@get:Classpath
abstract val runtimeClasspath: ConfigurableFileCollection

@TaskAction
fun generate() {
cleanup(outputSourcesDir)
cleanup(outputResourcesDir)

inputClassesDirs.files.forEach { lib: File ->
generateSources(lib)
val workQueue = workerExecutor.classLoaderIsolation {
it.classpath.from(runtimeClasspath)
}
}

private fun generateSources(lib: File) {
val modules = loadIr(lib, LockBasedStorageManager("Inspect"))
modules.forEach { module ->
val generator = SuiteSourceGenerator(
title,
module,
outputSourcesDir,
if (useBenchmarkJs) Platform.JsBenchmarkJs else Platform.JsBuiltIn
)
generator.generate()
@OptIn(RequiresKotlinCompilerEmbeddable::class)
workQueue.submit(GenerateJsSourceWorker::class.java) {
it.title.set(title)
it.inputClasses.from(inputClassesDirs)
it.inputDependencies.from(inputDependencies)
it.outputSourcesDir.set(outputSourcesDir)
it.outputResourcesDir.set(outputResourcesDir)
it.useBenchmarkJs.set(useBenchmarkJs)
}
}

private fun loadIr(lib: File, storageManager: StorageManager): List<ModuleDescriptor> {
// skip processing of empty dirs (fails if not to do it)
if (lib.listFiles() == null) return emptyList()
val dependencies = inputDependencies.files.filterNot { it.extension == "js" }.toSet()
val module = KlibResolver.JS.createModuleDescriptor(lib, dependencies, storageManager)
return listOf(module)
workQueue.await() // I'm not sure if waiting is necessary,
// but I suspect that the task dependencies aren't configured correctly,
// so: better-safe-than-sorry.
// Try removing await() when Benchmarks follows Gradle best practices.
}
}
Loading