From fdd020eab682d8393a77d68078e25e1f072bdf23 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Wed, 6 Oct 2021 15:16:26 +0700 Subject: [PATCH] [K/N] Tests for function attributes in bitcode generator Besides, well, tests themselves, this commit extends FileCheck infra to make it possible to test bitcode for direct and reverse interop. --- .../backend.native/tests/build.gradle | 66 +++++++-- .../function_attributes_at_callsite.kt | 23 ++++ .../tests/filecheck/signext_zeroext0.kt | 130 ++++++++++++++++++ .../filecheck/signext_zeroext_interop.kt | 63 +++++++++ .../signext_zeroext_interop_input.def | 20 +++ .../filecheck/signext_zeroext_objc_export.kt | 57 ++++++++ .../org/jetbrains/kotlin/FileCheckTest.kt | 14 ++ 7 files changed, 364 insertions(+), 9 deletions(-) create mode 100644 kotlin-native/backend.native/tests/filecheck/function_attributes_at_callsite.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/signext_zeroext0.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop_input.def create mode 100644 kotlin-native/backend.native/tests/filecheck/signext_zeroext_objc_export.kt diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 759b2f74656..9fb9f4cbb9d 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -5830,15 +5830,39 @@ Task fileCheckTest(String name, Closure 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" + def lib = task.interop + if (lib != null) { + UtilsKt.dependsOnKonanBuildingTask(task, lib, target) + } + if (!task.generateFramework) { + // 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" + if (lib != null) { + libraries { + artifact lib + } + } + } + } else { + // Framework support is much weakier than `frameworkTest`, but we don't need much. + framework(name, targets: [target]) { + srcFiles task.annotatedSource + baseDir "$testOutputFileCheck/$name" + extraOpts project.globalTestArgs + extraOpts "-Xtemporary-files-dir=$testOutputFileCheck/$name", "-Xsave-llvm-ir", "-Xmeaningful-bridge-names" + if (lib != null) { + libraries { + artifact lib + } + } + } } UtilsKt.dependsOnKonanBuildingTask(task, name, target) } @@ -5874,6 +5898,30 @@ fileCheckTest("filecheck_bce") { annotatedSource = project.file('filecheck/bce.kt') } +fileCheckTest("filecheck_signext_zeroext0") { + annotatedSource = project.file('filecheck/signext_zeroext0.kt') +} + +createInterop("filecheck_signext_zeroext_interop_input") { + it.defFile 'filecheck/signext_zeroext_interop_input.def' +} + +fileCheckTest("filecheck_signext_zeroext_interop") { + annotatedSource = project.file('filecheck/signext_zeroext_interop.kt') + interop = "filecheck_signext_zeroext_interop_input" +} + +fileCheckTest("filecheck_signext_zeroext_objc_export") { + annotatedSource = project.file('filecheck/signext_zeroext_objc_export.kt') + generateFramework = true + enabled = target.family.appleFamily +} + +fileCheckTest("filecheck_function_attributes_at_callsite") { + annotatedSource = project.file('filecheck/function_attributes_at_callsite.kt') +} + + dependencies { nopPluginApi project(kotlinCompilerModule) nopPluginApi project(":native:kotlin-native-utils") diff --git a/kotlin-native/backend.native/tests/filecheck/function_attributes_at_callsite.kt b/kotlin-native/backend.native/tests/filecheck/function_attributes_at_callsite.kt new file mode 100644 index 00000000000..4ffd728378f --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/function_attributes_at_callsite.kt @@ -0,0 +1,23 @@ +/* + * 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: declare void @ThrowException(%struct.ObjHeader*) #[[THROW_EXCEPTION_DECLARATION_ATTRIBUTES:[0-9]+]] + +// CHECK: void @"kfun:#flameThrower(){}kotlin.Nothing"() #[[FLAME_THROWER_DECLARATION_ATTRIBUTES:[0-9]+]] +fun flameThrower(): Nothing { + // CHECK: invoke void @ThrowException(%struct.ObjHeader* {{.*}}) #[[THROW_EXCEPTION_CALLSITE_ATTRIBUTES:[0-9]+]] + throw Throwable("🔥") +} + +// CHECK-LABEL: void @"kfun:#main(){}"() +fun main() { + // CHECK: call void @"kfun:#flameThrower(){}kotlin.Nothing"() #[[FLAME_THROWER_CALLSITE_ATTRIBUTES:[0-9]+]] + flameThrower() +} + +// CHECK-DAG: attributes #[[THROW_EXCEPTION_DECLARATION_ATTRIBUTES]] = { noreturn uwtable {{.+}} } +// CHECK-DAG: attributes #[[THROW_EXCEPTION_CALLSITE_ATTRIBUTES]] = { noreturn uwtable } +// CHECK-DAG: attributes #[[FLAME_THROWER_DECLARATION_ATTRIBUTES]] = { noreturn {{.+}} } +// CHECK-DAG: attributes #[[FLAME_THROWER_CALLSITE_ATTRIBUTES]] = { noreturn } \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/filecheck/signext_zeroext0.kt b/kotlin-native/backend.native/tests/filecheck/signext_zeroext0.kt new file mode 100644 index 00000000000..9f66bc761cf --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/signext_zeroext0.kt @@ -0,0 +1,130 @@ +/* + * 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: zeroext i1 @"kfun:#id(kotlin.Boolean){}kotlin.Boolean"(i1 zeroext %0) +fun id(arg: Boolean): Boolean { + return arg +} + +// CHECK-LABEL: signext i8 @"kfun:#id(kotlin.Byte){}kotlin.Byte"(i8 signext %0) +fun id(arg: Byte): Byte { + return arg +} + +// CHECK-LABEL: zeroext i16 @"kfun:#id(kotlin.Char){}kotlin.Char"(i16 zeroext %0) +fun id(arg: Char): Char { + return arg +} + +// CHECK-LABEL: signext i16 @"kfun:#id(kotlin.Short){}kotlin.Short"(i16 signext %0) +fun id(arg: Short): Short { + return arg +} + +// CHECK-LABEL: i32 @"kfun:#id(kotlin.Int){}kotlin.Int"(i32 %0) +fun id(arg: Int): Int { + return arg +} + +// CHECK-LABEL: i64 @"kfun:#id(kotlin.Long){}kotlin.Long"(i64 %0) +fun id(arg: Long): Long { + return arg +} + +// CHECK-LABEL: float @"kfun:#id(kotlin.Float){}kotlin.Float"(float %0) +fun id(arg: Float): Float { + return arg +} + +// CHECK-LABEL: double @"kfun:#id(kotlin.Double){}kotlin.Double"(double %0) +fun id(arg: Double): Double { + return arg +} + +// CHECK-LABEL: define zeroext i8 @"kfun:#id(kotlin.UByte){}kotlin.UByte"(i8 zeroext %0) +fun id(arg: UByte): UByte { + return arg +} + +// CHECK-LABEL: define zeroext i16 @"kfun:#id(kotlin.UShort){}kotlin.UShort"(i16 zeroext %0) +fun id(arg: UShort): UShort { + return arg +} + +// CHECK-LABEL: define i32 @"kfun:#id(kotlin.UInt){}kotlin.UInt"(i32 %0) +fun id(arg: UInt): UInt { + return arg +} + +// CHECK-LABEL: define i64 @"kfun:#id(kotlin.ULong){}kotlin.ULong"(i64 %0) +fun id(arg: ULong): ULong { + return arg +} + +fun checkPrimitives() { + // CHECK: call zeroext i1 @"kfun:#id(kotlin.Boolean){}kotlin.Boolean"(i1 zeroext {{.*}}) + id(true) + // CHECK: call signext i8 @"kfun:#id(kotlin.Byte){}kotlin.Byte"(i8 signext {{.*}}) + id(0.toByte()) + // CHECK: call zeroext i16 @"kfun:#id(kotlin.Char){}kotlin.Char"(i16 zeroext {{.*}}) + id('a') + // CHECK: call signext i16 @"kfun:#id(kotlin.Short){}kotlin.Short"(i16 signext {{.*}}) + id(0.toShort()) + // CHECK: call i32 @"kfun:#id(kotlin.Int){}kotlin.Int"(i32 {{.*}}) + id(0) + // CHECK: call i64 @"kfun:#id(kotlin.Long){}kotlin.Long"(i64 {{.*}}) + id(0L) + // CHECK: call float @"kfun:#id(kotlin.Float){}kotlin.Float"(float {{.*}}) + id(0.5f) + // CHECK: call double @"kfun:#id(kotlin.Double){}kotlin.Double"(double {{.*}}) + id(0.5) + // CHECK: call zeroext i8 @"kfun:#id(kotlin.UByte){}kotlin.UByte"(i8 zeroext {{.*}}) + id(0.toUByte()) + // CHECK: call zeroext i16 @"kfun:#id(kotlin.UShort){}kotlin.UShort"(i16 zeroext {{.*}}) + id(0.toUShort()) + // CHECK: call i32 @"kfun:#id(kotlin.UInt){}kotlin.UInt"(i32 {{.*}}) + id(15u) + // CHECK: call i64 @"kfun:#id(kotlin.ULong){}kotlin.ULong"(i64 {{.*}}) + id(15uL) +} + +value class CharWrapper(val ch: Char) + +// CHECK-LABEL: zeroext i16 @"kfun:#id(CharWrapper){}CharWrapper"(i16 zeroext %0) +fun id(arg: CharWrapper): CharWrapper { + return arg +} + +// Check that value classes doesn't affect parameter attributes + +fun checkInlineClasses() { + // CHECK: call zeroext i16 @"kfun:#id(CharWrapper){}CharWrapper"(i16 zeroext {{.*}}) + id(CharWrapper('c')) +} + +// CHECK-LABEL: %struct.ObjHeader* @"kfun:#nullableId(kotlin.Byte?){}kotlin.Byte?"(%struct.ObjHeader* %0, %struct.ObjHeader** %1) +fun nullableId(arg: Byte?): Byte? { + return arg +} + +// CHECK-LABEL: %struct.ObjHeader* @"kfun:#nullableId(CharWrapper?){}CharWrapper?"(%struct.ObjHeader* %0, %struct.ObjHeader** %1) +fun nullableId(arg: CharWrapper?): CharWrapper? { + return arg +} + +// Check that we don't pass primitive-specific attributes to their boxes +fun checkBoxes() { + // CHECK: invoke %struct.ObjHeader* @"kfun:#nullableId(kotlin.Byte?){}kotlin.Byte?"(%struct.ObjHeader* {{.*}}, %struct.ObjHeader** {{.*}}) + nullableId(1.toByte()) + // CHECK: invoke %struct.ObjHeader* @"kfun:#nullableId(CharWrapper?){}CharWrapper?"(%struct.ObjHeader* {{.*}}, %struct.ObjHeader** {{.*}}) + nullableId(CharWrapper('a')) +} + +// CHECK-LABEL: void @"kfun:#main(){}"() +fun main() { + checkPrimitives() + checkInlineClasses() + checkBoxes() +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop.kt b/kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop.kt new file mode 100644 index 00000000000..0c76ac0687f --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop.kt @@ -0,0 +1,63 @@ +/* + * 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. + */ +import signext_zeroext_interop_input.* +import kotlinx.cinterop.* + +// CHECK: declare zeroext i1 @Kotlin_Char_isHighSurrogate(i16 zeroext) + +// Check that we pass attributes to functions imported from runtime. +// CHECK-LABEL: void @"kfun:#checkRuntimeFunctionImport(){}"() +fun checkRuntimeFunctionImport() { + // CHECK: call zeroext i1 @Kotlin_Char_isHighSurrogate(i16 zeroext {{.*}}) + 'c'.isHighSurrogate() + // CHECK: call zeroext i1 @Kotlin_Float_isNaN(float {{.*}} + 0.0f.isNaN() +} + +// CHECK-LABEL: void @"kfun:#checkDirectInterop(){}"() +fun checkDirectInterop() { + // compiler generates quite lovely names for bridges + // (e.g. `_66696c65636865636b5f7369676e6578745f7a65726f6578745f696e7465726f70_knbridge0`), + // so we don't check exact function names here. + // CHECK: invoke signext i8 [[CHAR_ID_BRIDGE:@_.*_knbridge[0-9]+]](i8 signext {{.*}}) + char_id(0.toByte()) + // CHECK: invoke zeroext i8 [[UNSIGNED_CHAR_ID_BRIDGE:@_.*_knbridge[0-9]+]](i8 zeroext {{.*}}) + unsigned_char_id(0.toUByte()) + // CHECK: invoke signext i16 [[SHORT_ID_BRIDGE:@_.*_knbridge[0-9]+]](i16 signext {{.*}}) + short_id(0.toShort()) + // CHECK: invoke zeroext i16 [[UNSIGNED_SHORT_ID_BRIDGE:@_.*_knbridge[0-9]+]](i16 zeroext {{.*}}) + unsigned_short_id(0.toUShort()) + // CHECK: invoke i32 [[CALLBACK_USER_BRIDGE:@_.*_knbridge[0-9]+]](i8* bitcast (i32 (i32, i16)* [[STATIC_C_FUNCTION_BRIDGE:@_.*_kncfun[0-9]+]] to i8*)) + callbackUser(staticCFunction { int: Int, short: Short -> int + short }) +} + +// CHECK-LABEL: void @"kfun:#main(){}"() +fun main() { + checkRuntimeFunctionImport() + checkDirectInterop() +} + +// CHECK: define signext i8 [[CHAR_ID_BRIDGE]](i8 signext %0) +// CHECK: [[CHAR_ID_PTR:%[0-9]+]] = load i8 (i8)*, i8 (i8)** @{{.*char_id}} +// CHECK: call signext i8 [[CHAR_ID_PTR]](i8 signext %0) + +// CHECK: define zeroext i8 [[UNSIGNED_CHAR_ID_BRIDGE]](i8 zeroext %0) +// CHECK: [[UNSIGNED_CHAR_ID_PTR:%[0-9]+]] = load i8 (i8)*, i8 (i8)** @{{.*unsigned_char_id}} +// CHECK: call zeroext i8 [[UNSIGNED_CHAR_ID_PTR]](i8 zeroext %0) + +// CHECK: define signext i16 [[SHORT_ID_BRIDGE]](i16 signext %0) +// CHECK: [[SHORT_ID_PTR:%[0-9]+]] = load i16 (i16)*, i16 (i16)** @{{.*short_id}} +// CHECK: call signext i16 [[SHORT_ID_PTR]](i16 signext %0) + +// CHECK: define zeroext i16 [[UNSIGNED_SHORT_ID_BRIDGE]](i16 zeroext %0) +// CHECK: [[UNSIGNED_SHORT_ID_PTR:%[0-9]+]] = load i16 (i16)*, i16 (i16)** @{{.*unsigned_short_id}} +// CHECK: call zeroext i16 [[UNSIGNED_SHORT_ID_PTR]](i16 zeroext %0) + +// CHECK: define i32 [[STATIC_C_FUNCTION_BRIDGE]](i32 %0, i16 signext %1) +// CHECK: call i32 {{@_.*_knbridge[0-9]+}}(i32 %0, i16 signext %1) + +// CHECK: define i32 [[CALLBACK_USER_BRIDGE]](i8* %0) +// CHECK: [[CALLBACK_USER_PTR:%[0-9]+]] = load i32 (i8*)*, i32 (i8*)** @{{.*callbackUser}} +// CHECK: call i32 [[CALLBACK_USER_PTR]](i8* %0) diff --git a/kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop_input.def b/kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop_input.def new file mode 100644 index 00000000000..e80d912875c --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/signext_zeroext_interop_input.def @@ -0,0 +1,20 @@ +--- +char char_id(char c) { + return c; +} + +unsigned char unsigned_char_id(unsigned char c) { + return c; +} + +short short_id(short s) { + return s; +} + +unsigned short unsigned_short_id(unsigned short s) { + return s; +} + +int callbackUser(int (*fn)(int, short)) { + return fn(5,5); +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/filecheck/signext_zeroext_objc_export.kt b/kotlin-native/backend.native/tests/filecheck/signext_zeroext_objc_export.kt new file mode 100644 index 00000000000..353f9d290e9 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/signext_zeroext_objc_export.kt @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// +// Here we check generated ObjC bridges, not original declarations. +// + +// CHECK-LABEL: zeroext i16 @objc2kotlin_heyIJustMetYou(i8* %0, i8* %1, i16 zeroext %2) +fun heyIJustMetYou(arg: Char): Char { + // CHECK: invoke zeroext i16 @"kfun:#heyIJustMetYou(kotlin.Char){}kotlin.Char"(i16 zeroext %2) + return arg +} + +// CHECK-LABEL: signext i8 @objc2kotlin_andThisIsCrazy(i8* %0, i8* %1, i8 signext %2) +fun andThisIsCrazy(arg: Byte): Byte { + // CHECK: invoke signext i8 @"kfun:#andThisIsCrazy(kotlin.Byte){}kotlin.Byte"(i8 signext %2) + return arg +} + +// CHECK-LABEL: signext i16 @objc2kotlin_butHereIsMyNumber(i8* %0, i8* %1, i16 signext %2) +fun butHereIsMyNumber(arg: Short): Short { + // CHECK: invoke signext i16 @"kfun:#butHereIsMyNumber(kotlin.Short){}kotlin.Short"(i16 signext %2) + return arg +} + +// CHECK-LABEL: signext i8 @objc2kotlin_soCallMeMaybe(i8* %0, i8* %1, i8 signext %2) +fun soCallMeMaybe(arg: Boolean): Boolean { + //CHECK: invoke zeroext i1 @"kfun:#soCallMeMaybe(kotlin.Boolean){}kotlin.Boolean"(i1 zeroext {{.*}}) + return arg +} + +// CHECK-LABEL: zeroext i8 @objc2kotlin_itsHardToLook(i8* %0, i8* %1, i8 zeroext %2) +fun itsHardToLook(arg: UByte): UByte { + // CHECK: invoke zeroext i8 @"kfun:#itsHardToLook(kotlin.UByte){}kotlin.UByte"(i8 zeroext %2) + return arg +} + +// CHECK-LABEL: zeroext i16 @objc2kotlin_rightAtYouBaby(i8* %0, i8* %1, i16 zeroext %2) +fun rightAtYouBaby(arg: UShort): UShort { + // CHECK: invoke zeroext i16 @"kfun:#rightAtYouBaby(kotlin.UShort){}kotlin.UShort"(i16 zeroext %2) + return arg +} + +// CHECK-LABEL: float @objc2kotlin_butHereIsMyNumber1(i8* %0, i8* %1, float %2) +fun butHereIsMyNumber1(arg: Float): Float { + // CHECK: invoke float @"kfun:#butHereIsMyNumber1(kotlin.Float){}kotlin.Float"(float %2) + return arg +} + +// CHECK-LABEL: i8* @objc2kotlin_soCallMeMaybe1(i8* %0, i8* %1, i8* %2) +fun soCallMeMaybe1(arg: Any?): Any? { + // CHECK: invoke %struct.ObjHeader* @"kfun:#soCallMeMaybe1(kotlin.Any?){}kotlin.Any?"(%struct.ObjHeader* {{.*}}, %struct.ObjHeader** {{.*}}) + return arg +} + 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 index 70ffde15977..bc00b4432ba 100644 --- 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 @@ -42,6 +42,13 @@ open class FileCheckTest : DefaultTask() { @get:Internal lateinit var llvmIr: File + /** + * Optional cinterop task dependency. + */ + @Optional + @Input + var interop: String? = null + @TaskAction fun run() { runFileCheck(annotatedSource.toPath(), llvmIr.toPath()) @@ -54,6 +61,13 @@ open class FileCheckTest : DefaultTask() { @Optional var checkPrefix: String? = null + /** + * Should we generate framework instead of an executable? + * This option is useful for, well, checking framework-specific code. + */ + @Input + var generateFramework: Boolean = false + /** * Check that [inputFile] matches [annotatedFile] with FileCheck. */