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)
This commit is contained in:
Svyatoslav Scherbina
2019-01-10 13:59:58 +03:00
committed by SvyatoslavScherbina
parent 7f2103a0ee
commit 6ca196399a
40 changed files with 1781 additions and 623 deletions
@@ -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<Field>()
override val bitFields = mutableListOf<BitField>()
override val members = mutableListOf<StructMember>()
}
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<CXCursor>) {
val type = clang_getCursorType(cursor)
val fields = mutableListOf<StructMember>()
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<CXType>, containerType: CValue<CXType>) {
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) {
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<CXCursor>): 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<CXCursor>) =
convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor))
@@ -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<Field>
// TODO: merge two lists to preserve declaration order.
abstract val bitFields: List<BitField>
enum class Kind {
STRUCT, UNION
}
abstract val members: List<StructMember>
abstract val kind: Kind
val fields: List<Field> get() = members.filterIsInstance<Field>()
val bitFields: List<BitField> get() = members.filterIsInstance<BitField>()
}
/**
@@ -183,6 +183,23 @@ internal fun getFields(type: CValue<CXType>): List<CValue<CXCursor>> {
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<CXCursor>.isLeaf(): Boolean {
var hasChildren = false
@@ -107,7 +107,11 @@ class Arena(parent: NativeFreeablePlacement = nativeHeap) : ArenaBase(parent) {
* @param T must not be abstract
*/
inline fun <reified T : CVariable> NativePlacement.alloc(): T =
alloc(sizeOf<T>(), alignOf<T>()).reinterpret()
alloc(typeOf<T>()).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 <T : CVariable> CPointed.readValue(size: Long, align: Int): CValue<T> {
}
}
@PublishedApi internal fun <T : CVariable> CPointed.readValue(type: CVariable.Type): CValue<T> =
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 <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(sizeOf<T>(), alignOf<T>())
inline fun <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(typeOf<T>())
fun CValue<*>.write(location: NativePtr) {
// TODO: probably CValue must be redesigned.
@@ -65,89 +65,3 @@ import kotlin.native.internal.ExportForCompiler
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <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>>.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 <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>>.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>(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>(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>(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>(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>(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>(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>(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>(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>(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>(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>(COpaquePointerVar)
callWithVarargs(ptr.rawValue, resultBuffer.rawPtr, FFI_TYPE_KIND_POINTER, args, null, memScope)
resultBuffer.value
}
@@ -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<CPointerVar<*>>()[0] = argument?.getPointer(additionalPlacement)
FFI_TYPE_KIND_POINTER
}
is String -> {
location.reinterpret<CPointerVar<*>>()[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<COpaque>(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<IntVar>()[0] = argument
FFI_TYPE_KIND_SINT32
}
is Long -> {
location.reinterpret<LongVar>()[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<ByteVar>()[0] = argument
FFI_TYPE_KIND_SINT8
}
is Short -> if (isVariadic) {
convertArgument(argument.toInt(), isVariadic, location, additionalPlacement)
} else {
location.reinterpret<ShortVar>()[0] = argument
FFI_TYPE_KIND_SINT16
}
is UInt -> {
location.reinterpret<UIntVar>()[0] = argument
FFI_TYPE_KIND_UINT32
}
is ULong -> {
location.reinterpret<ULongVar>()[0] = argument
FFI_TYPE_KIND_UINT64
}
is UByte -> if (isVariadic) {
convertArgument(argument.toUInt(), isVariadic, location, additionalPlacement)
} else {
location.reinterpret<UByteVar>()[0] = argument
FFI_TYPE_KIND_UINT8
}
is UShort -> if (isVariadic) {
convertArgument(argument.toUInt(), isVariadic, location, additionalPlacement)
} else {
location.reinterpret<UShortVar>()[0] = argument
FFI_TYPE_KIND_UINT16
}
is Double -> {
location.reinterpret<DoubleVar>()[0] = argument
FFI_TYPE_KIND_DOUBLE
}
is Float -> if (isVariadic) {
convertArgument(argument.toDouble(), isVariadic, location, additionalPlacement)
} else {
location.reinterpret<FloatVar>()[0] = argument
FFI_TYPE_KIND_FLOAT
}
is CEnum -> convertArgument(argument.value, isVariadic, location, additionalPlacement)
is ForeignObjCObject -> {
location.reinterpret<COpaquePointerVar>()[0] = interpretCPointer(argument.objcPtr())
FFI_TYPE_KIND_POINTER
}
else -> throw Error("unsupported argument: $argument")
}
inline fun <reified T : CVariable> 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<T>()
}
fun callWithVarargs(codePtr: NativePtr, returnValuePtr: NativePtr, returnTypeKind: FfiTypeKind,
fixedArguments: Array<out Any?>, variadicArguments: Array<out Any?>?,
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<LongVar>(totalArgumentsNumber)
val arguments = argumentsPlacement.allocArray<CPointerVar<*>>(totalArgumentsNumber)
val types = argumentsPlacement.allocArray<COpaquePointerVar>(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
)
@@ -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
}
@@ -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
@@ -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
}
@@ -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<String> =
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<String>
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.*")