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.*")
@@ -0,0 +1,81 @@
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.file.*
import org.jetbrains.kotlin.konan.target.ClangArgs
class CStubsManager {
fun getUniqueName(prefix: String) = "$prefix${counter++}"
fun addStub(kotlinLocation: CompilerMessageLocation?, lines: List<String>) {
stubs += Stub(kotlinLocation, lines)
}
fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): File? {
if (stubs.isEmpty()) return null
val cSource = createTempFile("cstubs", ".c").deleteOnExit()
cSource.writeLines(stubs.flatMap { it.lines })
val bitcode = createTempFile("cstubs", ".bc").deleteOnExit()
val cSourcePath = cSource.absolutePath
val clangCommand = clang.clangC(cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath)
val result = Command(clangCommand).getResult(withErrors = true)
if (result.exitCode != 0) {
reportCompilationErrors(cSourcePath, result, messageCollector, verbose)
}
return bitcode
}
private fun reportCompilationErrors(
cSourcePath: String,
result: Command.Result,
messageCollector: MessageCollector,
verbose: Boolean
): Nothing {
val regex = Regex("${Regex.escape(cSourcePath)}:([0-9]+):[0-9]+: error: .*")
val errorLines = result.outputLines.mapNotNull { line ->
regex.matchEntire(line)?.let { matchResult ->
matchResult.groupValues[1].toInt()
}
}
val lineToStub = ArrayList<Stub>()
stubs.forEach { stub ->
repeat(stub.lines.size) { lineToStub.add(stub) }
}
val cSourceCopyPath = "cstubs.c"
if (verbose) {
File(cSourcePath).copyTo(File(cSourceCopyPath))
}
if (errorLines.isNotEmpty()) {
errorLines.forEach {
messageCollector.report(
CompilerMessageSeverity.ERROR,
"Unable to compile C bridge" + if (verbose) " at $cSourceCopyPath:$it" else "",
lineToStub[it - 1].kotlinLocation
)
}
} else {
messageCollector.report(
CompilerMessageSeverity.ERROR,
"Unable to compile C bridges",
null
)
}
throw KonanCompilationException()
}
private val stubs = mutableListOf<Stub>()
private class Stub(val kotlinLocation: CompilerMessageLocation?, val lines: List<String>)
private var counter = 0
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.konan
import llvm.LLVMLinkModules2
import llvm.LLVMModuleRef
import llvm.LLVMWriteBitcodeToFile
import org.jetbrains.kotlin.backend.konan.library.impl.buildLibrary
import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile
@@ -26,6 +27,12 @@ internal fun produceOutput(context: Context, phaser: PhaseManager) {
val tempFiles = context.config.tempFiles
val produce = config.get(KonanConfigKeys.PRODUCE)
phaser.phase(KonanPhase.C_STUBS) {
context.cStubsManager.compile(context.config.clang, context.messageCollector, context.phase!!.verbose)?.let {
parseAndLinkBitcodeFile(llvmModule, it.absolutePath)
}
}
when (produce) {
CompilerOutputKind.STATIC,
CompilerOutputKind.DYNAMIC,
@@ -50,11 +57,7 @@ internal fun produceOutput(context: Context, phaser: PhaseManager) {
phaser.phase(KonanPhase.BITCODE_LINKER) {
for (library in nativeLibraries) {
val libraryModule = parseBitcodeFile(library)
val failed = LLVMLinkModules2(llvmModule, libraryModule)
if (failed != 0) {
throw Error("failed to link $library") // TODO: retrieve error message from LLVM.
}
parseAndLinkBitcodeFile(llvmModule, library)
}
}
@@ -99,4 +102,10 @@ internal fun produceOutput(context: Context, phaser: PhaseManager) {
}
}
private fun parseAndLinkBitcodeFile(llvmModule: LLVMModuleRef, path: String) {
val parsedModule = parseBitcodeFile(path)
val failed = LLVMLinkModules2(llvmModule, parsedModule)
if (failed != 0) {
throw Error("failed to link $path") // TODO: retrieve error message from LLVM.
}
}
@@ -314,10 +314,13 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
}
lateinit var llvm: Llvm
val llvmImports: LlvmImports = Llvm.ImportsImpl(this)
lateinit var llvmDeclarations: LlvmDeclarations
lateinit var bitcodeFileName: String
lateinit var library: KonanLibraryWriter
val cStubsManager = CStubsManager()
var phase: KonanPhase? = null
var depth: Int = 0
@@ -33,6 +33,18 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns, vararg konanPrimitives:
val nativePointed = packageScope.getContributedClass(InteropFqNames.nativePointedName)
val cValuesRef = this.packageScope.getContributedClass("CValuesRef")
val cValues = this.packageScope.getContributedClass("CValues")
val cValue = this.packageScope.getContributedClass("CValue")
val cValueWrite = this.packageScope.getContributedFunctions("write")
.single { it.extensionReceiverParameter?.type?.constructor?.declarationDescriptor == cValue }
val cValueRead = this.packageScope.getContributedFunctions("readValue")
.single { it.valueParameters.size == 1 }
val allocType = this.packageScope.getContributedFunctions("alloc")
.single { it.extensionReceiverParameter != null
&& it.valueParameters.singleOrNull()?.name?.toString() == "type" }
val cPointer = this.packageScope.getContributedClass(InteropFqNames.cPointerName)
val cPointerRawValue = cPointer.unsubstitutedMemberScope.getContributedVariables("rawValue").single()
@@ -43,6 +55,10 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns, vararg konanPrimitives:
TypeUtils.getClassDescriptor(extensionReceiverParameter.type) == cPointer
}
val cstr = packageScope.getContributedVariables("cstr").single()
val wcstr = packageScope.getContributedVariables("wcstr").single()
val memScope = packageScope.getContributedClass("MemScope")
val nativePointedRawPtrGetter =
nativePointed.unsubstitutedMemberScope.getContributedVariables("rawPtr").single().getter!!
@@ -61,24 +77,6 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns, vararg konanPrimitives:
private fun KonanBuiltIns.getUnsignedClass(unsignedType: UnsignedType): ClassDescriptor =
this.builtInsModule.findClassAcrossModuleDependencies(unsignedType.classId)!!
val invokeImpls = mapOf(
builtIns.unit to "invokeImplUnitRet",
builtIns.boolean to "invokeImplBooleanRet",
builtIns.byte to "invokeImplByteRet",
builtIns.short to "invokeImplShortRet",
builtIns.int to "invokeImplIntRet",
builtIns.long to "invokeImplLongRet",
builtIns.getUnsignedClass(UnsignedType.UBYTE) to "invokeImplUByteRet",
builtIns.getUnsignedClass(UnsignedType.USHORT) to "invokeImplUShortRet",
builtIns.getUnsignedClass(UnsignedType.UINT) to "invokeImplUIntRet",
builtIns.getUnsignedClass(UnsignedType.ULONG) to "invokeImplULongRet",
builtIns.float to "invokeImplFloatRet",
builtIns.double to "invokeImplDoubleRet",
cPointer to "invokeImplPointerRet"
).mapValues { (_, name) ->
packageScope.getContributedFunctions(name).single()
}.toMap()
val objCObject = packageScope.getContributedClass("ObjCObject")
val objCObjectBase = packageScope.getContributedClass("ObjCObjectBase")
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.konan.descriptors.KonanSharedVariablesManager
import org.jetbrains.kotlin.backend.konan.descriptors.findPackage
import org.jetbrains.kotlin.backend.konan.descriptors.kotlinNativeInternal
import org.jetbrains.kotlin.backend.konan.ir.KonanIr
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
@@ -19,6 +20,9 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns
import org.jetbrains.kotlin.ir.builders.IrBuilder
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.name.Name
abstract internal class KonanBackendContext(val config: KonanConfig) : CommonBackendContext {
@@ -49,14 +53,23 @@ abstract internal class KonanBackendContext(val config: KonanConfig) : CommonBac
)
}
private fun IrElement.getCompilerMessageLocation(containingFile: IrFile): CompilerMessageLocation? {
val sourceRangeInfo = containingFile.fileEntry.getSourceRangeInfo(this.startOffset, this.endOffset)
return CompilerMessageLocation.create(
path = sourceRangeInfo.filePath,
line = sourceRangeInfo.startLineNumber + 1,
column = sourceRangeInfo.startColumnNumber + 1,
lineContent = null // TODO: retrieve the line content.
)
}
}
internal fun IrElement.getCompilerMessageLocation(containingFile: IrFile): CompilerMessageLocation? =
createCompilerMessageLocation(containingFile, this.startOffset, this.endOffset)
internal fun IrBuilderWithScope.getCompilerMessageLocation(): CompilerMessageLocation? {
val declaration = this.scope.scopeOwnerSymbol.owner as? IrDeclaration ?: return null
val file = declaration.findPackage() as? IrFile ?: return null
return createCompilerMessageLocation(file, startOffset, endOffset)
}
private fun createCompilerMessageLocation(containingFile: IrFile, startOffset: Int, endOffset: Int): CompilerMessageLocation? {
val sourceRangeInfo = containingFile.fileEntry.getSourceRangeInfo(startOffset, endOffset)
return CompilerMessageLocation.create(
path = sourceRangeInfo.filePath,
line = sourceRangeInfo.startLineNumber + 1,
column = sourceRangeInfo.startColumnNumber + 1,
lineContent = null // TODO: retrieve the line content.
)
}
@@ -58,6 +58,7 @@ enum class KonanPhase(val description: String,
/* ... ... */ ESCAPE_ANALYSIS("Escape analysis", BUILD_DFG, DESERIALIZE_DFG, enabled = false), // TODO: Requires devirtualization.
/* ... ... */ SERIALIZE_DFG("Data flow graph serializing", BUILD_DFG), // TODO: Requires escape analysis.
/* ... ... */ CODEGEN("Code Generation"),
/* ... ... */ C_STUBS("C stubs compilation"),
/* ... ... */ BITCODE_LINKER("Bitcode linking"),
/* */ LINK_STAGE("Link stage"),
/* ... */ OBJECT_FILES("Bitcode to object file"),
@@ -181,8 +181,7 @@ internal class LinkStage(val context: Context, val phaser: PhaseManager) {
try {
File(executable).delete()
linker.linkCommands(objectFiles = objectFiles, executable = executable,
libraries = linker.targetLibffi + linker.linkStaticLibraries(includedBinaries) +
context.config.defaultSystemLibraries,
libraries = linker.linkStaticLibraries(includedBinaries) + context.config.defaultSystemLibraries,
linkerArgs = entryPointSelector +
asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS)) +
BitcodeEmbedding.getLinkerOptions(context.config) +
@@ -0,0 +1,9 @@
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.name.FqName
object RuntimeNames {
val symbolName = FqName("kotlin.native.SymbolName")
val exportForCppRuntime = FqName("kotlin.native.internal.ExportForCppRuntime")
val cCall = FqName("kotlinx.cinterop.internal.CCall")
}
@@ -0,0 +1,803 @@
package org.jetbrains.kotlin.backend.konan.cgen
import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType
import org.jetbrains.kotlin.backend.konan.RuntimeNames
import org.jetbrains.kotlin.backend.konan.descriptors.createAnnotation
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.backend.konan.irasdescriptors.*
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrSpreadElement
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
internal interface KotlinStubs {
val irBuiltIns: IrBuiltIns
val symbols: KonanSymbols
val target: KonanTarget
fun addKotlin(declaration: IrDeclaration)
fun addC(lines: List<String>)
fun getUniqueCName(prefix: String): String
fun reportError(location: IrElement, message: String): Nothing
}
private class KotlinToCCallBuilder(
val irBuilder: IrBuilderWithScope,
cBridgeName: String,
val stubs: KotlinStubs
) {
val symbols: KonanSymbols get() = stubs.symbols
val bridgeCallBuilder = KotlinCallBuilder(irBuilder, symbols)
val bridgeBuilder = KotlinCBridgeBuilder(irBuilder.startOffset, irBuilder.endOffset, cBridgeName, stubs, isKotlinToC = true)
val cBridgeBodyLines = mutableListOf<String>()
val cCallBuilder = CCallBuilder()
val cFunctionBuilder = CFunctionBuilder()
}
private fun KotlinToCCallBuilder.passThroughBridge(argument: IrExpression, kotlinType: IrType, cType: CType): CVariable {
bridgeCallBuilder.arguments += argument
return bridgeBuilder.addParameter(kotlinType, cType).second
}
private fun KotlinToCCallBuilder.addArgument(
argument: IrExpression,
type: IrType,
variadic: Boolean,
parameter: IrValueParameter?
) {
val argumentPassing = mapParameter(type, variadic, parameter, argument)
val cArgument = with(argumentPassing) { passValue(argument) }
cCallBuilder.arguments += cArgument.expression
if (!variadic) cFunctionBuilder.addParameter(cArgument.type)
}
private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrCall) -> IrExpression = { it }): IrExpression =
bridgeCallBuilder.build(
bridgeBuilder.buildKotlinBridge().also {
this.stubs.addKotlin(it)
},
transformCall
)
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean): IrExpression {
require(expression.dispatchReceiver == null)
val cBridgeName = this.getUniqueCName("knbridge")
val callBuilder = KotlinToCCallBuilder(builder, cBridgeName, this)
val cFunctionBuilder = callBuilder.cFunctionBuilder
val callee = expression.symbol.owner
// TODO: consider computing all arguments before converting.
val targetPtrParameter: String?
val targetFunctionName: String
if (isInvoke) {
targetPtrParameter = callBuilder.passThroughBridge(
expression.extensionReceiver!!,
symbols.interopCPointer.typeWithStarProjections,
CTypes.voidPtr
).name
targetFunctionName = "targetPtr"
(0 until expression.valueArgumentsCount).forEach {
callBuilder.addArgument(
expression.getValueArgument(it)!!,
type = expression.getTypeArgument(it)!!,
variadic = false,
parameter = null
)
}
} else {
require(expression.extensionReceiver == null)
targetPtrParameter = null
targetFunctionName = this.getUniqueCName("target")
(0 until expression.valueArgumentsCount).forEach { index ->
val parameter = callee.valueParameters[index]
val argument = expression.getValueArgument(index)
if (parameter.isVararg) {
require(index == expression.valueArgumentsCount - 1)
require(argument is IrVararg?)
argument?.elements.orEmpty().forEach {
when (it) {
is IrExpression -> callBuilder.addArgument(it, it.type, variadic = true, parameter = null)
is IrSpreadElement ->
reportError(it, "spread operator is not supported for variadic C functions")
else -> error(it)
}
}
cFunctionBuilder.variadic = true
} else {
callBuilder.addArgument(argument!!, parameter.type, variadic = false, parameter = parameter)
}
}
}
val cLines = mutableListOf<String>()
val returnValuePassing = if (isInvoke) {
val returnType = expression.getTypeArgument(expression.typeArgumentsCount - 1)!!
mapReturnType(returnType, TypeLocation.FunctionCallResult(expression))
} else {
mapReturnType(callee.returnType, TypeLocation.FunctionCallResult(expression))
}
val result = with(returnValuePassing) {
callBuilder.returnValue(callBuilder.cCallBuilder.build(targetFunctionName))
}
val targetFunctionVariable = CVariable(CTypes.pointer(cFunctionBuilder.getType()), targetFunctionName)
if (isInvoke) {
callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = ${targetPtrParameter!!};")
} else {
val cCallSymbolName = callee.descriptor.annotations.findAnnotation(cCall)!!.getStringValue("id")
cLines += "extern const $targetFunctionVariable __asm(\"$cCallSymbolName\");" // Exported from cinterop stubs.
}
cLines += "${callBuilder.bridgeBuilder.buildCSignature(cBridgeName)} {"
cLines += callBuilder.cBridgeBodyLines
cLines += "}"
this.addC(cLines)
return result
}
private class CCallbackBuilder(
val stubs: KotlinStubs,
val location: IrElement
) {
val irBuiltIns: IrBuiltIns get() = stubs.irBuiltIns
val symbols: KonanSymbols get() = stubs.symbols
private val cBridgeName = stubs.getUniqueCName("knbridge")
fun buildCBridgeCall(): String = cBridgeCallBuilder.build(cBridgeName)
fun buildCBridge(): String = bridgeBuilder.buildCSignature(cBridgeName)
val bridgeBuilder = KotlinCBridgeBuilder(location.startOffset, location.endOffset, cBridgeName, stubs, isKotlinToC = false)
val kotlinCallBuilder = KotlinCallBuilder(bridgeBuilder.kotlinIrBuilder, symbols)
val kotlinBridgeStatements = mutableListOf<IrStatement>()
val cBridgeCallBuilder = CCallBuilder()
val cBodyLines = mutableListOf<String>()
val cFunctionBuilder = CFunctionBuilder()
}
private fun CCallbackBuilder.passThroughBridge(
cBridgeArgument: String,
cBridgeParameterType: CType,
kotlinBridgeParameterType: IrType
): IrValueParameter {
cBridgeCallBuilder.arguments += cBridgeArgument
return bridgeBuilder.addParameter(kotlinBridgeParameterType, cBridgeParameterType).first
}
private fun CCallbackBuilder.addParameter(it: IrValueParameter) {
val typeLocation = TypeLocation.FunctionPointerParameter(cFunctionBuilder.numberOfParameters, location)
val valuePassing = stubs.mapType(it.type, typeLocation)
val kotlinArgument = with(valuePassing) { receiveValue() }
kotlinCallBuilder.arguments += kotlinArgument
}
private fun CCallbackBuilder.build(function: IrSimpleFunction, signature: IrFunction): String {
val kotlinCall = kotlinCallBuilder.build(function)
with(stubs.mapReturnType(signature.returnType, TypeLocation.FunctionPointerReturnValue(location))) {
returnValue(kotlinCall)
}
val cLines = mutableListOf<String>()
val kotlinBridge = bridgeBuilder.buildKotlinBridge()
kotlinBridge.body = bridgeBuilder.kotlinIrBuilder.irBlockBody {
kotlinBridgeStatements.forEach { +it }
}
stubs.addKotlin(kotlinBridge)
val result = stubs.getUniqueCName("kncfun")
cLines += "${buildCBridge()};"
cLines += "${cFunctionBuilder.buildSignature(result)} {"
cLines += cBodyLines
cLines += "}"
stubs.addC(cLines)
return result
}
private fun KotlinStubs.generateCFunction(
function: IrSimpleFunction,
signature: IrSimpleFunction,
isObjCMethod: Boolean,
location: IrElement
): String {
val callbackBuilder = CCallbackBuilder(this, location)
signature.dispatchReceiverParameter?.let { callbackBuilder.addParameter(it) }
if (isObjCMethod) {
// assert(mapType(signature.dispatchReceiverParameter!!.type) is ObjCReferenceValuePassing)
// Selector is ignored:
with(TrivialValuePassing(symbols.nativePtrType, CTypes.voidPtr)) { callbackBuilder.receiveValue() }
}
signature.extensionReceiverParameter?.let { callbackBuilder.addParameter(it) }
signature.valueParameters.forEach {
callbackBuilder.addParameter(it)
}
return callbackBuilder.build(function, signature)
}
internal fun KotlinStubs.generateCFunctionPointer(
function: IrSimpleFunction,
signature: IrSimpleFunction,
isObjCMethod: Boolean,
expression: IrExpression
): IrExpression {
val cFunction = generateCFunction(function, signature, isObjCMethod, expression)
val fakeFunction = createFakeKotlinExternalFunction(signature, cFunction, isObjCMethod)
addKotlin(fakeFunction)
return IrFunctionReferenceImpl(
expression.startOffset,
expression.endOffset,
expression.type,
fakeFunction.symbol,
fakeFunction.descriptor,
0
)
}
private fun KotlinStubs.createFakeKotlinExternalFunction(
signature: IrSimpleFunction,
cFunctionName: String,
isObjCMethod: Boolean
): IrSimpleFunction {
val objCMethodImpAnnotation = if (isObjCMethod) {
val methodInfo = signature.getObjCMethodInfo()!!
createObjCMethodImpAnnotation(methodInfo.selector, methodInfo.encoding, symbols)
} else {
null
}
val bridgeAnnotations = Annotations.create(
listOfNotNull(
createAnnotation(symbols.symbolName.descriptor, "value" to cFunctionName),
objCMethodImpAnnotation
)
)
val bridgeDescriptor = WrappedSimpleFunctionDescriptor(bridgeAnnotations)
val bridge = IrFunctionImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
IrDeclarationOrigin.DEFINED,
IrSimpleFunctionSymbolImpl(bridgeDescriptor),
Name.identifier(cFunctionName),
Visibilities.PRIVATE,
Modality.FINAL,
signature.returnType,
isInline = false,
isExternal = true,
isTailrec = false,
isSuspend = false
)
bridgeDescriptor.bind(bridge)
return bridge
}
private fun createObjCMethodImpAnnotation(selector: String, encoding: String, symbols: KonanSymbols) =
createAnnotation(symbols.objCMethodImp.descriptor, "selector" to selector, "encoding" to encoding)
private val cCall = RuntimeNames.cCall
private fun IrType.isUnsigned(unsignedType: UnsignedType) = this is IrSimpleType && !this.hasQuestionMark &&
(this.classifier.owner as? IrClass)?.classId == unsignedType.classId
private fun IrType.isUByte() = this.isUnsigned(UnsignedType.UBYTE)
private fun IrType.isUShort() = this.isUnsigned(UnsignedType.USHORT)
private fun IrType.isUInt() = this.isUnsigned(UnsignedType.UINT)
private fun IrType.isULong() = this.isUnsigned(UnsignedType.ULONG)
private fun IrType.isCEnumType(): Boolean {
val simpleType = this as? IrSimpleType ?: return false
if (simpleType.hasQuestionMark) return false
val enumClass = simpleType.classifier.owner as? IrClass ?: return false
if (!enumClass.isEnumClass) return false
return enumClass.superTypes
.any { (it.classifierOrNull?.owner as? IrClass)?.fqNameSafe == FqName("kotlinx.cinterop.CEnum") }
}
private fun IrValueParameter.isWCStringParameter() =
this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier("WCString")))
private fun IrValueParameter.isCStringParameter() =
this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier("CString")))
private fun getStructSpelling(kotlinClass: IrClass): String? =
kotlinClass.descriptor.annotations
.findAnnotation(FqName("kotlinx.cinterop.internal.CStruct"))
?.getStringValue("spelling")
private fun getCStructType(kotlinClass: IrClass): CType? =
getStructSpelling(kotlinClass)?.let { CTypes.simple(it) }
private fun KotlinStubs.getNamedCStructType(kotlinClass: IrClass): CType? {
val cStructType = getCStructType(kotlinClass) ?: return null
val name = getUniqueCName("struct")
addC(listOf("typedef ${cStructType.render(name)};"))
return CTypes.simple(name)
}
// TODO: rework Boolean support.
private fun cBoolType(target: KonanTarget): CType? = when (target.family) {
Family.IOS -> CTypes.C99Bool
else -> CTypes.signedChar
}
private fun KotlinToCCallBuilder.mapParameter(
type: IrType,
variadic: Boolean,
parameter: IrValueParameter?,
argument: IrExpression
): KotlinToCArgumentPassing {
val classifier = type.classifierOrNull
return when {
classifier == symbols.interopCValues || // Note: this should not be accepted, but is required for compatibility
classifier == symbols.interopCValuesRef -> CValuesRefArgumentPassing
classifier == symbols.string && (variadic || parameter?.isCStringParameter() == true) ->
CStringArgumentPassing()
classifier == symbols.string && parameter?.isWCStringParameter() == true ->
WCStringArgumentPassing()
else -> stubs.mapType(type, TypeLocation.FunctionArgument(argument))
}
}
private sealed class TypeLocation(val element: IrElement) {
class FunctionArgument(val argument: IrExpression) : TypeLocation(argument)
class FunctionCallResult(val call: IrCall) : TypeLocation(call)
class FunctionPointerParameter(val index: Int, element: IrElement) : TypeLocation(element)
class FunctionPointerReturnValue(element: IrElement) : TypeLocation(element)
}
private fun KotlinStubs.mapReturnType(type: IrType, location: TypeLocation): ValueReturning =
when {
type.isUnit() -> VoidReturning
else -> mapType(type, location)
}
private fun KotlinStubs.mapType(type: IrType, location: TypeLocation): ValuePassing =
mapType(type, { reportUnsupportedType(it, type, location) })
private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) -> Nothing): ValuePassing = when {
type.isBoolean() -> BooleanValuePassing(
cBoolType(target) ?: reportUnsupportedType("unavailable on target platform"),
irBuiltIns
)
type.isByte() -> TrivialValuePassing(irBuiltIns.byteType, CTypes.signedChar)
type.isShort() -> TrivialValuePassing(irBuiltIns.shortType, CTypes.short)
type.isInt() -> TrivialValuePassing(irBuiltIns.intType, CTypes.int)
type.isLong() -> TrivialValuePassing(irBuiltIns.longType, CTypes.longLong)
type.isFloat() -> TrivialValuePassing(irBuiltIns.floatType, CTypes.float)
type.isDouble() -> TrivialValuePassing(irBuiltIns.doubleType, CTypes.double)
type.classifierOrNull == symbols.interopCPointer -> TrivialValuePassing(type, CTypes.voidPtr)
type.isUByte() -> UnsignedValuePassing(type, CTypes.signedChar, CTypes.unsignedChar)
type.isUShort() -> UnsignedValuePassing(type, CTypes.short, CTypes.unsignedShort)
type.isUInt() -> UnsignedValuePassing(type, CTypes.int, CTypes.unsignedInt)
type.isULong() -> UnsignedValuePassing(type, CTypes.longLong, CTypes.unsignedLongLong)
type.isCEnumType() -> {
val enumClass = type.getClass()!!
val value = enumClass.declarations
.filterIsInstance<IrProperty>()
.single { it.name.asString() == "value" }
CEnumValuePassing(enumClass, value, mapType(value.getter!!.returnType, reportUnsupportedType) as SimpleValuePassing)
}
type.classifierOrNull == symbols.interopCValue -> if (type.isNullable()) {
reportUnsupportedType("must not be nullable")
} else {
val kotlinClass = (type as IrSimpleType).arguments.singleOrNull()?.typeOrNull?.getClass()
?: reportUnsupportedType("must be parameterized with concrete class")
StructValuePassing(kotlinClass, getNamedCStructType(kotlinClass)
?: reportUnsupportedType("not a structure or too complex"))
}
type.isFunction() -> reportUnsupportedType("")
isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type)
else -> reportUnsupportedType("doesn't correspond to any C type")
}
private fun KotlinStubs.isObjCReferenceType(type: IrType): Boolean {
if (target.family != Family.OSX && target.family != Family.IOS) return false
if (type.isObjCObjectType()) return true
val descriptor = type.classifierOrNull?.descriptor ?: return false
val builtIns = irBuiltIns.builtIns
return when (descriptor) {
builtIns.string,
builtIns.list, builtIns.mutableList,
builtIns.set, builtIns.mutableSet,
builtIns.map, builtIns.mutableMap -> true
else -> false
}
}
private class CExpression(val expression: String, val type: CType)
private interface KotlinToCArgumentPassing {
fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression
}
private interface ValueReturning {
fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression
fun CCallbackBuilder.returnValue(expression: IrExpression)
}
private interface ValuePassing : KotlinToCArgumentPassing, ValueReturning {
fun CCallbackBuilder.receiveValue(): IrExpression
}
private abstract class SimpleValuePassing : ValuePassing {
abstract val kotlinBridgeType: IrType
abstract val cBridgeType: CType
abstract val cType: CType
abstract fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression
abstract fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression
abstract fun bridgedToC(expression: String): String
abstract fun cToBridged(expression: String): String
override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression {
val bridgeArgument = irBuilder.kotlinToBridged(expression)
val cBridgeValue = passThroughBridge(bridgeArgument, kotlinBridgeType, cBridgeType).name
return CExpression(bridgedToC(cBridgeValue), cType)
}
override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression {
cFunctionBuilder.setReturnType(cType)
bridgeBuilder.setReturnType(kotlinBridgeType, cBridgeType)
cBridgeBodyLines.add("return ${cToBridged(expression)};")
val kotlinBridgeCall = buildKotlinBridgeCall()
return irBuilder.bridgedToKotlin(kotlinBridgeCall, symbols)
}
override fun CCallbackBuilder.receiveValue(): IrExpression {
val cParameter = cFunctionBuilder.addParameter(cType)
val cBridgeArgument = cToBridged(cParameter.name)
val kotlinParameter = passThroughBridge(cBridgeArgument, cBridgeType, kotlinBridgeType)
return with(bridgeBuilder.kotlinIrBuilder) {
bridgedToKotlin(irGet(kotlinParameter), symbols)
}
}
override fun CCallbackBuilder.returnValue(expression: IrExpression) {
cFunctionBuilder.setReturnType(cType)
bridgeBuilder.setReturnType(kotlinBridgeType, cBridgeType)
kotlinBridgeStatements += with(bridgeBuilder.kotlinIrBuilder) {
irReturn(kotlinToBridged(expression))
}
val cBridgeCall = buildCBridgeCall()
cBodyLines += "return ${bridgedToC(cBridgeCall)};"
}
}
private class TrivialValuePassing(val kotlinType: IrType, override val cType: CType) : SimpleValuePassing() {
override val kotlinBridgeType: IrType
get() = kotlinType
override val cBridgeType: CType
get() = cType
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = expression
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression = expression
override fun bridgedToC(expression: String): String = expression
override fun cToBridged(expression: String): String = expression
}
private class UnsignedValuePassing(val kotlinType: IrType, val cSignedType: CType, override val cType: CType) : SimpleValuePassing() {
override val kotlinBridgeType: IrType
get() = kotlinType
override val cBridgeType: CType
get() = cSignedType
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = expression
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression = expression
override fun bridgedToC(expression: String): String = cType.cast(expression)
override fun cToBridged(expression: String): String = cBridgeType.cast(expression)
}
private class BooleanValuePassing(override val cType: CType, private val irBuiltIns: IrBuiltIns) : SimpleValuePassing() {
override val cBridgeType: CType get() = CTypes.signedChar
override val kotlinBridgeType: IrType get() = irBuiltIns.byteType
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = irIfThenElse(
irBuiltIns.byteType,
condition = expression,
thenPart = IrConstImpl.byte(startOffset, endOffset, irBuiltIns.byteType, 1),
elsePart = IrConstImpl.byte(startOffset, endOffset, irBuiltIns.byteType, 0)
)
override fun IrBuilderWithScope.bridgedToKotlin(
expression: IrExpression,
symbols: KonanSymbols
): IrExpression = irNot(irCall(symbols.areEqualByValue[PrimitiveBinaryType.BYTE]!!.owner).apply {
putValueArgument(0, expression)
putValueArgument(1, IrConstImpl.byte(startOffset, endOffset, irBuiltIns.byteType, 0))
})
override fun bridgedToC(expression: String): String = cType.cast(expression)
override fun cToBridged(expression: String): String = cBridgeType.cast(expression)
}
private class StructValuePassing(private val kotlinClass: IrClass, private val cType: CType) : ValuePassing {
override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression {
val cBridgeValue = passThroughBridge(
cValuesRefToPointer(expression),
symbols.interopCPointer.typeWithStarProjections,
CTypes.pointer(cType)
).name
return CExpression("*$cBridgeValue", cType)
}
override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression = with(irBuilder) {
cFunctionBuilder.setReturnType(cType)
bridgeBuilder.setReturnType(context.irBuiltIns.unitType, CTypes.void)
val kotlinPointed = scope.createTemporaryVariable(irCall(symbols.interopAllocType.owner).apply {
extensionReceiver = bridgeCallBuilder.getMemScope()
putValueArgument(0, getTypeObject())
})
bridgeCallBuilder.prepare += kotlinPointed
val cPointer = passThroughBridge(irGet(kotlinPointed), kotlinPointedType, CTypes.pointer(cType))
cBridgeBodyLines += "*${cPointer.name} = $expression;"
buildKotlinBridgeCall {
irBlock {
at(it)
+it
+readCValue(irGet(kotlinPointed), symbols)
}
}
}
override fun CCallbackBuilder.receiveValue(): IrExpression = with(bridgeBuilder.kotlinIrBuilder) {
val cParameter = cFunctionBuilder.addParameter(cType)
val kotlinPointed = passThroughBridge("&${cParameter.name}", CTypes.voidPtr, kotlinPointedType)
readCValue(irGet(kotlinPointed), symbols)
}
private fun IrBuilderWithScope.readCValue(kotlinPointed: IrExpression, symbols: KonanSymbols): IrExpression =
irCall(symbols.interopCValueRead.owner).apply {
extensionReceiver = kotlinPointed
putValueArgument(0, getTypeObject())
}
override fun CCallbackBuilder.returnValue(expression: IrExpression) = with(bridgeBuilder.kotlinIrBuilder) {
bridgeBuilder.setReturnType(irBuiltIns.unitType, CTypes.void)
cFunctionBuilder.setReturnType(cType)
val result = "callbackResult"
val cReturnValue = CVariable(cType, result)
cBodyLines += "$cReturnValue;"
val kotlinPtr = passThroughBridge("&$result", CTypes.voidPtr, symbols.nativePtrType)
kotlinBridgeStatements += irCall(symbols.interopCValueWrite.owner).apply {
extensionReceiver = expression
putValueArgument(0, irGet(kotlinPtr))
}
val cBridgeCall = buildCBridgeCall()
cBodyLines += "$cBridgeCall;"
cBodyLines += "return $result;"
}
private val kotlinPointedType: IrType get() = kotlinClass.defaultType
private fun IrBuilderWithScope.getTypeObject() =
irGetObject(
kotlinClass.declarations.filterIsInstance<IrClass>()
.single { it.isCompanion }.symbol
)
}
private class CEnumValuePassing(
val enumClass: IrClass,
val value: IrProperty,
val baseValuePassing: SimpleValuePassing
) : SimpleValuePassing() {
override val kotlinBridgeType: IrType
get() = baseValuePassing.kotlinBridgeType
override val cBridgeType: CType
get() = baseValuePassing.cBridgeType
override val cType: CType
get() = baseValuePassing.cType
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression {
val value = irCall(value.getter!!).apply {
dispatchReceiver = expression
}
return with(baseValuePassing) { kotlinToBridged(value) }
}
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression {
val companionClass = enumClass.declarations.filterIsInstance<IrClass>().single { it.isCompanion }
val byValue = companionClass.simpleFunctions().single { it.name.asString() == "byValue" }
return irCall(byValue).apply {
dispatchReceiver = irGetObject(companionClass.symbol)
putValueArgument(0, expression)
}
}
override fun bridgedToC(expression: String): String = with(baseValuePassing) { bridgedToC(expression) }
override fun cToBridged(expression: String): String = with(baseValuePassing) { cToBridged(expression) }
}
private class ObjCReferenceValuePassing(private val symbols: KonanSymbols, private val type: IrType) : SimpleValuePassing() {
override val kotlinBridgeType: IrType
get() = symbols.nativePtrType
override val cBridgeType: CType
get() = CTypes.voidPtr
override val cType: CType
get() = CTypes.voidPtr
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression =
irCall(symbols.interopObjCObjectRawValueGetter.owner).apply {
extensionReceiver = expression
}
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression =
irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply {
putValueArgument(0, expression)
}
override fun bridgedToC(expression: String): String = expression
override fun cToBridged(expression: String): String = expression
}
private class WCStringArgumentPassing : KotlinToCArgumentPassing {
override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression {
val wcstr = irBuilder.irCall(symbols.interopWcstr.owner).apply {
extensionReceiver = expression
}
return with(CValuesRefArgumentPassing) { passValue(wcstr) }
}
}
private class CStringArgumentPassing : KotlinToCArgumentPassing {
override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression {
val cstr = irBuilder.irCall(symbols.interopCstr.owner).apply {
extensionReceiver = expression
}
return with(CValuesRefArgumentPassing) { passValue(cstr) }
}
}
private object CValuesRefArgumentPassing : KotlinToCArgumentPassing {
override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression {
val bridgeArgument = cValuesRefToPointer(expression)
val cBridgeValue = passThroughBridge(
bridgeArgument,
symbols.interopCPointer.typeWithStarProjections.makeNullable(),
CTypes.voidPtr
)
return CExpression(cBridgeValue.name, cBridgeValue.type)
}
}
private fun KotlinToCCallBuilder.cValuesRefToPointer(
value: IrExpression
): IrExpression = if (value.type.classifierOrNull == symbols.interopCPointer) {
value // Optimization
} else with(irBuilder) {
val getPointerFunction = symbols.interopCValuesRef.owner
.simpleFunctions()
.single { it.name.asString() == "getPointer" }
fun getPointer(expression: IrExpression) = irCall(getPointerFunction).apply {
dispatchReceiver = expression
putValueArgument(0, bridgeCallBuilder.getMemScope())
}
if (!value.type.isNullable()) {
getPointer(value) // Optimization
} else irLetS(value) { valueVarSymbol ->
val valueVar = valueVarSymbol.owner
irIfThenElse(
type = symbols.interopCPointer.typeWithStarProjections.makeNullable(),
condition = irEqeqeq(irGet(valueVar), irNull()),
thenPart = irNull(),
elsePart = getPointer(irGet(valueVar))
)
}
}
private object VoidReturning : ValueReturning {
override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression {
bridgeBuilder.setReturnType(irBuilder.context.irBuiltIns.unitType, CTypes.void)
cFunctionBuilder.setReturnType(CTypes.void)
cBridgeBodyLines += "$expression;"
return buildKotlinBridgeCall()
}
override fun CCallbackBuilder.returnValue(expression: IrExpression) {
bridgeBuilder.setReturnType(irBuiltIns.unitType, CTypes.void)
cFunctionBuilder.setReturnType(CTypes.void)
kotlinBridgeStatements += bridgeBuilder.kotlinIrBuilder.irReturn(expression)
cBodyLines += "${buildCBridgeCall()};"
}
}
internal fun CType.cast(expression: String): String = "((${this.render("")})$expression)"
private fun KotlinStubs.reportUnsupportedType(reason: String, type: IrType, location: TypeLocation): Nothing {
val typeLocation: String = when (location) {
is TypeLocation.FunctionArgument -> ""
is TypeLocation.FunctionCallResult -> " of return value"
is TypeLocation.FunctionPointerParameter -> " of callback parameter ${location.index + 1}"
is TypeLocation.FunctionPointerReturnValue -> " of callback return value"
}
reportError(location.element, "type ${type.toKotlinType()}$typeLocation is not supported here" +
if (reason.isNotEmpty()) ": $reason" else "")
}
@@ -0,0 +1,234 @@
package org.jetbrains.kotlin.backend.konan.cgen
import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor
import org.jetbrains.kotlin.backend.common.descriptors.WrappedValueParameterDescriptor
import org.jetbrains.kotlin.backend.common.lower.irBlock
import org.jetbrains.kotlin.backend.common.lower.irThrow
import org.jetbrains.kotlin.backend.konan.descriptors.createAnnotation
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrTryImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.impl.IrUninitializedType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
internal class CFunctionBuilder {
private val parameters = mutableListOf<CVariable>()
private lateinit var returnType: CType
var variadic: Boolean = false
fun setReturnType(type: CType) {
require(!::returnType.isInitialized)
returnType = type
}
fun addParameter(type: CType): CVariable {
val result = CVariable(type, "p${counter++}")
parameters += result
return result
}
val numberOfParameters: Int get() = parameters.size
private var counter = 1
fun getType(): CType = CTypes.function(returnType, parameters.map { it.type }, variadic)
fun buildSignature(name: String): String = returnType.render(buildString {
append(name)
append('(')
parameters.joinTo(this)
if (parameters.isEmpty()) {
if (!variadic) append("void")
} else {
if (variadic) append(", ...")
}
append(')')
})
}
internal class KotlinBridgeBuilder(
startOffset: Int,
endOffset: Int,
cName: String,
stubs: KotlinStubs,
isExternal: Boolean
) {
private var counter = 0
private val bridge: IrFunction = createKotlinBridge(startOffset, endOffset, cName, stubs.symbols, isExternal)
val irBuilder: IrBuilderWithScope = irBuilder(stubs.irBuiltIns, bridge.symbol).at(startOffset, endOffset)
fun addParameter(type: IrType): IrValueParameter {
val index = counter++
val descriptor = WrappedValueParameterDescriptor()
return IrValueParameterImpl(
bridge.startOffset, bridge.endOffset, bridge.origin,
IrValueParameterSymbolImpl(descriptor),
Name.identifier("p$index"), index, type,
null, false, false
).apply {
descriptor.bind(this)
parent = bridge
bridge.valueParameters += this
}
}
fun setReturnType(type: IrType) {
bridge.returnType = type
}
fun build(): IrFunction = bridge
}
private fun createKotlinBridge(
startOffset: Int,
endOffset: Int,
cBridgeName: String,
symbols: KonanSymbols,
isExternal: Boolean
): IrFunctionImpl {
val bridgeAnnotations = Annotations.create(
listOf(
if (isExternal) {
createAnnotation(symbols.symbolName.descriptor, "value" to cBridgeName)
} else {
createAnnotation(symbols.exportForCppRuntime.descriptor, "name" to cBridgeName)
}
)
)
val bridgeDescriptor = WrappedSimpleFunctionDescriptor(bridgeAnnotations)
val bridge = IrFunctionImpl(
startOffset,
endOffset,
IrDeclarationOrigin.DEFINED,
IrSimpleFunctionSymbolImpl(bridgeDescriptor),
Name.identifier(cBridgeName),
Visibilities.PRIVATE,
Modality.FINAL,
IrUninitializedType,
isInline = false,
isExternal = isExternal,
isTailrec = false,
isSuspend = false
)
bridgeDescriptor.bind(bridge)
return bridge
}
internal class KotlinCBridgeBuilder(
startOffset: Int,
endOffset: Int,
cName: String,
stubs: KotlinStubs,
isKotlinToC: Boolean
) {
private val kotlinBridgeBuilder = KotlinBridgeBuilder(startOffset, endOffset, cName, stubs, isExternal = isKotlinToC)
private val cBridgeBuilder = CFunctionBuilder()
val kotlinIrBuilder: IrBuilderWithScope get() = kotlinBridgeBuilder.irBuilder
fun addParameter(kotlinType: IrType, cType: CType): Pair<IrValueParameter, CVariable> {
return kotlinBridgeBuilder.addParameter(kotlinType) to cBridgeBuilder.addParameter(cType)
}
fun setReturnType(kotlinReturnType: IrType, cReturnType: CType) {
kotlinBridgeBuilder.setReturnType(kotlinReturnType)
cBridgeBuilder.setReturnType(cReturnType)
}
fun buildCSignature(name: String): String = cBridgeBuilder.buildSignature(name)
fun buildKotlinBridge() = kotlinBridgeBuilder.build()
}
internal class KotlinCallBuilder(private val irBuilder: IrBuilderWithScope, private val symbols: KonanSymbols) {
val prepare = mutableListOf<IrStatement>()
val arguments = mutableListOf<IrExpression>()
val cleanup = mutableListOf<IrStatement>()
private var memScope: IrVariable? = null
fun getMemScope(): IrExpression = with(irBuilder) {
memScope?.let { return irGet(it) }
val newMemScope = scope.createTemporaryVariable(irCall(symbols.interopMemScope.owner.constructors.single()))
memScope = newMemScope
prepare += newMemScope
val clearImpl = symbols.interopMemScope.owner.simpleFunctions().single { it.name.asString() == "clearImpl" }
cleanup += irCall(clearImpl).apply {
dispatchReceiver = irGet(memScope!!)
}
irGet(newMemScope)
}
fun build(
function: IrFunction,
transformCall: (IrCall) -> IrExpression = { it }
): IrExpression {
val arguments = this.arguments.toMutableList()
val kotlinCall = irBuilder.irCall(function).run {
if (function.dispatchReceiverParameter != null) {
dispatchReceiver = arguments.removeAt(0)
}
if (function.extensionReceiverParameter != null) {
extensionReceiver = arguments.removeAt(0)
}
assert(arguments.size == function.valueParameters.size)
arguments.forEachIndexed { index, it -> putValueArgument(index, it) }
transformCall(this)
}
return if (prepare.isEmpty() && cleanup.isEmpty()) {
kotlinCall
} else {
irBuilder.irBlock(kotlinCall) {
prepare.forEach { +it }
if (cleanup.isEmpty()) {
+kotlinCall
} else {
// Note: generating try-catch as finally blocks are already lowered.
+IrTryImpl(startOffset, endOffset, kotlinCall.type).apply {
tryResult = kotlinCall
catches += irCatch(context.irBuiltIns.throwableType).apply {
result = irBlock(kotlinCall) {
cleanup.forEach { +it }
+irThrow(irGet(catchParameter))
}
}
}
}
}
}
}
}
internal class CCallBuilder {
val arguments = mutableListOf<String>()
fun build(function: String) = buildString {
append(function)
append('(')
arguments.joinTo(this)
append(')')
}
}
@@ -0,0 +1,58 @@
package org.jetbrains.kotlin.backend.konan.cgen
internal interface CType {
fun render(name: String): String
}
internal class CVariable(val type: CType, val name: String) {
override fun toString() = type.render(name)
}
internal object CTypes {
fun simple(type: String): CType = SimpleCType(type)
fun pointer(pointee: CType): CType = PointerCType(pointee)
fun function(returnType: CType, parameterTypes: List<CType>, variadic: Boolean): CType =
FunctionCType(returnType, parameterTypes, variadic)
val void = simple("void")
val voidPtr = pointer(void)
val signedChar = simple("signed char")
val unsignedChar = simple("unsigned char")
val short = simple("short")
val unsignedShort = simple("unsigned short")
val int = simple("int")
val unsignedInt = simple("unsigned int")
val longLong = simple("long long")
val unsignedLongLong = simple("unsigned long long")
val float = simple("float")
val double = simple("double")
val C99Bool = simple("_Bool")
val char = simple("char")
}
private class SimpleCType(private val type: String) : CType {
override fun render(name: String): String = if (name.isEmpty()) type else "$type $name"
}
private class PointerCType(private val pointee: CType) : CType {
override fun render(name: String): String = pointee.render("*$name")
}
private class FunctionCType(
private val returnType: CType,
private val parameterTypes: List<CType>,
private val variadic: Boolean
) : CType {
override fun render(name: String): String = returnType.render(buildString {
append("(")
append(name)
append(")(")
parameterTypes.joinTo(this) { it.render("") }
if (parameterTypes.isEmpty()) {
if (!variadic) append("void")
} else {
if (variadic) append(", ...")
}
append(')')
})
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.backend.konan.descriptors
import org.jetbrains.kotlin.backend.common.atMostOne
import org.jetbrains.kotlin.backend.konan.RuntimeNames
import org.jetbrains.kotlin.backend.konan.binaryTypeIsReference
import org.jetbrains.kotlin.backend.konan.isObjCClass
import org.jetbrains.kotlin.backend.konan.serialization.isExported
@@ -16,10 +17,12 @@ import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl
import org.jetbrains.kotlin.metadata.konan.KonanProtoBuf
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorFactory
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.constants.StringValue
@@ -270,5 +273,16 @@ fun CallableMemberDescriptor.externalSymbolOrThrow(): String? {
if (this.annotations.hasAnnotation(TypedIntrinsic)) return null
if (this.annotations.hasAnnotation(RuntimeNames.cCall)) return null
throw Error("external function ${this} must have @TypedIntrinsic, @SymbolName or @ObjCMethod annotation")
}
fun createAnnotation(
descriptor: ClassDescriptor,
vararg values: Pair<String, String>
): AnnotationDescriptor = AnnotationDescriptorImpl(
descriptor.defaultType,
values.map { (name, value) -> Name.identifier(name) to StringValue(value) }.toMap(),
SourceElement.NO_SOURCE
)
@@ -132,8 +132,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
val nativePtr = symbolTable.referenceClass(context.nativePtr)
val nativePtrType = nativePtr.typeWith(arguments = emptyList())
private fun unsignedClass(unsignedType: UnsignedType): IrClassSymbol =
symbolTable.referenceClass(builtIns.builtInsModule.findClassAcrossModuleDependencies(unsignedType.classId)!!)
private fun unsignedClass(unsignedType: UnsignedType): IrClassSymbol = classById(unsignedType.classId)
val uByte = unsignedClass(UnsignedType.UBYTE)
val uShort = unsignedClass(UnsignedType.USHORT)
@@ -181,9 +180,25 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
val arrayList = symbolTable.referenceClass(getArrayListClassDescriptor(context))
val symbolName = topLevelClass(RuntimeNames.symbolName)
val exportForCppRuntime = topLevelClass(RuntimeNames.exportForCppRuntime)
val objCMethodImp = symbolTable.referenceClass(context.interopBuiltIns.objCMethodImp)
val interopNativePointedGetRawPointer =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.nativePointedGetRawPointer)
val interopCPointer = symbolTable.referenceClass(context.interopBuiltIns.cPointer)
val interopCstr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cstr.getter!!)
val interopWcstr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.wcstr.getter!!)
val interopMemScope = symbolTable.referenceClass(context.interopBuiltIns.memScope)
val interopCValue = symbolTable.referenceClass(context.interopBuiltIns.cValue)
val interopCValues = symbolTable.referenceClass(context.interopBuiltIns.cValues)
val interopCValuesRef = symbolTable.referenceClass(context.interopBuiltIns.cValuesRef)
val interopCValueWrite = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cValueWrite)
val interopCValueRead = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cValueRead)
val interopAllocType = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocType)
val interopCPointerGetRawValue = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cPointerGetRawValue)
val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject)
@@ -194,6 +209,12 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
.single()
)
val interopObjCRetain = symbolTable.referenceSimpleFunction(
context.interopBuiltIns.packageScope
.getContributedFunctions(Name.identifier("objc_retain"), NoLookupLocation.FROM_BACKEND)
.single()
)
val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass)
val interopObjCObjectSuperInitCheck =
@@ -204,10 +225,6 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
val interopObjCObjectRawValueGetter =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr)
val interopInvokeImpls = context.interopBuiltIns.invokeImpls.mapValues { (_, function) ->
symbolTable.referenceSimpleFunction(function)
}
val interopInterpretObjCPointer =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.interpretObjCPointer)
@@ -397,20 +414,11 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
override val coroutineImpl get() = TODO()
val baseContinuationImpl = symbolTable.referenceClass(
builtIns.builtInsModule.findClassAcrossModuleDependencies(
ClassId.topLevel(FqName("kotlin.coroutines.native.internal.BaseContinuationImpl")))!!
)
val baseContinuationImpl = topLevelClass("kotlin.coroutines.native.internal.BaseContinuationImpl")
val restrictedContinuationImpl = symbolTable.referenceClass(
builtIns.builtInsModule.findClassAcrossModuleDependencies(
ClassId.topLevel(FqName("kotlin.coroutines.native.internal.RestrictedContinuationImpl")))!!
)
val restrictedContinuationImpl = topLevelClass("kotlin.coroutines.native.internal.RestrictedContinuationImpl")
val continuationImpl = symbolTable.referenceClass(
builtIns.builtInsModule.findClassAcrossModuleDependencies(
ClassId.topLevel(FqName("kotlin.coroutines.native.internal.ContinuationImpl")))!!
)
val continuationImpl = topLevelClass("kotlin.coroutines.native.internal.ContinuationImpl")
override val coroutineSuspendedGetter = symbolTable.referenceSimpleFunction(
coroutinesIntrinsicsPackage
@@ -418,10 +426,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
.filterNot { it.isExpect }.single().getter!!
)
val kotlinResult = symbolTable.referenceClass(
builtIns.builtInsModule.findClassAcrossModuleDependencies(
ClassId.topLevel(FqName("kotlin.Result")))!!
)
val kotlinResult = topLevelClass("kotlin.Result")
val kotlinResultGetOrThrow = symbolTable.referenceSimpleFunction(
builtInsPackage("kotlin")
@@ -477,6 +482,11 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
context.builtIns.builtInsModule.findClassAcrossModuleDependencies(
ClassId.topLevel(FqName("kotlin.native.concurrent.SharedImmutable")))!!
private fun topLevelClass(fqName: String): IrClassSymbol = topLevelClass(FqName(fqName))
private fun topLevelClass(fqName: FqName): IrClassSymbol = classById(ClassId.topLevel(fqName))
private fun classById(classId: ClassId): IrClassSymbol =
symbolTable.referenceClass(builtIns.builtInsModule.findClassAcrossModuleDependencies(classId)!!)
private fun internalFunction(name: String): IrSimpleFunctionSymbol =
symbolTable.referenceSimpleFunction(context.getInternalFunctions(name).single())
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.explicitParameters
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
@@ -34,6 +35,16 @@ val IrDeclarationParent.fqNameSafe: FqName get() = when (this) {
else -> error(this)
}
val IrClass.classId: ClassId?
get() {
val parent = this.parent
return when (parent) {
is IrClass -> parent.classId?.createNestedClassId(this.name)
is IrPackageFragment -> ClassId.topLevel(parent.fqName.child(this.name))
else -> null
}
}
val IrDeclaration.name: Name
get() = when (this) {
is IrSimpleFunction -> this.name
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.backend.konan.llvm
import llvm.LLVMTypeRef
import org.jetbrains.kotlin.backend.konan.RuntimeNames
import org.jetbrains.kotlin.backend.konan.descriptors.externalSymbolOrThrow
import org.jetbrains.kotlin.backend.konan.descriptors.getAnnotationValue
import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract
@@ -102,11 +103,11 @@ internal tailrec fun DeclarationDescriptor.isExported(): Boolean {
return true
}
private val symbolNameAnnotation = FqName("kotlin.native.SymbolName")
private val symbolNameAnnotation = RuntimeNames.symbolName
private val cnameAnnotation = FqName("kotlin.native.CName")
private val exportForCppRuntimeAnnotation = FqName("kotlin.native.internal.ExportForCppRuntime")
private val exportForCppRuntimeAnnotation = RuntimeNames.exportForCppRuntime
private val exportForCompilerAnnotation = FqName("kotlin.native.internal.ExportForCompiler")
@@ -302,7 +302,10 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val found = LLVMGetNamedFunction(llvmModule, name)
if (found != null) {
assert(getFunctionType(found) == type)
assert(getFunctionType(found) == type) {
"Expected: ${LLVMPrintTypeToString(type)!!.toKString()} " +
"found: ${LLVMPrintTypeToString(getFunctionType(found))!!.toKString()}"
}
assert(LLVMGetLinkage(found) == LLVMLinkage.LLVMExternalLinkage)
return found
} else {
@@ -330,11 +333,13 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
return function
}
private val usedLibraries = mutableSetOf<KonanLibrary>()
val imports get() = context.llvmImports
val imports = object : LlvmImports {
class ImportsImpl(private val context: Context) : LlvmImports {
private val allLibraries = context.librariesWithDependencies.toSet()
private val usedLibraries = mutableSetOf<KonanLibrary>()
private val allLibraries by lazy { context.librariesWithDependencies.toSet() }
override fun add(origin: CompiledKonanModuleOrigin) {
val library = when (origin) {
@@ -348,11 +353,13 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
usedLibraries.add(library)
}
override fun isImported(library: KonanLibrary): Boolean = library in usedLibraries
}
val librariesToLink: List<KonanLibrary> by lazy {
context.config.resolvedLibraries
.filterRoots { (!it.isDefault && !context.config.purgeUserLibs) || it.library in usedLibraries }
.filterRoots { (!it.isDefault && !context.config.purgeUserLibs) || imports.isImported(it.library) }
.getFullList(TopologicalLibraryOrder)
}
@@ -11,10 +11,12 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.konan.CompiledKonanModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.SyntheticModulesOrigin
import org.jetbrains.kotlin.descriptors.konan.konanModuleOrigin
import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.resolve.descriptorUtil.module
internal interface LlvmImports {
fun add(origin: CompiledKonanModuleOrigin)
fun isImported(library: KonanLibrary): Boolean
}
internal val DeclarationDescriptor.llvmSymbolOrigin: CompiledKonanModuleOrigin
@@ -398,9 +398,8 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
}
val llvmFunction = if (descriptor.isExternal) {
if (descriptor.isTypedIntrinsic || descriptor.isObjCBridgeBased()) {
return
}
if (descriptor.isTypedIntrinsic || descriptor.isObjCBridgeBased()
|| descriptor.annotations.hasAnnotation(RuntimeNames.cCall)) return
context.llvm.externalFunction(descriptor.symbolName, llvmFunctionType,
// Assume that `external fun` is defined in native libs attached to this module:
@@ -11,12 +11,17 @@ import org.jetbrains.kotlin.backend.common.peek
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cgen.KotlinStubs
import org.jetbrains.kotlin.backend.konan.cgen.generateCCall
import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionPointer
import org.jetbrains.kotlin.backend.konan.getInlinedClass
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.irasdescriptors.*
import org.jetbrains.kotlin.backend.konan.objcexport.namePrefix
import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType
import org.jetbrains.kotlin.backend.konan.llvm.llvmSymbolOrigin
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
import org.jetbrains.kotlin.builtins.UnsignedTypes
import org.jetbrains.kotlin.descriptors.*
@@ -29,6 +34,7 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
@@ -38,6 +44,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -743,14 +750,53 @@ internal class InteropLoweringPart2(val context: Context) : FileLoweringPass {
override fun lower(irFile: IrFile) {
val transformer = InteropTransformer(context, irFile)
irFile.transformChildrenVoid(transformer)
irFile.addChildren(transformer.newTopLevelDeclarations)
}
}
private class InteropTransformer(val context: Context, val irFile: IrFile) : IrBuildingTransformer(context) {
val newTopLevelDeclarations = mutableListOf<IrDeclaration>()
val interop = context.interopBuiltIns
val symbols = context.ir.symbols
private inline fun <T> generateWithStubs(element: IrElement? = null, block: KotlinStubs.() -> T): T =
createKotlinStubs(element).block()
private fun createKotlinStubs(element: IrElement?): KotlinStubs {
val location = if (element != null) {
element.getCompilerMessageLocation(irFile)
} else {
builder.getCompilerMessageLocation()
}
return object : KotlinStubs {
override val irBuiltIns get() = context.irBuiltIns
override val symbols get() = context.ir.symbols
override fun addKotlin(declaration: IrDeclaration) {
newTopLevelDeclarations += declaration
}
override fun addC(lines: List<String>) {
context.cStubsManager.addStub(location, lines)
}
override fun getUniqueCName(prefix: String) =
"_${context.moduleDescriptor.namePrefix}_${context.cStubsManager.getUniqueName(prefix)}"
override val target get() = context.config.target
override fun reportError(location: IrElement, message: String): Nothing =
context.reportCompilationError(message, irFile, location)
}
}
private fun generateCFunctionPointer(function: IrSimpleFunction, expression: IrExpression): IrExpression =
generateWithStubs { generateCFunctionPointer(function, function, false, expression) }
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this)
@@ -774,7 +820,10 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
}
}
fun reportError(message: String): Nothing = context.reportCompilationError(message, irFile, expression)
if (function.descriptor.annotations.hasAnnotation(RuntimeNames.cCall)) {
context.llvmImports.add(function.descriptor.llvmSymbolOrigin)
return generateWithStubs { generateCCall(expression, builder, isInvoke = false) }
}
val intrinsicType = tryGetIntrinsicType(expression)
@@ -801,7 +850,8 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
IntrinsicType.INTEROP_STATIC_C_FUNCTION -> {
val irCallableReference = unwrapStaticFunctionArgument(expression.getValueArgument(0)!!)
if (irCallableReference == null || irCallableReference.getArguments().isNotEmpty()) {
if (irCallableReference == null || irCallableReference.getArguments().isNotEmpty()
|| irCallableReference.symbol !is IrSimpleFunctionSymbol) {
context.reportCompilationError(
"${descriptor.fqNameSafe} must take an unbound, non-capturing function or lambda",
irFile, expression
@@ -813,13 +863,6 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
val target = targetSymbol.owner
val signatureTypes = target.allParameters.map { it.type } + target.returnType
signatureTypes.forEachIndexed { index, type ->
type.ensureSupportedInCallbacks(
isReturnType = (index == signatureTypes.lastIndex),
reportError = ::reportError
)
}
descriptor.typeParameters.forEachIndexed { index, typeParameterDescriptor ->
val typeArgument = expression.getTypeArgument(typeParameterDescriptor)!!.toKotlinType()
val signatureType = signatureTypes[index].toKotlinType()
@@ -832,39 +875,10 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
}
}
IrFunctionReferenceImpl(
builder.startOffset, builder.endOffset,
expression.type,
targetSymbol, target.descriptor,
typeArgumentsCount = 0)
generateCFunctionPointer(target as IrSimpleFunction, expression)
}
IntrinsicType.INTEROP_FUNPTR_INVOKE -> {
// Replace by `invokeImpl${type}Ret`:
val returnType =
expression.getTypeArgument(descriptor.typeParameters.single { it.name.asString() == "R" })!!
returnType.checkCTypeNullability(::reportError)
val invokeImpl = symbols.interopInvokeImpls[returnType.getClass()?.descriptor] ?:
context.reportCompilationError(
"Invocation of C function pointer with return type '${returnType.toKotlinType()}' is not supported yet",
irFile, expression
)
builder.irCall(invokeImpl).apply {
putValueArgument(0, expression.extensionReceiver)
val varargParameter = invokeImpl.owner.valueParameters[1]
val varargArgument = IrVarargImpl(
startOffset, endOffset, varargParameter.type, varargParameter.varargElementType!!
).apply {
descriptor.valueParameters.forEach {
this.addElement(expression.getValueArgument(it)!!)
}
}
putValueArgument(varargParameter.index, varargArgument)
}
generateWithStubs { generateCCall(expression, builder, isInvoke = true) }
}
IntrinsicType.INTEROP_SIGN_EXTEND, IntrinsicType.INTEROP_NARROW -> {
@@ -24,14 +24,17 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.*
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.impl.IrStarProjectionImpl
@@ -44,6 +47,14 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes
import java.lang.reflect.Proxy
internal fun irBuilder(irBuiltIns: IrBuiltIns, scopeOwnerSymbol: IrSymbol): IrBuilderWithScope =
object : IrBuilderWithScope(
IrGeneratorContextBase(irBuiltIns),
Scope(scopeOwnerSymbol),
UNDEFINED_OFFSET,
UNDEFINED_OFFSET
) {}
//TODO: delete file on next kotlin dependency update
internal fun IrExpression.isNullConst() = this is IrConst<*> && this.kind == IrConstKind.Null
@@ -506,6 +517,26 @@ fun IrBuilderWithScope.irCallOp(
fun IrBuilderWithScope.irSetVar(variable: IrVariable, value: IrExpression) =
irSetVar(variable.symbol, value)
fun IrBuilderWithScope.irCatch(type: IrType) =
IrCatchImpl(
startOffset, endOffset,
WrappedVariableDescriptor().let { descriptor ->
IrVariableImpl(
startOffset,
endOffset,
IrDeclarationOrigin.IR_TEMPORARY_VARIABLE,
IrVariableSymbolImpl(descriptor),
Name.identifier("e"),
type,
false,
false,
false
).apply {
descriptor.bind(this)
}
}
)
/**
* Binds the arguments explicitly represented in the IR to the parameters of the accessed function.
* The arguments are to be evaluated in the same order as they appear in the resulting list.
+10
View File
@@ -2882,6 +2882,10 @@ kotlinNativeInterop {
defFile 'interop/basics/ctypes.def'
}
ccallbacksAndVarargs {
defFile 'interop/basics/ccallbacksAndVarargs.def'
}
if (isMac()) {
objcSmoke {
defFile 'interop/objc/objcSmoke.def'
@@ -2982,6 +2986,12 @@ task interop_types(type: RunInteropKonanTest) {
interop = 'ctypes'
}
task interop_callbacksAndVarargs(type: RunInteropKonanTest) {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/basics/callbacksAndVarargs.kt"
interop = 'ccallbacksAndVarargs'
}
task interop_echo_server(type: RunInteropKonanTest) {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
if (!isMac()) {
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
import kotlin.test.*
import kotlinx.cinterop.*
import ccallbacksAndVarargs.*
fun main(args: Array<String>) {
assertEquals(42, getX(staticCFunction { -> cValue<S> { x = 42 } }))
applyCallback(cValue { x = 17 }, staticCFunction { it: CValue<S> ->
assertEquals(17, it.useContents { x })
})
assertEquals(66, makeS(66, 111).useContents { x })
assertEquals(E.ONE, makeE(1))
getVarargs(
0,
true,
2.toByte(),
Short.MIN_VALUE,
42,
Long.MAX_VALUE,
3.14f,
2.71,
0x1234ABCDL.toCPointer<COpaque>(),
UByte.MAX_VALUE,
22.toUShort(),
111u,
ULong.MAX_VALUE,
E.TWO,
cValue<S> { x = 15 }
).useContents {
assertEquals(1, a1)
assertEquals(2.toByte(), a2)
assertEquals(Short.MIN_VALUE, a3)
assertEquals(42, a4)
assertEquals(Long.MAX_VALUE, a5)
assertEquals(3.14f, a6)
assertEquals(2.71, a7)
assertEquals(0x1234ABCDL, a8.toLong())
assertEquals(UByte.MAX_VALUE, a9)
assertEquals(22.toUShort(), a10)
assertEquals(111u, a11)
assertEquals(ULong.MAX_VALUE, a12)
assertEquals(E.TWO, a13)
assertEquals(15, a14.x)
}
}
@@ -0,0 +1,72 @@
---
#include <stdarg.h>
struct S {
int x;
};
static int getX(struct S (*callback)(void)) {
return callback().x;
}
static void applyCallback(struct S s, void (*callback)(struct S)) {
callback(s);
}
static struct S makeS(int x, ...) {
return (struct S){ x };
}
enum E {
ZERO, ONE, TWO
};
static enum E makeE(int ordinal, ...) {
return ordinal;
}
struct Args {
char a1;
char a2;
short a3;
int a4;
long long a5;
float a6;
double a7;
void* a8;
unsigned char a9;
unsigned short a10;
unsigned int a11;
unsigned long long a12;
enum E a13;
struct S a14;
};
static struct Args getVarargs(int ignore, ...) {
va_list args;
va_start(args, ignore);
struct Args result = {
va_arg(args, char),
va_arg(args, char),
va_arg(args, short),
va_arg(args, int),
va_arg(args, long long),
va_arg(args, double),
va_arg(args, double),
va_arg(args, void*),
va_arg(args, unsigned char),
va_arg(args, unsigned short),
va_arg(args, unsigned int),
va_arg(args, unsigned long long),
va_arg(args, enum E),
va_arg(args, struct S)
};
va_end(args);
return result;
}
+16 -24
View File
@@ -36,10 +36,10 @@ llvmVersion = 6.0.1
# Mac OS X.
llvmHome.macos_x64 = clang-llvm-6.0.1-darwin-macos
targetToolchain.macos_x64 = target-toolchain-8-macos_x64
libffiDir.macos_x64 = libffi-3.2.1-3-darwin-macos
arch.macos_x64 = x86_64
targetSysRoot.macos_x64 = target-sysroot-8-macos_x64
libffiDir.macos_x64 = libffi-3.2.1-3-darwin-macos
llvmLtoFlags.macos_x64 =
llvmLtoOptFlags.macos_x64 = -O3 -function-sections
llvmLtoNooptFlags.macos_x64 = -O1
@@ -65,7 +65,7 @@ target-toolchain-8-macos_x64.default = \
# Apple's 32-bit iOS.
targetToolchain.macos_x64-ios_arm32 = target-toolchain-8-macos_x64
dependencies.macos_x64-ios_arm32 = \
libffi-3.2.1-3-darwin-ios \
libffi-3.2.1-3-darwin-macos \
clang-llvm-6.0.1-darwin-macos
target-sysroot-8-ios_arm32.default = \
@@ -75,7 +75,6 @@ arch.ios_arm32 = armv7
entrySelector.ios_arm32 = -alias _Konan_main _main
# Shared with 64-bit version.
targetSysRoot.ios_arm32 = target-sysroot-8-ios_arm64
libffiDir.ios_arm32 = libffi-3.2.1-3-darwin-ios
llvmLtoFlags.ios_arm32 =
llvmLtoOptFlags.ios_arm32 = -O3 -function-sections
linkerNoDebugFlags.ios_arm32 = -S
@@ -91,7 +90,7 @@ osVersionMin.ios_arm32 = 9.0
# Apple's 64-bit iOS.
targetToolchain.macos_x64-ios_arm64 = target-toolchain-8-macos_x64
dependencies.macos_x64-ios_arm64 = \
libffi-3.2.1-3-darwin-ios \
libffi-3.2.1-3-darwin-macos \
clang-llvm-6.0.1-darwin-macos
target-sysroot-8-ios_arm64.default = \
@@ -100,7 +99,6 @@ target-sysroot-8-ios_arm64.default = \
arch.ios_arm64 = arm64
entrySelector.ios_arm64 = -alias _Konan_main _main
targetSysRoot.ios_arm64 = target-sysroot-8-ios_arm64
libffiDir.ios_arm64 = libffi-3.2.1-3-darwin-ios
llvmLtoFlags.ios_arm64 =
llvmLtoOptFlags.ios_arm64 = -O3 -function-sections
linkerNoDebugFlags.ios_arm64 = -S
@@ -116,7 +114,7 @@ osVersionMin.ios_arm64 = 9.0
# Apple's iOS simulator.
targetToolchain.macos_x64-ios_x64 = target-toolchain-8-macos_x64
dependencies.macos_x64-ios_x64 = \
libffi-3.2.1-2-darwin-ios_sim \
libffi-3.2.1-3-darwin-macos \
clang-llvm-6.0.1-darwin-macos
target-sysroot-8-ios_x64.default = \
@@ -125,7 +123,6 @@ target-sysroot-8-ios_x64.default = \
arch.ios_x64 = x86_64
entrySelector.ios_x64 = -alias _Konan_main _main
targetSysRoot.ios_x64 = target-sysroot-8-ios_x64
libffiDir.ios_x64 = libffi-3.2.1-2-darwin-ios_sim
llvmLtoFlags.ios_x64 =
llvmLtoOptFlags.ios_x64 = -O3 -function-sections
llvmLtoNooptFlags.ios_x64 = -O1
@@ -142,10 +139,10 @@ osVersionMin.ios_x64 = 9.0
llvmHome.linux_x64 = clang-llvm-6.0.1-linux-x86-64
gccToolchain.linux_x64 = target-gcc-toolchain-3-linux-x86-64
targetToolchain.linux_x64 = target-gcc-toolchain-3-linux-x86-64/x86_64-unknown-linux-gnu
libffiDir.linux_x64 = libffi-3.2.1-2-linux-x86-64
quadruple.linux_x64 = x86_64-unknown-linux-gnu
targetSysRoot.linux_x64 = target-gcc-toolchain-3-linux-x86-64/x86_64-unknown-linux-gnu/sysroot
libffiDir.linux_x64 = libffi-3.2.1-2-linux-x86-64
# targetSysroot-relative.
libGcc.linux_x64 = ../../lib/gcc/x86_64-unknown-linux-gnu/4.8.5
llvmLtoFlags.linux_x64 =
@@ -174,8 +171,7 @@ dependencies.linux_x64-linux_arm32_hfp = \
clang-llvm-6.0.1-linux-x86-64 \
target-gcc-toolchain-3-linux-x86-64 \
target-sysroot-1-raspberrypi \
libffi-3.2.1-2-linux-x86-64 \
libffi-3.2.1-2-raspberrypi
libffi-3.2.1-2-linux-x86-64
quadruple.linux_arm32_hfp = armv6-unknown-linux-gnueabihf
entrySelector.linux_arm32_hfp = --defsym main=Konan_main
@@ -184,7 +180,6 @@ linkerDynamicFlags.linux_arm32_hfp = -shared
linkerOptimizationFlags.linux_arm32_hfp = --gc-sections
targetSysRoot.linux_arm32_hfp = target-sysroot-1-raspberrypi
# We could reuse host toolchain here.
libffiDir.linux_arm32_hfp = libffi-3.2.1-2-raspberrypi
linkerKonanFlags.linux_arm32_hfp = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread \
--defsym __cxa_demangle=Konan_cxa_demangle
# targetSysroot-relative.
@@ -207,8 +202,7 @@ dependencies.linux_x64-linux_mips32 = \
target-gcc-toolchain-2-linux-mips \
target-gcc-toolchain-3-linux-x86-64 \
target-sysroot-2-mips \
libffi-3.2.1-2-linux-x86-64 \
libffi-3.2.1-2-linux-mips
libffi-3.2.1-2-linux-x86-64
quadruple.linux_mips32 = mips-unknown-linux-gnu
entrySelector.linux_mips32 = --defsym main=Konan_main
@@ -216,7 +210,6 @@ linkerOptimizationFlags.linux_mips32 = --gc-sections
linkerDynamicFlags.linux_mips32 = -shared
targetSysRoot.linux_mips32 = target-sysroot-2-mips
# We could reuse host toolchain here.
libffiDir.linux_mips32 = libffi-3.2.1-2-linux-mips
linkerKonanFlags.linux_mips32 = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread \
--defsym __cxa_demangle=Konan_cxa_demangle -z notext
# targetSysroot-relative.
@@ -236,15 +229,13 @@ dependencies.linux_x64-linux_mipsel32 = \
target-gcc-toolchain-2-linux-mips \
target-gcc-toolchain-3-linux-x86-64 \
target-sysroot-2-mipsel \
libffi-3.2.1-2-linux-x86-64 \
libffi-3.2.1-2-linux-mipsel
libffi-3.2.1-2-linux-x86-64
quadruple.linux_mipsel32 = mipsel-unknown-linux-gnu
entrySelector.linux_mipsel32 = --defsym main=Konan_main
linkerOptimizationFlags.linux_mipsel32 = --gc-sections
linkerDynamicFlags.linux_mipsel32 = -shared
targetSysRoot.linux_mipsel32 = target-sysroot-2-mipsel
# We could reuse host toolchain here.
libffiDir.linux_mipsel32 = libffi-3.2.1-2-linux-mipsel
linkerKonanFlags.linux_mipsel32 = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread \
--defsym __cxa_demangle=Konan_cxa_demangle -z notext
# targetSysroot-relative.
@@ -264,13 +255,13 @@ dependencies.macos_x64-android_arm32 = \
clang-llvm-6.0.1-darwin-macos \
target-sysroot-21-android_arm32 \
target-toolchain-21-osx-android_arm32 \
libffi-3.2.1-2-android_arm32
libffi-3.2.1-3-darwin-macos
targetToolchain.linux_x64-android_arm32 = target-toolchain-21-linux-android_arm32
dependencies.linux_x64-android_arm32 = \
clang-llvm-6.0.1-linux-x86-64 \
target-sysroot-21-android_arm32 \
target-toolchain-21-linux-android_arm32 \
libffi-3.2.1-2-android_arm32
libffi-3.2.1-2-linux-x86-64
quadruple.android_arm32 = arm-linux-androideabi
entrySelector.android_arm32 = -Wl,--defsym -Wl,main=Konan_main
@@ -279,7 +270,6 @@ llvmLtoOptFlags.android_arm32 = -O3 -function-sections
linkerNoDebugFlags.android_arm32 = -Wl,-S
llvmLtoNooptFlags.android_arm32 = -O1
targetSysRoot.android_arm32 = target-sysroot-21-android_arm32
libffiDir.android_arm32 = libffi-3.2.1-2-android_arm32
linkerKonanFlags.android_arm32 = -lm -latomic -lstdc++ -landroid
# Android ARM64, based on NDK for android-21.
@@ -289,29 +279,28 @@ dependencies.macos_x64-android_arm64 = \
clang-llvm-6.0.1-darwin-macos \
target-sysroot-21-android_arm64 \
target-toolchain-21-osx-android_arm64 \
libffi-3.2.1-2-android_arm64
libffi-3.2.1-3-darwin-macos
targetToolchain.linux_x64-android_arm64 = target-toolchain-21-linux-android_arm64
dependencies.linux_x64-android_arm64 = \
clang-llvm-6.0.1-linux-x86-64 \
target-sysroot-21-android_arm64 \
target-toolchain-21-linux-android_arm64 \
libffi-3.2.1-2-android_arm64
libffi-3.2.1-2-linux-x86-64
quadruple.android_arm64 = aarch64-linux-android
entrySelector.android_arm64 = -Wl,--defsym -Wl,main=Konan_main
llvmLtoFlags.android_arm64 = -emulated-tls -relocation-model=pic
targetSysRoot.android_arm64 = target-sysroot-21-android_arm64
libffiDir.android_arm64 = libffi-3.2.1-2-android_arm64
linkerKonanFlags.android_arm64 = -lm -latomic -lstdc++ -landroid
linkerNoDebugFlags.android_arm64 = -Wl,-S
# Windows x86-64, based on mingw-w64.
llvmHome.mingw_x64 = msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1
targetToolchain.mingw_x64 = msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1
libffiDir.mingw_x64 = libffi-3.2.1-mingw-w64-x86-64
quadruple.mingw_x64 = x86_64-w64-mingw32
targetSysRoot.mingw_x64 = msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1
libffiDir.mingw_x64 = libffi-3.2.1-mingw-w64-x86-64
# For using with Universal Windows Platform (UWP) we need to use this slower option.
# See https://youtrack.jetbrains.com/issue/KT-27654.
# TODO: remove, once fixed in mingw, check with the bug testcase.
@@ -333,18 +322,21 @@ dependencies.mingw_x64 = \
# WebAssembly 32-bit.
targetToolchain.macos_x64-wasm32 = target-toolchain-3-macos-wasm
dependencies.macos_x64-wasm32 = \
libffi-3.2.1-3-darwin-macos \
clang-llvm-6.0.1-darwin-macos \
target-sysroot-2-wasm \
target-toolchain-3-macos-wasm
targetToolchain.linux_x64-wasm32 = target-toolchain-2-linux-wasm
dependencies.linux_x64-wasm32 = \
libffi-3.2.1-2-linux-x86-64 \
clang-llvm-6.0.1-linux-x86-64 \
target-sysroot-2-wasm \
target-toolchain-2-linux-wasm
targetToolchain.mingw_x64-wasm32 = target-toolchain-2-mingw-wasm
dependencies.mingw_x64-wasm32 = \
libffi-3.2.1-mingw-w64-x86-64 \
msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1 \
target-sysroot-2-wasm \
target-toolchain-2-mingw-wasm
+3
View File
@@ -25,6 +25,7 @@ boardSpecificClangFlags.zephyr_stm32f4_disco = -mabi=aapcs -mthumb -mcpu=cortex-
targetToolchain.linux_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-linux/arm-none-eabi
dependencies.linux_x64-zephyr_stm32f4_disco = \
libffi-3.2.1-2-linux-x86-64 \
clang-llvm-6.0.1-linux-x86-64 \
gcc-arm-none-eabi-7-2017-q4-major-linux \
target-sysroot-2-wasm
@@ -32,11 +33,13 @@ dependencies.linux_x64-zephyr_stm32f4_disco = \
targetToolchain.macos_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-mac/arm-none-eabi
dependencies.macos_x64-zephyr_stm32f4_disco = \
gcc-arm-none-eabi-7-2017-q4-major-mac \
libffi-3.2.1-3-darwin-macos \
clang-llvm-6.0.1-darwin-macos \
target-sysroot-2-wasm
targetToolchain.mingw_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-win32/arm-none-eabi
dependencies.mingw_x64-zephyr_stm32f4_disco = \
libffi-3.2.1-mingw-w64-x86-64 \
msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1 \
gcc-arm-none-eabi-7-2017-q4-major-win32 \
target-sysroot-2-wasm
-2
View File
@@ -23,8 +23,6 @@ targetList.each { targetName ->
if (!isWindows())
compilerArgs '-fPIC'
includeRuntime(delegate)
if (rootProject.hasProperty("${targetName}LibffiDir"))
compilerArgs '-I' + project.file(rootProject.ext.get("${targetName}LibffiDir") + "/include")
linkerArgs project.file("../common/build/$targetName/hash.bc").path
}
-74
View File
@@ -18,85 +18,11 @@
#include <stdio.h>
#include <stdint.h>
#ifndef KONAN_NO_FFI
#include <ffi.h>
#endif
#include "Memory.h"
#include "Types.h"
namespace {
typedef int FfiTypeKind;
#ifndef KONAN_NO_FFI
// Also declared in Varargs.kt
const FfiTypeKind FFI_TYPE_KIND_VOID = 0;
const FfiTypeKind FFI_TYPE_KIND_SINT8 = 1;
const FfiTypeKind FFI_TYPE_KIND_SINT16 = 2;
const FfiTypeKind FFI_TYPE_KIND_SINT32 = 3;
const FfiTypeKind FFI_TYPE_KIND_SINT64 = 4;
const FfiTypeKind FFI_TYPE_KIND_FLOAT = 5;
const FfiTypeKind FFI_TYPE_KIND_DOUBLE = 6;
const FfiTypeKind FFI_TYPE_KIND_POINTER = 7;
const FfiTypeKind FFI_TYPE_KIND_UINT8 = 8;
const FfiTypeKind FFI_TYPE_KIND_UINT16 = 9;
const FfiTypeKind FFI_TYPE_KIND_UINT32 = 10;
const FfiTypeKind FFI_TYPE_KIND_UINT64 = 11;
ffi_type* convertFfiTypeKindToType(FfiTypeKind typeKind) {
switch (typeKind) {
case FFI_TYPE_KIND_VOID: return &ffi_type_void;
case FFI_TYPE_KIND_SINT8: return &ffi_type_sint8;
case FFI_TYPE_KIND_SINT16: return &ffi_type_sint16;
case FFI_TYPE_KIND_SINT32: return &ffi_type_sint32;
case FFI_TYPE_KIND_SINT64: return &ffi_type_sint64;
case FFI_TYPE_KIND_FLOAT: return &ffi_type_float;
case FFI_TYPE_KIND_DOUBLE: return &ffi_type_double;
case FFI_TYPE_KIND_POINTER: return &ffi_type_pointer;
case FFI_TYPE_KIND_UINT8: return &ffi_type_uint8;
case FFI_TYPE_KIND_UINT16: return &ffi_type_uint16;
case FFI_TYPE_KIND_UINT32: return &ffi_type_uint32;
case FFI_TYPE_KIND_UINT64: return &ffi_type_uint64;
default: assert(false); return nullptr;
}
}
#endif // KONAN_NO_FFI
} // namespace
extern "C" {
void Kotlin_Interop_callFunctionPointer(void* codePtr, void* returnValuePtr, FfiTypeKind returnTypeKind,
void** arguments, intptr_t* argumentTypeKinds,
int totalArgumentsNumber, int variadicArgumentsNumber) {
#ifdef KONAN_NO_FFI
RuntimeAssert(false, "Vararg calls are not supported on this platform");
#else
ffi_type** argumentTypes = (ffi_type**)argumentTypeKinds;
// In-place convertion:
for (int i = 0; i < totalArgumentsNumber; ++i) {
argumentTypes[i] = convertFfiTypeKindToType((FfiTypeKind) argumentTypeKinds[i]);
}
ffi_type* returnType = convertFfiTypeKindToType(returnTypeKind);
ffi_cif cif;
if (variadicArgumentsNumber < 0) {
// Non-variadic.
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgumentsNumber, returnType, argumentTypes);
} else {
int fixedArgumentsNumber = totalArgumentsNumber - variadicArgumentsNumber;
ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI,
fixedArgumentsNumber, totalArgumentsNumber,
returnType, argumentTypes);
}
ffi_call(&cif, (void (*)())codePtr, returnValuePtr, arguments);
#endif
}
KNativePtr Kotlin_Interop_createStablePointer(KRef any) {
return CreateStablePointer(any);
}
@@ -69,7 +69,10 @@ open class Command(initialCommand: List<String>) {
/**
* If withErrors is true then output from error stream will be added
*/
fun getOutputLines(withErrors: Boolean = false): List<String> {
fun getOutputLines(withErrors: Boolean = false): List<String> =
getResult(withErrors, handleError = true).outputLines
fun getResult(withErrors: Boolean, handleError: Boolean = false): Result {
log()
val outputFile = createTempFile()
@@ -80,7 +83,7 @@ open class Command(initialCommand: List<String>) {
builder.redirectInput(Redirect.INHERIT)
builder.redirectError(Redirect.INHERIT)
builder.redirectOutput(ProcessBuilder.Redirect.to(outputFile))
builder.redirectOutput(Redirect.to(outputFile))
.redirectErrorStream(withErrors)
// Note: getting process output could be done without redirecting to temporary file,
// however this would require managing a thread to read `process.inputStream` because
@@ -88,14 +91,16 @@ open class Command(initialCommand: List<String>) {
val process = builder.start()
val code = process.waitFor()
handleExitCode(code)
if (handleError) handleExitCode(code)
return outputFile.readLines()
return Result(code, outputFile.readLines())
} finally {
outputFile.delete()
}
}
class Result(val exitCode: Int, val outputLines: List<String>)
private fun handleExitCode(code: Int) {
if (code != 0) throw KonanExternalToolFailure("The ${command[0]} command returned non-zero exit code: $code.", command[0])
}
@@ -24,6 +24,7 @@ interface Configurables : TargetableExternalStorage {
val llvmHome get() = hostString("llvmHome")
val llvmVersion get() = hostString("llvmVersion")
val libffiDir get() = hostString("libffiDir")
// TODO: Delegate to a map?
val llvmLtoNooptFlags get() = targetList("llvmLtoNooptFlags")
@@ -37,7 +38,6 @@ interface Configurables : TargetableExternalStorage {
val linkerDynamicFlags get() = targetList("linkerDynamicFlags")
val llvmDebugOptFlags get() = targetList("llvmDebugOptFlags")
val targetSysRoot get() = targetString("targetSysRoot")
val libffiDir get() = targetString("libffiDir")
// Notice: these ones are host-target.
val targetToolchain get() = hostTargetString("targetToolchain")
@@ -45,7 +45,6 @@ interface Configurables : TargetableExternalStorage {
val absoluteTargetSysRoot get() = absolute(targetSysRoot)
val absoluteTargetToolchain get() = absolute(targetToolchain)
val absoluteLlvmHome get() = absolute(llvmHome)
val absoluteLibffiDir get() = absolute(libffiDir)
}
interface NonAppleConfigurables : Configurables {
@@ -44,9 +44,6 @@ abstract class LinkerFlags(val configurables: Configurables)
else -> error("Don't know libLTO location for this platform.")
}
val targetLibffi = configurables.libffiDir?.let { listOf("${configurables.absoluteLibffiDir}/lib/libffi.a") }
?: emptyList()
open val useCompilerDriverAsLinker: Boolean get() = false // TODO: refactor.
abstract fun linkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,