Rework interop callbacks

*   Do not rely on type inference and `CFunctionType`;
*   Represent struct-typed parameters and return values as `CValue<*>`
    (currently supported only on JVM).
This commit is contained in:
Svyatoslav Scherbina
2017-04-20 12:31:53 +03:00
committed by SvyatoslavScherbina
parent cc73c7e009
commit 3f84ba462f
12 changed files with 2532 additions and 1733 deletions
+1 -8
View File
@@ -275,14 +275,7 @@ methods available:
### Callbacks ###
To convert Kotlin function to pointer to C function,
`staticCFunction(::kotlinFunction)` can be used. Currently `staticCFunction`
heavily relies on type inference, so the expression `staticCFunction(...)`
should be either assigned to the variable having proper type explicitly
specified, or passed to the function, e.g.
```
glutDisplayFunc(staticCFunction(::display))
```
`staticCFunction(::kotlinFunction)` can be used.
Note that some function types are not supported currently. For example,
it is not possible to get pointer to function that receives or returns structs
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -137,7 +137,7 @@ internal fun visitChildren(parent: CValue<CXCursor>, visitor: CursorVisitor) {
clang_visitChildren(parent, staticCFunction { cursor, parent, clientData ->
@Suppress("NAME_SHADOWING", "UNCHECKED_CAST")
val visitor = StableObjPtr.fromValue(clientData!!).get() as CursorVisitor
visitor(cursor.readValue(), parent.readValue())
visitor(cursor, parent)
}, clientData)
}
@@ -16,6 +16,15 @@
package kotlinx.cinterop
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KType
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.reflect
/**
* This class provides a way to create a stable handle to any Kotlin object.
* Its [value] can be safely passed to native code e.g. to be received in a Kotlin callback.
@@ -59,112 +68,348 @@ data class StableObjPtr private constructor(val value: COpaquePointer) {
}
/**
* Describes the type of C function with adapter for Kotlin functions.
*
* The instances of this class are supposed to be Kotlin object declarations (singletons),
* because it is required by [CAdaptedFunctionType] and
* because creating the instance implies allocating some amount of non-freeable memory for the instance itself
* and for any unique Kotlin function "converted" to this type.
*
* Native function type definition consists in the following:
* - Definitions of native function's parameter and return types to be passed into the constructor
* - Implementation of [invoke] method which describes how to convert between these types and Kotlin types used in [F]
*
* @param F Kotlin function type corresponding to given native function type
*/
abstract class CAdaptedFunctionTypeImpl<F : Function<*>>
protected constructor(returnType: CType, vararg paramTypes: CType) : CAdaptedFunctionType<F> {
override fun fromStatic(function: F): NativePtr {
// TODO: optimize synchronization
synchronized(cache) {
return cache.getOrPut(function, { createFromStatic(function) })
}
private fun getFieldCType(type: KType): CType<*> {
val classifier = type.classifier
if (classifier is KClass<*> && classifier.isSubclassOf(CStructVar::class)) {
return getStructCType(classifier)
}
/**
* Describes the C type of a function's parameter or return value.
* It is supposed to be constructed using the primitive types (such as [SInt32]) and the [Struct] combinator.
*
* This description omits the details that are irrelevant for the ABI.
*/
protected open class CType internal constructor(val ffiType: ffi_type) {
internal constructor(ffiTypePtr: Long) : this(interpretPointed<ffi_type>(ffiTypePtr))
}
protected object Void : CType(ffiTypeVoid())
protected object UInt8 : CType(ffiTypeUInt8())
protected object SInt8 : CType(ffiTypeSInt8())
protected object UInt16 : CType(ffiTypeUInt16())
protected object SInt16 : CType(ffiTypeSInt16())
protected object UInt32 : CType(ffiTypeUInt32())
protected object SInt32 : CType(ffiTypeSInt32())
protected object UInt64 : CType(ffiTypeUInt64())
protected object SInt64 : CType(ffiTypeSInt64())
protected object Pointer : CType(ffiTypePointer())
protected class Struct(vararg elementTypes: CType) : CType(
ffiTypeStruct(
elementTypes.map { it.ffiType }
)
)
/**
* This method should invoke given Kotlin function.
*
* @param args array of pointers to arguments to be passed to [function]
* @param ret pointer to memory to be filled with return value of [function]
*/
protected abstract fun invoke(function: F, args: CArrayPointer<COpaquePointerVar>, ret: COpaquePointer)
companion object {
init {
loadCallbacksLibrary()
}
}
private val ffiCif = ffiCreateCif(returnType.ffiType, paramTypes.map { it.ffiType })
/**
* Allocates a native function of this type for given Kotlin function.
*/
private fun createFromStatic(function: F): NativePtr {
if (!isStatic(function)) {
throw IllegalArgumentException()
}
val impl: UserData = { ret: COpaquePointer, args: CArrayPointer<COpaquePointerVar> ->
invoke(function, args, ret)
}
return ffiCreateClosure(ffiCif, impl)
}
/**
* Returns `true` if given function is *static* as defined in [fromStatic].
*/
private fun isStatic(function: Function<*>): Boolean {
// TODO: revise
try {
with(function.javaClass.getDeclaredField("INSTANCE")) {
if (!java.lang.reflect.Modifier.isStatic(modifiers) || !java.lang.reflect.Modifier.isFinal(modifiers)) {
return false
}
isAccessible = true // TODO: undo
return get(null) == function
}
} catch (e: NoSuchFieldException) {
return false
}
}
private val cache = mutableMapOf<F, NativePtr>()
return getArgOrRetValCType(type)
}
private typealias UserData = (ret: COpaquePointer, args: CArrayPointer<COpaquePointerVar>)->Unit
private fun getVariableCType(type: KType): CType<*>? {
val classifier = type.classifier
return when (classifier) {
!is KClass<*> -> null
ByteVarOf::class -> SInt8
ShortVarOf::class -> SInt16
IntVarOf::class -> SInt32
LongVarOf::class -> SInt64
CPointerVarOf::class -> Pointer
// TODO: floats, enums.
else -> if (classifier.isSubclassOf(CStructVar::class)) {
getStructCType(classifier)
} else {
null
}
}
}
private val structTypeCache = ConcurrentHashMap<Class<*>, CType<*>>()
private fun getStructCType(structClass: KClass<*>): CType<*> = structTypeCache.computeIfAbsent(structClass.java) {
// Note that struct classes are not supposed to be user-defined,
// so they don't require to be checked strictly.
val annotations = structClass.annotations
val cNaturalStruct = annotations.filterIsInstance<CNaturalStruct>().firstOrNull() ?:
error("struct ${structClass.simpleName} has custom layout")
val propertiesByName = structClass.declaredMemberProperties.groupBy { it.name }
val fields = cNaturalStruct.fieldNames.map {
propertiesByName[it]!!.single()
}
val fieldCTypes = mutableListOf<CType<*>>()
for (field in fields) {
val lengthAnnotation = field.annotations.filterIsInstance<CLength>().firstOrNull()
if (lengthAnnotation == null) {
val fieldType = getFieldCType(field.returnType)
fieldCTypes.add(fieldType)
} else {
assert(field.returnType.classifier == CPointer::class)
val length = lengthAnnotation.value
if (length != 0) {
val pointed = field.returnType.arguments.single().type!!
val pointedCType = getVariableCType(pointed) ?: TODO("array element type '$pointed'")
// Represent array field as repeated element-typed fields:
repeat(length) {
fieldCTypes.add(pointedCType)
}
}
}
}
val structType = structClass.companionObjectInstance as CVariable.Type
Struct(structType.size, structType.align, fieldCTypes)
}
private fun getStructValueCType(type: KType): CType<*> {
val structClass = type.arguments.singleOrNull()?.type?.classifier as? KClass<*> ?:
error("'$type' type is incomplete")
return getStructCType(structClass)
}
private fun getEnumCType(classifier: KClass<*>): CEnumType? {
val rawValueType = classifier.declaredMemberProperties.single().returnType
val rawValueCType = when (rawValueType.classifier) {
Byte::class -> SInt8
Short::class -> SInt16
Int::class -> SInt32
Long::class -> SInt64
else -> error("'${classifier.simpleName}' has unexpected value type '$rawValueType'")
}
@Suppress("UNCHECKED_CAST")
return CEnumType(rawValueCType as CType<Number>)
}
private fun getArgOrRetValCType(type: KType): CType<*> {
val classifier = type.classifier
val result = when (classifier) {
!is KClass<*> -> null
Unit::class -> Void
Byte::class -> SInt8
Short::class -> SInt16
Int::class -> SInt32
Long::class -> SInt64
CPointer::class -> Pointer
// TODO: floats
CValue::class -> getStructValueCType(type)
else -> if (classifier.isSubclassOf(CEnum::class)) {
getEnumCType(classifier)
} else {
null
}
} ?: error("$type is not supported in callback signature")
if (type.isMarkedNullable != (classifier == CPointer::class)) {
if (type.isMarkedNullable) {
error("$type must not be nullable when used in callback signature")
} else {
error("$type must be nullable when used in callback signature")
}
}
return result
}
private fun createStaticCFunction(function: Function<*>): CPointer<CFunction<*>> {
val errorMessage = "staticCFunction must take an unbound, non-capturing function"
if (!isStatic(function)) {
throw IllegalArgumentException(errorMessage)
}
val kFunction = function as? KFunction<*> ?: function.reflect() ?:
throw IllegalArgumentException(errorMessage)
val returnType = getArgOrRetValCType(kFunction.returnType)
val paramTypes = kFunction.parameters.map { getArgOrRetValCType(it.type) }
@Suppress("UNCHECKED_CAST")
return interpretCPointer(createStaticCFunctionImpl(returnType as CType<Any?>, paramTypes, function))!!
}
/**
* Returns `true` if given function is *static* as defined in [staticCFunction].
*/
private fun isStatic(function: Function<*>): Boolean {
// TODO: revise
try {
with(function.javaClass.getDeclaredField("INSTANCE")) {
if (!java.lang.reflect.Modifier.isStatic(modifiers) || !java.lang.reflect.Modifier.isFinal(modifiers)) {
return false
}
isAccessible = true // TODO: undo
return get(null) == function
// If the class has static final "INSTANCE" field, and only the value of this field is accepted,
// then each class is handled at most once, so these checks prevent memory leaks.
}
} catch (e: NoSuchFieldException) {
return false
}
}
private val createdStaticFunctions = ConcurrentHashMap<Class<*>, CPointer<CFunction<*>>>()
@Suppress("UNCHECKED_CAST")
internal fun <F : Function<*>> staticCFunctionImpl(function: F) =
createdStaticFunctions.computeIfAbsent(function.javaClass) {
createStaticCFunction(function)
} as CPointer<CFunction<F>>
private val invokeMethods = (0 .. 22).map { arity ->
Class.forName("kotlin.jvm.functions.Function$arity").getMethod("invoke",
*Array<Class<*>>(arity) { java.lang.Object::class.java })
}
private fun createStaticCFunctionImpl(
returnType: CType<Any?>,
paramTypes: List<CType<*>>,
function: Function<*>
): NativePtr {
val ffiCif = ffiCreateCif(returnType.ffiType, paramTypes.map { it.ffiType })
val arity = paramTypes.size
val pt = paramTypes.toTypedArray()
@Suppress("UNCHECKED_CAST")
val impl: FfiClosureImpl = when (arity) {
0 -> {
val f = function as () -> Any?
ffiClosureImpl(returnType) { args ->
f()
}
}
1 -> {
val f = function as (Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0))
}
}
2 -> {
val f = function as (Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1))
}
}
3 -> {
val f = function as (Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2))
}
}
4 -> {
val f = function as (Any?, Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2), pt.read(args, 3))
}
}
5 -> {
val f = function as (Any?, Any?, Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2), pt.read(args, 3), pt.read(args, 4))
}
}
else -> {
val invokeMethod = invokeMethods[arity]
ffiClosureImpl(returnType) { args ->
val arguments = Array(arity) { pt.read(args, it) }
invokeMethod.invoke(function, *arguments)
}
}
}
return ffiCreateClosure(ffiCif, impl)
}
@Suppress("NOTHING_TO_INLINE")
private inline fun Array<CType<*>>.read(args: CArrayPointer<COpaquePointerVar>, index: Int) =
this[index].read(args[index].rawValue)
private inline fun ffiClosureImpl(
returnType: CType<Any?>,
crossinline invoke: (args: CArrayPointer<COpaquePointerVar>) -> Any?
): FfiClosureImpl {
return { ret, args ->
val result = invoke(args)
returnType.write(ret.rawValue, result)
}
}
/**
* Describes the bridge between Kotlin type `T` and the corresponding C type of a function's parameter or return value.
* It is supposed to be constructed using the primitive types (such as [SInt32]), the [Struct] combinator
* and the [CEnumType] wrapper.
*
* This description omits the details that are irrelevant for the ABI.
*/
private abstract class CType<T> internal constructor(val ffiType: ffi_type) {
internal constructor(ffiTypePtr: Long) : this(interpretPointed<ffi_type>(ffiTypePtr))
abstract fun read(location: NativePtr): T
abstract fun write(location: NativePtr, value: T): Unit
}
private object Void : CType<Any?>(ffiTypeVoid()) {
override fun read(location: NativePtr) = throw UnsupportedOperationException()
override fun write(location: NativePtr, value: Any?) {
// nothing to do.
}
}
private object SInt8 : CType<Byte>(ffiTypeSInt8()) {
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).value
override fun write(location: NativePtr, value: Byte) {
interpretPointed<ByteVar>(location).value = value
}
}
private object SInt16 : CType<Short>(ffiTypeSInt16()) {
override fun read(location: NativePtr) = interpretPointed<ShortVar>(location).value
override fun write(location: NativePtr, value: Short) {
interpretPointed<ShortVar>(location).value = value
}
}
private object SInt32 : CType<Int>(ffiTypeSInt32()) {
override fun read(location: NativePtr) = interpretPointed<IntVar>(location).value
override fun write(location: NativePtr, value: Int) {
interpretPointed<IntVar>(location).value = value
}
}
private object SInt64 : CType<Long>(ffiTypeSInt64()) {
override fun read(location: NativePtr) = interpretPointed<LongVar>(location).value
override fun write(location: NativePtr, value: Long) {
interpretPointed<LongVar>(location).value = value
}
}
private object Pointer : CType<CPointer<*>?>(ffiTypePointer()) {
override fun read(location: NativePtr) = interpretPointed<CPointerVar<*>>(location).value
override fun write(location: NativePtr, value: CPointer<*>?) {
interpretPointed<CPointerVar<*>>(location).value = value
}
}
private class Struct(val size: Long, val align: Int, elementTypes: List<CType<*>>) : CType<CValue<*>>(
ffiTypeStruct(
elementTypes.map { it.ffiType }
)
) {
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).readValue<CStructVar>(size, align)
override fun write(location: NativePtr, value: CValue<*>) {
// TODO: probably CValue must be redesigned.
val fakePlacement = object : NativePlacement {
var used = false
override fun alloc(size: Long, align: Int): NativePointed {
assert(!used)
assert (size == this@Struct.size)
assert (align == this@Struct.align)
used = true
return interpretPointed<ByteVar>(location)
}
}
value.getPointer(fakePlacement)
assert (fakePlacement.used)
}
}
private class CEnumType(private val rawValueCType: CType<Number>) : CType<CEnum>(rawValueCType.ffiType) {
override fun read(location: NativePtr): CEnum {
TODO("enum-typed callback parameters")
}
override fun write(location: NativePtr, value: CEnum) {
rawValueCType.write(location, value.value)
}
}
private typealias FfiClosureImpl = (ret: COpaquePointer, args: CArrayPointer<COpaquePointerVar>)->Unit
private typealias UserData = FfiClosureImpl
private fun loadCallbacksLibrary() {
System.loadLibrary("callbacks")
@@ -233,7 +478,7 @@ private fun ffiCreateCif(returnType: ffi_type, paramTypes: List<ffi_type>): ffi_
return interpretPointed(res)
}
@Suppress("UNUSED_PARAMETER")
@Suppress("UNUSED_PARAMETER", "UNUSED")
private fun ffiFunImpl0(ffiCif: Long, ret: Long, args: Long, userData: Any) {
@Suppress("UNCHECKED_CAST")
ffiFunImpl(interpretCPointer(ret)!!,
@@ -259,8 +504,8 @@ private external fun ffiCreateClosure0(ffiCif: Long, userData: Any): Long
*
* @param ffiCif describes the type of the function to create
*/
private fun ffiCreateClosure(ffiCif: ffi_cif, userData: UserData): NativePtr {
val res = ffiCreateClosure0(ffiCif.rawPtr, userData)
private fun ffiCreateClosure(ffiCif: ffi_cif, impl: FfiClosureImpl): NativePtr {
val res = ffiCreateClosure0(ffiCif.rawPtr, userData = impl)
when (res) {
0L -> throw OutOfMemoryError()
@@ -52,17 +52,81 @@ fun <T : CPointed> interpretCPointer(rawValue: NativePtr) =
CPointer<T>(rawValue)
}
inline fun <reified T : CAdaptedFunctionType<*>> CAdaptedFunctionType.Companion.getInstanceOf(): T =
T::class.objectInstance!!
internal fun CPointer<*>.cPointerToString() = "CPointer(raw=0x%x)".format(rawValue)
/**
* Returns a pointer to `T`-typed C function which calls given Kotlin *static* function.
* @see CAdaptedFunctionType.fromStatic
*/
inline fun <reified F : Function<*>, reified T : CAdaptedFunctionType<F>> staticCFunction(body: F): CFunctionPointer<T> {
val type = CAdaptedFunctionType.getInstanceOf<T>()
return interpretPointed<CFunction<T>>(type.fromStatic(body)).ptr
}
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class CLength(val value: Int)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class CNaturalStruct(vararg val fieldNames: String)
fun <R> staticCFunction(function: () -> R): CPointer<CFunction<() -> R>> =
staticCFunctionImpl(function)
fun <P1, R> staticCFunction(function: (P1) -> R): CPointer<CFunction<(P1) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, R> staticCFunction(function: (P1, P2) -> R): CPointer<CFunction<(P1, P2) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, R> staticCFunction(function: (P1, P2, P3) -> R): CPointer<CFunction<(P1, P2, P3) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, R> staticCFunction(function: (P1, P2, P3, P4) -> R): CPointer<CFunction<(P1, P2, P3, P4) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, R> staticCFunction(function: (P1, P2, P3, P4, P5) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>> =
staticCFunctionImpl(function)
@@ -381,43 +381,16 @@ inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.set(index: Long
typealias CArrayPointer<T> = CPointer<T>
typealias CArrayPointerVar<T> = CPointerVar<T>
/**
* The type of C function.
*/
interface CFunctionType
/**
* The type of C function constructed from some Kotlin function, possibly using an adapter.
* The (non-abstract) implementation classes are supposed to be object declarations.
*/
interface CAdaptedFunctionType<F : Function<*>> : CFunctionType {
/**
* Returns a raw pointer to C function of this type, which calls given Kotlin *static* function.
*
* This inconvenient method should not be used directly; use [staticCFunction] instead.
*
* @param function must be *static*, i.e. an (unbound) reference to a Kotlin function or
* a closure which doesn't capture any variable
*/
fun fromStatic(function: F): NativePtr
companion object
}
/**
* The C function.
*/
class CFunction<T : CFunctionType>(override val rawPtr: NativePtr) : CPointed
class CFunction<T : Function<*>>(override val rawPtr: NativePtr) : CPointed
/**
* The pointer to [CFunction].
* TODO: remove.
* Returns a pointer to C function which calls given Kotlin *static* function.
*
* @param function must be *static*, i.e. an (unbound) reference to a Kotlin function or
* a closure which doesn't capture any variable
*/
typealias CFunctionPointer<T> = CPointer<CFunction<T>>
/**
* The variable containing a [CFunctionPointer].
* TODO: remove.
*/
typealias CFunctionPointerVar<T> = CPointerVarOf<CFunctionPointer<T>>
@Deprecated("The function type is too general. Supply argument with known arity.", level = DeprecationLevel.ERROR)
external fun <F : Function<*>> staticCFunction(function: F): CPointer<CFunction<F>>
@@ -45,12 +45,3 @@ private external fun disposeStablePointer(pointer: COpaquePointer)
@SymbolName("Kotlin_Interop_derefStablePointer")
private external fun derefStablePointer(pointer: COpaquePointer): Any
/**
* The type of C function which can be constructed from the appropriate Kotlin function without using any adapter.
*/
abstract class CTriviallyAdaptedFunctionType<F : Function<*>> : CAdaptedFunctionType<F> {
@konan.internal.Intrinsic
override final fun fromStatic(function: F): NativePtr =
throw Error("CTriviallyAdaptedFunctionType.fromStatic is called virtually")
}
@@ -34,21 +34,50 @@ fun <T : CVariable> typeOf(): CVariable.Type = throw Error("typeOf() is called w
@Intrinsic external fun CPointer<*>.getRawValue(): NativePtr
inline fun <reified T : CAdaptedFunctionType<*>> CAdaptedFunctionType.Companion.getInstanceOf(): T =
TODO("CAdaptedFunctionType.getInstanceOf 11")
internal fun CPointer<*>.cPointerToString() = "CPointer(raw=$rawValue)"
/**
* Returns a pointer to `T`-typed C function which calls given Kotlin *static* function.
* @see CAdaptedFunctionType.fromStatic
*/
// TODO: This function is not inline, whereas the same name function in JvmTypes.kt
// is inline. We can't make it inline here, because native interop lowering
// expects to find and transform it. And native interop lowering is done after
// inline expansions.
fun <F : Function<*>, T : CAdaptedFunctionType<F>> staticCFunction(body: F): CFunctionPointer<T> {
val type = CAdaptedFunctionType.getInstanceOf<T>()
return interpretPointed<CFunction<T>>(type.fromStatic(body)).ptr
}
@Intrinsic external fun <R> staticCFunction(function: () -> R): CPointer<CFunction<() -> R>>
@Intrinsic external fun <P1, R> staticCFunction(function: (P1) -> R): CPointer<CFunction<(P1) -> R>>
@Intrinsic external fun <P1, P2, R> staticCFunction(function: (P1, P2) -> R): CPointer<CFunction<(P1, P2) -> R>>
@Intrinsic external fun <P1, P2, P3, R> staticCFunction(function: (P1, P2, P3) -> R): CPointer<CFunction<(P1, P2, P3) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, R> staticCFunction(function: (P1, P2, P3, P4) -> R): CPointer<CFunction<(P1, P2, P3, P4) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, R> staticCFunction(function: (P1, P2, P3, P4, P5) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>>
@Intrinsic external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>>
@@ -152,15 +152,6 @@ class StubGenerator(
val functionsToBind = nativeIndex.functions.filter { it.name !in excludedFunctions }
private val usedFunctionTypes = mutableMapOf<FunctionType, String>()
val FunctionType.kotlinName: String
get() {
return usedFunctionTypes.getOrPut(this, {
"CFunctionType" + (usedFunctionTypes.size + 1)
})
}
private val macroConstantsByName = nativeIndex.macroConstants.associateBy { it.name }
/**
@@ -442,7 +433,7 @@ class StubGenerator(
}
}
is FunctionType -> byRefTypeMirror("CFunction<${type.kotlinName}>")
is FunctionType -> byRefTypeMirror("CFunction<${getKotlinFunctionType(type)}>")
is Typedef -> {
val baseType = mirror(type.def.aliased)
@@ -485,6 +476,17 @@ class StubGenerator(
* Constructs [OutValueBinding] for the value of given C type.
*/
fun getOutValueBinding(type: Type): OutValueBinding {
if (type.unwrapTypedefs() is RecordType) {
// TODO: this case should probably be handled more generally.
val typeMirror = mirror(type)
return OutValueBinding(
kotlinType = "CValue<${typeMirror.pointedTypeName}>",
kotlinConv = { name -> "$name.getPointer(memScope).rawValue" }, // TODO: eliminate this copying
memScoped = true,
kotlinJniBridgeType = "NativePtr"
)
}
val mirror = mirror(type)
return OutValueBinding(
@@ -539,16 +541,6 @@ class StubGenerator(
kotlinJniBridgeType = "NativePtr"
)
}
if (type.unwrapTypedefs() is RecordType) {
// TODO: this case should probably be handled more generally.
val typeMirror = mirror(type)
return OutValueBinding(
kotlinType = "CValue<${typeMirror.pointedTypeName}>",
kotlinConv = { name -> "$name.getPointer(memScope).rawValue" }, // TODO: eliminate this copying
memScoped = true,
kotlinJniBridgeType = "NativePtr"
)
}
return getOutValueBinding(type)
}
@@ -569,6 +561,15 @@ class StubGenerator(
* Constructs [InValueBinding] for the value of given C type.
*/
fun getInValueBinding(type: Type): InValueBinding {
if (type.unwrapTypedefs() is RecordType) {
val typeMirror = mirror(type)
return InValueBinding(
kotlinJniBridgeType = "NativePtr",
conv = { name -> "interpretPointed<${typeMirror.pointedTypeName}>($name).readValue()" },
kotlinType = "CValue<${typeMirror.pointedTypeName}>"
)
}
val mirror = mirror(type)
return InValueBinding(
@@ -581,15 +582,6 @@ class StubGenerator(
fun getCFunctionRetValBinding(func: FunctionDecl): InValueBinding {
when {
func.returnsVoid() -> return InValueBinding("Unit")
func.returnsRecord() -> {
// TODO: this case should probably be handled more generally.
val typeMirror = mirror(func.returnType)
return InValueBinding(
kotlinJniBridgeType = "NativePtr",
conv = { name -> "interpretPointed<${typeMirror.pointedTypeName}>($name).readValue()" },
kotlinType = "CValue<${typeMirror.pointedTypeName}>"
)
}
}
return getInValueBinding(func.returnType)
@@ -606,6 +598,23 @@ class StubGenerator(
getCallbackRetValBinding(type.returnType).kotlinType
}
private fun getArrayLength(type: ArrayType): Long {
val unwrappedElementType = type.elemType.unwrapTypedefs()
val elementLength = if (unwrappedElementType is ArrayType) {
getArrayLength(unwrappedElementType)
} else {
1L
}
val elementCount = when (type) {
is ConstArrayType -> type.length
is IncompleteArrayType -> 0L
else -> TODO(type.toString())
}
return elementLength * elementCount
}
/**
* Produces to [out] the definition of Kotlin class representing the reference to given struct.
*/
@@ -616,6 +625,12 @@ class StubGenerator(
return
}
if (platform == KotlinPlatform.JVM) {
if (def.hasNaturalLayout) {
out("@CNaturalStruct(${def.fields.joinToString { it.name.quoteAsKotlinLiteral() }})")
}
}
block("class ${decl.kotlinName.asSimpleName()}(override val rawPtr: NativePtr) : CStructVar()") {
out("")
out("companion object : Type(${def.size}, ${def.align})") // FIXME: align
@@ -628,9 +643,17 @@ class StubGenerator(
assert(field.offset % 8 == 0L)
val offset = field.offset / 8
val fieldRefType = mirror(field.type)
if (field.type.unwrapTypedefs() is ArrayType) {
val unwrappedFieldType = field.type.unwrapTypedefs()
if (unwrappedFieldType is ArrayType) {
val type = (fieldRefType as TypeMirror.ByValue).valueTypeName
if (platform == KotlinPlatform.JVM) {
val length = getArrayLength(unwrappedFieldType)
// TODO: @CLength should probably be used on types instead of properties.
out("@CLength($length)")
}
out("val ${field.name.asSimpleName()}: $type")
out(" get() = arrayMemberAt($offset)")
} else {
@@ -990,51 +1013,6 @@ class StubGenerator(
return sb.toString()
}
private fun getFfiStructType(elementTypes: List<Type>) =
"Struct(" +
elementTypes.map { getFfiType(it) }.joinToString(", ") +
")"
private fun getFfiType(type: Type): String {
return when(type) {
is VoidType -> "Void"
is CharType -> "SInt8" // TODO: libffi has separate representation for char type.
is IntegerType -> when (type.size) {
1 -> if (type.isSigned) "SInt8" else "UInt8"
2 -> if (type.isSigned) "SInt16" else "UInt16"
4 -> if (type.isSigned) "SInt32" else "UInt32"
8 -> if (type.isSigned) "SInt64" else "UInt64"
else -> TODO(type.toString())
}
is PointerType -> "Pointer"
is ConstArrayType -> getFfiStructType(
Array(type.length.toInt(), { type.elemType }).toList()
)
is EnumType -> getFfiType(type.def.baseType)
is RecordType -> {
val def = type.decl.def!!
if (!def.hasNaturalLayout) {
throw NotImplementedError(type.kotlinName)
}
getFfiStructType(def.fields.map { it.type })
}
is Typedef -> getFfiType(type.def.aliased)
else -> throw NotImplementedError(type.toString())
}
}
private fun getArgFfiType(type: Type) = when (type.unwrapTypedefs()) {
is ArrayType -> "Pointer"
else -> getFfiType(type)
}
private fun getRetValFfiType(type: Type) = getArgFfiType(type)
private fun generateFunctionType(type: FunctionType, name: String) = when (platform) {
KotlinPlatform.JVM -> generateJvmFunctionType(type, name)
KotlinPlatform.NATIVE -> generateNativeFunctionType(type, name)
}
/**
* Returns `true` iff the function binding for Kotlin Native
* requires non-trivial Kotlin adapter to convert arguments.
@@ -1064,64 +1042,6 @@ class StubGenerator(
return true
}
private fun FunctionType.requiresAdapterOnNative(): Boolean {
assert (platform == KotlinPlatform.NATIVE)
return (parameterTypes + returnType).any {
val unwrappedType = it.unwrapTypedefs()
unwrappedType is RecordType || (unwrappedType is EnumType && unwrappedType.def.isStrictEnum)
}
}
private fun generateNativeFunctionType(type: FunctionType, name: String) {
if (type.requiresAdapterOnNative()) {
out("object $name : CFunctionType {}")
} else {
val kotlinFunctionType = getKotlinFunctionType(type)
out("object $name : CTriviallyAdaptedFunctionType<$kotlinFunctionType>()")
}
}
private fun generateJvmFunctionType(type: FunctionType, name: String) {
val kotlinFunctionType = getKotlinFunctionType(type)
val constructorArgs = try {
listOf(getRetValFfiType(type.returnType)) +
type.parameterTypes.map { getArgFfiType(it) }
} catch (e: Throwable) {
log("Warning: cannot generate definition for function type $name")
out("object $name : CFunctionType {}")
return
}
val constructorArgsStr = constructorArgs.joinToString(", ")
block("object $name : CAdaptedFunctionTypeImpl<$kotlinFunctionType>($constructorArgsStr)") {
block("override fun invoke(function: $kotlinFunctionType, args: CArrayPointer<COpaquePointerVar>, ret: COpaquePointer)") {
val args = type.parameterTypes.mapIndexed { i, paramType ->
val pointedTypeName = mirror(paramType).pointedTypeName
val ref = "args[$i]!!.reinterpret<$pointedTypeName>().pointed"
when (paramType.unwrapTypedefs()) {
is RecordType -> ref
else -> "$ref.value"
}
}.joinToString(", ")
out("val res = function($args)")
when (type.returnType.unwrapTypedefs()) {
is RecordType -> throw NotImplementedError()
is VoidType -> {} // nothing to do
else -> {
val pointedTypeName = mirror(type.returnType).pointedTypeName
out("ret.reinterpret<$pointedTypeName>().pointed.value = res")
}
}
}
}
}
private fun integerLiteral(type: Type, value: Long): String? {
if (value == Long.MIN_VALUE) {
return "${value + 1} - 1" // Workaround for "The value is out of range" compile error.
@@ -1287,12 +1207,6 @@ class StubGenerator(
}
}
usedFunctionTypes.entries.forEach {
stubs.add(
generateKotlinFragmentBy { generateFunctionType(it.key, it.value) }
)
}
return stubs
}
@@ -144,20 +144,7 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val bitsToDouble = packageScope.getContributedFunctions("bitsToDouble").single()
val staticCFunction = packageScope.getContributedFunctions("staticCFunction").single()
private val triviallyAdaptedFunctionTypeClass =
packageScope.getContributedClassifier("CTriviallyAdaptedFunctionType") as ClassDescriptor
private val trivallyAdaptedFunctionTypeType =
triviallyAdaptedFunctionTypeClass.defaultType.replace(
newArguments = listOf(
StarProjectionImpl(triviallyAdaptedFunctionTypeClass.declaredTypeParameters.single())
)
)
fun isTriviallyAdaptedFunctionType(type: KotlinType): Boolean =
type.isSubtypeOf(trivallyAdaptedFunctionTypeType)
val staticCFunction = packageScope.getContributedFunctions("staticCFunction").toSet()
val signExtend = packageScope.getContributedFunctions("signExtend").single()
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.konan.Context
@@ -275,27 +276,42 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
builder.readValue(receiver, typeArgument) ?: expression
}
interop.staticCFunction -> {
in interop.staticCFunction -> {
val argument = expression.getValueArgument(0)!!
if (argument !is IrCallableReference || argument.getArguments().isNotEmpty()) {
context.reportCompilationError(
"${interop.staticCFunction.fqNameSafe} must take an unbound, non-capturing function",
"${descriptor.fqNameSafe} must take an unbound, non-capturing function",
irFile, expression
)
// TODO: should probably be reported during analysis.
}
val cFunctionType = expression.getTypeArgument(descriptor.typeParameters[1])!!
val target = argument.descriptor.original
val signatureTypes = target.allParameters.map { it.type } + target.returnType!!
if (interop.isTriviallyAdaptedFunctionType(cFunctionType)) {
IrCallableReferenceImpl(
builder.startOffset, builder.endOffset,
expression.type,
argument.descriptor,
typeArguments = null)
} else {
TODO("$cFunctionType requiring non-trivial C adapter")
signatureTypes.forEachIndexed { index, type ->
type.ensureSupportedInCallbacks(
isReturnType = (index == signatureTypes.lastIndex),
reportError = { context.reportCompilationError(it, irFile, expression) }
)
}
descriptor.typeParameters.forEachIndexed { index, typeParameterDescriptor ->
val typeArgument = expression.getTypeArgument(typeParameterDescriptor)!!
val signatureType = signatureTypes[index]
if (typeArgument != signatureType) {
context.reportCompilationError(
"C function signature element mismatch: expected '$signatureType', got '$typeArgument'",
irFile, expression
)
}
}
IrCallableReferenceImpl(
builder.startOffset, builder.endOffset,
expression.type,
target,
typeArguments = null)
}
interop.signExtend, interop.narrow -> {
@@ -346,6 +362,29 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
}
}
private fun KotlinType.ensureSupportedInCallbacks(isReturnType: Boolean, reportError: (String) -> Nothing) {
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 (TypeUtils.getClassDescriptor(this) == interop.cPointer) {
if (this.isMarkedNullable) {
return
}
reportError("Type $this must be nullable when used in callback signature")
}
reportError("Type $this is not supported in callback signature")
}
private fun IrCall.getSingleTypeArgument(): KotlinType {
val typeParameter = descriptor.original.typeParameters.single()
return getTypeArgument(typeParameter)!!