Add basic support for calling C function pointers

Fix #739
This commit is contained in:
Svyatoslav Scherbina
2017-08-17 14:44:45 +03:00
committed by SvyatoslavScherbina
parent c848ee519d
commit 5586d29764
7 changed files with 270 additions and 11 deletions
@@ -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
@@ -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 {
@@ -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
+12
View File
@@ -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()) {
@@ -0,0 +1,44 @@
headerFilter = NOTHING
---
#include <stdio.h>
#include <stdlib.h>
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;
}
@@ -0,0 +1,21 @@
import kotlinx.cinterop.*
import cfunptr.*
fun main(args: Array<String>) {
val atoiPtr = getAtoiPtr()!!
val getPrintIntPtrPtr = getGetPrintIntPtrPtr()!!
val printIntPtr = getPrintIntPtrPtr()!!.reinterpret<CFunction<(Int) -> Unit>>()
val fortyTwo = memScoped {
atoiPtr("42".cstr.getPointer(memScope))
}
printIntPtr(fortyTwo)
printIntPtr(
getDoubleToIntPtr()!!(
getAddPtr()!!(5.1, 12.2)
)
)
}