From 5586d2976435bba4c9abe6ad0704a0e98f8c7d0c Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 17 Aug 2017 14:44:45 +0300 Subject: [PATCH] Add basic support for calling C function pointers Fix #739 --- .../kotlinx/cinterop/FunctionPointers.kt | 120 ++++++++++++++++++ .../kotlin/backend/konan/InteropUtils.kt | 22 ++++ .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 4 + .../backend/konan/lower/InteropLowering.kt | 58 +++++++-- backend.native/tests/build.gradle | 12 ++ .../tests/interop/basics/cfunptr.def | 44 +++++++ backend.native/tests/interop/basics/funptr.kt | 21 +++ 7 files changed, 270 insertions(+), 11 deletions(-) create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt create mode 100644 backend.native/tests/interop/basics/cfunptr.def create mode 100644 backend.native/tests/interop/basics/funptr.kt diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt new file mode 100644 index 00000000000..c122e9671f5 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/FunctionPointers.kt @@ -0,0 +1,120 @@ +/* + * 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 konan.internal.Intrinsic +import konan.internal.ExportForCompiler + +@Intrinsic external operator fun CPointer R>>.invoke(): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8): R + +@Intrinsic external operator fun CPointer R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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): R + +@Intrinsic 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 + +@Intrinsic 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, emptyArray(), memScope) +} + +@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, emptyArray(), 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, emptyArray(), 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, emptyArray(), 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, emptyArray(), 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, emptyArray(), 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, emptyArray(), 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, emptyArray(), memScope) + resultBuffer.value +} 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 17d3364f8c3..97d6cc91bee 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 @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.util.OperatorNameConventions private val cPointerName = "CPointer" private val nativePointedName = "NativePointed" @@ -105,6 +106,27 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val readBits = packageScope.getContributedFunctions("readBits").single() val writeBits = packageScope.getContributedFunctions("writeBits").single() + val cFunctionPointerInvokes = packageScope.getContributedFunctions(OperatorNameConventions.INVOKE.asString()) + .filter { + val extensionReceiverParameter = it.extensionReceiverParameter + it.isOperator && + extensionReceiverParameter != null && + TypeUtils.getClassDescriptor(extensionReceiverParameter.type) == cPointer + }.toSet() + + val invokeImpls = mapOf( + builtIns.unit to "invokeImplUnitRet", + builtIns.byte to "invokeImplByteRet", + builtIns.short to "invokeImplShortRet", + builtIns.int to "invokeImplIntRet", + builtIns.long to "invokeImplLongRet", + builtIns.float to "invokeImplFloatRet", + builtIns.double to "invokeImplDoubleRet", + cPointer to "invokeImplPointerRet" + ).mapValues { (_, name) -> + packageScope.getContributedFunctions(name).single() + }.toMap() + val objCObject = packageScope.getContributedClassifier("ObjCObject") as ClassDescriptor val objCPointerHolder = packageScope.getContributedClassifier("ObjCPointerHolder") as ClassDescriptor 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 1eff4d2982a..a44addc6ce1 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 @@ -61,6 +61,10 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym val interopObjCObjectRawValueGetter = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr.getter!!) + val interopInvokeImpls = context.interopBuiltIns.invokeImpls.mapValues { (_, function) -> + symbolTable.referenceSimpleFunction(function) + } + val getNativeNullPtr = symbolTable.referenceSimpleFunction(context.builtIns.getNativeNullPtr) val boxFunctions = ValueType.values().associate { 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 f46e20b84a8..0f473de0b57 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 @@ -354,6 +354,8 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB } } + fun reportError(message: String): Nothing = context.reportCompilationError(message, irFile, expression) + return when (descriptor) { interop.cPointerRawValue.getter -> // Replace by the intrinsic call to be handled by code generator: @@ -399,7 +401,7 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB signatureTypes.forEachIndexed { index, type -> type.ensureSupportedInCallbacks( isReturnType = (index == signatureTypes.lastIndex), - reportError = { context.reportCompilationError(it, irFile, expression) } + reportError = ::reportError ) } @@ -495,33 +497,67 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB } } + in interop.cFunctionPointerInvokes -> { + // Replace by `invokeImpl${type}Ret`: + + val returnType = + expression.getTypeArgument(descriptor.typeParameters.single { it.name.asString() == "R" })!! + + returnType.checkCTypeNullability(::reportError) + + val invokeImpl = symbols.interopInvokeImpls[TypeUtils.getClassDescriptor(returnType)] ?: + context.reportCompilationError( + "Invocation of C function pointer with return type '$returnType' is not supported yet", + irFile, expression + ) + + builder.irCall(invokeImpl).apply { + putValueArgument(0, expression.extensionReceiver) + + val varargParameter = invokeImpl.descriptor.valueParameters[1] + val varargArgument = IrVarargImpl( + startOffset, endOffset, varargParameter.type, varargParameter.varargElementType!! + ).apply { + descriptor.valueParameters.forEach { + this.addElement(expression.getValueArgument(it)!!) + } + } + putValueArgument(varargParameter, varargArgument) + } + } + else -> expression } } private fun KotlinType.ensureSupportedInCallbacks(isReturnType: Boolean, reportError: (String) -> Nothing) { + this.checkCTypeNullability(reportError) + if (isReturnType && KotlinBuiltIns.isUnit(this)) { return } - if (KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(this)) { - if (!this.isMarkedNullable) { - return - } - reportError("Type $this must not be nullable when used in callback signature") + if (KotlinBuiltIns.isPrimitiveType(this)) { + return } if (TypeUtils.getClassDescriptor(this) == interop.cPointer) { - if (this.isMarkedNullable) { - return - } - - reportError("Type $this must be nullable when used in callback signature") + return } reportError("Type $this is not supported in callback signature") } + private fun KotlinType.checkCTypeNullability(reportError: (String) -> Nothing) { + if (KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(this) && this.isMarkedNullable) { + reportError("Type $this must not be nullable when used in C function signature") + } + + if (TypeUtils.getClassDescriptor(this) == interop.cPointer && !this.isMarkedNullable) { + reportError("Type $this must be nullable when used in C function signature") + } + } + private fun unwrapStaticFunctionArgument(argument: IrExpression): IrFunctionReference? { if (argument is IrFunctionReference) { return argument diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index ee9a1d634ce..e834a39b284 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1994,6 +1994,11 @@ kotlinNativeInterop { flavor 'native' } + cfunptr { + defFile 'interop/basics/cfunptr.def' + flavor 'native' + } + if (isMac()) { opengl { defFile '../../samples/opengl/src/main/c_interop/opengl.def' @@ -2057,6 +2062,13 @@ task interop_bitfields(type: RunInteropKonanTest) { interop = 'bitfields' } +task interop_funptr(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. + goldValue = "42\n17\n" + source = "interop/basics/funptr.kt" + interop = 'cfunptr' +} + 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/cfunptr.def b/backend.native/tests/interop/basics/cfunptr.def new file mode 100644 index 00000000000..047c59f8b62 --- /dev/null +++ b/backend.native/tests/interop/basics/cfunptr.def @@ -0,0 +1,44 @@ +headerFilter = NOTHING +--- +#include +#include + +typedef int (*atoiPtrType)(const char*); + +static atoiPtrType getAtoiPtr() { + return &atoi; +} + +static void __printInt(int x) { + printf("%d\n", x); +} + +static void* __getPrintIntPtr() { + return &__printInt; +} + +typedef void* (*getPrintIntPtrPtrType)(void); + +static getPrintIntPtrPtrType getGetPrintIntPtrPtr() { + return &__getPrintIntPtr; +} + +static double __add(double x, double y) { + return x + y; +} + +typedef double (*addPtrType)(double, double); + +static addPtrType getAddPtr() { + return &__add; +} + +static int __doubleToInt(double x) { + return (int) x; +} + +typedef int (*doubleToIntPtrType)(double); + +static doubleToIntPtrType getDoubleToIntPtr() { + return &__doubleToInt; +} \ No newline at end of file diff --git a/backend.native/tests/interop/basics/funptr.kt b/backend.native/tests/interop/basics/funptr.kt new file mode 100644 index 00000000000..e1d204863a5 --- /dev/null +++ b/backend.native/tests/interop/basics/funptr.kt @@ -0,0 +1,21 @@ +import kotlinx.cinterop.* +import cfunptr.* + +fun main(args: Array) { + val atoiPtr = getAtoiPtr()!! + + val getPrintIntPtrPtr = getGetPrintIntPtrPtr()!! + val printIntPtr = getPrintIntPtrPtr()!!.reinterpret Unit>>() + + val fortyTwo = memScoped { + atoiPtr("42".cstr.getPointer(memScope)) + } + + printIntPtr(fortyTwo) + + printIntPtr( + getDoubleToIntPtr()!!( + getAddPtr()!!(5.1, 12.2) + ) + ) +} \ No newline at end of file