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:
committed by
SvyatoslavScherbina
parent
7f2103a0ee
commit
6ca196399a
+29
-42
@@ -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))
|
||||
|
||||
|
||||
+21
-8
@@ -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
|
||||
}
|
||||
+7
-2
@@ -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
|
||||
|
||||
+82
@@ -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
|
||||
}
|
||||
+32
-62
@@ -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.*")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user