From 6ca196399a245821887c019898d80f5f7f8931c8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 10 Jan 2019 13:59:58 +0300 Subject: [PATCH] Implement C stubs generation in compiler Use it in * C varargs support (thus dropping libffi from runtime) * C callbacks support (thus enabling callbacks with structs) --- .../kotlin/native/interop/indexer/Indexer.kt | 71 +- .../native/interop/indexer/NativeIndex.kt | 29 +- .../kotlin/native/interop/indexer/Utils.kt | 17 + .../src/main/kotlin/kotlinx/cinterop/Utils.kt | 11 +- .../kotlinx/cinterop/FunctionPointers.kt | 86 -- .../native/kotlin/kotlinx/cinterop/Varargs.kt | 204 ----- .../kotlinx/cinterop/internal/Annotations.kt | 17 + .../kotlin/native/interop/gen/ObjCStubs.kt | 9 +- .../native/interop/gen/StructRendering.kt | 82 ++ .../native/interop/gen/jvm/StubGenerator.kt | 94 +- .../kotlin/backend/konan/CStubsManager.kt | 81 ++ .../kotlin/backend/konan/CompilerOutput.kt | 21 +- .../jetbrains/kotlin/backend/konan/Context.kt | 3 + .../kotlin/backend/konan/InteropUtils.kt | 34 +- .../backend/konan/KonanBackendContext.kt | 33 +- .../kotlin/backend/konan/KonanPhases.kt | 1 + .../kotlin/backend/konan/LinkStage.kt | 3 +- .../kotlin/backend/konan/RuntimeNames.kt | 9 + .../kotlin/backend/konan/cgen/CBridgeGen.kt | 803 ++++++++++++++++++ .../backend/konan/cgen/CBridgeGenUtils.kt | 234 +++++ .../backend/konan/cgen/CSyntaxSupport.kt | 58 ++ .../descriptors/LegacyDescriptorUtils.kt | 14 + .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 54 +- .../konan/irasdescriptors/NewIrUtils.kt | 11 + .../backend/konan/llvm/BinaryInterface.kt | 5 +- .../kotlin/backend/konan/llvm/ContextUtils.kt | 17 +- .../kotlin/backend/konan/llvm/Imports.kt | 2 + .../backend/konan/llvm/LlvmDeclarations.kt | 5 +- .../backend/konan/lower/InteropLowering.kt | 94 +- .../org/jetbrains/kotlin/ir/util/IrUtils2.kt | 31 + backend.native/tests/build.gradle | 10 + .../interop/basics/callbacksAndVarargs.kt | 51 ++ .../interop/basics/ccallbacksAndVarargs.def | 72 ++ konan/konan.properties | 40 +- konan/platforms/zephyr/stm32f4_disco | 3 + runtime/build.gradle | 2 - runtime/src/main/cpp/Interop.cpp | 74 -- .../kotlin/konan/exec/ExecuteCommand.kt | 13 +- .../kotlin/konan/target/Configurables.kt | 3 +- .../jetbrains/kotlin/konan/target/Linker.kt | 3 - 40 files changed, 1781 insertions(+), 623 deletions(-) delete mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt create mode 100644 backend.native/tests/interop/basics/callbacksAndVarargs.kt create mode 100644 backend.native/tests/interop/basics/ccallbacksAndVarargs.def diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 98e5ebdece4..6e257129e64 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -27,14 +27,11 @@ private class StructDeclImpl(spelling: String, override val location: Location) private class StructDefImpl( size: Long, align: Int, decl: StructDecl, - hasNaturalLayout: Boolean + override val kind: Kind ) : StructDef( - size, align, decl, - hasNaturalLayout = hasNaturalLayout + size, align, decl ) { - - override val fields = mutableListOf() - override val bitFields = mutableListOf() + override val members = mutableListOf() } private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) { @@ -196,39 +193,56 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue) { val type = clang_getCursorType(cursor) + + val fields = mutableListOf() + addDeclaredFields(fields, type, type) + val size = clang_Type_getSizeOf(type) val align = clang_Type_getAlignOf(type).toInt() val structDef = StructDefImpl( size, align, structDecl, - hasNaturalLayout = structHasNaturalLayout(cursor) + when (cursor.kind) { + CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION + CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT + else -> error(cursor.kind) + } ) - structDecl.def = structDef + structDef.members += fields - addDeclaredFields(structDef, type, type) + structDecl.def = structDef } - private fun addDeclaredFields(structDef: StructDefImpl, structType: CValue, containerType: CValue) { + private fun addDeclaredFields(result: MutableList, structType: CValue, containerType: CValue) { getFields(containerType).forEach { fieldCursor -> val name = getCursorSpelling(fieldCursor) if (name.isNotEmpty()) { val fieldType = convertCursorType(fieldCursor) val offset = clang_Type_getOffsetOf(structType, name) - if (clang_Cursor_isBitField(fieldCursor) == 0) { - val typeAlign = clang_Type_getAlignOf(clang_getCursorType(fieldCursor)) - structDef.fields.add(Field(name, fieldType, offset, typeAlign)) + val member = if (offset < 0) { + IncompleteField(name, fieldType) + } else if (clang_Cursor_isBitField(fieldCursor) == 0) { + val canonicalFieldType = clang_getCanonicalType(clang_getCursorType(fieldCursor)) + Field( + name, + fieldType, + offset, + clang_Type_getSizeOf(canonicalFieldType), + clang_Type_getAlignOf(canonicalFieldType) + ) } else { val size = clang_getFieldDeclBitWidth(fieldCursor) - structDef.bitFields.add(BitField(name, fieldType, offset, size)) + BitField(name, fieldType, offset, size) } + result.add(member) } else { // Unnamed field. val fieldType = clang_getCursorType(fieldCursor) when (fieldType.kind) { CXTypeKind.CXType_Record -> { // Unnamed struct fields also contribute their fields: - addDeclaredFields(structDef, structType, fieldType) + addDeclaredFields(result, structType, fieldType) } else -> { // Nothing. @@ -447,33 +461,6 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean return Typedef(typedefDef) } - /** - * Computes [StructDef.hasNaturalLayout] property. - */ - fun structHasNaturalLayout(structDefCursor: CValue): Boolean { - val defKind = structDefCursor.kind - - when (defKind) { - - CXCursorKind.CXCursor_UnionDecl -> return false - - CXCursorKind.CXCursor_StructDecl -> { - var hasAttributes = false - - visitChildren(structDefCursor) { cursor, _ -> - if (clang_isAttribute(cursor.kind) != 0) { - hasAttributes = true - } - CXChildVisitResult.CXChildVisit_Continue - } - - return !hasAttributes - } - - else -> throw IllegalArgumentException(defKind.toString()) - } - } - private fun convertCursorType(cursor: CValue) = convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor)) diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index 3d942d86196..3ee5e49aef8 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -85,15 +85,24 @@ interface TypeDeclaration { val location: Location } +sealed class StructMember(val name: String, val type: Type) { + abstract val offset: Long? +} + /** * C struct field. */ -class Field(val name: String, val type: Type, val offset: Long, val typeAlign: Long) +class Field(name: String, type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long) + : StructMember(name, type) val Field.isAligned: Boolean get() = offset % (typeAlign * 8) == 0L -class BitField(val name: String, val type: Type, val offset: Long, val size: Int) +class BitField(name: String, type: Type, override val offset: Long, val size: Int) : StructMember(name, type) + +class IncompleteField(name: String, type: Type) : StructMember(name, type) { + override val offset: Long? get() = null +} /** * C struct declaration. @@ -109,13 +118,17 @@ abstract class StructDecl(val spelling: String) : TypeDeclaration { * @param hasNaturalLayout must be `false` if the struct has unnatural layout, e.g. it is `packed`. * May be `false` even if the struct has natural layout. */ -abstract class StructDef(val size: Long, val align: Int, - val decl: StructDecl, - val hasNaturalLayout: Boolean) { +abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) { - abstract val fields: List - // TODO: merge two lists to preserve declaration order. - abstract val bitFields: List + enum class Kind { + STRUCT, UNION + } + + abstract val members: List + abstract val kind: Kind + + val fields: List get() = members.filterIsInstance() + val bitFields: List get() = members.filterIsInstance() } /** diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt index ca1a0d4b2d8..64e175dc382 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -183,6 +183,23 @@ internal fun getFields(type: CValue): List> { return result } +fun StructDef.fieldsHaveDefaultAlignment(): Boolean { + fun alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and (alignment - 1).inv() + + var offset = 0L + this.members.forEach { + when (it) { + is Field -> { + if (alignUp(offset, it.typeAlign) * 8 != it.offset) return false + offset = it.offset / 8 + it.typeSize + } + is BitField -> return false + } + } + + return true +} + internal fun CValue.isLeaf(): Boolean { var hasChildren = false diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index 1d14a81c424..907f8a38129 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -107,7 +107,11 @@ class Arena(parent: NativeFreeablePlacement = nativeHeap) : ArenaBase(parent) { * @param T must not be abstract */ inline fun NativePlacement.alloc(): T = - alloc(sizeOf(), alignOf()).reinterpret() + alloc(typeOf()).reinterpret() + +@PublishedApi +internal fun NativePlacement.alloc(type: CVariable.Type): NativePointed = + alloc(type.size, type.align) /** * Allocates variable of given type and initializes it applying given block. @@ -263,9 +267,12 @@ fun CPointed.readValue(size: Long, align: Int): CValue { } } +@PublishedApi internal fun CPointed.readValue(type: CVariable.Type): CValue = + readValue(type.size, type.align) + // Note: can't be declared as property due to possible clash with a struct field. // TODO: find better name. -inline fun T.readValue(): CValue = this.readValue(sizeOf(), alignOf()) +inline fun T.readValue(): CValue = this.readValue(typeOf()) fun CValue<*>.write(location: NativePtr) { // TODO: probably CValue must be redesigned. diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt index 8cfa7db7f18..b59df85afcc 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt @@ -65,89 +65,3 @@ import kotlin.native.internal.ExportForCompiler @TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21): R @TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R - -@ExportForCompiler -private fun invokeImplUnitRet(ptr: COpaquePointer, vararg args: Any?): Unit = memScoped { - callWithVarargs(ptr.rawValue, nativeNullPtr, FFI_TYPE_KIND_VOID, args, null, memScope) -} - -@ExportForCompiler -private fun invokeImplBooleanRet(ptr: COpaquePointer, vararg args: Any?): Boolean = - invokeImplByteRet(ptr, *args).toBoolean() - -@ExportForCompiler -private fun invokeImplByteRet(ptr: COpaquePointer, vararg args: Any?): Byte = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(ByteVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_SINT8, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplShortRet(ptr: COpaquePointer, vararg args: Any?): Short = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(ShortVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_SINT16, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplIntRet(ptr: COpaquePointer, vararg args: Any?): Int = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(IntVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_SINT32, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplLongRet(ptr: COpaquePointer, vararg args: Any?): Long = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(LongVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_SINT64, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplUByteRet(ptr: COpaquePointer, vararg args: Any?): UByte = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(UByteVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_UINT8, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplUShortRet(ptr: COpaquePointer, vararg args: Any?): UShort = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(UShortVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_UINT16, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplUIntRet(ptr: COpaquePointer, vararg args: Any?): UInt = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(UIntVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_UINT32, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplULongRet(ptr: COpaquePointer, vararg args: Any?): ULong = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(ULongVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_UINT64, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplFloatRet(ptr: COpaquePointer, vararg args: Any?): Float = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(FloatVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_FLOAT, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplDoubleRet(ptr: COpaquePointer, vararg args: Any?): Double = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(DoubleVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_DOUBLE, args, null, memScope) - resultBuffer.value -} - -@ExportForCompiler -private fun invokeImplPointerRet(ptr: COpaquePointer, vararg args: Any?): COpaquePointer? = memScoped { - val resultBuffer = allocFfiReturnValueBuffer(COpaquePointerVar) - callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_POINTER, args, null, memScope) - resultBuffer.value -} diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt deleted file mode 100644 index 90fb9448c21..00000000000 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package kotlinx.cinterop -import kotlin.native.* - -private const val MAX_ARGUMENT_SIZE = 8 - - -typealias FfiTypeKind = Int -// Also declared in Interop.cpp -const val FFI_TYPE_KIND_VOID: FfiTypeKind = 0 -const val FFI_TYPE_KIND_SINT8: FfiTypeKind = 1 -const val FFI_TYPE_KIND_SINT16: FfiTypeKind = 2 -const val FFI_TYPE_KIND_SINT32: FfiTypeKind = 3 -const val FFI_TYPE_KIND_SINT64: FfiTypeKind = 4 -const val FFI_TYPE_KIND_FLOAT: FfiTypeKind = 5 -const val FFI_TYPE_KIND_DOUBLE: FfiTypeKind = 6 -const val FFI_TYPE_KIND_POINTER: FfiTypeKind = 7 -const val FFI_TYPE_KIND_UINT8: FfiTypeKind = 8 -const val FFI_TYPE_KIND_UINT16: FfiTypeKind = 9 -const val FFI_TYPE_KIND_UINT32: FfiTypeKind = 10 -const val FFI_TYPE_KIND_UINT64: FfiTypeKind = 11 - -private tailrec fun convertArgument( - argument: Any?, isVariadic: Boolean, location: COpaquePointer, - additionalPlacement: AutofreeScope -): FfiTypeKind = when (argument) { // FIXME: optimize - is CValuesRef<*>? -> { - location.reinterpret>()[0] = argument?.getPointer(additionalPlacement) - FFI_TYPE_KIND_POINTER - } - - is String -> { - location.reinterpret>()[0] = if (!isVariadic) { - // If it is fixed argument, then it is not C string because it must have been already converted; - // then treat it as NSString. - // TODO: handle fixed NSString arguments in the stub instead. - interpretCPointer(argument.objcPtr()) - } else { - // It is passed as variadic argument; no type information available, so treat it as C string. - argument.cstr.getPointer(additionalPlacement) - } - FFI_TYPE_KIND_POINTER - } - - is Int -> { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_SINT32 - } - - is Long -> { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_SINT64 - } - - is Boolean -> convertArgument(argument.toByte(), isVariadic, location, additionalPlacement) - - is Byte -> if (isVariadic) { - convertArgument(argument.toInt(), isVariadic, location, additionalPlacement) - } else { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_SINT8 - } - - is Short -> if (isVariadic) { - convertArgument(argument.toInt(), isVariadic, location, additionalPlacement) - } else { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_SINT16 - } - - is UInt -> { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_UINT32 - } - - is ULong -> { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_UINT64 - } - - is UByte -> if (isVariadic) { - convertArgument(argument.toUInt(), isVariadic, location, additionalPlacement) - } else { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_UINT8 - } - - is UShort -> if (isVariadic) { - convertArgument(argument.toUInt(), isVariadic, location, additionalPlacement) - } else { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_UINT16 - } - - is Double -> { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_DOUBLE - } - - is Float -> if (isVariadic) { - convertArgument(argument.toDouble(), isVariadic, location, additionalPlacement) - } else { - location.reinterpret()[0] = argument - FFI_TYPE_KIND_FLOAT - } - - is CEnum -> convertArgument(argument.value, isVariadic, location, additionalPlacement) - - is ForeignObjCObject -> { - location.reinterpret()[0] = interpretCPointer(argument.objcPtr()) - FFI_TYPE_KIND_POINTER - } - - else -> throw Error("unsupported argument: $argument") -} - -inline fun NativePlacement.allocFfiReturnValueBuffer(type: CVariable.Type): T { - var size = type.size - var align = type.align - - // libffi requires return value buffer to be no smaller than system register size; - // TODO: system register size is not exactly the same as pointer size. - - if (size < pointerSize) { - size = pointerSize.toLong() - } - - if (align < pointerSize) { - align = pointerSize - } - - return this.alloc(size, align).reinterpret() -} - -fun callWithVarargs(codePtr: NativePtr, returnValuePtr: NativePtr, returnTypeKind: FfiTypeKind, - fixedArguments: Array, variadicArguments: Array?, - argumentsPlacement: AutofreeScope) { - - val totalArgumentsNumber = fixedArguments.size + if (variadicArguments == null) 0 else variadicArguments.size - - // All supported arguments take at most 8 bytes each: - val argumentsStorage = argumentsPlacement.allocArray(totalArgumentsNumber) - val arguments = argumentsPlacement.allocArray>(totalArgumentsNumber) - val types = argumentsPlacement.allocArray(totalArgumentsNumber) - - var index = 0 - - inline fun addArgument(argument: Any?, isVariadic: Boolean) { - val storage = (argumentsStorage + index)!! - val typeKind = convertArgument(argument, isVariadic = isVariadic, - location = storage, additionalPlacement = argumentsPlacement) - - types[index] = typeKind.toLong().toCPointer() - arguments[index] = storage - - ++index - } - - for (argument in fixedArguments) { - addArgument(argument, isVariadic = false) - } - - val variadicArgumentsNumber: Int - - if (variadicArguments != null) { - for (argument in variadicArguments) { - addArgument(argument, isVariadic = true) - } - variadicArgumentsNumber = variadicArguments.size - } else { - variadicArgumentsNumber = -1 - } - - assert (index == totalArgumentsNumber) - - callFunctionPointer(codePtr, returnValuePtr, returnTypeKind, arguments.rawValue, types.rawValue, - totalArgumentsNumber, variadicArgumentsNumber) -} - -@SymbolName("Kotlin_Interop_callFunctionPointer") -private external fun callFunctionPointer( - codePtr: NativePtr, - returnValuePtr: NativePtr, - returnTypeKind: FfiTypeKind, - arguments: NativePtr, - argumentTypeKinds: NativePtr, - totalArgumentsNumber: Int, - variadicArgumentsNumber: Int -) diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt new file mode 100644 index 00000000000..c613f4700cf --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt @@ -0,0 +1,17 @@ +package kotlinx.cinterop.internal + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +annotation class CStruct(val spelling: String) + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +public annotation class CCall(val id: String) { + @Target(AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.BINARY) + annotation class CString + + @Target(AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.BINARY) + annotation class WCString +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index bb7bbd2b5bf..fc8e866c7f8 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -342,8 +342,13 @@ private fun Type.isIntegerLikeType(): Boolean = when (this) { false } else { def.size <= 4 && - def.bitFields.all { it.type.isIntegerLikeType() } && - def.fields.all { it.offset == 0L && it.type.isIntegerLikeType() } + def.members.all { + when (it) { + is BitField -> it.type.isIntegerLikeType() + is Field -> it.offset == 0L && it.type.isIntegerLikeType() + is IncompleteField -> false + } + } } } is ObjCPointer, is PointerType, CharType, BoolType -> true diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt new file mode 100644 index 00000000000..7605c47b0ed --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt @@ -0,0 +1,82 @@ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.indexer.* + +fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) { + StructDef.Kind.STRUCT -> tryRenderStruct(def) + StructDef.Kind.UNION -> tryRenderUnion(def) +} + +private fun tryRenderStruct(def: StructDef): String? { + val isPackedStruct = def.fields.any { !it.isAligned } + + var offset = 0L + + return buildString { + append("struct") + if (isPackedStruct) append(" __attribute__((packed))") + append(" { ") + + def.members.forEachIndexed { index, it -> + val name = "p$index" + val decl = when (it) { + is Field -> { + val defaultAlignment = if (isPackedStruct) 1L else it.typeAlign + val alignment = guessAlignment(offset, it.offsetBytes, defaultAlignment) ?: return null + + offset = it.offsetBytes + it.typeSize + + tryRenderVar(it.type, name) + ?.plus(if (alignment == defaultAlignment) "" else "__attribute__((aligned($alignment)))") + } + + is BitField, // TODO: tryRenderVar(it.type, name)?.plus(" : ${it.size}") + is IncompleteField -> null // e.g. flexible array member. + } ?: return null + append("$decl; ") + } + + append("}") + } +} + +private fun guessAlignment(offset: Long, paddedOffset: Long, defaultAlignment: Long): Long? = + longArrayOf(defaultAlignment, 1L, 2L, 4L, 8L, 16L, 32L).firstOrNull { + alignUp(offset, it) == paddedOffset + } + +private fun alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and ((alignment - 1).inv()) + +private fun tryRenderUnion(def: StructDef): String? = + if (def.members.any { it.offset != 0L }) null else buildString { + append("union { ") + def.members.forEachIndexed { index, it -> + val decl = when (it) { + is Field -> tryRenderVar(it.type, "p$index") + is BitField, is IncompleteField -> null + } ?: return null + + append("$decl; ") + } + append("}") + + } + +private fun tryRenderVar(type: Type, name: String): String? = when (type) { + CharType, BoolType -> "char $name" + is IntegerType -> "${type.spelling} $name" + is FloatingType -> "${type.spelling} $name" + is RecordType -> "${tryRenderStructOrUnion(type.decl.def!!)} $name" + is EnumType -> tryRenderVar(type.def.baseType, name) + is PointerType -> "void* $name" + is ConstArrayType -> tryRenderVar(type.elemType, "$name[${type.length}]") + is IncompleteArrayType -> tryRenderVar(type.elemType, "$name[]") + is Typedef -> tryRenderVar(type.def.aliased, name) + is ObjCPointer -> "void* $name" + else -> null +} + +private val Field.offsetBytes: Long get() { + require(this.offset % 8 == 0L) + return this.offset / 8 +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index c53143809cf..4b58799c838 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -354,8 +354,12 @@ class StubGenerator( } if (platform == KotlinPlatform.JVM) { - if (def.hasNaturalLayout) { - out("@CNaturalStruct(${def.fields.joinToString { it.name.quoteAsKotlinLiteral() }})") + if (def.kind == StructDef.Kind.STRUCT && def.fieldsHaveDefaultAlignment()) { + out("@CNaturalStruct(${def.members.joinToString { it.name.quoteAsKotlinLiteral() }})") + } + } else { + tryRenderStructOrUnion(def)?.let { + out("@CStruct".applyToStrings(it)) } } @@ -368,8 +372,6 @@ class StubGenerator( for (field in def.fields) { try { assert(field.name.isNotEmpty()) - - if (field.offset < 0) throw NotImplementedError(); assert(field.offset % 8 == 0L) val offset = field.offset / 8 val fieldRefType = mirror(field.type) @@ -588,16 +590,13 @@ class StubGenerator( return stubs } - private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg && - // Neither takes nor returns structs by value: - !this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType }) - - private fun FunctionDecl.returnsRecord(): Boolean = this.returnType.unwrapTypedefs() is RecordType private fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType private inner class KotlinFunctionStub(val func: FunctionDecl) : KotlinStub, NativeBacked { override fun generate(context: StubGenerationContext): Sequence = - if (context.nativeBridges.isSupported(this)) { + if (isCCall) { + sequenceOf("@CCall".applyToStrings(cCallSymbolName!!), "external $header") + } else if (context.nativeBridges.isSupported(this)) { block(header, bodyLines) } else { sequenceOf( @@ -608,6 +607,8 @@ class StubGenerator( private val header: String private val bodyLines: List + private val isCCall: Boolean + private val cCallSymbolName: String? init { // TODO: support dumpShims @@ -627,11 +628,19 @@ class StubGenerator( val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type) val bridgeArgument = if (representCFunctionParameterAsString(func, parameter.type)) { - kotlinParameters.add(parameterName to KotlinTypes.string.makeNullable()) + val annotations = when (platform) { + KotlinPlatform.JVM -> "" + KotlinPlatform.NATIVE -> "@CCall.CString " + } + kotlinParameters.add(annotations + parameterName to KotlinTypes.string.makeNullable()) bodyGenerator.pushMemScoped() "$parameterName?.cstr?.getPointer(memScope)" } else if (representCFunctionParameterAsWString(func, parameter.type)) { - kotlinParameters.add(parameterName to KotlinTypes.string.makeNullable()) + val annotations = when (platform) { + KotlinPlatform.JVM -> "" + KotlinPlatform.NATIVE -> "@CCall.WCString " + } + kotlinParameters.add(annotations + parameterName to KotlinTypes.string.makeNullable()) bodyGenerator.pushMemScoped() "$parameterName?.wcstr?.getPointer(memScope)" } else if (representAsValuesRef != null) { @@ -647,7 +656,7 @@ class StubGenerator( bridgeArguments.add(TypedKotlinValue(parameter.type, bridgeArgument)) } - if (!func.generateAsFfiVarargs()) { + if (!func.isVararg || platform != KotlinPlatform.NATIVE) { val result = mappingBridgeGenerator.kotlinToNative( bodyGenerator, this, @@ -657,37 +666,19 @@ class StubGenerator( "${func.name}(${nativeValues.joinToString()})" } bodyGenerator.out("return $result") + isCCall = false + cCallSymbolName = null } else { - val returnTypeKind = getFfiTypeKind(func.returnType) - kotlinParameters.add("vararg variadicArguments" to KotlinTypes.any.makeNullable()) - bodyGenerator.pushMemScoped() + isCCall = true // TODO: don't generate unused body in this case. + cCallSymbolName = "knifunptr_" + pkgName.replace('.', '_') + nextUniqueId() - val resultVar = "kniResult" - - val resultPtr = if (!func.returnsVoid()) { - val returnType = mirror(func.returnType).pointedType.render(kotlinFile) - bodyGenerator.out("val $resultVar = allocFfiReturnValueBuffer<$returnType>(typeOf<$returnType>())") - "$resultVar.rawPtr" - } else { - "nativeNullPtr" - } - val fixedArguments = bridgeArguments.joinToString(", ") { it.value } - - val functionPtr = simpleBridgeGenerator.kotlinToNative( + simpleBridgeGenerator.insertNativeBridge( this, - BridgedType.NATIVE_PTR, - emptyList() - ) { - func.name - } - - bodyGenerator.out("callWithVarargs($functionPtr, $resultPtr, $returnTypeKind, " + - "arrayOf($fixedArguments), variadicArguments, memScope)") - - if (!func.returnsVoid()) { - bodyGenerator.out("return $resultVar.value") - } + emptyList(), + listOf("extern const void* $cCallSymbolName __asm(${cCallSymbolName.quoteAsKotlinLiteral()});", + "extern const void* $cCallSymbolName = &${func.name};") + ) } val returnType = if (func.returnsVoid()) { @@ -705,28 +696,6 @@ class StubGenerator( } } - private fun getFfiTypeKind(type: Type): String { - val unwrappedType = type.unwrapTypedefs() - return when (unwrappedType) { - is VoidType -> "FFI_TYPE_KIND_VOID" - is PointerType -> "FFI_TYPE_KIND_POINTER" - is IntegerType -> when (unwrappedType.size) { - 1 -> "FFI_TYPE_KIND_SINT8" - 2 -> "FFI_TYPE_KIND_SINT16" - 4 -> "FFI_TYPE_KIND_SINT32" - 8 -> "FFI_TYPE_KIND_SINT64" - else -> TODO(unwrappedType.toString()) - } - is FloatingType -> when (unwrappedType.size) { - 4 -> "FFI_TYPE_KIND_FLOAT" - 8 -> "FFI_TYPE_KIND_DOUBLE" - else -> TODO(unwrappedType.toString()) - } - is EnumType -> getFfiTypeKind(unwrappedType.def.baseType) - else -> TODO(unwrappedType.toString()) - } - } - private fun integerLiteral(type: Type, value: Long): String? { val integerType = type.unwrapTypedefs() as? IntegerType ?: return null return integerLiteral(integerType.size, declarationMapper.isMappedToSigned(integerType), value) @@ -943,6 +912,7 @@ class StubGenerator( } if (platform == KotlinPlatform.NATIVE) { out("import kotlin.native.SymbolName") + out("import kotlinx.cinterop.internal.*") } out("import kotlinx.cinterop.*") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt new file mode 100644 index 00000000000..93d0001cd32 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt @@ -0,0 +1,81 @@ +package org.jetbrains.kotlin.backend.konan + +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.konan.exec.Command +import org.jetbrains.kotlin.konan.file.* +import org.jetbrains.kotlin.konan.target.ClangArgs + +class CStubsManager { + + fun getUniqueName(prefix: String) = "$prefix${counter++}" + + fun addStub(kotlinLocation: CompilerMessageLocation?, lines: List) { + stubs += Stub(kotlinLocation, lines) + } + + fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): File? { + if (stubs.isEmpty()) return null + + val cSource = createTempFile("cstubs", ".c").deleteOnExit() + cSource.writeLines(stubs.flatMap { it.lines }) + + val bitcode = createTempFile("cstubs", ".bc").deleteOnExit() + + val cSourcePath = cSource.absolutePath + val clangCommand = clang.clangC(cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath) + val result = Command(clangCommand).getResult(withErrors = true) + if (result.exitCode != 0) { + reportCompilationErrors(cSourcePath, result, messageCollector, verbose) + } + + return bitcode + } + + private fun reportCompilationErrors( + cSourcePath: String, + result: Command.Result, + messageCollector: MessageCollector, + verbose: Boolean + ): Nothing { + val regex = Regex("${Regex.escape(cSourcePath)}:([0-9]+):[0-9]+: error: .*") + val errorLines = result.outputLines.mapNotNull { line -> + regex.matchEntire(line)?.let { matchResult -> + matchResult.groupValues[1].toInt() + } + } + + val lineToStub = ArrayList() + stubs.forEach { stub -> + repeat(stub.lines.size) { lineToStub.add(stub) } + } + + val cSourceCopyPath = "cstubs.c" + if (verbose) { + File(cSourcePath).copyTo(File(cSourceCopyPath)) + } + + if (errorLines.isNotEmpty()) { + errorLines.forEach { + messageCollector.report( + CompilerMessageSeverity.ERROR, + "Unable to compile C bridge" + if (verbose) " at $cSourceCopyPath:$it" else "", + lineToStub[it - 1].kotlinLocation + ) + } + } else { + messageCollector.report( + CompilerMessageSeverity.ERROR, + "Unable to compile C bridges", + null + ) + } + + throw KonanCompilationException() + } + + private val stubs = mutableListOf() + private class Stub(val kotlinLocation: CompilerMessageLocation?, val lines: List) + private var counter = 0 +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt index 0e6214a2591..df67548963f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.backend.konan import llvm.LLVMLinkModules2 +import llvm.LLVMModuleRef import llvm.LLVMWriteBitcodeToFile import org.jetbrains.kotlin.backend.konan.library.impl.buildLibrary import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile @@ -26,6 +27,12 @@ internal fun produceOutput(context: Context, phaser: PhaseManager) { val tempFiles = context.config.tempFiles val produce = config.get(KonanConfigKeys.PRODUCE) + phaser.phase(KonanPhase.C_STUBS) { + context.cStubsManager.compile(context.config.clang, context.messageCollector, context.phase!!.verbose)?.let { + parseAndLinkBitcodeFile(llvmModule, it.absolutePath) + } + } + when (produce) { CompilerOutputKind.STATIC, CompilerOutputKind.DYNAMIC, @@ -50,11 +57,7 @@ internal fun produceOutput(context: Context, phaser: PhaseManager) { phaser.phase(KonanPhase.BITCODE_LINKER) { for (library in nativeLibraries) { - val libraryModule = parseBitcodeFile(library) - val failed = LLVMLinkModules2(llvmModule, libraryModule) - if (failed != 0) { - throw Error("failed to link $library") // TODO: retrieve error message from LLVM. - } + parseAndLinkBitcodeFile(llvmModule, library) } } @@ -99,4 +102,10 @@ internal fun produceOutput(context: Context, phaser: PhaseManager) { } } - +private fun parseAndLinkBitcodeFile(llvmModule: LLVMModuleRef, path: String) { + val parsedModule = parseBitcodeFile(path) + val failed = LLVMLinkModules2(llvmModule, parsedModule) + if (failed != 0) { + throw Error("failed to link $path") // TODO: retrieve error message from LLVM. + } +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 8386b4280fe..609375adc09 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -314,10 +314,13 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { } lateinit var llvm: Llvm + val llvmImports: LlvmImports = Llvm.ImportsImpl(this) lateinit var llvmDeclarations: LlvmDeclarations lateinit var bitcodeFileName: String lateinit var library: KonanLibraryWriter + val cStubsManager = CStubsManager() + var phase: KonanPhase? = null var depth: Int = 0 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index 539605f9e13..67eaabfaa5c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -33,6 +33,18 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns, vararg konanPrimitives: val nativePointed = packageScope.getContributedClass(InteropFqNames.nativePointedName) + val cValuesRef = this.packageScope.getContributedClass("CValuesRef") + val cValues = this.packageScope.getContributedClass("CValues") + val cValue = this.packageScope.getContributedClass("CValue") + val cValueWrite = this.packageScope.getContributedFunctions("write") + .single { it.extensionReceiverParameter?.type?.constructor?.declarationDescriptor == cValue } + val cValueRead = this.packageScope.getContributedFunctions("readValue") + .single { it.valueParameters.size == 1 } + + val allocType = this.packageScope.getContributedFunctions("alloc") + .single { it.extensionReceiverParameter != null + && it.valueParameters.singleOrNull()?.name?.toString() == "type" } + val cPointer = this.packageScope.getContributedClass(InteropFqNames.cPointerName) val cPointerRawValue = cPointer.unsubstitutedMemberScope.getContributedVariables("rawValue").single() @@ -43,6 +55,10 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns, vararg konanPrimitives: TypeUtils.getClassDescriptor(extensionReceiverParameter.type) == cPointer } + val cstr = packageScope.getContributedVariables("cstr").single() + val wcstr = packageScope.getContributedVariables("wcstr").single() + val memScope = packageScope.getContributedClass("MemScope") + val nativePointedRawPtrGetter = nativePointed.unsubstitutedMemberScope.getContributedVariables("rawPtr").single().getter!! @@ -61,24 +77,6 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns, vararg konanPrimitives: private fun KonanBuiltIns.getUnsignedClass(unsignedType: UnsignedType): ClassDescriptor = this.builtInsModule.findClassAcrossModuleDependencies(unsignedType.classId)!! - val invokeImpls = mapOf( - builtIns.unit to "invokeImplUnitRet", - builtIns.boolean to "invokeImplBooleanRet", - builtIns.byte to "invokeImplByteRet", - builtIns.short to "invokeImplShortRet", - builtIns.int to "invokeImplIntRet", - builtIns.long to "invokeImplLongRet", - builtIns.getUnsignedClass(UnsignedType.UBYTE) to "invokeImplUByteRet", - builtIns.getUnsignedClass(UnsignedType.USHORT) to "invokeImplUShortRet", - builtIns.getUnsignedClass(UnsignedType.UINT) to "invokeImplUIntRet", - builtIns.getUnsignedClass(UnsignedType.ULONG) to "invokeImplULongRet", - builtIns.float to "invokeImplFloatRet", - builtIns.double to "invokeImplDoubleRet", - cPointer to "invokeImplPointerRet" - ).mapValues { (_, name) -> - packageScope.getContributedFunctions(name).single() - }.toMap() - val objCObject = packageScope.getContributedClass("ObjCObject") val objCObjectBase = packageScope.getContributedClass("ObjCObjectBase") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt index f8e00d085df..62e0c919825 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.konan.descriptors.KonanSharedVariablesManager +import org.jetbrains.kotlin.backend.konan.descriptors.findPackage import org.jetbrains.kotlin.backend.konan.descriptors.kotlinNativeInternal import org.jetbrains.kotlin.backend.konan.ir.KonanIr import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys @@ -19,6 +20,9 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns +import org.jetbrains.kotlin.ir.builders.IrBuilder +import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope +import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.name.Name abstract internal class KonanBackendContext(val config: KonanConfig) : CommonBackendContext { @@ -49,14 +53,23 @@ abstract internal class KonanBackendContext(val config: KonanConfig) : CommonBac ) } - private fun IrElement.getCompilerMessageLocation(containingFile: IrFile): CompilerMessageLocation? { - val sourceRangeInfo = containingFile.fileEntry.getSourceRangeInfo(this.startOffset, this.endOffset) - return CompilerMessageLocation.create( - path = sourceRangeInfo.filePath, - line = sourceRangeInfo.startLineNumber + 1, - column = sourceRangeInfo.startColumnNumber + 1, - lineContent = null // TODO: retrieve the line content. - ) - } - } + +internal fun IrElement.getCompilerMessageLocation(containingFile: IrFile): CompilerMessageLocation? = + createCompilerMessageLocation(containingFile, this.startOffset, this.endOffset) + +internal fun IrBuilderWithScope.getCompilerMessageLocation(): CompilerMessageLocation? { + val declaration = this.scope.scopeOwnerSymbol.owner as? IrDeclaration ?: return null + val file = declaration.findPackage() as? IrFile ?: return null + return createCompilerMessageLocation(file, startOffset, endOffset) +} + +private fun createCompilerMessageLocation(containingFile: IrFile, startOffset: Int, endOffset: Int): CompilerMessageLocation? { + val sourceRangeInfo = containingFile.fileEntry.getSourceRangeInfo(startOffset, endOffset) + return CompilerMessageLocation.create( + path = sourceRangeInfo.filePath, + line = sourceRangeInfo.startLineNumber + 1, + column = sourceRangeInfo.startColumnNumber + 1, + lineContent = null // TODO: retrieve the line content. + ) +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index e2ae1c7658d..f6509f6d3bb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -58,6 +58,7 @@ enum class KonanPhase(val description: String, /* ... ... */ ESCAPE_ANALYSIS("Escape analysis", BUILD_DFG, DESERIALIZE_DFG, enabled = false), // TODO: Requires devirtualization. /* ... ... */ SERIALIZE_DFG("Data flow graph serializing", BUILD_DFG), // TODO: Requires escape analysis. /* ... ... */ CODEGEN("Code Generation"), + /* ... ... */ C_STUBS("C stubs compilation"), /* ... ... */ BITCODE_LINKER("Bitcode linking"), /* */ LINK_STAGE("Link stage"), /* ... */ OBJECT_FILES("Bitcode to object file"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index 6840fdf24ad..c0f705dc983 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -181,8 +181,7 @@ internal class LinkStage(val context: Context, val phaser: PhaseManager) { try { File(executable).delete() linker.linkCommands(objectFiles = objectFiles, executable = executable, - libraries = linker.targetLibffi + linker.linkStaticLibraries(includedBinaries) + - context.config.defaultSystemLibraries, + libraries = linker.linkStaticLibraries(includedBinaries) + context.config.defaultSystemLibraries, linkerArgs = entryPointSelector + asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS)) + BitcodeEmbedding.getLinkerOptions(context.config) + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt new file mode 100644 index 00000000000..ac134587ed4 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt @@ -0,0 +1,9 @@ +package org.jetbrains.kotlin.backend.konan + +import org.jetbrains.kotlin.name.FqName + +object RuntimeNames { + val symbolName = FqName("kotlin.native.SymbolName") + val exportForCppRuntime = FqName("kotlin.native.internal.ExportForCppRuntime") + val cCall = FqName("kotlinx.cinterop.internal.CCall") +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt new file mode 100644 index 00000000000..96431b5929f --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt @@ -0,0 +1,803 @@ +package org.jetbrains.kotlin.backend.konan.cgen + +import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor +import org.jetbrains.kotlin.backend.common.lower.at +import org.jetbrains.kotlin.backend.common.lower.irNot +import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType +import org.jetbrains.kotlin.backend.konan.RuntimeNames +import org.jetbrains.kotlin.backend.konan.descriptors.createAnnotation +import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue +import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols +import org.jetbrains.kotlin.backend.konan.irasdescriptors.* +import org.jetbrains.kotlin.builtins.UnsignedType +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrSpreadElement +import org.jetbrains.kotlin.ir.expressions.IrVararg +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +internal interface KotlinStubs { + val irBuiltIns: IrBuiltIns + val symbols: KonanSymbols + val target: KonanTarget + fun addKotlin(declaration: IrDeclaration) + fun addC(lines: List) + fun getUniqueCName(prefix: String): String + + fun reportError(location: IrElement, message: String): Nothing +} + +private class KotlinToCCallBuilder( + val irBuilder: IrBuilderWithScope, + cBridgeName: String, + val stubs: KotlinStubs +) { + + val symbols: KonanSymbols get() = stubs.symbols + + val bridgeCallBuilder = KotlinCallBuilder(irBuilder, symbols) + val bridgeBuilder = KotlinCBridgeBuilder(irBuilder.startOffset, irBuilder.endOffset, cBridgeName, stubs, isKotlinToC = true) + val cBridgeBodyLines = mutableListOf() + val cCallBuilder = CCallBuilder() + val cFunctionBuilder = CFunctionBuilder() + +} + +private fun KotlinToCCallBuilder.passThroughBridge(argument: IrExpression, kotlinType: IrType, cType: CType): CVariable { + bridgeCallBuilder.arguments += argument + return bridgeBuilder.addParameter(kotlinType, cType).second +} + +private fun KotlinToCCallBuilder.addArgument( + argument: IrExpression, + type: IrType, + variadic: Boolean, + parameter: IrValueParameter? +) { + val argumentPassing = mapParameter(type, variadic, parameter, argument) + val cArgument = with(argumentPassing) { passValue(argument) } + cCallBuilder.arguments += cArgument.expression + if (!variadic) cFunctionBuilder.addParameter(cArgument.type) +} + +private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrCall) -> IrExpression = { it }): IrExpression = + bridgeCallBuilder.build( + bridgeBuilder.buildKotlinBridge().also { + this.stubs.addKotlin(it) + }, + transformCall + ) + +internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean): IrExpression { + require(expression.dispatchReceiver == null) + + val cBridgeName = this.getUniqueCName("knbridge") + val callBuilder = KotlinToCCallBuilder(builder, cBridgeName, this) + + val cFunctionBuilder = callBuilder.cFunctionBuilder + + val callee = expression.symbol.owner + + // TODO: consider computing all arguments before converting. + + val targetPtrParameter: String? + val targetFunctionName: String + + if (isInvoke) { + targetPtrParameter = callBuilder.passThroughBridge( + expression.extensionReceiver!!, + symbols.interopCPointer.typeWithStarProjections, + CTypes.voidPtr + ).name + targetFunctionName = "targetPtr" + + (0 until expression.valueArgumentsCount).forEach { + callBuilder.addArgument( + expression.getValueArgument(it)!!, + type = expression.getTypeArgument(it)!!, + variadic = false, + parameter = null + ) + } + } else { + require(expression.extensionReceiver == null) + targetPtrParameter = null + targetFunctionName = this.getUniqueCName("target") + + (0 until expression.valueArgumentsCount).forEach { index -> + val parameter = callee.valueParameters[index] + val argument = expression.getValueArgument(index) + if (parameter.isVararg) { + require(index == expression.valueArgumentsCount - 1) + require(argument is IrVararg?) + argument?.elements.orEmpty().forEach { + when (it) { + is IrExpression -> callBuilder.addArgument(it, it.type, variadic = true, parameter = null) + + is IrSpreadElement -> + reportError(it, "spread operator is not supported for variadic C functions") + + else -> error(it) + } + } + cFunctionBuilder.variadic = true + } else { + callBuilder.addArgument(argument!!, parameter.type, variadic = false, parameter = parameter) + } + } + } + + val cLines = mutableListOf() + + val returnValuePassing = if (isInvoke) { + val returnType = expression.getTypeArgument(expression.typeArgumentsCount - 1)!! + mapReturnType(returnType, TypeLocation.FunctionCallResult(expression)) + } else { + mapReturnType(callee.returnType, TypeLocation.FunctionCallResult(expression)) + } + + val result = with(returnValuePassing) { + callBuilder.returnValue(callBuilder.cCallBuilder.build(targetFunctionName)) + } + + val targetFunctionVariable = CVariable(CTypes.pointer(cFunctionBuilder.getType()), targetFunctionName) + if (isInvoke) { + callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = ${targetPtrParameter!!};") + } else { + val cCallSymbolName = callee.descriptor.annotations.findAnnotation(cCall)!!.getStringValue("id") + cLines += "extern const $targetFunctionVariable __asm(\"$cCallSymbolName\");" // Exported from cinterop stubs. + } + + cLines += "${callBuilder.bridgeBuilder.buildCSignature(cBridgeName)} {" + cLines += callBuilder.cBridgeBodyLines + cLines += "}" + + this.addC(cLines) + + return result +} + +private class CCallbackBuilder( + val stubs: KotlinStubs, + val location: IrElement +) { + + val irBuiltIns: IrBuiltIns get() = stubs.irBuiltIns + val symbols: KonanSymbols get() = stubs.symbols + + private val cBridgeName = stubs.getUniqueCName("knbridge") + + fun buildCBridgeCall(): String = cBridgeCallBuilder.build(cBridgeName) + fun buildCBridge(): String = bridgeBuilder.buildCSignature(cBridgeName) + + val bridgeBuilder = KotlinCBridgeBuilder(location.startOffset, location.endOffset, cBridgeName, stubs, isKotlinToC = false) + val kotlinCallBuilder = KotlinCallBuilder(bridgeBuilder.kotlinIrBuilder, symbols) + val kotlinBridgeStatements = mutableListOf() + val cBridgeCallBuilder = CCallBuilder() + val cBodyLines = mutableListOf() + val cFunctionBuilder = CFunctionBuilder() + +} + +private fun CCallbackBuilder.passThroughBridge( + cBridgeArgument: String, + cBridgeParameterType: CType, + kotlinBridgeParameterType: IrType +): IrValueParameter { + cBridgeCallBuilder.arguments += cBridgeArgument + return bridgeBuilder.addParameter(kotlinBridgeParameterType, cBridgeParameterType).first +} + +private fun CCallbackBuilder.addParameter(it: IrValueParameter) { + val typeLocation = TypeLocation.FunctionPointerParameter(cFunctionBuilder.numberOfParameters, location) + val valuePassing = stubs.mapType(it.type, typeLocation) + + val kotlinArgument = with(valuePassing) { receiveValue() } + kotlinCallBuilder.arguments += kotlinArgument +} + +private fun CCallbackBuilder.build(function: IrSimpleFunction, signature: IrFunction): String { + val kotlinCall = kotlinCallBuilder.build(function) + + with(stubs.mapReturnType(signature.returnType, TypeLocation.FunctionPointerReturnValue(location))) { + returnValue(kotlinCall) + } + + val cLines = mutableListOf() + + val kotlinBridge = bridgeBuilder.buildKotlinBridge() + kotlinBridge.body = bridgeBuilder.kotlinIrBuilder.irBlockBody { + kotlinBridgeStatements.forEach { +it } + } + stubs.addKotlin(kotlinBridge) + + val result = stubs.getUniqueCName("kncfun") + + cLines += "${buildCBridge()};" + cLines += "${cFunctionBuilder.buildSignature(result)} {" + cLines += cBodyLines + cLines += "}" + + stubs.addC(cLines) + + return result +} + +private fun KotlinStubs.generateCFunction( + function: IrSimpleFunction, + signature: IrSimpleFunction, + isObjCMethod: Boolean, + location: IrElement +): String { + val callbackBuilder = CCallbackBuilder(this, location) + + signature.dispatchReceiverParameter?.let { callbackBuilder.addParameter(it) } + if (isObjCMethod) { +// assert(mapType(signature.dispatchReceiverParameter!!.type) is ObjCReferenceValuePassing) + // Selector is ignored: + with(TrivialValuePassing(symbols.nativePtrType, CTypes.voidPtr)) { callbackBuilder.receiveValue() } + } + signature.extensionReceiverParameter?.let { callbackBuilder.addParameter(it) } + + signature.valueParameters.forEach { + callbackBuilder.addParameter(it) + } + + return callbackBuilder.build(function, signature) +} + +internal fun KotlinStubs.generateCFunctionPointer( + function: IrSimpleFunction, + signature: IrSimpleFunction, + isObjCMethod: Boolean, + expression: IrExpression +): IrExpression { + val cFunction = generateCFunction(function, signature, isObjCMethod, expression) + val fakeFunction = createFakeKotlinExternalFunction(signature, cFunction, isObjCMethod) + addKotlin(fakeFunction) + + return IrFunctionReferenceImpl( + expression.startOffset, + expression.endOffset, + expression.type, + fakeFunction.symbol, + fakeFunction.descriptor, + 0 + ) +} + +private fun KotlinStubs.createFakeKotlinExternalFunction( + signature: IrSimpleFunction, + cFunctionName: String, + isObjCMethod: Boolean +): IrSimpleFunction { + val objCMethodImpAnnotation = if (isObjCMethod) { + val methodInfo = signature.getObjCMethodInfo()!! + createObjCMethodImpAnnotation(methodInfo.selector, methodInfo.encoding, symbols) + } else { + null + } + val bridgeAnnotations = Annotations.create( + listOfNotNull( + createAnnotation(symbols.symbolName.descriptor, "value" to cFunctionName), + objCMethodImpAnnotation + ) + ) + val bridgeDescriptor = WrappedSimpleFunctionDescriptor(bridgeAnnotations) + val bridge = IrFunctionImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + IrDeclarationOrigin.DEFINED, + IrSimpleFunctionSymbolImpl(bridgeDescriptor), + Name.identifier(cFunctionName), + Visibilities.PRIVATE, + Modality.FINAL, + signature.returnType, + isInline = false, + isExternal = true, + isTailrec = false, + isSuspend = false + ) + bridgeDescriptor.bind(bridge) + + return bridge +} + +private fun createObjCMethodImpAnnotation(selector: String, encoding: String, symbols: KonanSymbols) = + createAnnotation(symbols.objCMethodImp.descriptor, "selector" to selector, "encoding" to encoding) + +private val cCall = RuntimeNames.cCall + +private fun IrType.isUnsigned(unsignedType: UnsignedType) = this is IrSimpleType && !this.hasQuestionMark && + (this.classifier.owner as? IrClass)?.classId == unsignedType.classId + +private fun IrType.isUByte() = this.isUnsigned(UnsignedType.UBYTE) +private fun IrType.isUShort() = this.isUnsigned(UnsignedType.USHORT) +private fun IrType.isUInt() = this.isUnsigned(UnsignedType.UINT) +private fun IrType.isULong() = this.isUnsigned(UnsignedType.ULONG) + +private fun IrType.isCEnumType(): Boolean { + val simpleType = this as? IrSimpleType ?: return false + if (simpleType.hasQuestionMark) return false + val enumClass = simpleType.classifier.owner as? IrClass ?: return false + if (!enumClass.isEnumClass) return false + + return enumClass.superTypes + .any { (it.classifierOrNull?.owner as? IrClass)?.fqNameSafe == FqName("kotlinx.cinterop.CEnum") } +} + +private fun IrValueParameter.isWCStringParameter() = + this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier("WCString"))) + +private fun IrValueParameter.isCStringParameter() = + this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier("CString"))) + +private fun getStructSpelling(kotlinClass: IrClass): String? = + kotlinClass.descriptor.annotations + .findAnnotation(FqName("kotlinx.cinterop.internal.CStruct")) + ?.getStringValue("spelling") + +private fun getCStructType(kotlinClass: IrClass): CType? = + getStructSpelling(kotlinClass)?.let { CTypes.simple(it) } + +private fun KotlinStubs.getNamedCStructType(kotlinClass: IrClass): CType? { + val cStructType = getCStructType(kotlinClass) ?: return null + val name = getUniqueCName("struct") + addC(listOf("typedef ${cStructType.render(name)};")) + return CTypes.simple(name) +} + +// TODO: rework Boolean support. +private fun cBoolType(target: KonanTarget): CType? = when (target.family) { + Family.IOS -> CTypes.C99Bool + else -> CTypes.signedChar +} + +private fun KotlinToCCallBuilder.mapParameter( + type: IrType, + variadic: Boolean, + parameter: IrValueParameter?, + argument: IrExpression +): KotlinToCArgumentPassing { + val classifier = type.classifierOrNull + return when { + classifier == symbols.interopCValues || // Note: this should not be accepted, but is required for compatibility + classifier == symbols.interopCValuesRef -> CValuesRefArgumentPassing + + classifier == symbols.string && (variadic || parameter?.isCStringParameter() == true) -> + CStringArgumentPassing() + + classifier == symbols.string && parameter?.isWCStringParameter() == true -> + WCStringArgumentPassing() + + else -> stubs.mapType(type, TypeLocation.FunctionArgument(argument)) + } +} + +private sealed class TypeLocation(val element: IrElement) { + class FunctionArgument(val argument: IrExpression) : TypeLocation(argument) + class FunctionCallResult(val call: IrCall) : TypeLocation(call) + + class FunctionPointerParameter(val index: Int, element: IrElement) : TypeLocation(element) + class FunctionPointerReturnValue(element: IrElement) : TypeLocation(element) +} + +private fun KotlinStubs.mapReturnType(type: IrType, location: TypeLocation): ValueReturning = + when { + type.isUnit() -> VoidReturning + else -> mapType(type, location) + } + +private fun KotlinStubs.mapType(type: IrType, location: TypeLocation): ValuePassing = + mapType(type, { reportUnsupportedType(it, type, location) }) + +private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) -> Nothing): ValuePassing = when { + type.isBoolean() -> BooleanValuePassing( + cBoolType(target) ?: reportUnsupportedType("unavailable on target platform"), + irBuiltIns + ) + + type.isByte() -> TrivialValuePassing(irBuiltIns.byteType, CTypes.signedChar) + type.isShort() -> TrivialValuePassing(irBuiltIns.shortType, CTypes.short) + type.isInt() -> TrivialValuePassing(irBuiltIns.intType, CTypes.int) + type.isLong() -> TrivialValuePassing(irBuiltIns.longType, CTypes.longLong) + type.isFloat() -> TrivialValuePassing(irBuiltIns.floatType, CTypes.float) + type.isDouble() -> TrivialValuePassing(irBuiltIns.doubleType, CTypes.double) + type.classifierOrNull == symbols.interopCPointer -> TrivialValuePassing(type, CTypes.voidPtr) + type.isUByte() -> UnsignedValuePassing(type, CTypes.signedChar, CTypes.unsignedChar) + type.isUShort() -> UnsignedValuePassing(type, CTypes.short, CTypes.unsignedShort) + type.isUInt() -> UnsignedValuePassing(type, CTypes.int, CTypes.unsignedInt) + type.isULong() -> UnsignedValuePassing(type, CTypes.longLong, CTypes.unsignedLongLong) + + type.isCEnumType() -> { + val enumClass = type.getClass()!! + val value = enumClass.declarations + .filterIsInstance() + .single { it.name.asString() == "value" } + + CEnumValuePassing(enumClass, value, mapType(value.getter!!.returnType, reportUnsupportedType) as SimpleValuePassing) + } + + type.classifierOrNull == symbols.interopCValue -> if (type.isNullable()) { + reportUnsupportedType("must not be nullable") + } else { + val kotlinClass = (type as IrSimpleType).arguments.singleOrNull()?.typeOrNull?.getClass() + ?: reportUnsupportedType("must be parameterized with concrete class") + + StructValuePassing(kotlinClass, getNamedCStructType(kotlinClass) + ?: reportUnsupportedType("not a structure or too complex")) + } + + type.isFunction() -> reportUnsupportedType("") + isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type) + + else -> reportUnsupportedType("doesn't correspond to any C type") +} + +private fun KotlinStubs.isObjCReferenceType(type: IrType): Boolean { + if (target.family != Family.OSX && target.family != Family.IOS) return false + + if (type.isObjCObjectType()) return true + + val descriptor = type.classifierOrNull?.descriptor ?: return false + val builtIns = irBuiltIns.builtIns + + return when (descriptor) { + builtIns.string, + builtIns.list, builtIns.mutableList, + builtIns.set, builtIns.mutableSet, + builtIns.map, builtIns.mutableMap -> true + else -> false + } +} + +private class CExpression(val expression: String, val type: CType) + +private interface KotlinToCArgumentPassing { + fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression +} + +private interface ValueReturning { + fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression + fun CCallbackBuilder.returnValue(expression: IrExpression) +} + +private interface ValuePassing : KotlinToCArgumentPassing, ValueReturning { + fun CCallbackBuilder.receiveValue(): IrExpression +} + +private abstract class SimpleValuePassing : ValuePassing { + abstract val kotlinBridgeType: IrType + abstract val cBridgeType: CType + abstract val cType: CType + abstract fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression + abstract fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression + abstract fun bridgedToC(expression: String): String + abstract fun cToBridged(expression: String): String + + override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression { + val bridgeArgument = irBuilder.kotlinToBridged(expression) + val cBridgeValue = passThroughBridge(bridgeArgument, kotlinBridgeType, cBridgeType).name + return CExpression(bridgedToC(cBridgeValue), cType) + } + + override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression { + cFunctionBuilder.setReturnType(cType) + bridgeBuilder.setReturnType(kotlinBridgeType, cBridgeType) + cBridgeBodyLines.add("return ${cToBridged(expression)};") + val kotlinBridgeCall = buildKotlinBridgeCall() + return irBuilder.bridgedToKotlin(kotlinBridgeCall, symbols) + } + + override fun CCallbackBuilder.receiveValue(): IrExpression { + val cParameter = cFunctionBuilder.addParameter(cType) + val cBridgeArgument = cToBridged(cParameter.name) + val kotlinParameter = passThroughBridge(cBridgeArgument, cBridgeType, kotlinBridgeType) + return with(bridgeBuilder.kotlinIrBuilder) { + bridgedToKotlin(irGet(kotlinParameter), symbols) + } + } + + override fun CCallbackBuilder.returnValue(expression: IrExpression) { + cFunctionBuilder.setReturnType(cType) + bridgeBuilder.setReturnType(kotlinBridgeType, cBridgeType) + + kotlinBridgeStatements += with(bridgeBuilder.kotlinIrBuilder) { + irReturn(kotlinToBridged(expression)) + } + val cBridgeCall = buildCBridgeCall() + cBodyLines += "return ${bridgedToC(cBridgeCall)};" + } +} + +private class TrivialValuePassing(val kotlinType: IrType, override val cType: CType) : SimpleValuePassing() { + override val kotlinBridgeType: IrType + get() = kotlinType + override val cBridgeType: CType + get() = cType + + override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = expression + override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression = expression + override fun bridgedToC(expression: String): String = expression + override fun cToBridged(expression: String): String = expression +} + +private class UnsignedValuePassing(val kotlinType: IrType, val cSignedType: CType, override val cType: CType) : SimpleValuePassing() { + override val kotlinBridgeType: IrType + get() = kotlinType + override val cBridgeType: CType + get() = cSignedType + + override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = expression + + override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression = expression + + override fun bridgedToC(expression: String): String = cType.cast(expression) + + override fun cToBridged(expression: String): String = cBridgeType.cast(expression) +} + +private class BooleanValuePassing(override val cType: CType, private val irBuiltIns: IrBuiltIns) : SimpleValuePassing() { + override val cBridgeType: CType get() = CTypes.signedChar + override val kotlinBridgeType: IrType get() = irBuiltIns.byteType + + override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = irIfThenElse( + irBuiltIns.byteType, + condition = expression, + thenPart = IrConstImpl.byte(startOffset, endOffset, irBuiltIns.byteType, 1), + elsePart = IrConstImpl.byte(startOffset, endOffset, irBuiltIns.byteType, 0) + ) + + override fun IrBuilderWithScope.bridgedToKotlin( + expression: IrExpression, + symbols: KonanSymbols + ): IrExpression = irNot(irCall(symbols.areEqualByValue[PrimitiveBinaryType.BYTE]!!.owner).apply { + putValueArgument(0, expression) + putValueArgument(1, IrConstImpl.byte(startOffset, endOffset, irBuiltIns.byteType, 0)) + }) + + override fun bridgedToC(expression: String): String = cType.cast(expression) + + override fun cToBridged(expression: String): String = cBridgeType.cast(expression) +} + +private class StructValuePassing(private val kotlinClass: IrClass, private val cType: CType) : ValuePassing { + override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression { + val cBridgeValue = passThroughBridge( + cValuesRefToPointer(expression), + symbols.interopCPointer.typeWithStarProjections, + CTypes.pointer(cType) + ).name + + return CExpression("*$cBridgeValue", cType) + } + + override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression = with(irBuilder) { + cFunctionBuilder.setReturnType(cType) + bridgeBuilder.setReturnType(context.irBuiltIns.unitType, CTypes.void) + + val kotlinPointed = scope.createTemporaryVariable(irCall(symbols.interopAllocType.owner).apply { + extensionReceiver = bridgeCallBuilder.getMemScope() + putValueArgument(0, getTypeObject()) + }) + + bridgeCallBuilder.prepare += kotlinPointed + + val cPointer = passThroughBridge(irGet(kotlinPointed), kotlinPointedType, CTypes.pointer(cType)) + cBridgeBodyLines += "*${cPointer.name} = $expression;" + + buildKotlinBridgeCall { + irBlock { + at(it) + +it + +readCValue(irGet(kotlinPointed), symbols) + } + } + } + + override fun CCallbackBuilder.receiveValue(): IrExpression = with(bridgeBuilder.kotlinIrBuilder) { + val cParameter = cFunctionBuilder.addParameter(cType) + val kotlinPointed = passThroughBridge("&${cParameter.name}", CTypes.voidPtr, kotlinPointedType) + + readCValue(irGet(kotlinPointed), symbols) + } + + private fun IrBuilderWithScope.readCValue(kotlinPointed: IrExpression, symbols: KonanSymbols): IrExpression = + irCall(symbols.interopCValueRead.owner).apply { + extensionReceiver = kotlinPointed + putValueArgument(0, getTypeObject()) + } + + override fun CCallbackBuilder.returnValue(expression: IrExpression) = with(bridgeBuilder.kotlinIrBuilder) { + bridgeBuilder.setReturnType(irBuiltIns.unitType, CTypes.void) + cFunctionBuilder.setReturnType(cType) + + val result = "callbackResult" + val cReturnValue = CVariable(cType, result) + cBodyLines += "$cReturnValue;" + val kotlinPtr = passThroughBridge("&$result", CTypes.voidPtr, symbols.nativePtrType) + + kotlinBridgeStatements += irCall(symbols.interopCValueWrite.owner).apply { + extensionReceiver = expression + putValueArgument(0, irGet(kotlinPtr)) + } + val cBridgeCall = buildCBridgeCall() + cBodyLines += "$cBridgeCall;" + cBodyLines += "return $result;" + } + + private val kotlinPointedType: IrType get() = kotlinClass.defaultType + + private fun IrBuilderWithScope.getTypeObject() = + irGetObject( + kotlinClass.declarations.filterIsInstance() + .single { it.isCompanion }.symbol + ) + +} + +private class CEnumValuePassing( + val enumClass: IrClass, + val value: IrProperty, + val baseValuePassing: SimpleValuePassing +) : SimpleValuePassing() { + override val kotlinBridgeType: IrType + get() = baseValuePassing.kotlinBridgeType + override val cBridgeType: CType + get() = baseValuePassing.cBridgeType + override val cType: CType + get() = baseValuePassing.cType + + override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression { + val value = irCall(value.getter!!).apply { + dispatchReceiver = expression + } + + return with(baseValuePassing) { kotlinToBridged(value) } + } + + override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression { + val companionClass = enumClass.declarations.filterIsInstance().single { it.isCompanion } + val byValue = companionClass.simpleFunctions().single { it.name.asString() == "byValue" } + + return irCall(byValue).apply { + dispatchReceiver = irGetObject(companionClass.symbol) + putValueArgument(0, expression) + } + } + + override fun bridgedToC(expression: String): String = with(baseValuePassing) { bridgedToC(expression) } + override fun cToBridged(expression: String): String = with(baseValuePassing) { cToBridged(expression) } +} + +private class ObjCReferenceValuePassing(private val symbols: KonanSymbols, private val type: IrType) : SimpleValuePassing() { + override val kotlinBridgeType: IrType + get() = symbols.nativePtrType + override val cBridgeType: CType + get() = CTypes.voidPtr + override val cType: CType + get() = CTypes.voidPtr + + override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = + irCall(symbols.interopObjCObjectRawValueGetter.owner).apply { + extensionReceiver = expression + } + + override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression = + irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply { + putValueArgument(0, expression) + } + + override fun bridgedToC(expression: String): String = expression + override fun cToBridged(expression: String): String = expression + +} + +private class WCStringArgumentPassing : KotlinToCArgumentPassing { + + override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression { + val wcstr = irBuilder.irCall(symbols.interopWcstr.owner).apply { + extensionReceiver = expression + } + return with(CValuesRefArgumentPassing) { passValue(wcstr) } + } + +} + +private class CStringArgumentPassing : KotlinToCArgumentPassing { + + override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression { + val cstr = irBuilder.irCall(symbols.interopCstr.owner).apply { + extensionReceiver = expression + } + return with(CValuesRefArgumentPassing) { passValue(cstr) } + } + +} + +private object CValuesRefArgumentPassing : KotlinToCArgumentPassing { + override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression { + val bridgeArgument = cValuesRefToPointer(expression) + val cBridgeValue = passThroughBridge( + bridgeArgument, + symbols.interopCPointer.typeWithStarProjections.makeNullable(), + CTypes.voidPtr + ) + return CExpression(cBridgeValue.name, cBridgeValue.type) + } +} + +private fun KotlinToCCallBuilder.cValuesRefToPointer( + value: IrExpression +): IrExpression = if (value.type.classifierOrNull == symbols.interopCPointer) { + value // Optimization +} else with(irBuilder) { + val getPointerFunction = symbols.interopCValuesRef.owner + .simpleFunctions() + .single { it.name.asString() == "getPointer" } + + fun getPointer(expression: IrExpression) = irCall(getPointerFunction).apply { + dispatchReceiver = expression + putValueArgument(0, bridgeCallBuilder.getMemScope()) + } + + if (!value.type.isNullable()) { + getPointer(value) // Optimization + } else irLetS(value) { valueVarSymbol -> + val valueVar = valueVarSymbol.owner + irIfThenElse( + type = symbols.interopCPointer.typeWithStarProjections.makeNullable(), + condition = irEqeqeq(irGet(valueVar), irNull()), + thenPart = irNull(), + elsePart = getPointer(irGet(valueVar)) + ) + } +} + +private object VoidReturning : ValueReturning { + override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression { + bridgeBuilder.setReturnType(irBuilder.context.irBuiltIns.unitType, CTypes.void) + cFunctionBuilder.setReturnType(CTypes.void) + cBridgeBodyLines += "$expression;" + return buildKotlinBridgeCall() + } + + override fun CCallbackBuilder.returnValue(expression: IrExpression) { + bridgeBuilder.setReturnType(irBuiltIns.unitType, CTypes.void) + cFunctionBuilder.setReturnType(CTypes.void) + kotlinBridgeStatements += bridgeBuilder.kotlinIrBuilder.irReturn(expression) + cBodyLines += "${buildCBridgeCall()};" + } +} + +internal fun CType.cast(expression: String): String = "((${this.render("")})$expression)" + +private fun KotlinStubs.reportUnsupportedType(reason: String, type: IrType, location: TypeLocation): Nothing { + val typeLocation: String = when (location) { + is TypeLocation.FunctionArgument -> "" + is TypeLocation.FunctionCallResult -> " of return value" + is TypeLocation.FunctionPointerParameter -> " of callback parameter ${location.index + 1}" + is TypeLocation.FunctionPointerReturnValue -> " of callback return value" + } + + reportError(location.element, "type ${type.toKotlinType()}$typeLocation is not supported here" + + if (reason.isNotEmpty()) ": $reason" else "") +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt new file mode 100644 index 00000000000..2450fc7c620 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt @@ -0,0 +1,234 @@ +package org.jetbrains.kotlin.backend.konan.cgen + +import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor +import org.jetbrains.kotlin.backend.common.descriptors.WrappedValueParameterDescriptor +import org.jetbrains.kotlin.backend.common.lower.irBlock +import org.jetbrains.kotlin.backend.common.lower.irThrow +import org.jetbrains.kotlin.backend.konan.descriptors.createAnnotation +import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.IrTryImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.impl.IrUninitializedType +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.Name + +internal class CFunctionBuilder { + private val parameters = mutableListOf() + private lateinit var returnType: CType + + var variadic: Boolean = false + + fun setReturnType(type: CType) { + require(!::returnType.isInitialized) + returnType = type + } + + fun addParameter(type: CType): CVariable { + val result = CVariable(type, "p${counter++}") + parameters += result + return result + } + + val numberOfParameters: Int get() = parameters.size + + private var counter = 1 + + fun getType(): CType = CTypes.function(returnType, parameters.map { it.type }, variadic) + + fun buildSignature(name: String): String = returnType.render(buildString { + append(name) + append('(') + parameters.joinTo(this) + if (parameters.isEmpty()) { + if (!variadic) append("void") + } else { + if (variadic) append(", ...") + } + append(')') + }) + +} + +internal class KotlinBridgeBuilder( + startOffset: Int, + endOffset: Int, + cName: String, + stubs: KotlinStubs, + isExternal: Boolean +) { + private var counter = 0 + private val bridge: IrFunction = createKotlinBridge(startOffset, endOffset, cName, stubs.symbols, isExternal) + val irBuilder: IrBuilderWithScope = irBuilder(stubs.irBuiltIns, bridge.symbol).at(startOffset, endOffset) + + fun addParameter(type: IrType): IrValueParameter { + val index = counter++ + val descriptor = WrappedValueParameterDescriptor() + + return IrValueParameterImpl( + bridge.startOffset, bridge.endOffset, bridge.origin, + IrValueParameterSymbolImpl(descriptor), + Name.identifier("p$index"), index, type, + null, false, false + ).apply { + descriptor.bind(this) + parent = bridge + bridge.valueParameters += this + } + } + + fun setReturnType(type: IrType) { + bridge.returnType = type + } + + fun build(): IrFunction = bridge +} + +private fun createKotlinBridge( + startOffset: Int, + endOffset: Int, + cBridgeName: String, + symbols: KonanSymbols, + isExternal: Boolean +): IrFunctionImpl { + val bridgeAnnotations = Annotations.create( + listOf( + if (isExternal) { + createAnnotation(symbols.symbolName.descriptor, "value" to cBridgeName) + } else { + createAnnotation(symbols.exportForCppRuntime.descriptor, "name" to cBridgeName) + } + ) + ) + val bridgeDescriptor = WrappedSimpleFunctionDescriptor(bridgeAnnotations) + val bridge = IrFunctionImpl( + startOffset, + endOffset, + IrDeclarationOrigin.DEFINED, + IrSimpleFunctionSymbolImpl(bridgeDescriptor), + Name.identifier(cBridgeName), + Visibilities.PRIVATE, + Modality.FINAL, + IrUninitializedType, + isInline = false, + isExternal = isExternal, + isTailrec = false, + isSuspend = false + ) + bridgeDescriptor.bind(bridge) + return bridge +} + +internal class KotlinCBridgeBuilder( + startOffset: Int, + endOffset: Int, + cName: String, + stubs: KotlinStubs, + isKotlinToC: Boolean +) { + private val kotlinBridgeBuilder = KotlinBridgeBuilder(startOffset, endOffset, cName, stubs, isExternal = isKotlinToC) + private val cBridgeBuilder = CFunctionBuilder() + + val kotlinIrBuilder: IrBuilderWithScope get() = kotlinBridgeBuilder.irBuilder + + fun addParameter(kotlinType: IrType, cType: CType): Pair { + return kotlinBridgeBuilder.addParameter(kotlinType) to cBridgeBuilder.addParameter(cType) + } + + fun setReturnType(kotlinReturnType: IrType, cReturnType: CType) { + kotlinBridgeBuilder.setReturnType(kotlinReturnType) + cBridgeBuilder.setReturnType(cReturnType) + } + + fun buildCSignature(name: String): String = cBridgeBuilder.buildSignature(name) + + fun buildKotlinBridge() = kotlinBridgeBuilder.build() +} + +internal class KotlinCallBuilder(private val irBuilder: IrBuilderWithScope, private val symbols: KonanSymbols) { + val prepare = mutableListOf() + val arguments = mutableListOf() + val cleanup = mutableListOf() + + private var memScope: IrVariable? = null + + fun getMemScope(): IrExpression = with(irBuilder) { + memScope?.let { return irGet(it) } + + val newMemScope = scope.createTemporaryVariable(irCall(symbols.interopMemScope.owner.constructors.single())) + memScope = newMemScope + + prepare += newMemScope + + val clearImpl = symbols.interopMemScope.owner.simpleFunctions().single { it.name.asString() == "clearImpl" } + cleanup += irCall(clearImpl).apply { + dispatchReceiver = irGet(memScope!!) + } + + irGet(newMemScope) + } + + fun build( + function: IrFunction, + transformCall: (IrCall) -> IrExpression = { it } + ): IrExpression { + val arguments = this.arguments.toMutableList() + + val kotlinCall = irBuilder.irCall(function).run { + if (function.dispatchReceiverParameter != null) { + dispatchReceiver = arguments.removeAt(0) + } + if (function.extensionReceiverParameter != null) { + extensionReceiver = arguments.removeAt(0) + } + assert(arguments.size == function.valueParameters.size) + arguments.forEachIndexed { index, it -> putValueArgument(index, it) } + + transformCall(this) + } + + return if (prepare.isEmpty() && cleanup.isEmpty()) { + kotlinCall + } else { + irBuilder.irBlock(kotlinCall) { + prepare.forEach { +it } + if (cleanup.isEmpty()) { + +kotlinCall + } else { + // Note: generating try-catch as finally blocks are already lowered. + +IrTryImpl(startOffset, endOffset, kotlinCall.type).apply { + tryResult = kotlinCall + catches += irCatch(context.irBuiltIns.throwableType).apply { + result = irBlock(kotlinCall) { + cleanup.forEach { +it } + +irThrow(irGet(catchParameter)) + } + } + } + } + } + } + } +} + +internal class CCallBuilder { + val arguments = mutableListOf() + + fun build(function: String) = buildString { + append(function) + append('(') + arguments.joinTo(this) + append(')') + } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt new file mode 100644 index 00000000000..273e208f4cc --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt @@ -0,0 +1,58 @@ +package org.jetbrains.kotlin.backend.konan.cgen + +internal interface CType { + fun render(name: String): String +} + +internal class CVariable(val type: CType, val name: String) { + override fun toString() = type.render(name) +} + +internal object CTypes { + fun simple(type: String): CType = SimpleCType(type) + fun pointer(pointee: CType): CType = PointerCType(pointee) + fun function(returnType: CType, parameterTypes: List, variadic: Boolean): CType = + FunctionCType(returnType, parameterTypes, variadic) + + val void = simple("void") + val voidPtr = pointer(void) + val signedChar = simple("signed char") + val unsignedChar = simple("unsigned char") + val short = simple("short") + val unsignedShort = simple("unsigned short") + val int = simple("int") + val unsignedInt = simple("unsigned int") + val longLong = simple("long long") + val unsignedLongLong = simple("unsigned long long") + val float = simple("float") + val double = simple("double") + val C99Bool = simple("_Bool") + val char = simple("char") +} + +private class SimpleCType(private val type: String) : CType { + override fun render(name: String): String = if (name.isEmpty()) type else "$type $name" +} + +private class PointerCType(private val pointee: CType) : CType { + override fun render(name: String): String = pointee.render("*$name") +} + +private class FunctionCType( + private val returnType: CType, + private val parameterTypes: List, + private val variadic: Boolean +) : CType { + override fun render(name: String): String = returnType.render(buildString { + append("(") + append(name) + append(")(") + parameterTypes.joinTo(this) { it.render("") } + if (parameterTypes.isEmpty()) { + if (!variadic) append("void") + } else { + if (variadic) append(", ...") + } + append(')') + }) +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt index 2eafae30019..a771c865ea8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.backend.konan.descriptors import org.jetbrains.kotlin.backend.common.atMostOne +import org.jetbrains.kotlin.backend.konan.RuntimeNames import org.jetbrains.kotlin.backend.konan.binaryTypeIsReference import org.jetbrains.kotlin.backend.konan.isObjCClass import org.jetbrains.kotlin.backend.konan.serialization.isExported @@ -16,10 +17,12 @@ import org.jetbrains.kotlin.builtins.isSuspendFunctionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl import org.jetbrains.kotlin.metadata.konan.KonanProtoBuf import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.DescriptorFactory import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.constants.StringValue @@ -270,5 +273,16 @@ fun CallableMemberDescriptor.externalSymbolOrThrow(): String? { if (this.annotations.hasAnnotation(TypedIntrinsic)) return null + if (this.annotations.hasAnnotation(RuntimeNames.cCall)) return null + throw Error("external function ${this} must have @TypedIntrinsic, @SymbolName or @ObjCMethod annotation") } + +fun createAnnotation( + descriptor: ClassDescriptor, + vararg values: Pair +): AnnotationDescriptor = AnnotationDescriptorImpl( + descriptor.defaultType, + values.map { (name, value) -> Name.identifier(name) to StringValue(value) }.toMap(), + SourceElement.NO_SOURCE +) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 147b1beb24c..34913e07023 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -132,8 +132,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val nativePtr = symbolTable.referenceClass(context.nativePtr) val nativePtrType = nativePtr.typeWith(arguments = emptyList()) - private fun unsignedClass(unsignedType: UnsignedType): IrClassSymbol = - symbolTable.referenceClass(builtIns.builtInsModule.findClassAcrossModuleDependencies(unsignedType.classId)!!) + private fun unsignedClass(unsignedType: UnsignedType): IrClassSymbol = classById(unsignedType.classId) val uByte = unsignedClass(UnsignedType.UBYTE) val uShort = unsignedClass(UnsignedType.USHORT) @@ -181,9 +180,25 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val arrayList = symbolTable.referenceClass(getArrayListClassDescriptor(context)) + val symbolName = topLevelClass(RuntimeNames.symbolName) + val exportForCppRuntime = topLevelClass(RuntimeNames.exportForCppRuntime) + + val objCMethodImp = symbolTable.referenceClass(context.interopBuiltIns.objCMethodImp) + val interopNativePointedGetRawPointer = symbolTable.referenceSimpleFunction(context.interopBuiltIns.nativePointedGetRawPointer) + val interopCPointer = symbolTable.referenceClass(context.interopBuiltIns.cPointer) + val interopCstr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cstr.getter!!) + val interopWcstr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.wcstr.getter!!) + val interopMemScope = symbolTable.referenceClass(context.interopBuiltIns.memScope) + val interopCValue = symbolTable.referenceClass(context.interopBuiltIns.cValue) + val interopCValues = symbolTable.referenceClass(context.interopBuiltIns.cValues) + val interopCValuesRef = symbolTable.referenceClass(context.interopBuiltIns.cValuesRef) + val interopCValueWrite = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cValueWrite) + val interopCValueRead = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cValueRead) + val interopAllocType = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocType) + val interopCPointerGetRawValue = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cPointerGetRawValue) val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject) @@ -194,6 +209,12 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val .single() ) + val interopObjCRetain = symbolTable.referenceSimpleFunction( + context.interopBuiltIns.packageScope + .getContributedFunctions(Name.identifier("objc_retain"), NoLookupLocation.FROM_BACKEND) + .single() + ) + val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass) val interopObjCObjectSuperInitCheck = @@ -204,10 +225,6 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val interopObjCObjectRawValueGetter = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr) - val interopInvokeImpls = context.interopBuiltIns.invokeImpls.mapValues { (_, function) -> - symbolTable.referenceSimpleFunction(function) - } - val interopInterpretObjCPointer = symbolTable.referenceSimpleFunction(context.interopBuiltIns.interpretObjCPointer) @@ -397,20 +414,11 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val override val coroutineImpl get() = TODO() - val baseContinuationImpl = symbolTable.referenceClass( - builtIns.builtInsModule.findClassAcrossModuleDependencies( - ClassId.topLevel(FqName("kotlin.coroutines.native.internal.BaseContinuationImpl")))!! - ) + val baseContinuationImpl = topLevelClass("kotlin.coroutines.native.internal.BaseContinuationImpl") - val restrictedContinuationImpl = symbolTable.referenceClass( - builtIns.builtInsModule.findClassAcrossModuleDependencies( - ClassId.topLevel(FqName("kotlin.coroutines.native.internal.RestrictedContinuationImpl")))!! - ) + val restrictedContinuationImpl = topLevelClass("kotlin.coroutines.native.internal.RestrictedContinuationImpl") - val continuationImpl = symbolTable.referenceClass( - builtIns.builtInsModule.findClassAcrossModuleDependencies( - ClassId.topLevel(FqName("kotlin.coroutines.native.internal.ContinuationImpl")))!! - ) + val continuationImpl = topLevelClass("kotlin.coroutines.native.internal.ContinuationImpl") override val coroutineSuspendedGetter = symbolTable.referenceSimpleFunction( coroutinesIntrinsicsPackage @@ -418,10 +426,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val .filterNot { it.isExpect }.single().getter!! ) - val kotlinResult = symbolTable.referenceClass( - builtIns.builtInsModule.findClassAcrossModuleDependencies( - ClassId.topLevel(FqName("kotlin.Result")))!! - ) + val kotlinResult = topLevelClass("kotlin.Result") val kotlinResultGetOrThrow = symbolTable.referenceSimpleFunction( builtInsPackage("kotlin") @@ -477,6 +482,11 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val context.builtIns.builtInsModule.findClassAcrossModuleDependencies( ClassId.topLevel(FqName("kotlin.native.concurrent.SharedImmutable")))!! + private fun topLevelClass(fqName: String): IrClassSymbol = topLevelClass(FqName(fqName)) + private fun topLevelClass(fqName: FqName): IrClassSymbol = classById(ClassId.topLevel(fqName)) + private fun classById(classId: ClassId): IrClassSymbol = + symbolTable.referenceClass(builtIns.builtInsModule.findClassAcrossModuleDependencies(classId)!!) + private fun internalFunction(name: String): IrSimpleFunctionSymbol = symbolTable.referenceSimpleFunction(context.getInternalFunctions(name).single()) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/NewIrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/NewIrUtils.kt index d2c7c699a09..911d6f439e4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/NewIrUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/NewIrUtils.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.explicitParameters +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor @@ -34,6 +35,16 @@ val IrDeclarationParent.fqNameSafe: FqName get() = when (this) { else -> error(this) } +val IrClass.classId: ClassId? + get() { + val parent = this.parent + return when (parent) { + is IrClass -> parent.classId?.createNestedClassId(this.name) + is IrPackageFragment -> ClassId.topLevel(parent.fqName.child(this.name)) + else -> null + } + } + val IrDeclaration.name: Name get() = when (this) { is IrSimpleFunction -> this.name diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index 539b9c8f1d0..ab312ec2fe5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.backend.konan.llvm import llvm.LLVMTypeRef +import org.jetbrains.kotlin.backend.konan.RuntimeNames import org.jetbrains.kotlin.backend.konan.descriptors.externalSymbolOrThrow import org.jetbrains.kotlin.backend.konan.descriptors.getAnnotationValue import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract @@ -102,11 +103,11 @@ internal tailrec fun DeclarationDescriptor.isExported(): Boolean { return true } -private val symbolNameAnnotation = FqName("kotlin.native.SymbolName") +private val symbolNameAnnotation = RuntimeNames.symbolName private val cnameAnnotation = FqName("kotlin.native.CName") -private val exportForCppRuntimeAnnotation = FqName("kotlin.native.internal.ExportForCppRuntime") +private val exportForCppRuntimeAnnotation = RuntimeNames.exportForCppRuntime private val exportForCompilerAnnotation = FqName("kotlin.native.internal.ExportForCompiler") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 1f65dff2347..9ac23504f0f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -302,7 +302,10 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val found = LLVMGetNamedFunction(llvmModule, name) if (found != null) { - assert(getFunctionType(found) == type) + assert(getFunctionType(found) == type) { + "Expected: ${LLVMPrintTypeToString(type)!!.toKString()} " + + "found: ${LLVMPrintTypeToString(getFunctionType(found))!!.toKString()}" + } assert(LLVMGetLinkage(found) == LLVMLinkage.LLVMExternalLinkage) return found } else { @@ -330,11 +333,13 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { return function } - private val usedLibraries = mutableSetOf() + val imports get() = context.llvmImports - val imports = object : LlvmImports { + class ImportsImpl(private val context: Context) : LlvmImports { - private val allLibraries = context.librariesWithDependencies.toSet() + private val usedLibraries = mutableSetOf() + + private val allLibraries by lazy { context.librariesWithDependencies.toSet() } override fun add(origin: CompiledKonanModuleOrigin) { val library = when (origin) { @@ -348,11 +353,13 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { usedLibraries.add(library) } + + override fun isImported(library: KonanLibrary): Boolean = library in usedLibraries } val librariesToLink: List by lazy { context.config.resolvedLibraries - .filterRoots { (!it.isDefault && !context.config.purgeUserLibs) || it.library in usedLibraries } + .filterRoots { (!it.isDefault && !context.config.purgeUserLibs) || imports.isImported(it.library) } .getFullList(TopologicalLibraryOrder) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Imports.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Imports.kt index bf34a7d8f2a..2bc2ff9d339 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Imports.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Imports.kt @@ -11,10 +11,12 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.konan.CompiledKonanModuleOrigin import org.jetbrains.kotlin.descriptors.konan.SyntheticModulesOrigin import org.jetbrains.kotlin.descriptors.konan.konanModuleOrigin +import org.jetbrains.kotlin.konan.library.KonanLibrary import org.jetbrains.kotlin.resolve.descriptorUtil.module internal interface LlvmImports { fun add(origin: CompiledKonanModuleOrigin) + fun isImported(library: KonanLibrary): Boolean } internal val DeclarationDescriptor.llvmSymbolOrigin: CompiledKonanModuleOrigin diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt index 95b182a8938..93b9a9ace2b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt @@ -398,9 +398,8 @@ private class DeclarationsGeneratorVisitor(override val context: Context) : } val llvmFunction = if (descriptor.isExternal) { - if (descriptor.isTypedIntrinsic || descriptor.isObjCBridgeBased()) { - return - } + if (descriptor.isTypedIntrinsic || descriptor.isObjCBridgeBased() + || descriptor.annotations.hasAnnotation(RuntimeNames.cCall)) return context.llvm.externalFunction(descriptor.symbolName, llvmFunctionType, // Assume that `external fun` is defined in native libs attached to this module: diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 40d4c6007f1..2d6f7fa630d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -11,12 +11,17 @@ import org.jetbrains.kotlin.backend.common.peek import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.konan.* +import org.jetbrains.kotlin.backend.konan.cgen.KotlinStubs +import org.jetbrains.kotlin.backend.konan.cgen.generateCCall +import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionPointer import org.jetbrains.kotlin.backend.konan.getInlinedClass import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.backend.konan.irasdescriptors.* +import org.jetbrains.kotlin.backend.konan.objcexport.namePrefix import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType +import org.jetbrains.kotlin.backend.konan.llvm.llvmSymbolOrigin import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType import org.jetbrains.kotlin.builtins.UnsignedTypes import org.jetbrains.kotlin.descriptors.* @@ -29,6 +34,7 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* @@ -38,6 +44,7 @@ import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -743,14 +750,53 @@ internal class InteropLoweringPart2(val context: Context) : FileLoweringPass { override fun lower(irFile: IrFile) { val transformer = InteropTransformer(context, irFile) irFile.transformChildrenVoid(transformer) + + irFile.addChildren(transformer.newTopLevelDeclarations) } } private class InteropTransformer(val context: Context, val irFile: IrFile) : IrBuildingTransformer(context) { + val newTopLevelDeclarations = mutableListOf() + val interop = context.interopBuiltIns val symbols = context.ir.symbols + private inline fun generateWithStubs(element: IrElement? = null, block: KotlinStubs.() -> T): T = + createKotlinStubs(element).block() + + private fun createKotlinStubs(element: IrElement?): KotlinStubs { + val location = if (element != null) { + element.getCompilerMessageLocation(irFile) + } else { + builder.getCompilerMessageLocation() + } + + return object : KotlinStubs { + override val irBuiltIns get() = context.irBuiltIns + override val symbols get() = context.ir.symbols + + override fun addKotlin(declaration: IrDeclaration) { + newTopLevelDeclarations += declaration + } + + override fun addC(lines: List) { + context.cStubsManager.addStub(location, lines) + } + + override fun getUniqueCName(prefix: String) = + "_${context.moduleDescriptor.namePrefix}_${context.cStubsManager.getUniqueName(prefix)}" + + override val target get() = context.config.target + + override fun reportError(location: IrElement, message: String): Nothing = + context.reportCompilationError(message, irFile, location) + } + } + + private fun generateCFunctionPointer(function: IrSimpleFunction, expression: IrExpression): IrExpression = + generateWithStubs { generateCFunctionPointer(function, function, false, expression) } + override fun visitCall(expression: IrCall): IrExpression { expression.transformChildrenVoid(this) @@ -774,7 +820,10 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB } } - fun reportError(message: String): Nothing = context.reportCompilationError(message, irFile, expression) + if (function.descriptor.annotations.hasAnnotation(RuntimeNames.cCall)) { + context.llvmImports.add(function.descriptor.llvmSymbolOrigin) + return generateWithStubs { generateCCall(expression, builder, isInvoke = false) } + } val intrinsicType = tryGetIntrinsicType(expression) @@ -801,7 +850,8 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB IntrinsicType.INTEROP_STATIC_C_FUNCTION -> { val irCallableReference = unwrapStaticFunctionArgument(expression.getValueArgument(0)!!) - if (irCallableReference == null || irCallableReference.getArguments().isNotEmpty()) { + if (irCallableReference == null || irCallableReference.getArguments().isNotEmpty() + || irCallableReference.symbol !is IrSimpleFunctionSymbol) { context.reportCompilationError( "${descriptor.fqNameSafe} must take an unbound, non-capturing function or lambda", irFile, expression @@ -813,13 +863,6 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB val target = targetSymbol.owner val signatureTypes = target.allParameters.map { it.type } + target.returnType - signatureTypes.forEachIndexed { index, type -> - type.ensureSupportedInCallbacks( - isReturnType = (index == signatureTypes.lastIndex), - reportError = ::reportError - ) - } - descriptor.typeParameters.forEachIndexed { index, typeParameterDescriptor -> val typeArgument = expression.getTypeArgument(typeParameterDescriptor)!!.toKotlinType() val signatureType = signatureTypes[index].toKotlinType() @@ -832,39 +875,10 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB } } - IrFunctionReferenceImpl( - builder.startOffset, builder.endOffset, - expression.type, - targetSymbol, target.descriptor, - typeArgumentsCount = 0) + generateCFunctionPointer(target as IrSimpleFunction, expression) } IntrinsicType.INTEROP_FUNPTR_INVOKE -> { - // Replace by `invokeImpl${type}Ret`: - - val returnType = - expression.getTypeArgument(descriptor.typeParameters.single { it.name.asString() == "R" })!! - - returnType.checkCTypeNullability(::reportError) - - val invokeImpl = symbols.interopInvokeImpls[returnType.getClass()?.descriptor] ?: - context.reportCompilationError( - "Invocation of C function pointer with return type '${returnType.toKotlinType()}' is not supported yet", - irFile, expression - ) - - builder.irCall(invokeImpl).apply { - putValueArgument(0, expression.extensionReceiver) - - val varargParameter = invokeImpl.owner.valueParameters[1] - val varargArgument = IrVarargImpl( - startOffset, endOffset, varargParameter.type, varargParameter.varargElementType!! - ).apply { - descriptor.valueParameters.forEach { - this.addElement(expression.getValueArgument(it)!!) - } - } - putValueArgument(varargParameter.index, varargArgument) - } + generateWithStubs { generateCCall(expression, builder, isInvoke = true) } } IntrinsicType.INTEROP_SIGN_EXTEND, IntrinsicType.INTEROP_NARROW -> { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt index 5c0a8f55748..f2e79eda583 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt @@ -24,14 +24,17 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.* +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl import org.jetbrains.kotlin.ir.types.impl.IrStarProjectionImpl @@ -44,6 +47,14 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes import java.lang.reflect.Proxy +internal fun irBuilder(irBuiltIns: IrBuiltIns, scopeOwnerSymbol: IrSymbol): IrBuilderWithScope = + object : IrBuilderWithScope( + IrGeneratorContextBase(irBuiltIns), + Scope(scopeOwnerSymbol), + UNDEFINED_OFFSET, + UNDEFINED_OFFSET + ) {} + //TODO: delete file on next kotlin dependency update internal fun IrExpression.isNullConst() = this is IrConst<*> && this.kind == IrConstKind.Null @@ -506,6 +517,26 @@ fun IrBuilderWithScope.irCallOp( fun IrBuilderWithScope.irSetVar(variable: IrVariable, value: IrExpression) = irSetVar(variable.symbol, value) +fun IrBuilderWithScope.irCatch(type: IrType) = + IrCatchImpl( + startOffset, endOffset, + WrappedVariableDescriptor().let { descriptor -> + IrVariableImpl( + startOffset, + endOffset, + IrDeclarationOrigin.IR_TEMPORARY_VARIABLE, + IrVariableSymbolImpl(descriptor), + Name.identifier("e"), + type, + false, + false, + false + ).apply { + descriptor.bind(this) + } + } + ) + /** * Binds the arguments explicitly represented in the IR to the parameters of the accessed function. * The arguments are to be evaluated in the same order as they appear in the resulting list. diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index c5c9dfd2d91..c3f6b0ed2c8 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2882,6 +2882,10 @@ kotlinNativeInterop { defFile 'interop/basics/ctypes.def' } + ccallbacksAndVarargs { + defFile 'interop/basics/ccallbacksAndVarargs.def' + } + if (isMac()) { objcSmoke { defFile 'interop/objc/objcSmoke.def' @@ -2982,6 +2986,12 @@ task interop_types(type: RunInteropKonanTest) { interop = 'ctypes' } +task interop_callbacksAndVarargs(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. + source = "interop/basics/callbacksAndVarargs.kt" + interop = 'ccallbacksAndVarargs' +} + task interop_echo_server(type: RunInteropKonanTest) { disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. if (!isMac()) { diff --git a/backend.native/tests/interop/basics/callbacksAndVarargs.kt b/backend.native/tests/interop/basics/callbacksAndVarargs.kt new file mode 100644 index 00000000000..cf94a4f0a75 --- /dev/null +++ b/backend.native/tests/interop/basics/callbacksAndVarargs.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +import kotlin.test.* +import kotlinx.cinterop.* +import ccallbacksAndVarargs.* + +fun main(args: Array) { + assertEquals(42, getX(staticCFunction { -> cValue { x = 42 } })) + applyCallback(cValue { x = 17 }, staticCFunction { it: CValue -> + assertEquals(17, it.useContents { x }) + }) + + assertEquals(66, makeS(66, 111).useContents { x }) + assertEquals(E.ONE, makeE(1)) + + getVarargs( + 0, + true, + 2.toByte(), + Short.MIN_VALUE, + 42, + Long.MAX_VALUE, + 3.14f, + 2.71, + 0x1234ABCDL.toCPointer(), + UByte.MAX_VALUE, + 22.toUShort(), + 111u, + ULong.MAX_VALUE, + E.TWO, + cValue { x = 15 } + ).useContents { + assertEquals(1, a1) + assertEquals(2.toByte(), a2) + assertEquals(Short.MIN_VALUE, a3) + assertEquals(42, a4) + assertEquals(Long.MAX_VALUE, a5) + assertEquals(3.14f, a6) + assertEquals(2.71, a7) + assertEquals(0x1234ABCDL, a8.toLong()) + assertEquals(UByte.MAX_VALUE, a9) + assertEquals(22.toUShort(), a10) + assertEquals(111u, a11) + assertEquals(ULong.MAX_VALUE, a12) + assertEquals(E.TWO, a13) + assertEquals(15, a14.x) + } +} diff --git a/backend.native/tests/interop/basics/ccallbacksAndVarargs.def b/backend.native/tests/interop/basics/ccallbacksAndVarargs.def new file mode 100644 index 00000000000..c808d5023ac --- /dev/null +++ b/backend.native/tests/interop/basics/ccallbacksAndVarargs.def @@ -0,0 +1,72 @@ + + +--- + +#include + +struct S { + int x; +}; + +static int getX(struct S (*callback)(void)) { + return callback().x; +} + +static void applyCallback(struct S s, void (*callback)(struct S)) { + callback(s); +} + +static struct S makeS(int x, ...) { + return (struct S){ x }; +} + +enum E { + ZERO, ONE, TWO +}; + +static enum E makeE(int ordinal, ...) { + return ordinal; +} + +struct Args { + char a1; + char a2; + short a3; + int a4; + long long a5; + float a6; + double a7; + void* a8; + unsigned char a9; + unsigned short a10; + unsigned int a11; + unsigned long long a12; + enum E a13; + struct S a14; +}; + +static struct Args getVarargs(int ignore, ...) { + va_list args; + va_start(args, ignore); + + struct Args result = { + va_arg(args, char), + va_arg(args, char), + va_arg(args, short), + va_arg(args, int), + va_arg(args, long long), + va_arg(args, double), + va_arg(args, double), + va_arg(args, void*), + va_arg(args, unsigned char), + va_arg(args, unsigned short), + va_arg(args, unsigned int), + va_arg(args, unsigned long long), + va_arg(args, enum E), + va_arg(args, struct S) + }; + + va_end(args); + + return result; +} \ No newline at end of file diff --git a/konan/konan.properties b/konan/konan.properties index 98c1c70a745..348ac027545 100644 --- a/konan/konan.properties +++ b/konan/konan.properties @@ -36,10 +36,10 @@ llvmVersion = 6.0.1 # Mac OS X. llvmHome.macos_x64 = clang-llvm-6.0.1-darwin-macos targetToolchain.macos_x64 = target-toolchain-8-macos_x64 +libffiDir.macos_x64 = libffi-3.2.1-3-darwin-macos arch.macos_x64 = x86_64 targetSysRoot.macos_x64 = target-sysroot-8-macos_x64 -libffiDir.macos_x64 = libffi-3.2.1-3-darwin-macos llvmLtoFlags.macos_x64 = llvmLtoOptFlags.macos_x64 = -O3 -function-sections llvmLtoNooptFlags.macos_x64 = -O1 @@ -65,7 +65,7 @@ target-toolchain-8-macos_x64.default = \ # Apple's 32-bit iOS. targetToolchain.macos_x64-ios_arm32 = target-toolchain-8-macos_x64 dependencies.macos_x64-ios_arm32 = \ - libffi-3.2.1-3-darwin-ios \ + libffi-3.2.1-3-darwin-macos \ clang-llvm-6.0.1-darwin-macos target-sysroot-8-ios_arm32.default = \ @@ -75,7 +75,6 @@ arch.ios_arm32 = armv7 entrySelector.ios_arm32 = -alias _Konan_main _main # Shared with 64-bit version. targetSysRoot.ios_arm32 = target-sysroot-8-ios_arm64 -libffiDir.ios_arm32 = libffi-3.2.1-3-darwin-ios llvmLtoFlags.ios_arm32 = llvmLtoOptFlags.ios_arm32 = -O3 -function-sections linkerNoDebugFlags.ios_arm32 = -S @@ -91,7 +90,7 @@ osVersionMin.ios_arm32 = 9.0 # Apple's 64-bit iOS. targetToolchain.macos_x64-ios_arm64 = target-toolchain-8-macos_x64 dependencies.macos_x64-ios_arm64 = \ - libffi-3.2.1-3-darwin-ios \ + libffi-3.2.1-3-darwin-macos \ clang-llvm-6.0.1-darwin-macos target-sysroot-8-ios_arm64.default = \ @@ -100,7 +99,6 @@ target-sysroot-8-ios_arm64.default = \ arch.ios_arm64 = arm64 entrySelector.ios_arm64 = -alias _Konan_main _main targetSysRoot.ios_arm64 = target-sysroot-8-ios_arm64 -libffiDir.ios_arm64 = libffi-3.2.1-3-darwin-ios llvmLtoFlags.ios_arm64 = llvmLtoOptFlags.ios_arm64 = -O3 -function-sections linkerNoDebugFlags.ios_arm64 = -S @@ -116,7 +114,7 @@ osVersionMin.ios_arm64 = 9.0 # Apple's iOS simulator. targetToolchain.macos_x64-ios_x64 = target-toolchain-8-macos_x64 dependencies.macos_x64-ios_x64 = \ - libffi-3.2.1-2-darwin-ios_sim \ + libffi-3.2.1-3-darwin-macos \ clang-llvm-6.0.1-darwin-macos target-sysroot-8-ios_x64.default = \ @@ -125,7 +123,6 @@ target-sysroot-8-ios_x64.default = \ arch.ios_x64 = x86_64 entrySelector.ios_x64 = -alias _Konan_main _main targetSysRoot.ios_x64 = target-sysroot-8-ios_x64 -libffiDir.ios_x64 = libffi-3.2.1-2-darwin-ios_sim llvmLtoFlags.ios_x64 = llvmLtoOptFlags.ios_x64 = -O3 -function-sections llvmLtoNooptFlags.ios_x64 = -O1 @@ -142,10 +139,10 @@ osVersionMin.ios_x64 = 9.0 llvmHome.linux_x64 = clang-llvm-6.0.1-linux-x86-64 gccToolchain.linux_x64 = target-gcc-toolchain-3-linux-x86-64 targetToolchain.linux_x64 = target-gcc-toolchain-3-linux-x86-64/x86_64-unknown-linux-gnu +libffiDir.linux_x64 = libffi-3.2.1-2-linux-x86-64 quadruple.linux_x64 = x86_64-unknown-linux-gnu targetSysRoot.linux_x64 = target-gcc-toolchain-3-linux-x86-64/x86_64-unknown-linux-gnu/sysroot -libffiDir.linux_x64 = libffi-3.2.1-2-linux-x86-64 # targetSysroot-relative. libGcc.linux_x64 = ../../lib/gcc/x86_64-unknown-linux-gnu/4.8.5 llvmLtoFlags.linux_x64 = @@ -174,8 +171,7 @@ dependencies.linux_x64-linux_arm32_hfp = \ clang-llvm-6.0.1-linux-x86-64 \ target-gcc-toolchain-3-linux-x86-64 \ target-sysroot-1-raspberrypi \ - libffi-3.2.1-2-linux-x86-64 \ - libffi-3.2.1-2-raspberrypi + libffi-3.2.1-2-linux-x86-64 quadruple.linux_arm32_hfp = armv6-unknown-linux-gnueabihf entrySelector.linux_arm32_hfp = --defsym main=Konan_main @@ -184,7 +180,6 @@ linkerDynamicFlags.linux_arm32_hfp = -shared linkerOptimizationFlags.linux_arm32_hfp = --gc-sections targetSysRoot.linux_arm32_hfp = target-sysroot-1-raspberrypi # We could reuse host toolchain here. -libffiDir.linux_arm32_hfp = libffi-3.2.1-2-raspberrypi linkerKonanFlags.linux_arm32_hfp = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread \ --defsym __cxa_demangle=Konan_cxa_demangle # targetSysroot-relative. @@ -207,8 +202,7 @@ dependencies.linux_x64-linux_mips32 = \ target-gcc-toolchain-2-linux-mips \ target-gcc-toolchain-3-linux-x86-64 \ target-sysroot-2-mips \ - libffi-3.2.1-2-linux-x86-64 \ - libffi-3.2.1-2-linux-mips + libffi-3.2.1-2-linux-x86-64 quadruple.linux_mips32 = mips-unknown-linux-gnu entrySelector.linux_mips32 = --defsym main=Konan_main @@ -216,7 +210,6 @@ linkerOptimizationFlags.linux_mips32 = --gc-sections linkerDynamicFlags.linux_mips32 = -shared targetSysRoot.linux_mips32 = target-sysroot-2-mips # We could reuse host toolchain here. -libffiDir.linux_mips32 = libffi-3.2.1-2-linux-mips linkerKonanFlags.linux_mips32 = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread \ --defsym __cxa_demangle=Konan_cxa_demangle -z notext # targetSysroot-relative. @@ -236,15 +229,13 @@ dependencies.linux_x64-linux_mipsel32 = \ target-gcc-toolchain-2-linux-mips \ target-gcc-toolchain-3-linux-x86-64 \ target-sysroot-2-mipsel \ - libffi-3.2.1-2-linux-x86-64 \ - libffi-3.2.1-2-linux-mipsel + libffi-3.2.1-2-linux-x86-64 quadruple.linux_mipsel32 = mipsel-unknown-linux-gnu entrySelector.linux_mipsel32 = --defsym main=Konan_main linkerOptimizationFlags.linux_mipsel32 = --gc-sections linkerDynamicFlags.linux_mipsel32 = -shared targetSysRoot.linux_mipsel32 = target-sysroot-2-mipsel # We could reuse host toolchain here. -libffiDir.linux_mipsel32 = libffi-3.2.1-2-linux-mipsel linkerKonanFlags.linux_mipsel32 = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread \ --defsym __cxa_demangle=Konan_cxa_demangle -z notext # targetSysroot-relative. @@ -264,13 +255,13 @@ dependencies.macos_x64-android_arm32 = \ clang-llvm-6.0.1-darwin-macos \ target-sysroot-21-android_arm32 \ target-toolchain-21-osx-android_arm32 \ - libffi-3.2.1-2-android_arm32 + libffi-3.2.1-3-darwin-macos targetToolchain.linux_x64-android_arm32 = target-toolchain-21-linux-android_arm32 dependencies.linux_x64-android_arm32 = \ clang-llvm-6.0.1-linux-x86-64 \ target-sysroot-21-android_arm32 \ target-toolchain-21-linux-android_arm32 \ - libffi-3.2.1-2-android_arm32 + libffi-3.2.1-2-linux-x86-64 quadruple.android_arm32 = arm-linux-androideabi entrySelector.android_arm32 = -Wl,--defsym -Wl,main=Konan_main @@ -279,7 +270,6 @@ llvmLtoOptFlags.android_arm32 = -O3 -function-sections linkerNoDebugFlags.android_arm32 = -Wl,-S llvmLtoNooptFlags.android_arm32 = -O1 targetSysRoot.android_arm32 = target-sysroot-21-android_arm32 -libffiDir.android_arm32 = libffi-3.2.1-2-android_arm32 linkerKonanFlags.android_arm32 = -lm -latomic -lstdc++ -landroid # Android ARM64, based on NDK for android-21. @@ -289,29 +279,28 @@ dependencies.macos_x64-android_arm64 = \ clang-llvm-6.0.1-darwin-macos \ target-sysroot-21-android_arm64 \ target-toolchain-21-osx-android_arm64 \ - libffi-3.2.1-2-android_arm64 + libffi-3.2.1-3-darwin-macos targetToolchain.linux_x64-android_arm64 = target-toolchain-21-linux-android_arm64 dependencies.linux_x64-android_arm64 = \ clang-llvm-6.0.1-linux-x86-64 \ target-sysroot-21-android_arm64 \ target-toolchain-21-linux-android_arm64 \ - libffi-3.2.1-2-android_arm64 + libffi-3.2.1-2-linux-x86-64 quadruple.android_arm64 = aarch64-linux-android entrySelector.android_arm64 = -Wl,--defsym -Wl,main=Konan_main llvmLtoFlags.android_arm64 = -emulated-tls -relocation-model=pic targetSysRoot.android_arm64 = target-sysroot-21-android_arm64 -libffiDir.android_arm64 = libffi-3.2.1-2-android_arm64 linkerKonanFlags.android_arm64 = -lm -latomic -lstdc++ -landroid linkerNoDebugFlags.android_arm64 = -Wl,-S # Windows x86-64, based on mingw-w64. llvmHome.mingw_x64 = msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1 targetToolchain.mingw_x64 = msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1 +libffiDir.mingw_x64 = libffi-3.2.1-mingw-w64-x86-64 quadruple.mingw_x64 = x86_64-w64-mingw32 targetSysRoot.mingw_x64 = msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1 -libffiDir.mingw_x64 = libffi-3.2.1-mingw-w64-x86-64 # For using with Universal Windows Platform (UWP) we need to use this slower option. # See https://youtrack.jetbrains.com/issue/KT-27654. # TODO: remove, once fixed in mingw, check with the bug testcase. @@ -333,18 +322,21 @@ dependencies.mingw_x64 = \ # WebAssembly 32-bit. targetToolchain.macos_x64-wasm32 = target-toolchain-3-macos-wasm dependencies.macos_x64-wasm32 = \ + libffi-3.2.1-3-darwin-macos \ clang-llvm-6.0.1-darwin-macos \ target-sysroot-2-wasm \ target-toolchain-3-macos-wasm targetToolchain.linux_x64-wasm32 = target-toolchain-2-linux-wasm dependencies.linux_x64-wasm32 = \ + libffi-3.2.1-2-linux-x86-64 \ clang-llvm-6.0.1-linux-x86-64 \ target-sysroot-2-wasm \ target-toolchain-2-linux-wasm targetToolchain.mingw_x64-wasm32 = target-toolchain-2-mingw-wasm dependencies.mingw_x64-wasm32 = \ + libffi-3.2.1-mingw-w64-x86-64 \ msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1 \ target-sysroot-2-wasm \ target-toolchain-2-mingw-wasm diff --git a/konan/platforms/zephyr/stm32f4_disco b/konan/platforms/zephyr/stm32f4_disco index 7cf0ddfe0d2..4a6f0b5ed36 100644 --- a/konan/platforms/zephyr/stm32f4_disco +++ b/konan/platforms/zephyr/stm32f4_disco @@ -25,6 +25,7 @@ boardSpecificClangFlags.zephyr_stm32f4_disco = -mabi=aapcs -mthumb -mcpu=cortex- targetToolchain.linux_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-linux/arm-none-eabi dependencies.linux_x64-zephyr_stm32f4_disco = \ + libffi-3.2.1-2-linux-x86-64 \ clang-llvm-6.0.1-linux-x86-64 \ gcc-arm-none-eabi-7-2017-q4-major-linux \ target-sysroot-2-wasm @@ -32,11 +33,13 @@ dependencies.linux_x64-zephyr_stm32f4_disco = \ targetToolchain.macos_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-mac/arm-none-eabi dependencies.macos_x64-zephyr_stm32f4_disco = \ gcc-arm-none-eabi-7-2017-q4-major-mac \ + libffi-3.2.1-3-darwin-macos \ clang-llvm-6.0.1-darwin-macos \ target-sysroot-2-wasm targetToolchain.mingw_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-win32/arm-none-eabi dependencies.mingw_x64-zephyr_stm32f4_disco = \ + libffi-3.2.1-mingw-w64-x86-64 \ msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1 \ gcc-arm-none-eabi-7-2017-q4-major-win32 \ target-sysroot-2-wasm diff --git a/runtime/build.gradle b/runtime/build.gradle index e93af07cce9..b852a961f75 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -23,8 +23,6 @@ targetList.each { targetName -> if (!isWindows()) compilerArgs '-fPIC' includeRuntime(delegate) - if (rootProject.hasProperty("${targetName}LibffiDir")) - compilerArgs '-I' + project.file(rootProject.ext.get("${targetName}LibffiDir") + "/include") linkerArgs project.file("../common/build/$targetName/hash.bc").path } diff --git a/runtime/src/main/cpp/Interop.cpp b/runtime/src/main/cpp/Interop.cpp index 57843602238..eaad8762600 100644 --- a/runtime/src/main/cpp/Interop.cpp +++ b/runtime/src/main/cpp/Interop.cpp @@ -18,85 +18,11 @@ #include #include -#ifndef KONAN_NO_FFI -#include -#endif - #include "Memory.h" #include "Types.h" - -namespace { - -typedef int FfiTypeKind; - -#ifndef KONAN_NO_FFI -// Also declared in Varargs.kt -const FfiTypeKind FFI_TYPE_KIND_VOID = 0; -const FfiTypeKind FFI_TYPE_KIND_SINT8 = 1; -const FfiTypeKind FFI_TYPE_KIND_SINT16 = 2; -const FfiTypeKind FFI_TYPE_KIND_SINT32 = 3; -const FfiTypeKind FFI_TYPE_KIND_SINT64 = 4; -const FfiTypeKind FFI_TYPE_KIND_FLOAT = 5; -const FfiTypeKind FFI_TYPE_KIND_DOUBLE = 6; -const FfiTypeKind FFI_TYPE_KIND_POINTER = 7; -const FfiTypeKind FFI_TYPE_KIND_UINT8 = 8; -const FfiTypeKind FFI_TYPE_KIND_UINT16 = 9; -const FfiTypeKind FFI_TYPE_KIND_UINT32 = 10; -const FfiTypeKind FFI_TYPE_KIND_UINT64 = 11; - -ffi_type* convertFfiTypeKindToType(FfiTypeKind typeKind) { - switch (typeKind) { - case FFI_TYPE_KIND_VOID: return &ffi_type_void; - case FFI_TYPE_KIND_SINT8: return &ffi_type_sint8; - case FFI_TYPE_KIND_SINT16: return &ffi_type_sint16; - case FFI_TYPE_KIND_SINT32: return &ffi_type_sint32; - case FFI_TYPE_KIND_SINT64: return &ffi_type_sint64; - case FFI_TYPE_KIND_FLOAT: return &ffi_type_float; - case FFI_TYPE_KIND_DOUBLE: return &ffi_type_double; - case FFI_TYPE_KIND_POINTER: return &ffi_type_pointer; - case FFI_TYPE_KIND_UINT8: return &ffi_type_uint8; - case FFI_TYPE_KIND_UINT16: return &ffi_type_uint16; - case FFI_TYPE_KIND_UINT32: return &ffi_type_uint32; - case FFI_TYPE_KIND_UINT64: return &ffi_type_uint64; - - default: assert(false); return nullptr; - } -} -#endif // KONAN_NO_FFI - -} // namespace - extern "C" { -void Kotlin_Interop_callFunctionPointer(void* codePtr, void* returnValuePtr, FfiTypeKind returnTypeKind, - void** arguments, intptr_t* argumentTypeKinds, - int totalArgumentsNumber, int variadicArgumentsNumber) { -#ifdef KONAN_NO_FFI - RuntimeAssert(false, "Vararg calls are not supported on this platform"); -#else - ffi_type** argumentTypes = (ffi_type**)argumentTypeKinds; - // In-place convertion: - for (int i = 0; i < totalArgumentsNumber; ++i) { - argumentTypes[i] = convertFfiTypeKindToType((FfiTypeKind) argumentTypeKinds[i]); - } - ffi_type* returnType = convertFfiTypeKindToType(returnTypeKind); - - ffi_cif cif; - if (variadicArgumentsNumber < 0) { - // Non-variadic. - ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgumentsNumber, returnType, argumentTypes); - } else { - int fixedArgumentsNumber = totalArgumentsNumber - variadicArgumentsNumber; - ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, - fixedArgumentsNumber, totalArgumentsNumber, - returnType, argumentTypes); - } - - ffi_call(&cif, (void (*)())codePtr, returnValuePtr, arguments); -#endif -} - KNativePtr Kotlin_Interop_createStablePointer(KRef any) { return CreateStablePointer(any); } diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt index 52d73f4cef0..b40392f83e3 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt @@ -69,7 +69,10 @@ open class Command(initialCommand: List) { /** * If withErrors is true then output from error stream will be added */ - fun getOutputLines(withErrors: Boolean = false): List { + fun getOutputLines(withErrors: Boolean = false): List = + getResult(withErrors, handleError = true).outputLines + + fun getResult(withErrors: Boolean, handleError: Boolean = false): Result { log() val outputFile = createTempFile() @@ -80,7 +83,7 @@ open class Command(initialCommand: List) { builder.redirectInput(Redirect.INHERIT) builder.redirectError(Redirect.INHERIT) - builder.redirectOutput(ProcessBuilder.Redirect.to(outputFile)) + builder.redirectOutput(Redirect.to(outputFile)) .redirectErrorStream(withErrors) // Note: getting process output could be done without redirecting to temporary file, // however this would require managing a thread to read `process.inputStream` because @@ -88,14 +91,16 @@ open class Command(initialCommand: List) { val process = builder.start() val code = process.waitFor() - handleExitCode(code) + if (handleError) handleExitCode(code) - return outputFile.readLines() + return Result(code, outputFile.readLines()) } finally { outputFile.delete() } } + class Result(val exitCode: Int, val outputLines: List) + private fun handleExitCode(code: Int) { if (code != 0) throw KonanExternalToolFailure("The ${command[0]} command returned non-zero exit code: $code.", command[0]) } diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Configurables.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Configurables.kt index ef51de65153..94c07099064 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Configurables.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Configurables.kt @@ -24,6 +24,7 @@ interface Configurables : TargetableExternalStorage { val llvmHome get() = hostString("llvmHome") val llvmVersion get() = hostString("llvmVersion") + val libffiDir get() = hostString("libffiDir") // TODO: Delegate to a map? val llvmLtoNooptFlags get() = targetList("llvmLtoNooptFlags") @@ -37,7 +38,6 @@ interface Configurables : TargetableExternalStorage { val linkerDynamicFlags get() = targetList("linkerDynamicFlags") val llvmDebugOptFlags get() = targetList("llvmDebugOptFlags") val targetSysRoot get() = targetString("targetSysRoot") - val libffiDir get() = targetString("libffiDir") // Notice: these ones are host-target. val targetToolchain get() = hostTargetString("targetToolchain") @@ -45,7 +45,6 @@ interface Configurables : TargetableExternalStorage { val absoluteTargetSysRoot get() = absolute(targetSysRoot) val absoluteTargetToolchain get() = absolute(targetToolchain) val absoluteLlvmHome get() = absolute(llvmHome) - val absoluteLibffiDir get() = absolute(libffiDir) } interface NonAppleConfigurables : Configurables { diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt index c67aa290ff3..da582d62732 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt @@ -44,9 +44,6 @@ abstract class LinkerFlags(val configurables: Configurables) else -> error("Don't know libLTO location for this platform.") } - val targetLibffi = configurables.libffiDir?.let { listOf("${configurables.absoluteLibffiDir}/lib/libffi.a") } - ?: emptyList() - open val useCompilerDriverAsLinker: Boolean get() = false // TODO: refactor. abstract fun linkCommands(objectFiles: List, executable: ExecutableFile,