diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 867b5c80abc..bc9685c290f 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -144,6 +144,8 @@ allprojects { // backent.native/tests/coverage ext.testOutputCoverage = rootProject.file("$testOutputRoot/coverage") + + ext.testOutputFileCheck = rootProject.file("$testOutputRoot/filecheck") } testOutputExternal.mkdirs() testOutputStdlib.mkdirs() @@ -211,6 +213,7 @@ task sanity { // Add regular gradle test tasks dependsOn(tasksOf(Test)) dependsOn(tasksOf(CoverageTest)) + dependsOn(tasksOf(FileCheckTest)) dependsOn(":kotlin-native:Interop:Indexer:check") } @@ -5811,6 +5814,53 @@ pluginTest("runtime_basic_init", "nopPlugin") { flags = ["-tr"] } + +Task fileCheckTest(String name, Closure configureClosure) { + return project.tasks.create(name, FileCheckTest) { task -> + task.configure(configureClosure) + task.llvmIr = project.file("$testOutputFileCheck/$name/out.ll") + if (task.enabled) { + konanArtifacts { + // We could use `bitcode` here to make things faster. But: + // 1. `bitcode` has no other usages. + // 2. Unification should help porting to the new test infra. + // 3. Compiler might behave differently when outputting bitcode instead of object files. + program(name, targets: [ target ]) { + srcFiles task.annotatedSource + baseDir "$testOutputFileCheck/$name" + extraOpts project.globalTestArgs + extraOpts "-Xtemporary-files-dir=$testOutputFileCheck/$name", "-Xsave-llvm-ir" + } + UtilsKt.dependsOnKonanBuildingTask(task, name, target) + } + } + } +} + +fileCheckTest("filecheck_smoke0") { + annotatedSource = project.file('filecheck/smoke0.kt') +} + +fileCheckTest("filecheck_replace_invoke_with_call") { + annotatedSource = project.file('filecheck/replace_invoke_with_call.kt') +} + +fileCheckTest("filecheck_intrinsics") { + annotatedSource = project.file('filecheck/intrinsics.kt') +} + +fileCheckTest("filecheck_escape_analysis_enabled") { + annotatedSource = project.file('filecheck/escape_analysis.kt') + enabled = project.globalTestArgs.contains('-opt') + checkPrefix = "CHECK-OPT" +} + +fileCheckTest("filecheck_escape_analysis_disabled") { + annotatedSource = project.file('filecheck/escape_analysis.kt') + enabled = project.globalTestArgs.contains('-g') + checkPrefix = "CHECK-DEBUG" +} + dependencies { nopPluginApi project(kotlinCompilerModule) nopPluginApi project(":native:kotlin-native-utils") diff --git a/kotlin-native/backend.native/tests/filecheck/escape_analysis.kt b/kotlin-native/backend.native/tests/filecheck/escape_analysis.kt new file mode 100644 index 00000000000..cb4ffb92871 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/escape_analysis.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2010-2021 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. + */ + +class A(val x: Int) + +// CHECK-LABEL: "kfun:#main(){}" +fun main() { + // CHECK-DEBUG: call %struct.ObjHeader* @AllocInstance + // CHECK-OPT: alloca %"kclassbody:A#internal" + val a = A(5) + println(a.x) +// CHECK-LABEL: epilogue +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/filecheck/intrinsics.kt b/kotlin-native/backend.native/tests/filecheck/intrinsics.kt new file mode 100644 index 00000000000..ab00fa8e161 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/intrinsics.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2021 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. + */ + +// CHECK-LABEL: "kfun:#and(kotlin.Int;kotlin.Int){}kotlin.Int" +fun and(a: Int, b: Int): Int { + // CHECK: and {{.*}}, {{.*}} + return a and b +// CHECK-LABEL: epilogue +} + +// CHECK-LABEL: "kfun:#ieee754(kotlin.Float;kotlin.Float){}kotlin.Boolean" +fun ieee754(a: Float, b: Float): Boolean { + // CHECK: fcmp oeq float {{.*}}, {{.*}} + return a == b +// CHECK-LABEL: epilogue +} + +// CHECK-LABEL: "kfun:#main(){}" +fun main() { + val x = and(1, 2) + val y = ieee754(0.0f, 1.0f) +// CHECK-LABEL: epilogue +} diff --git a/kotlin-native/backend.native/tests/filecheck/replace_invoke_with_call.kt b/kotlin-native/backend.native/tests/filecheck/replace_invoke_with_call.kt new file mode 100644 index 00000000000..4c1ebb24c4a --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/replace_invoke_with_call.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2021 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. + */ + +fun flameThrower() { + throw Throwable("🔥") +} + +// CHECK-LABEL: "kfun:#f1(){}" +fun f1() { + // CHECK: call void @"kfun:#flameThrower(){}"() + flameThrower() +// CHECK-LABEL: epilogue +} + +// CHECK-LABEL: "kfun:#f2(){}" +fun f2() { + try { + // CHECK: invoke void @"kfun:#flameThrower(){}"() + flameThrower() + } catch (t: Throwable) {} +// CHECK-LABEL: epilogue +} + +// CHECK-LABEL: "kfun:#main(){}" +fun main() { + f1() + f2() +// CHECK-LABEL: epilogue +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/filecheck/smoke0.kt b/kotlin-native/backend.native/tests/filecheck/smoke0.kt new file mode 100644 index 00000000000..f0768bc2c1b --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/smoke0.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2021 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. + */ + +// CHECK-LABEL: "kfun:#id(kotlin.Any?){}kotlin.Any?" +fun id(a: Any?): Any? { + return a +// CHECK-LABEL: epilogue +} + +// CHECK-LABEL: "kfun:#main(){}" +fun main() { + // CHECK: invoke %struct.ObjHeader* @"kfun:#id(kotlin.Any?){}kotlin.Any?" + val x = id("Hello") + // CHECK: invoke void @"kfun:kotlin.io#println(kotlin.Any?){}"(%struct.ObjHeader* {{.*}}) + println(x) +// CHECK-LABEL: epilogue +} \ No newline at end of file diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FileCheckTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FileCheckTest.kt new file mode 100644 index 00000000000..70ffde15977 --- /dev/null +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FileCheckTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2021 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 + +import groovy.lang.Closure +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileProgramTask +import org.jetbrains.kotlin.konan.target.AppleConfigurables +import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileTask +import java.io.File +import java.nio.file.Path +import java.nio.file.Files + +/** + * Gradle task that wraps FileCheck LLVM utility. + */ +open class FileCheckTest : DefaultTask() { + + private val target = project.testTarget + private val platform = project.platformManager.platform(target) + private val configurables = platform.configurables + + private val llvmBin = "${configurables.absoluteLlvmHome}/bin" + + private val fileCheck = "$llvmBin/FileCheck" + + /** + * File annotated with FileCheck directives. + */ + @InputFile + lateinit var annotatedSource: File + + /** + * LLVM IR that should match [annotatedSource]. + */ + @get:Internal + lateinit var llvmIr: File + + @TaskAction + fun run() { + runFileCheck(annotatedSource.toPath(), llvmIr.toPath()) + } + + /** + * What prefix should checked for pattern instead of default CHECK? + */ + @Input + @Optional + var checkPrefix: String? = null + + /** + * Check that [inputFile] matches [annotatedFile] with FileCheck. + */ + private fun runFileCheck(annotatedFile: Path, inputFile: Path): ProcessOutput { + val args = mutableListOf(annotatedFile.toAbsolutePath().toString(), "--input-file", inputFile.toAbsolutePath().toString()) + checkPrefix?.let { + args.addAll(listOf("--check-prefix", it)) + } + return runProcess(localExecutor(project), fileCheck, *args.toTypedArray()) + .ensureSuccessful(fileCheck, *args.toTypedArray()) + } + + private fun ProcessOutput.ensureSuccessful(vararg command: String): ProcessOutput { + if (exitCode != 0) { + println(""" + ${command.joinToString(separator = " ")} failed. + exitCode: $exitCode + stdout: + $stdOut + stderr: + $stdErr + """.trimIndent()) + throw TestFailedException("${command.joinToString(separator = " ")} failed") + } + return this + } +} \ No newline at end of file