diff --git a/.github/workflows/kt-bench.yml b/.github/workflows/kt-bench.yml index ffb9a26..37e4dfb 100644 --- a/.github/workflows/kt-bench.yml +++ b/.github/workflows/kt-bench.yml @@ -74,8 +74,34 @@ jobs: name: ${{ matrix.target }}-benchmarks path: kt/aoc2024-exe/build/reports/benchmarks + graalvm: + needs: [ assemble, get-inputs ] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: inputs + path: inputs + - uses: graalvm/setup-graalvm@v1 + with: + java-version: 21 + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: true + - run: ./gradlew --no-configuration-cache -Pagent nativeBenchmarkRun + working-directory: kt + env: + AOC2024_DATADIR: ${{ github.workspace }}/inputs + - uses: actions/upload-artifact@v4 + with: + name: graalvm-benchmarks + path: kt/graalvm/build/reports/benchmarks + docs: - needs: [ jmh-visualizer, build ] + needs: [ jmh-visualizer, build, graalvm ] runs-on: ubuntu-latest steps: @@ -97,13 +123,17 @@ jobs: with: name: linuxX64-benchmarks path: benchmarks + - uses: actions/download-artifact@v4 + with: + name: graalvm-benchmarks + path: benchmarks - run: rm -rf jmh-visualizer - run: unzip -d jmh-visualizer jmh-visualizer.zip - name: Create provided.js run: | - shopt -s failglob + shopt -s failglob globstar names=() jsonargs=() - for file in benchmarks/main/*/*.json; do + for file in benchmarks/**/*.json; do name=${file##*/} name=${name%.*} name=${name%Bench} diff --git a/.github/workflows/kt.yml b/.github/workflows/kt.yml index 0285c68..9e493c2 100644 --- a/.github/workflows/kt.yml +++ b/.github/workflows/kt.yml @@ -47,6 +47,23 @@ jobs: name: aoc2024-js path: kt/build/js/packages/aoc2024-aoc2024-exe/kotlin/* + graalvm: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: 21 + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: gradle/actions/setup-gradle@v4 + - run: ./gradlew --no-configuration-cache nativeTest nativeCompile + working-directory: kt + - uses: actions/upload-artifact@v4 + with: + name: aoc2024-native + path: kt/graalvm/build/native/nativeCompile/* + run-jvm: needs: [ get-inputs, build ] runs-on: ubuntu-latest @@ -68,6 +85,23 @@ jobs: env: AOC2024_DATADIR: inputs + run-graalvm: + needs: [ get-inputs, graalvm ] + runs-on: ubuntu-latest + + steps: + - uses: actions/download-artifact@v4 + with: + name: inputs + path: inputs + - uses: actions/download-artifact@v4 + with: + name: aoc2024-native + - run: chmod +x aoc2024-native + - run: ./aoc2024-native + env: + AOC2024_DATADIR: inputs + run-native: needs: [ get-inputs, build ] runs-on: ubuntu-latest diff --git a/kt/README.md b/kt/README.md index cf8e9ea..1d45887 100644 --- a/kt/README.md +++ b/kt/README.md @@ -26,3 +26,25 @@ Run all checks, including [Detekt](https://detekt.github.io/) static code analys ```sh ./gradlew check ``` + +## [GraalVM](https://www.graalvm.org/) + +Run the test suite as a GraalVM Native Image: + +```sh +export GRAALVM_HOME=... +./gradlew --no-configuration-cache :graalvm:nativeTest +``` + +Run [JMH](https://openjdk.java.net/projects/code-tools/jmh/) benchmarks as a GraalVM Native Image: + +```sh +export GRAALVM_HOME=... +./gradlew --no-configuration-cache -Pagent :graalvm:nativeBenchmarkRun +``` + +Print solutions for the inputs provided in local data files as a GraalVM Native Image: + +```sh +./gradlew --no-configuration-cache :graalvm:nativeRun +``` diff --git a/kt/aoc2024-exe/build.gradle.kts b/kt/aoc2024-exe/build.gradle.kts index 0042955..a7f4837 100644 --- a/kt/aoc2024-exe/build.gradle.kts +++ b/kt/aoc2024-exe/build.gradle.kts @@ -125,3 +125,26 @@ benchmark { } } } + +afterEvaluate { + val jvmBenchBenchmarkJar by tasks.existing + configurations.consumable("jvmBenchmark") { + attributes { + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) + attribute( + TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, + objects.named(TargetJvmEnvironment.STANDARD_JVM), + ) + attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.JAR), + ) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EMBEDDED)) + } + outgoing { + capability("com.github.ephemient.aoc2024:aoc2024-bench:1.0") + artifact(jvmBenchBenchmarkJar) + } + } +} diff --git a/kt/aoc2024-lib/build.gradle.kts b/kt/aoc2024-lib/build.gradle.kts index c2c7fb6..2ade09d 100644 --- a/kt/aoc2024-lib/build.gradle.kts +++ b/kt/aoc2024-lib/build.gradle.kts @@ -46,6 +46,66 @@ kotlin { } } +val jvmTestCompilation = kotlin.jvm().compilations.getByName("test") +val jvmTestJar by tasks.registering(Jar::class) { + group = BasePlugin.BUILD_GROUP + description = "Assembles an archive containing the test classes." + archiveClassifier = "test" + from(jvmTestCompilation.output.allOutputs) +} +for ((name, base, usage) in listOf( + Triple("jvmTestApiElements", "jvmTestApi", Usage.JAVA_API), + Triple("jvmTestRuntimeElements", "jvmTestRuntimeClasspath", Usage.JAVA_RUNTIME), +)) { + configurations.consumable(name) { + extendsFrom(configurations.getByName(base)) + attributes { + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) + attribute( + TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, + objects.named(TargetJvmEnvironment.STANDARD_JVM), + ) + attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.JAR), + ) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(usage)) + } + outgoing { + capability("com.github.ephemient.aoc2024:aoc2024-test:1.0") + artifact(jvmTestJar) { + type = ArtifactTypeDefinition.JAR_TYPE + } + variants.create("classes") { + attributes { + attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.CLASSES), + ) + } + for (classesDir in jvmTestCompilation.output.classesDirs) { + artifact(classesDir) { + type = ArtifactTypeDefinition.JVM_CLASS_DIRECTORY + builtBy(jvmTestCompilation.compileTaskProvider) + } + } + } + variants.create("resources") { + attributes { + attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.RESOURCES), + ) + } + artifact(jvmTestCompilation.output.resourcesDir) { + type = ArtifactTypeDefinition.JVM_RESOURCES_DIRECTORY + builtBy(jvmTestCompilation.processResourcesTaskName) + } + } + } + } +} + dependencies { detektPlugins(libs.bundles.detekt.plugins) } diff --git a/kt/graalvm/build.gradle.kts b/kt/graalvm/build.gradle.kts new file mode 100644 index 0000000..3e951a9 --- /dev/null +++ b/kt/graalvm/build.gradle.kts @@ -0,0 +1,94 @@ +import org.graalvm.buildtools.gradle.internal.agent.AgentConfigurationFactory +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.function.Predicate + +plugins { + application + alias(libs.plugins.native.image) +} + +application { + mainClass.set("com.github.ephemient.aoc2024.exe.Main") +} + +val benchmarkDir = layout.buildDirectory + .dir( + "reports/benchmarks/main/" + + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME).replace(':', '.') + ) + +val benchmark by sourceSets.creating +val benchmarkRun by tasks.registering(JavaExec::class) { + System.getenv("GRAALVM_HOME")?.ifEmpty { null }?.let { setExecutable("$it/bin/java") } + mainClass = "org.openjdk.jmh.Main" + classpath(benchmark.output, benchmark.runtimeClasspath) + args("-f", 0, "-wi", 1, "-w", "0s", "-r", "0s", "-bm", "avgt", "-tu", "us", "-i", 1) + args("-rf", "json", "-rff", "/dev/null") + outputs.dir(AgentConfigurationFactory.getAgentOutputDirectoryForTask(layout, name)) +} +val syncBenchmarkRunMetadata by tasks.registering(Sync::class) { + into(layout.buildDirectory.dir("generated/resources/benchmark")) + from(benchmarkRun) { + into("META-INF/native-image/com.github.ephemient.aoc2024/benchmark") + } +} +graalvmNative { + binaries { + getByName("main") { + imageName = "aoc2024-native" + } + create("benchmark") { + imageName = "aoc2024-native-benchmark" + mainClass = "org.openjdk.jmh.Main" + classpath(syncBenchmarkRunMetadata, benchmark.output, benchmark.runtimeClasspath) + runtimeArgs("-f", 0, "-wi", 1, "-w", "1s", "-r", "1s", "-bm", "avgt", "-tu", "us") + val benchmarkFile = benchmarkDir.get().file("graalvmBench.json") + runtimeArgs("-rf", "json", "-rff", benchmarkFile.asFile) + findProperty("benchmarkInclude")?.let { runtimeArgs("-e", it) } + findProperty("benchmarkExclude")?.let { runtimeArgs(it) } + } + } + agent { + tasksToInstrumentPredicate = Predicate { it.name == "benchmarkRun" } + } +} +tasks.named("nativeBenchmarkRun") { + val benchmarkDir = benchmarkDir.get() + outputs.file(benchmarkDir.file("graalvmBench.json")) + doFirst { benchmarkDir.asFile.mkdirs() } +} + +val externalTestClasses = configurations.dependencyScope("externalTestClasses") +val externalTestClasspath = configurations.resolvable("externalTestClasspath") { + extendsFrom(externalTestClasses.get()) + isTransitive = false + attributes { + attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.CLASSES), + ) + } +} +configurations.testImplementation { + extendsFrom(externalTestClasses.get()) +} +tasks.named("test") { + testClassesDirs = files(testClassesDirs, externalTestClasspath.get()) + useJUnitPlatform() +} + +dependencies { + implementation(projects.aoc2024Exe) + testImplementation(libs.junit.jupiter.api) + externalTestClasses(projects.aoc2024Lib) { + capabilities { + requireCapability("com.github.ephemient.aoc2024:aoc2024-test:1.0") + } + } + benchmark.implementationConfigurationName(projects.aoc2024Exe) { + capabilities { + requireCapability("com.github.ephemient.aoc2024:aoc2024-bench:1.0") + } + } +} diff --git a/kt/graalvm/src/benchmark/resources/META-INF/native-image/com.github.ephemient.aoc22024/graalvm/native-image.properties b/kt/graalvm/src/benchmark/resources/META-INF/native-image/com.github.ephemient.aoc22024/graalvm/native-image.properties new file mode 100644 index 0000000..5a7339c --- /dev/null +++ b/kt/graalvm/src/benchmark/resources/META-INF/native-image/com.github.ephemient.aoc22024/graalvm/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=org.openjdk.jmh.infra,org.openjdk.jmh.util.Utils,org.openjdk.jmh.runner.InfraControl,org.openjdk.jmh.runner.InfraControlL0,org.openjdk.jmh.runner.InfraControlL1,org.openjdk.jmh.runner.InfraControlL2,org.openjdk.jmh.runner.InfraControlL3,org.openjdk.jmh.runner.InfraControlL4 diff --git a/kt/graalvm/src/test/resources/META-INF/native-image/com.github.ephemient.aoc22024/graalvm/native-image.properties b/kt/graalvm/src/test/resources/META-INF/native-image/com.github.ephemient.aoc22024/graalvm/native-image.properties new file mode 100644 index 0000000..443d873 --- /dev/null +++ b/kt/graalvm/src/test/resources/META-INF/native-image/com.github.ephemient.aoc22024/graalvm/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=kotlin.annotation.AnnotationRetention,kotlin.annotation.AnnotationTarget diff --git a/kt/gradle/libs.versions.toml b/kt/gradle/libs.versions.toml index 8b11083..3015f19 100644 --- a/kt/gradle/libs.versions.toml +++ b/kt/gradle/libs.versions.toml @@ -4,6 +4,7 @@ junit-jupiter = "5.11.3" kotlin = "2.1.0" kotlinx-benchmark = "0.4.13" kotlinx-coroutines = "1.9.0" +native-image = "0.10.3" okio = "3.9.1" [plugins] @@ -11,6 +12,7 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlin-plugin-allopen = { id = "org.jetbrains.kotlin.plugin.allopen", version.ref = "kotlin" } kotlinx-benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "kotlinx-benchmark" } +native-image = { id = "org.graalvm.buildtools.native", version.ref = "native-image" } [libraries] detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } diff --git a/kt/settings.gradle.kts b/kt/settings.gradle.kts index 5983e50..1dab860 100644 --- a/kt/settings.gradle.kts +++ b/kt/settings.gradle.kts @@ -14,4 +14,4 @@ gradle.afterProject { } rootProject.name = "aoc2024" -include("aoc2024-exe", "aoc2024-lib") +include("aoc2024-exe", "aoc2024-lib", "graalvm")