Implement very basic Objective-C interop

Also refactor StubGenerator
This commit is contained in:
Svyatoslav Scherbina
2017-07-17 14:34:09 +03:00
committed by SvyatoslavScherbina
parent a38835cb46
commit d3185e3e19
57 changed files with 4020 additions and 920 deletions
@@ -25,19 +25,47 @@ private class StructDeclImpl(spelling: String) : StructDecl(spelling) {
override var def: StructDefImpl? = null
}
private class StructDefImpl(size: Long, align: Int, decl: StructDecl, hasNaturalLayout: Boolean) :
StructDef(size, align, decl, hasNaturalLayout) {
private class StructDefImpl(
size: Long, align: Int, decl: StructDecl,
hasNaturalLayout: Boolean, hasUnalignedFields: Boolean
) : StructDef(
size, align, decl,
hasNaturalLayout = hasNaturalLayout, hasUnalignedFields = hasUnalignedFields
) {
override val fields = mutableListOf<Field>()
}
private class EnumDefImpl(spelling: String, type: PrimitiveType) : EnumDef(spelling, type) {
private class EnumDefImpl(spelling: String, type: Type) : EnumDef(spelling, type) {
override val constants = mutableListOf<EnumConstant>()
}
private interface ObjCClassOrProtocolImpl {
val protocols: MutableList<ObjCProtocol>
val methods: MutableList<ObjCMethod>
val properties: MutableList<ObjCProperty>
}
private class ObjCProtocolImpl(name: String) : ObjCProtocol(name), ObjCClassOrProtocolImpl {
override val protocols = mutableListOf<ObjCProtocol>()
override val methods = mutableListOf<ObjCMethod>()
override val properties = mutableListOf<ObjCProperty>()
}
private class ObjCClassImpl(name: String) : ObjCClass(name), ObjCClassOrProtocolImpl {
override val protocols = mutableListOf<ObjCProtocol>()
override val methods = mutableListOf<ObjCMethod>()
override val properties = mutableListOf<ObjCProperty>()
override var baseClass: ObjCClass? = null
}
internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
private data class DeclarationID(val usr: String)
private sealed class DeclarationID {
data class USR(val usr: String) : DeclarationID()
object VaListTag : DeclarationID()
object BuiltinVaList : DeclarationID()
}
private val structById = mutableMapOf<DeclarationID, StructDeclImpl>()
@@ -49,21 +77,39 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
override val enums: List<EnumDef>
get() = enumById.values.toList()
private val objCClassesByName = mutableMapOf<String, ObjCClassImpl>()
override val objCClasses: List<ObjCClass> get() = objCClassesByName.values.toList()
private val objCProtocolsByName = mutableMapOf<String, ObjCProtocolImpl>()
override val objCProtocols: List<ObjCProtocol> get() = objCProtocolsByName.values.toList()
private val typedefById = mutableMapOf<DeclarationID, TypedefDef>()
override val typedefs: List<TypedefDef>
get() = typedefById.values.toList()
val functionByName = mutableMapOf<String, FunctionDecl>()
private val functionById = mutableMapOf<DeclarationID, FunctionDecl>()
override val functions: List<FunctionDecl>
get() = functionByName.values.toList()
get() = functionById.values.toList()
override val macroConstants = mutableListOf<ConstantDef>()
private fun getDeclarationId(cursor: CValue<CXCursor>): DeclarationID {
val usr = clang_getCursorUSR(cursor).convertAndDispose()
return DeclarationID(usr)
if (usr == "") {
val kind = cursor.kind
val spelling = getCursorSpelling(cursor)
return when (kind to spelling) {
CXCursorKind.CXCursor_StructDecl to "__va_list_tag" -> DeclarationID.VaListTag
CXCursorKind.CXCursor_TypedefDecl to "__builtin_va_list" -> DeclarationID.BuiltinVaList
else -> error(spelling)
}
}
return DeclarationID.USR(usr)
}
private fun getStructDeclAt(cursor: CValue<CXCursor>): StructDeclImpl {
@@ -91,8 +137,13 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
val type = clang_getCursorType(cursor)
val size = clang_Type_getSizeOf(type)
val align = clang_Type_getAlignOf(type).toInt()
val hasNaturalLayout = structHasNaturalLayout(cursor)
val structDef = StructDefImpl(size, align, structDecl, hasNaturalLayout)
val structDef = StructDefImpl(
size, align, structDecl,
hasNaturalLayout = structHasNaturalLayout(cursor),
hasUnalignedFields = structHasUnalignedFields(cursor)
)
structDecl.def = structDef
visitChildren(cursor) { childCursor: CValue<CXCursor>, _: CValue<CXCursor> ->
@@ -117,7 +168,7 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
val cursorType = clang_getCursorType(cursor)
val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose()
val baseType = convertType(clang_getEnumDeclIntegerType(cursor)) as PrimitiveType
val baseType = convertType(clang_getEnumDeclIntegerType(cursor))
val enumDef = EnumDefImpl(typeSpelling, baseType)
@@ -137,11 +188,112 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
}
}
private fun getObjCCategoryClassCursor(cursor: CValue<CXCursor>): CValue<CXCursor> {
assert(cursor.kind == CXCursorKind.CXCursor_ObjCCategoryDecl)
var classRef: CValue<CXCursor>? = null
visitChildren(cursor) { child, _ ->
if (child.kind == CXCursorKind.CXCursor_ObjCClassRef) {
classRef = child
CXChildVisitResult.CXChildVisit_Break
} else {
CXChildVisitResult.CXChildVisit_Continue
}
}
return clang_getCursorReferenced(classRef!!).apply {
assert(this.kind == CXCursorKind.CXCursor_ObjCInterfaceDecl)
}
}
private fun getObjCClassAt(cursor: CValue<CXCursor>): ObjCClassImpl {
assert(cursor.kind == CXCursorKind.CXCursor_ObjCInterfaceDecl) { cursor.kind }
val name = clang_getCursorDisplayName(cursor).convertAndDispose()
objCClassesByName[name]?.let { return it }
val result = ObjCClassImpl(name)
objCClassesByName[name] = result
addChildrenToClassOrProtocol(cursor, result)
return result
}
private fun getObjCProtocolAt(cursor: CValue<CXCursor>): ObjCProtocolImpl? {
assert(cursor.kind == CXCursorKind.CXCursor_ObjCProtocolDecl) { cursor.kind }
if (clang_isCursorDefinition(cursor) == 0) {
val definition = clang_getCursorDefinition(cursor)
if (clang_isCursorDefinition(cursor) == 0) return null
return getObjCProtocolAt(definition)
}
val name = clang_getCursorDisplayName(cursor).convertAndDispose()
objCProtocolsByName[name]?.let { return it }
val result = ObjCProtocolImpl(name)
objCProtocolsByName[name] = result
addChildrenToClassOrProtocol(cursor, result)
return result
}
private fun addChildrenToClassOrProtocol(cursor: CValue<CXCursor>, result: ObjCClassOrProtocolImpl) {
visitChildren(cursor) { child, _ ->
when (child.kind) {
CXCursorKind.CXCursor_ObjCSuperClassRef -> {
assert(cursor.kind == CXCursorKind.CXCursor_ObjCInterfaceDecl)
result as ObjCClassImpl
assert(result.baseClass == null)
result.baseClass = getObjCClassAt(clang_getCursorReferenced(child))
}
CXCursorKind.CXCursor_ObjCProtocolRef -> {
getObjCProtocolAt(clang_getCursorReferenced(child))?.let {
if (it !in result.protocols) {
result.protocols.add(it)
}
}
}
CXCursorKind.CXCursor_ObjCClassMethodDecl, CXCursorKind.CXCursor_ObjCInstanceMethodDecl -> {
getObjCMethod(child)?.let { method ->
result.methods.removeAll { method.replaces(it) }
result.methods.add(method)
}
}
else -> {}
}
CXChildVisitResult.CXChildVisit_Continue
}
}
fun getTypedef(type: CValue<CXType>): Type {
val declCursor = clang_getTypeDeclaration(type)
val name = getCursorSpelling(declCursor)
val underlying = convertType(clang_getTypedefDeclUnderlyingType(declCursor))
if (clang_getCursorLexicalParent(declCursor).kind != CXCursorKind.CXCursor_TranslationUnit) {
// Objective-C type parameters are represented as non-top-level typedefs.
// Erase for now:
return underlying
}
if (library.language == Language.OBJECTIVE_C) {
if (name == "BOOL" || name == "Boolean") {
assert(clang_Type_getSizeOf(type) == 1L)
return BoolType
}
if (underlying is ObjCPointer && (name == "Class" || name == "id") ||
underlying is PointerType && name == "SEL") {
// Ignore implicit Objective-C typedefs:
return underlying
}
}
if ((underlying is RecordType && underlying.decl.spelling.split(' ').last() == name) ||
(underlying is EnumType && underlying.def.spelling.split(' ').last() == name)) {
@@ -189,10 +341,36 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
}
}
private fun convertCursorType(cursor: CValue<CXCursor>) =
convertType(clang_getCursorType(cursor))
/**
* Computes [StructDef.hasUnalignedFields] property.
*/
fun structHasUnalignedFields(structDefCursor: CValue<CXCursor>): Boolean {
var hasUnalignedFields = false
visitChildren(structDefCursor) { child, _ ->
if (clang_getCursorKind(child) == CXCursorKind.CXCursor_FieldDecl &&
clang_Cursor_isBitField(child) == 0 &&
clang_Cursor_getOffsetOfField(child) %
(clang_Type_getAlignOf(clang_getCursorType(child)) * 8) != 0L) {
fun convertType(type: CValue<CXType>): Type {
hasUnalignedFields = true
CXChildVisitResult.CXChildVisit_Break
} else {
CXChildVisitResult.CXChildVisit_Continue
}
}
return hasUnalignedFields
}
private fun convertCursorType(cursor: CValue<CXCursor>) =
convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor))
private inline fun objCType(supplier: () -> ObjCPointer) = when (library.language) {
Language.C -> UnsupportedType
Language.OBJECTIVE_C -> supplier()
}
fun convertType(type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>? = null): Type {
val primitiveType = convertUnqualifiedPrimitiveType(type)
if (primitiveType != UnsupportedType) {
return primitiveType
@@ -217,7 +395,17 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
CXType_Void -> VoidType
CXType_Typedef -> getTypedef(type)
CXType_Typedef -> {
val declCursor = clang_getTypeDeclaration(type)
val declSpelling = getCursorSpelling(declCursor)
val underlying = convertType(clang_getTypedefDeclUnderlyingType(declCursor))
when {
declSpelling == "instancetype" && underlying is ObjCPointer ->
ObjCInstanceType(getNullability(type, typeAttributes))
else -> getTypedef(type)
}
}
CXType_Record -> RecordType(getStructDeclAt(clang_getTypeDeclaration(type)))
CXType_Enum -> EnumType(getEnumDefAt(clang_getTypeDeclaration(type)))
@@ -245,16 +433,58 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
convertFunctionType(type)
}
CXType_ObjCObjectPointer -> objCType {
val declaration = clang_getTypeDeclaration(clang_getPointeeType(type))
val declarationKind = declaration.kind
val nullability = getNullability(type, typeAttributes)
when (declarationKind) {
CXCursorKind.CXCursor_NoDeclFound -> ObjCIdType(nullability, getProtocols(type))
CXCursorKind.CXCursor_ObjCInterfaceDecl ->
ObjCObjectPointer(getObjCClassAt(declaration), nullability, getProtocols(type))
else -> TODO(declarationKind.toString())
}
}
CXType_ObjCId -> objCType { ObjCIdType(getNullability(type, typeAttributes), getProtocols(type)) }
CXType_ObjCClass -> objCType { ObjCClassPointer(getNullability(type, typeAttributes), getProtocols(type)) }
CXType_ObjCSel -> PointerType(VoidType)
CXType_BlockPointer -> objCType { ObjCIdType(getNullability(type, typeAttributes), getProtocols(type)) }
else -> UnsupportedType
}
}
private fun getNullability(
type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>?
): ObjCPointer.Nullability {
if (typeAttributes == null) return ObjCPointer.Nullability.Unspecified
return when (clang_Type_getNullabilityKind(type, typeAttributes)) {
CXNullabilityKind.CXNullabilityKind_Nullable -> ObjCPointer.Nullability.Nullable
CXNullabilityKind.CXNullabilityKind_NonNull -> ObjCPointer.Nullability.NonNull
CXNullabilityKind.CXNullabilityKind_Unspecified -> ObjCPointer.Nullability.Unspecified
}
}
private fun getProtocols(type: CValue<CXType>): List<ObjCProtocol> {
val num = clang_Type_getNumProtocols(type)
return (0 until num).mapNotNull { index ->
getObjCProtocolAt(clang_Type_getProtocol(type, index))
}
}
private fun convertFunctionType(type: CValue<CXType>): Type {
val kind = type.kind
assert (kind == CXType_Unexposed || kind == CXType_FunctionProto)
return if (clang_isFunctionTypeVariadic(type) != 0) {
UnsupportedType
VoidType // make this function pointer opaque.
} else {
val returnType = convertType(clang_getResultType(type))
val numArgs = clang_getNumArgTypes(type)
@@ -286,38 +516,160 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
}
CXIdxEntity_Function -> {
val name = entityName!!
val returnType = convertType(clang_getCursorResultType(cursor))
val argNum = clang_Cursor_getNumArguments(cursor)
val args = (0 .. argNum - 1).map {
val argCursor = clang_Cursor_getArgument(cursor, it)
val argName = getCursorSpelling(argCursor)
val type = convertCursorType(argCursor)
Parameter(argName, type)
if (isAvailable(cursor)) {
functionById[getDeclarationId(cursor)] = getFunction(cursor)
}
val binaryName = when (library.language) {
Language.C -> clang_Cursor_getMangling(cursor).convertAndDispose()
}
val definitionCursor = clang_getCursorDefinition(cursor)
val isDefined = (clang_Cursor_isNull(definitionCursor) == 0)
val isVararg = clang_Cursor_isVariadic(cursor) != 0
functionByName[name] = FunctionDecl(name, args, returnType, binaryName, isDefined, isVararg)
}
CXIdxEntity_Enum -> {
getEnumDefAt(cursor)
}
CXIdxEntity_ObjCClass -> {
if (isAvailable(cursor)) {
getObjCClassAt(clang_getCursorReferenced(cursor))
}
}
CXIdxEntity_ObjCCategory -> {
val classCursor = getObjCCategoryClassCursor(cursor)
if (isAvailable(classCursor)) {
val objCClass = getObjCClassAt(classCursor)
addChildrenToClassOrProtocol(cursor, objCClass)
}
}
CXIdxEntity_ObjCProtocol -> {
if (isAvailable(cursor)) {
getObjCProtocolAt(cursor)
}
}
CXIdxEntity_ObjCProperty -> {
val container = clang_getCursorSemanticParent(cursor)
if (isAvailable(cursor) && isAvailable(container)) {
val propertyInfo = clang_index_getObjCPropertyDeclInfo(info.ptr)!!.pointed
val getter = getObjCMethod(propertyInfo.getter!!.pointed.cursor.readValue())
val setter = propertyInfo.setter?.let {
getObjCMethod(it.pointed.cursor.readValue())
}
if (getter != null) {
val property = ObjCProperty(entityName!!, getter, setter)
val classOrProtocol: ObjCClassOrProtocolImpl? = when (container.kind) {
CXCursorKind.CXCursor_ObjCCategoryDecl -> {
val classCursor = getObjCCategoryClassCursor(container)
if (isAvailable(classCursor)) {
getObjCClassAt(classCursor)
} else {
null
}
}
CXCursorKind.CXCursor_ObjCInterfaceDecl -> getObjCClassAt(container)
CXCursorKind.CXCursor_ObjCProtocolDecl -> getObjCProtocolAt(container)!!
else -> error(container.kind)
}
if (classOrProtocol != null) {
classOrProtocol.properties.removeAll { property.replaces(it) }
classOrProtocol.properties.add(property)
}
}
}
}
else -> {
// Ignore declaration.
}
}
}
private fun getFunction(cursor: CValue<CXCursor>): FunctionDecl {
val name = clang_getCursorSpelling(cursor).convertAndDispose()
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
val parameters = getFunctionParameters(cursor)
val binaryName = when (library.language) {
Language.C, Language.OBJECTIVE_C -> clang_Cursor_getMangling(cursor).convertAndDispose()
}
val definitionCursor = clang_getCursorDefinition(cursor)
val isDefined = (clang_Cursor_isNull(definitionCursor) == 0)
val isVararg = clang_Cursor_isVariadic(cursor) != 0
return FunctionDecl(name, parameters, returnType, binaryName, isDefined, isVararg)
}
private fun getObjCMethod(cursor: CValue<CXCursor>): ObjCMethod? {
if (!isAvailable(cursor)) {
return null
}
val selector = clang_getCursorDisplayName(cursor).convertAndDispose()
// Ignore some very special methods:
when (selector) {
"dealloc", "retain", "release", "autorelease", "retainCount", "self" -> return null
}
val encoding = clang_getDeclObjCTypeEncoding(cursor).convertAndDispose()
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
val parameters = getFunctionParameters(cursor)
val isClass = when (cursor.kind) {
CXCursorKind.CXCursor_ObjCClassMethodDecl -> true
CXCursorKind.CXCursor_ObjCInstanceMethodDecl -> false
else -> error(cursor.kind)
}
return ObjCMethod(selector, encoding, parameters, returnType,
isClass = isClass,
nsConsumesSelf = hasAttribute(cursor, NS_CONSUMES_SELF),
nsReturnsRetained = hasAttribute(cursor, NS_RETURNS_RETAINED),
isOptional = (clang_Cursor_isObjCOptional(cursor) != 0),
isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0))
}
// TODO: unavailable declarations should be imported as deprecated.
private fun isAvailable(cursor: CValue<CXCursor>): Boolean = when (clang_getCursorAvailability(cursor)) {
CXAvailabilityKind.CXAvailability_Available,
CXAvailabilityKind.CXAvailability_Deprecated -> true
CXAvailabilityKind.CXAvailability_NotAvailable,
CXAvailabilityKind.CXAvailability_NotAccessible -> false
}
private fun getFunctionParameters(cursor: CValue<CXCursor>): List<Parameter> {
val argNum = clang_Cursor_getNumArguments(cursor)
val args = (0..argNum - 1).map {
val argCursor = clang_Cursor_getArgument(cursor, it)
val argName = getCursorSpelling(argCursor)
val type = convertCursorType(argCursor)
Parameter(argName, type,
nsConsumed = hasAttribute(argCursor, NS_CONSUMED))
}
return args
}
private val NS_CONSUMED = "ns_consumed"
private val NS_CONSUMES_SELF = "ns_consumes_self"
private val NS_RETURNS_RETAINED = "ns_returns_retained"
private fun hasAttribute(cursor: CValue<CXCursor>, name: String): Boolean {
var result = false
visitChildren(cursor) { child, _ ->
if (clang_isAttribute(child.kind) != 0 && clang_Cursor_getAttributeSpelling(child)?.toKString() == name) {
result = true
CXChildVisitResult.CXChildVisit_Break
} else {
CXChildVisitResult.CXChildVisit_Continue
}
}
return result
}
}
fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex {
@@ -27,7 +27,7 @@ internal fun findMacroConstants(library: NativeLibrary, nativeIndex: NativeIndex
val names = collectMacroConstantsNames(library)
// TODO: apply user-defined filters.
val constants = expandMacroConstants(library, names, typeConverter = nativeIndex::convertType)
val constants = expandMacroConstants(library, names, typeConverter = { nativeIndex.convertType(it) })
nativeIndex.macroConstants.addAll(constants)
}
@@ -109,7 +109,7 @@ private fun reparseWithCodeSnippet(library: NativeLibrary,
// so the code pattern should force the constant evaluation that corresponds to language rules.
val codeSnippetLines = when (library.language) {
// Note: __auto_type is a GNU extension which is supported by clang.
Language.C -> listOf(
Language.C, Language.OBJECTIVE_C -> listOf(
"const __auto_type KNI_INDEXER_VARIABLE = $name;",
// Clang evaluate API doesn't provide a way to get a `long long` value yet;
// so extract such values from the enum declaration:
@@ -16,8 +16,9 @@
package org.jetbrains.kotlin.native.interop.indexer
enum class Language {
C
enum class Language(val sourceFileExtension: String) {
C("c"),
OBJECTIVE_C("m")
}
data class NativeLibrary(val includes: List<String>,
@@ -39,6 +40,8 @@ fun buildNativeIndex(library: NativeLibrary): NativeIndex = buildNativeIndexImpl
abstract class NativeIndex {
abstract val structs: List<StructDecl>
abstract val enums: List<EnumDef>
abstract val objCClasses: List<ObjCClass>
abstract val objCProtocols: List<ObjCProtocol>
abstract val typedefs: List<TypedefDef>
abstract val functions: List<FunctionDecl>
abstract val macroConstants: List<ConstantDef>
@@ -65,7 +68,8 @@ abstract class StructDecl(val spelling: String) {
*/
abstract class StructDef(val size: Long, val align: Int,
val decl: StructDecl,
val hasNaturalLayout: Boolean) {
val hasNaturalLayout: Boolean,
val hasUnalignedFields: Boolean) {
abstract val fields: List<Field>
}
@@ -78,15 +82,48 @@ class EnumConstant(val name: String, val value: Long, val isExplicitlyDefined: B
/**
* C enum definition.
*/
abstract class EnumDef(val spelling: String, val baseType: PrimitiveType) {
abstract class EnumDef(val spelling: String, val baseType: Type) {
abstract val constants: List<EnumConstant>
}
sealed class ObjCClassOrProtocol(val name: String) {
abstract val protocols: List<ObjCProtocol>
abstract val methods: List<ObjCMethod>
abstract val properties: List<ObjCProperty>
}
data class ObjCMethod(
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
val isOptional: Boolean, val isInit: Boolean
) {
fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType
fun getReturnType(container: ObjCClassOrProtocol): Type = if (returnType is ObjCInstanceType) {
when (container) {
is ObjCClass -> ObjCObjectPointer(container, returnType.nullability, protocols = emptyList())
is ObjCProtocol -> ObjCIdType(returnType.nullability, protocols = listOf(container))
}
} else {
returnType
}
}
data class ObjCProperty(val name: String, val getter: ObjCMethod, val setter: ObjCMethod?) {
fun getType(container: ObjCClassOrProtocol): Type = getter.getReturnType(container)
}
abstract class ObjCClass(name: String) : ObjCClassOrProtocol(name) {
abstract val baseClass: ObjCClass?
}
abstract class ObjCProtocol(name: String) : ObjCClassOrProtocol(name)
/**
* C function parameter.
*/
class Parameter(val name: String?, val type: Type)
class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
/**
* C function declaration.
@@ -117,6 +154,8 @@ interface PrimitiveType : Type
object CharType : PrimitiveType
object BoolType : PrimitiveType
data class IntegerType(val size: Int, val isSigned: Boolean, val spelling: String) : PrimitiveType
// TODO: floating type is not actually defined entirely by its size.
@@ -142,4 +181,34 @@ data class IncompleteArrayType(override val elemType: Type) : ArrayType
data class Typedef(val def: TypedefDef) : Type
sealed class ObjCPointer : Type {
enum class Nullability {
Nullable, NonNull, Unspecified
}
abstract val nullability: Nullability
}
sealed class ObjCQualifiedPointer : ObjCPointer() {
abstract val protocols: List<ObjCProtocol>
}
data class ObjCObjectPointer(
val def: ObjCClass,
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCClassPointer(
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCIdType(
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCInstanceType(override val nullability: Nullability) : ObjCPointer()
object UnsupportedType : Type
@@ -176,10 +176,7 @@ internal fun Appendable.appendPreamble(library: NativeLibrary) = this.apply {
* Creates temporary source file which includes the library.
*/
internal fun NativeLibrary.createTempSource(): File {
val suffix = when (language) {
Language.C -> ".c"
}
val result = createTempFile(suffix = suffix)
val result = createTempFile(suffix = ".${language.sourceFileExtension}")
result.deleteOnExit()
result.bufferedWriter().use { writer ->
@@ -478,4 +475,10 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat
result.add(mainFile!!)
return result
}
}
fun ObjCMethod.replaces(other: ObjCMethod): Boolean =
this.isClass == other.isClass && this.selector == other.selector
fun ObjCProperty.replaces(other: ObjCProperty): Boolean =
this.getter.replaces(other.getter)
@@ -379,22 +379,7 @@ private class Struct(val size: Long, val align: Int, elementTypes: List<CType<*>
) {
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).readValue<CStructVar>(size, align)
override fun write(location: NativePtr, value: CValue<*>) {
// TODO: probably CValue must be redesigned.
val fakePlacement = object : NativePlacement {
var used = false
override fun alloc(size: Long, align: Int): NativePointed {
assert(!used)
assert (size == this@Struct.size)
assert (align == this@Struct.align)
used = true
return interpretPointed<ByteVar>(location)
}
}
value.getPointer(fakePlacement)
assert (fakePlacement.used)
}
override fun write(location: NativePtr, value: CValue<*>) = value.write(location)
}
private class CEnumType(private val rawValueCType: CType<Number>) : CType<CEnum>(rawValueCType.ffiType) {
@@ -67,6 +67,25 @@ abstract class CValuesRef<T : CPointed> {
abstract fun getPointer(placement: NativePlacement): CPointer<T>
}
inline fun <T : CPointed, R> CValuesRef<T>?.usePointer(block: (CPointer<T>?) -> R): R {
val allocated: Boolean
val pointer: CPointer<T>? = if (this is CPointer<T>?) {
allocated = false
this
} else {
allocated = true
this!!.getPointer(nativeHeap)
}
return try {
block(pointer)
} finally {
if (allocated) {
nativeHeap.free(pointer.rawValue)
}
}
}
/**
* The (possibly empty) sequence of immutable C values.
* It is self-contained and doesn't depend on native memory.
@@ -241,6 +260,11 @@ abstract class CEnumVar : CPrimitiveVar()
// generics below are used for typedef support
// these classes are not supposed to be used directly, instead the typealiases are provided.
@Suppress("FINAL_UPPER_BOUND")
class BooleanVarOf<T : Boolean>(override val rawPtr: NativePtr) : CPrimitiveVar() {
companion object : Type(1)
}
@Suppress("FINAL_UPPER_BOUND")
class ByteVarOf<T : Byte>(override val rawPtr: NativePtr) : CPrimitiveVar() {
companion object : Type(1)
@@ -271,6 +295,7 @@ class DoubleVarOf<T : Double>(override val rawPtr: NativePtr) : CPrimitiveVar()
companion object : Type(8)
}
typealias BooleanVar = BooleanVarOf<Boolean>
typealias ByteVar = ByteVarOf<Byte>
typealias ShortVar = ShortVarOf<Short>
typealias IntVar = IntVarOf<Int>
@@ -278,6 +303,20 @@ typealias LongVar = LongVarOf<Long>
typealias FloatVar = FloatVarOf<Float>
typealias DoubleVar = DoubleVarOf<Double>
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
var <T : Boolean> BooleanVarOf<T>.value: T
get() {
val byte = nativeMemUtils.getByte(this)
return byte.toBoolean() as T
}
set(value) = nativeMemUtils.putByte(this, value.toByte())
@Suppress("NOTHING_TO_INLINE")
inline fun Boolean.toByte(): Byte = if (this) 1 else 0
@Suppress("NOTHING_TO_INLINE")
inline fun Byte.toBoolean() = (this - 0 != 0)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
var <T : Byte> ByteVarOf<T>.value: T
get() = nativeMemUtils.getByte(this) as T
@@ -218,6 +218,21 @@ fun <T : CVariable> CPointed.readValue(size: Long, align: Int): CValue<T> {
// TODO: find better name.
inline fun <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(sizeOf<T>(), alignOf<T>())
fun CValue<*>.write(location: NativePtr) {
// TODO: probably CValue must be redesigned.
val fakePlacement = object : NativePlacement {
var used = false
override fun alloc(size: Long, align: Int): NativePointed {
assert(!used)
used = true
return interpretPointed<ByteVar>(location)
}
}
this.getPointer(fakePlacement)
assert(fakePlacement.used)
}
// TODO: optimize
fun <T : CVariable> CValues<T>.getBytes(): ByteArray = memScoped {
val result = ByteArray(size)
@@ -0,0 +1,156 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE")
package kotlinx.cinterop
interface ObjCObject
interface ObjCClass : ObjCObject
typealias ObjCObjectMeta = ObjCClass
abstract class ObjCObjectBase protected constructor() : ObjCObject {
final override fun equals(other: Any?): Boolean = TODO()
final override fun hashCode(): Int = TODO()
final override fun toString(): String = TODO()
}
abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {}
external fun optional(): Nothing
/**
* The runtime representation of any [ObjCObject].
*/
@ExportTypeInfo("theObjCPointerHolderTypeInfo")
class ObjCPointerHolder(inline val rawPtr: NativePtr) {
init {
assert(rawPtr != nativeNullPtr)
objc_retain(rawPtr)
}
final override fun equals(other: Any?): Boolean = TODO()
final override fun hashCode(): Int = TODO()
final override fun toString(): String = TODO()
}
@konan.internal.Intrinsic
@konan.internal.ExportForCompiler
private external fun ObjCObject.initFromPtr(ptr: NativePtr)
@konan.internal.ExportForCompiler
private fun ObjCObject.initFrom(other: ObjCObject?) = this.initFromPtr(other!!.rawPtr)
fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) // TODO: make private
inline fun <T : ObjCObject?> interpretObjCPointerOrNull(rawPtr: NativePtr): T? = if (rawPtr != nativeNullPtr) {
ObjCPointerHolder(rawPtr).uncheckedCast<T>()
} else {
null
}
inline fun <T : ObjCObject?> interpretObjCPointer(rawPtr: NativePtr): T? = interpretObjCPointerOrNull<T>(rawPtr)!!
inline val ObjCObject.rawPtr: NativePtr get() = (this.uncheckedCast<ObjCPointerHolder>()).rawPtr
inline val ObjCObject?.rawPtr: NativePtr get() = if (this != null) {
(this.uncheckedCast<ObjCPointerHolder>()).rawPtr
} else {
nativeNullPtr
}
class ObjCObjectVar<T : ObjCObject?>(override val rawPtr: NativePtr) : CVariable {
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
class ObjCStringVarOf<T : String?>(override val rawPtr: NativePtr) : CVariable {
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
var <T : String?> ObjCStringVarOf<T>.value: T
get() = TODO()
set(value) = TODO()
@konan.internal.Intrinsic external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer?
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class ExternalObjCClass()
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCMethod(val selector: String, val bridge: String)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCBridge(val selector: String, val encoding: String, val imp: String)
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCConstructor(val initSelector: String)
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
annotation class InteropStubs()
@konan.internal.ExportForCompiler
private fun <T : ObjCObject> allocObjCObject(clazz: NativePtr): T {
val rawResult = objc_allocWithZone(clazz)
if (rawResult == nativeNullPtr) {
throw OutOfMemoryError("Unable to allocate Objective-C object")
}
val result = interpretObjCPointerOrNull<T>(rawResult)!!
// `objc_allocWithZone` returns retained pointer. Balance it:
objc_release(rawResult)
// TODO: do not retain this pointer in `interpretObjCPointerOrNull` instead.
return result
}
@konan.internal.Intrinsic
@konan.internal.ExportForCompiler
private external fun <T : ObjCObject> getObjCClass(): NativePtr
@konan.internal.Intrinsic external fun getMessenger(superClass: NativePtr): COpaquePointer?
@konan.internal.Intrinsic external fun getMessengerLU(superClass: NativePtr): COpaquePointer?
// Konan runtme:
@SymbolName("CreateNSStringFromKString")
external fun CreateNSStringFromKString(str: String?): NativePtr
@SymbolName("CreateKStringFromNSString")
external fun CreateKStringFromNSString(ptr: NativePtr): String?
// Objective-C runtime:
@SymbolName("objc_retainAutoreleaseReturnValue")
external fun objc_retainAutoreleaseReturnValue(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_objc_autoreleasePoolPush")
external fun objc_autoreleasePoolPush(): NativePtr
@SymbolName("Kotlin_objc_autoreleasePoolPop")
external fun objc_autoreleasePoolPop(ptr: NativePtr)
@SymbolName("Kotlin_objc_allocWithZone")
private external fun objc_allocWithZone(clazz: NativePtr): NativePtr
@SymbolName("Kotlin_objc_retain")
external fun objc_retain(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_objc_release")
external fun objc_release(ptr: NativePtr)
@@ -0,0 +1,31 @@
/*
* 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
inline fun <R> autoreleasepool(block: () -> R): R {
val pool = objc_autoreleasePoolPush()
return try {
block()
} finally {
objc_autoreleasePoolPop(pool)
}
}
// TODO: null checks
var <T : ObjCObject?> ObjCObjectVar<T>.value: T
get() = interpretObjCPointerOrNull<T>(nativeMemUtils.getNativePtr(this)).uncheckedCast<T>()
set(value) = nativeMemUtils.putNativePtr(this, value.rawPtr)
@@ -66,6 +66,11 @@ private tailrec fun convertArgument(
is CEnum -> convertArgument(argument.value, isVariadic, location, additionalPlacement)
is ObjCPointerHolder -> {
location.reinterpret<COpaquePointerVar>()[0] = interpretCPointer(argument.rawPtr)
FFI_TYPE_KIND_POINTER
}
else -> throw Error("unsupported argument: $argument")
}
@@ -0,0 +1,77 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
class NativeCodeBuilder {
val lines = mutableListOf<String>()
fun out(line: String): Unit {
lines.add(line)
}
}
inline fun buildNativeCodeLines(block: NativeCodeBuilder.() -> Unit): List<String> {
val builder = NativeCodeBuilder()
builder.block()
return builder.lines
}
class KotlinCodeBuilder {
private val lines = mutableListOf<String>()
private val freeStack = mutableListOf<String>()
private val nesting get() = freeStack.size
fun out(line: String) {
lines.add(" ".repeat(nesting) + line)
}
fun pushBlock(line: String, free: String = "") {
out(line)
freeStack.add(free)
}
private fun popBlocks() {
while (freeStack.isNotEmpty()) {
val free = freeStack.last()
freeStack.removeAt(freeStack.lastIndex)
out("} $free".trim())
}
}
fun build(): List<String> {
this.popBlocks()
val result = this.lines.toList()
this.lines.clear()
return result
}
}
inline fun buildKotlinCodeLines(block: KotlinCodeBuilder.() -> Unit): List<String> {
val builder = KotlinCodeBuilder()
builder.block()
return builder.build()
}
interface StubGenerationContext {
val nativeBridges: NativeBridges
fun addTopLevelDeclaration(lines: List<String>)
}
interface KotlinStub {
fun generate(context: StubGenerationContext): Sequence<String>
}
@@ -0,0 +1,77 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
val kotlinKeywords = setOf(
"as", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in",
"interface", "is", "null", "object", "package", "return", "super", "this", "throw",
"true", "try", "typealias", "val", "var", "when", "while"
)
/**
* The expression written in native language.
*/
typealias NativeExpression = String
/**
* The expression written in Kotlin.
*/
typealias KotlinExpression = String
/**
* For this identifier constructs the string to be parsed by Kotlin as `SimpleName`
* defined [here](https://kotlinlang.org/docs/reference/grammar.html#SimpleName).
*/
fun String.asSimpleName(): String = if (this in kotlinKeywords) {
"`$this`"
} else {
this
}
/**
* Returns the expression to be parsed by Kotlin as string literal with given contents,
* i.e. transforms `foo$bar` to `"foo\$bar"`.
*/
fun String.quoteAsKotlinLiteral(): KotlinExpression {
val sb = StringBuilder()
sb.append('"')
this.forEach { c ->
val escaped = when (c) {
in 'a' .. 'z', in 'A' .. 'Z', in '0' .. '9', '_', '@', ':', '{', '}', '=', '[', ']', '^', '#', '*' -> c.toString()
'$' -> "\\$"
else -> "\\u" + "%04X".format(c.toInt()) // TODO: improve result readability by preserving more characters.
}
sb.append(escaped)
}
sb.append('"')
return sb.toString()
}
fun block(header: String, lines: Iterable<String>) = block(header, lines.asSequence())
fun block(header: String, lines: Sequence<String>) =
sequenceOf("$header {") +
lines.map { " $it" } +
sequenceOf("}")
val annotationForUnableToImport
get() = "@Deprecated(${"Unable to import this declaration".quoteAsKotlinLiteral()}, level = DeprecationLevel.ERROR)"
fun String.applyToStrings(vararg arguments: String) =
"${this}(${arguments.joinToString { it.quoteAsKotlinLiteral() }})"
@@ -0,0 +1,45 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.Type
data class TypedKotlinValue(val type: Type, val value: KotlinExpression)
data class TypedNativeValue(val type: Type, val value: NativeExpression)
/**
* Generates bridges between Kotlin and native, passing arbitrary native-typed values.
*
* It does the same as [SimpleBridgeGenerator] except that it supports any native types, e.g. struct values.
*/
interface MappingBridgeGenerator {
fun kotlinToNative(
builder: KotlinCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
kotlinValues: List<TypedKotlinValue>,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression
fun nativeToKotlin(
builder: NativeCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
nativeValues: List<TypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression
}
@@ -0,0 +1,196 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.RecordType
import org.jetbrains.kotlin.native.interop.indexer.Type
import org.jetbrains.kotlin.native.interop.indexer.VoidType
/**
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
* maps the type using [mirror].
*/
class MappingBridgeGeneratorImpl(
val declarationMapper: DeclarationMapper,
val simpleBridgeGenerator: SimpleBridgeGenerator
) : MappingBridgeGenerator {
override fun kotlinToNative(
builder: KotlinCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
kotlinValues: List<TypedKotlinValue>,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression {
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
kotlinValues.forEachIndexed { index, (type, value) ->
if (type.unwrapTypedefs() is RecordType) {
val tmpVarName = "kni$index"
builder.pushBlock("$value.usePointer { $tmpVarName ->")
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawValue"))
} else {
val info = mirror(declarationMapper, type).info
bridgeArguments.add(BridgeTypedKotlinValue(info.bridgedType, info.argToBridged(value)))
}
}
val unwrappedReturnType = returnType.unwrapTypedefs()
val kniRetVal = "kniRetVal"
val bridgeReturnType = when (unwrappedReturnType) {
VoidType -> BridgedType.VOID
is RecordType -> {
val mirror = mirror(declarationMapper, returnType)
val tmpVarName = kniRetVal
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedTypeName}>()")
builder.pushBlock("try {", free = "finally { nativeHeap.free($tmpVarName) }")
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawPtr"))
BridgedType.VOID
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.bridgedType
}
}
val callExpr = simpleBridgeGenerator.kotlinToNative(
nativeBacked, bridgeReturnType, bridgeArguments
) { bridgeNativeValues ->
val nativeValues = mutableListOf<String>()
kotlinValues.forEachIndexed { index, (type, _) ->
val unwrappedType = type.unwrapTypedefs()
if (unwrappedType is RecordType) {
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
} else {
nativeValues.add(mirror(declarationMapper, type).info.cFromBridged(bridgeNativeValues[index]))
}
}
val nativeResult = block(nativeValues)
when (unwrappedReturnType) {
is VoidType -> {
out(nativeResult + ";")
""
}
is RecordType -> {
out("*(${unwrappedReturnType.decl.spelling}*)${bridgeNativeValues.last()} = $nativeResult;")
""
}
else -> {
nativeResult
}
}
}
val result = when (unwrappedReturnType) {
is VoidType -> callExpr
is RecordType -> {
builder.out(callExpr)
"$kniRetVal.readValue()"
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.argFromBridged(callExpr)
}
}
return result
}
override fun nativeToKotlin(
builder: NativeCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
nativeValues: List<TypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression {
val bridgeArguments = mutableListOf<BridgeTypedNativeValue>()
nativeValues.forEachIndexed { index, (type, value) ->
val bridgeArgument = if (type.unwrapTypedefs() is RecordType) {
BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$value")
} else {
val info = mirror(declarationMapper, type).info
BridgeTypedNativeValue(info.bridgedType, value)
}
bridgeArguments.add(bridgeArgument)
}
val unwrappedReturnType = returnType.unwrapTypedefs()
val kniRetVal = "kniRetVal"
val bridgeReturnType = when (unwrappedReturnType) {
VoidType -> BridgedType.VOID
is RecordType -> {
val tmpVarName = kniRetVal
builder.out("${unwrappedReturnType.decl.spelling} $tmpVarName;")
bridgeArguments.add(BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$tmpVarName"))
BridgedType.VOID
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.bridgedType
}
}
val callExpr = simpleBridgeGenerator.nativeToKotlin(
nativeBacked,
bridgeReturnType,
bridgeArguments
) { bridgeKotlinValues ->
val kotlinValues = mutableListOf<String>()
nativeValues.forEachIndexed { index, (type, _) ->
val mirror = mirror(declarationMapper, type)
if (type.unwrapTypedefs() is RecordType) {
kotlinValues.add(
"interpretPointed<${mirror.pointedTypeName}>(${bridgeKotlinValues[index]}).readValue()"
)
} else {
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index]))
}
}
val kotlinResult = block(kotlinValues)
when (unwrappedReturnType) {
is RecordType -> {
"$kotlinResult.write(${bridgeKotlinValues.last()})"
}
is VoidType -> {
kotlinResult
}
else -> {
mirror(declarationMapper, returnType).info.argToBridged(kotlinResult)
}
}
}
val result = when (unwrappedReturnType) {
is VoidType -> callExpr
is RecordType -> {
builder.out("$callExpr;")
kniRetVal
}
else -> {
mirror(declarationMapper, returnType).info.cFromBridged(callExpr)
}
}
return result
}
}
@@ -0,0 +1,334 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
interface DeclarationMapper {
fun getKotlinNameForPointed(structDecl: StructDecl): String
fun isMappedToStrict(enumDef: EnumDef): Boolean
fun getKotlinNameForValue(enumDef: EnumDef): String
}
val PrimitiveType.kotlinType: String
get() = when (this) {
is CharType -> "Byte"
is BoolType -> "Boolean"
// TODO: C primitive types should probably be generated as type aliases for Kotlin types.
is IntegerType -> when (this.size) {
1 -> "Byte"
2 -> "Short"
4 -> "Int"
8 -> "Long"
else -> TODO(this.toString())
}
is FloatingType -> when (this.size) {
4 -> "Float"
8 -> "Double"
else -> TODO(this.toString())
}
else -> throw NotImplementedError()
}
private val PrimitiveType.bridgedType: BridgedType
get() {
val kotlinType = this.kotlinType
return BridgedType.values().single {
it.kotlinType == kotlinType
}
}
private val ObjCPointer.isNullable: Boolean
get() = this.nullability != ObjCPointer.Nullability.NonNull
/**
* Describes the Kotlin types used to represent some C type.
*/
sealed class TypeMirror(val pointedTypeName: String, val info: TypeInfo) {
/**
* Type to be used in bindings for argument or return value.
*/
abstract val argType: String
/**
* Mirror for C type to be represented in Kotlin as by-value type.
*/
class ByValue(pointedTypeName: String, info: TypeInfo, val valueTypeName: String) :
TypeMirror(pointedTypeName, info) {
override val argType: String
get() = valueTypeName +
if (info is TypeInfo.Pointer ||
(info is TypeInfo.ObjCPointerInfo && info.type.isNullable)) "?" else ""
}
/**
* Mirror for C type to be represented in Kotlin as by-ref type.
*/
class ByRef(pointedTypeName: String, info: TypeInfo) : TypeMirror(pointedTypeName, info) {
override val argType: String
get() = "CValue<$pointedTypeName>"
}
}
/**
* Describes various type conversions for [TypeMirror].
*/
sealed class TypeInfo {
/**
* The conversion from [TypeMirror.argType] to [bridgedType].
*/
abstract fun argToBridged(name: String): String
/**
* The conversion from [bridgedType] to [TypeMirror.argType].
*/
abstract fun argFromBridged(name: String): String
abstract val bridgedType: BridgedType
open fun cFromBridged(name: String): String = name
open fun cToBridged(name: String): String = name
/**
* If this info is for [TypeMirror.ByValue], then this method describes how to
* construct pointed-type from value type.
*/
abstract fun constructPointedType(valueType: String): String
class Primitive(override val bridgedType: BridgedType, val varTypeName: String) : TypeInfo() {
override fun argToBridged(name: String) = name
override fun argFromBridged(name: String) = name
override fun constructPointedType(valueType: String) = "${varTypeName}Of<$valueType>"
}
class Boolean : TypeInfo() {
override fun argToBridged(name: String) = "$name.toByte()"
override fun argFromBridged(name: String) = "$name.toBoolean()"
override val bridgedType: BridgedType get() = BridgedType.BYTE
override fun cFromBridged(name: String) = "($name) ? 1 : 0"
override fun cToBridged(name: String) = "($name) ? 1 : 0"
override fun constructPointedType(valueType: String) = "BooleanVarOf<$valueType>"
}
class Enum(val className: String, override val bridgedType: BridgedType) : TypeInfo() {
override fun argToBridged(name: String) = "$name.value"
override fun argFromBridged(name: String) = "$className.byValue($name)"
override fun constructPointedType(valueType: String) = "$className.Var" // TODO: improve
}
class Pointer(val pointee: String) : TypeInfo() {
override fun argToBridged(name: String) = "$name.rawValue"
override fun argFromBridged(name: String) = "interpretCPointer<$pointee>($name)"
override val bridgedType: BridgedType
get() = BridgedType.NATIVE_PTR
override fun cFromBridged(name: String) = "(void*)$name" // Note: required for JVM
override fun constructPointedType(valueType: String) = "CPointerVarOf<$valueType>"
}
class ObjCPointerInfo(val typeName: String, val type: ObjCPointer) : TypeInfo() {
override fun argToBridged(name: String) = "$name.rawPtr"
override fun argFromBridged(name: String) = "interpretObjCPointerOrNull<$typeName>($name)" +
if (type.isNullable) "" else "!!"
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
override fun constructPointedType(valueType: String) = "ObjCObjectVar<$valueType>"
}
class NSString(val type: ObjCPointer) : TypeInfo() {
override fun argToBridged(name: String) = "CreateNSStringFromKString($name)"
override fun argFromBridged(name: String) = "CreateKStringFromNSString($name)" +
if (type.isNullable) "" else "!!"
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
override fun constructPointedType(valueType: String): String {
return "ObjCStringVarOf<$valueType>"
}
}
class ByRef(val pointed: String) : TypeInfo() {
override fun argToBridged(name: String) = error(pointed)
override fun argFromBridged(name: String) = error(pointed)
override val bridgedType: BridgedType get() = error(pointed)
override fun cFromBridged(name: String) = error(pointed)
override fun cToBridged(name: String) = error(pointed)
// TODO: this method must not exist
override fun constructPointedType(valueType: String): String = error(pointed)
}
}
fun mirrorPrimitiveType(type: PrimitiveType): TypeMirror.ByValue {
val varTypeName = when (type) {
is CharType -> "ByteVar"
is BoolType -> "BooleanVar"
is IntegerType -> when (type.size) {
1 -> "ByteVar"
2 -> "ShortVar"
4 -> "IntVar"
8 -> "LongVar"
else -> TODO(type.toString())
}
is FloatingType -> when (type.size) {
4 -> "FloatVar"
8 -> "DoubleVar"
else -> TODO(type.toString())
}
else -> TODO(type.toString())
}
val info = if (type == BoolType) {
TypeInfo.Boolean()
} else {
TypeInfo.Primitive(type.bridgedType, varTypeName)
}
return TypeMirror.ByValue(varTypeName, info, type.kotlinType)
}
private fun byRefTypeMirror(pointedTypeName: String) : TypeMirror.ByRef {
val info = TypeInfo.ByRef(pointedTypeName)
return TypeMirror.ByRef(pointedTypeName, info)
}
fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when (type) {
is PrimitiveType -> mirrorPrimitiveType(type)
is RecordType -> byRefTypeMirror(declarationMapper.getKotlinNameForPointed(type.decl).asSimpleName())
is EnumType -> {
val kotlinName = declarationMapper.getKotlinNameForValue(type.def)
when {
declarationMapper.isMappedToStrict(type.def) -> {
val classSimpleName = kotlinName.asSimpleName()
val bridgedType = (type.def.baseType.unwrapTypedefs() as PrimitiveType).bridgedType
val info = TypeInfo.Enum(classSimpleName, bridgedType)
TypeMirror.ByValue("$classSimpleName.Var", info, classSimpleName)
}
!type.def.isAnonymous -> {
val baseTypeMirror = mirror(declarationMapper, type.def.baseType)
TypeMirror.ByValue("${kotlinName}Var", baseTypeMirror.info, kotlinName.asSimpleName())
}
else -> mirror(declarationMapper, type.def.baseType)
}
}
is PointerType -> {
val pointeeType = type.pointeeType
val unwrappedPointeeType = pointeeType.unwrapTypedefs()
if (unwrappedPointeeType is VoidType) {
val info = TypeInfo.Pointer("COpaque")
TypeMirror.ByValue("COpaquePointerVar", info, "COpaquePointer")
} else if (unwrappedPointeeType is ArrayType) {
mirror(declarationMapper, pointeeType)
} else {
val pointeeMirror = mirror(declarationMapper, pointeeType)
val info = TypeInfo.Pointer(pointeeMirror.pointedTypeName)
TypeMirror.ByValue("CPointerVar<${pointeeMirror.pointedTypeName}>", info,
"CPointer<${pointeeMirror.pointedTypeName}>")
}
}
is ArrayType -> {
// TODO: array type doesn't exactly correspond neither to pointer nor to value.
val elemTypeMirror = mirror(declarationMapper, type.elemType)
if (type.elemType.unwrapTypedefs() is ArrayType) {
elemTypeMirror
} else {
val info = TypeInfo.Pointer(elemTypeMirror.pointedTypeName)
TypeMirror.ByValue("CArrayPointerVar<${elemTypeMirror.pointedTypeName}>", info,
"CArrayPointer<${elemTypeMirror.pointedTypeName}>")
}
}
is FunctionType -> byRefTypeMirror("CFunction<${getKotlinFunctionType(declarationMapper, type)}>")
is Typedef -> {
val baseType = mirror(declarationMapper, type.def.aliased)
val name = type.def.name
when (baseType) {
is TypeMirror.ByValue -> TypeMirror.ByValue("${name}Var", baseType.info, name.asSimpleName())
is TypeMirror.ByRef -> TypeMirror.ByRef(name.asSimpleName(), baseType.info)
}
}
is ObjCPointer -> objCPointerMirror(type)
else -> TODO(type.toString())
}
private fun objCPointerMirror(type: ObjCPointer): TypeMirror.ByValue {
if (type is ObjCObjectPointer && type.def.name == "NSString") {
val info = TypeInfo.NSString(type)
val valueType = if (type.isNullable) "String?" else "String"
return TypeMirror.ByValue(info.constructPointedType(valueType), info, valueType)
}
val typeName = when (type) {
is ObjCIdType -> type.protocols.firstOrNull()?.kotlinName ?: "ObjCObject"
is ObjCClassPointer -> "ObjCClass"
is ObjCObjectPointer -> type.def.name
is ObjCInstanceType -> TODO(type.toString()) // Must have already been handled.
}
return objCPointerMirror(typeName.asSimpleName(), type)
}
private fun objCPointerMirror(typeName: String, type: ObjCPointer): TypeMirror.ByValue {
val valueType = if (type.isNullable) "$typeName?" else typeName
return TypeMirror.ByValue("ObjCObjectVar<$valueType>",
TypeInfo.ObjCPointerInfo(typeName, type), typeName)
}
fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): String {
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
"Unit"
} else {
mirror(declarationMapper, type.returnType).argType
}
return "(" +
type.parameterTypes.map { mirror(declarationMapper, it).argType }.joinToString(", ") +
") -> " +
returnType
}
@@ -0,0 +1,498 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator
import org.jetbrains.kotlin.native.interop.indexer.*
private fun ObjCMethod.getKotlinParameterNames(): List<String> {
val selectorParts = this.selector.split(":")
val result = mutableListOf<String>()
// The names of all parameters except first must depend only on the selector:
this.parameters.forEachIndexed { index, parameter ->
if (index > 0) {
var name = selectorParts[index]
if (name.isEmpty()) {
name = "_$index"
}
while (name in result) {
name = "_$name"
}
result.add(name)
}
}
this.parameters.firstOrNull()?.let {
var name = it.name ?: ""
if (name.isEmpty()) {
name = "arg"
}
while (name in result) {
name = "_$name"
}
result.add(0, name)
}
return result
}
class ObjCMethodStub(stubGenerator: StubGenerator,
val method: ObjCMethod,
private val container: ObjCClassOrProtocol) : KotlinStub, NativeBacked {
override fun generate(context: StubGenerationContext): Sequence<String> =
if (context.nativeBridges.isSupported(this)) {
val result = mutableListOf<String>()
result.add("@ObjCMethod".applyToStrings(method.selector, bridgeName))
result.add(header)
if (method.isInit && container is ObjCClass) {
result.add("")
result.add("@ObjCConstructor".applyToStrings(method.selector))
result.add("constructor($joinedKotlinParameters) {}")
}
context.addTopLevelDeclaration(
listOf("@konan.internal.ExportForCompiler",
"@ObjCBridge".applyToStrings(method.selector, method.encoding, implementationTemplate))
+ block(bridgeHeader, bodyLines)
)
result.asSequence()
} else {
sequenceOf(
annotationForUnableToImport,
header
)
}
private val bodyLines: List<String>
private val joinedKotlinParameters: String
private val header: String
private val implementationTemplate: String
private val bridgeName: String
private val bridgeHeader: String
private val isOverride = method.isOverride(container)
init {
val bodyGenerator = KotlinCodeBuilder()
val kotlinParameters = mutableListOf<Pair<String, String>>()
val kotlinObjCBridgeParameters = mutableListOf<Pair<String, String>>()
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
val kniReceiverParameter = "kniR"
val kniSuperClassParameter = "kniSC"
val voidPtr = PointerType(VoidType)
val returnType = method.getReturnType(container)
val messengerGetter = if (returnType.isLargeOrUnaligned()) "getMessengerLU" else "getMessenger"
kotlinObjCBridgeParameters.add(kniSuperClassParameter to "NativePtr")
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)"))
if (method.nsConsumesSelf) {
// TODO: do this later due to possible exceptions
bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr)")
}
kotlinObjCBridgeParameters.add(kniReceiverParameter to "ObjCObject")
nativeBridgeArguments.add(
TypedKotlinValue(voidPtr,
"getReceiverOrSuper($kniReceiverParameter.rawPtr, $kniSuperClassParameter)"))
val kotlinParameterNames = method.getKotlinParameterNames()
method.parameters.forEachIndexed { index, it ->
val name = kotlinParameterNames[index]
val kotlinType = stubGenerator.mirror(it.type).argType
kotlinParameters.add(name to kotlinType)
kotlinObjCBridgeParameters.add(name to kotlinType)
nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName()))
}
val kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
"Unit"
} else {
stubGenerator.mirror(returnType).argType
}
val result = stubGenerator.mappingBridgeGenerator.kotlinToNative(
bodyGenerator,
this@ObjCMethodStub,
returnType,
nativeBridgeArguments
) { nativeValues ->
val selector = "@selector(${method.selector})"
val messengerParameterTypes = mutableListOf<String>()
messengerParameterTypes.add("void*")
messengerParameterTypes.add("SEL")
method.parameters.forEach {
messengerParameterTypes.add(it.getTypeStringRepresentation())
}
val messengerReturnType = returnType.getStringRepresentation()
val messengerType =
"$messengerReturnType (* ${method.cAttributes}) (${messengerParameterTypes.joinToString()})"
val messenger = "(($messengerType) ${nativeValues.first()})"
val messengerArguments = listOf(nativeValues[1]) + selector + nativeValues.drop(2)
"$messenger(${messengerArguments.joinToString()})"
}
bodyGenerator.out("return $result")
this.implementationTemplate = genImplementationTemplate(stubGenerator)
this.bodyLines = bodyGenerator.build()
bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}"
this.bridgeHeader = "internal fun $bridgeName(" +
"${kotlinObjCBridgeParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" }})" +
": $kotlinReturnType"
this.joinedKotlinParameters = kotlinParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" }
this.header = buildString {
if (container is ObjCClass) append("external ")
if (isOverride) {
append("override ")
} else if (container is ObjCClass) {
append("open ")
}
append("fun ${method.kotlinName.asSimpleName()}($joinedKotlinParameters): $kotlinReturnType")
if (container is ObjCProtocol && method.isOptional) append(" = optional()")
}
}
private fun genImplementationTemplate(stubGenerator: StubGenerator): String {
val codeBuilder = NativeCodeBuilder()
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
return result
}
}
private fun Type.isLargeOrUnaligned(): Boolean {
val unwrappedType = this.unwrapTypedefs()
return when (unwrappedType) {
is RecordType -> unwrappedType.decl.def!!.size > 16 || this.hasUnalignedMembers()
else -> false
}
}
private fun Type.hasUnalignedMembers(): Boolean = when (this) {
is Typedef -> this.def.aliased.hasUnalignedMembers()
is RecordType -> this.decl.def!!.let { def ->
def.hasUnalignedFields || // Check members of fields too:
def.fields.any { it.type.hasUnalignedMembers() }
}
is ArrayType -> this.elemType.hasUnalignedMembers()
else -> false
// TODO: should the recursive checks be made in indexer when computing `hasUnalignedFields`?
}
private val ObjCMethod.kotlinName: String get() = selector.split(":").first()
private val ObjCClassOrProtocol.protocolsWithSupers: Sequence<ObjCProtocol>
get() = this.protocols.asSequence().flatMap { sequenceOf(it) + it.protocolsWithSupers }
private val ObjCClassOrProtocol.immediateSuperTypes: Sequence<ObjCClassOrProtocol>
get() {
val baseClass = (this as? ObjCClass)?.baseClass
if (baseClass != null) {
return sequenceOf(baseClass) + this.protocols.asSequence()
}
return this.protocols.asSequence()
}
private val ObjCClassOrProtocol.selfAndSuperTypes: Sequence<ObjCClassOrProtocol>
get() = sequenceOf(this) + this.superTypes
private val ObjCClassOrProtocol.superTypes: Sequence<ObjCClassOrProtocol>
get() = this.immediateSuperTypes.flatMap { it.selfAndSuperTypes }.distinct()
private fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence<ObjCMethod> =
this.methods.asSequence().filter { it.isClass == isClass }
private fun ObjCClassOrProtocol.inheritedMethods(isClass: Boolean): Sequence<ObjCMethod> =
this.superTypes.flatMap { it.declaredMethods(isClass) }.distinctBy { it.selector }
private fun ObjCClassOrProtocol.methodsWithInherited(isClass: Boolean): Sequence<ObjCMethod> =
this.selfAndSuperTypes.flatMap { it.declaredMethods(isClass) }.distinctBy { it.selector }
private fun ObjCMethod.isOverride(container: ObjCClassOrProtocol): Boolean =
container.superTypes.any { superType -> superType.methods.any(this::replaces) }
abstract class ObjCContainerStub(stubGenerator: StubGenerator,
private val container: ObjCClassOrProtocol,
private val isMeta: Boolean) : KotlinStub {
private val methods: List<ObjCMethod>
init {
val superMethods = container.inheritedMethods(isMeta)
// Add all methods declared in the class or protocol:
var methods = container.declaredMethods(isMeta)
// Exclude those which are identically declared in super types:
methods -= superMethods
// Add some special methods from super types:
methods += superMethods.filter { it.returnsInstancetype() || it.isInit }
// Add methods from adopted protocols that must be implemented according to Kotlin rules:
if (container is ObjCClass) {
methods += container.protocolsWithSupers.flatMap { it.declaredMethods(isMeta) }.filter { !it.isOptional }
}
// Add methods inherited from multiple supertypes that must be defined according to Kotlin rules:
methods += container.immediateSuperTypes
.flatMap { superType ->
val methodsWithInherited = superType.methodsWithInherited(isMeta)
// Select only those which are represented as non-abstract in Kotlin:
when (superType) {
is ObjCClass -> methodsWithInherited
is ObjCProtocol -> methodsWithInherited.filter { it.isOptional }
}
}
.groupBy { it.selector }
.mapNotNull { (_, inheritedMethods) -> if (inheritedMethods.size > 1) inheritedMethods.first() else null }
this.methods = methods.distinctBy { it.selector }.toList()
}
private val methodStubs = methods.map {
ObjCMethodStub(stubGenerator, it, container)
}
private val properties: List<ObjCProperty>
init {
val superProperties = container.superTypes.flatMap { it.properties.asSequence() }
this.properties = container.properties.filter {
it.getter.isClass == isMeta &&
// Select only properties that don't override anything:
superProperties.none(it::replaces)
}
}
val propertyStubs = properties.map {
ObjCPropertyStub(stubGenerator, it, container)
}
private val classHeader: String
init {
val nameSuffix = if (isMeta) "Meta" else ""
val supers = mutableListOf<String>()
if (container is ObjCClass) {
supers.add(container.baseClassName)
}
container.protocols.forEach {
supers.add(it.kotlinName)
}
if (supers.isEmpty()) {
assert(container is ObjCProtocol)
supers.add("ObjCObject")
}
val keywords = when (container) {
is ObjCClass -> "open class"
is ObjCProtocol -> "interface"
}
val supersString = supers.joinToString { "$it$nameSuffix".asSimpleName() }
val name = "${container.kotlinName}$nameSuffix".asSimpleName()
this.classHeader = "@ExternalObjCClass $keywords $name : $supersString"
}
open fun generateBody(context: StubGenerationContext): Sequence<String> {
var result = (propertyStubs.asSequence() + methodStubs.asSequence())
.flatMap { sequenceOf("") + it.generate(context) }
if (container is ObjCClass && methodStubs.none {
it.method.isInit && it.method.parameters.isEmpty() && context.nativeBridges.isSupported(it)
}) {
// Always generate default constructor.
// If it is not produced for an init method, then include it manually:
result += sequenceOf("", "protected constructor() {}")
}
return result
}
override fun generate(context: StubGenerationContext): Sequence<String> = block(classHeader, generateBody(context))
}
open class ObjCClassOrProtocolStub(
stubGenerator: StubGenerator,
private val container: ObjCClassOrProtocol
) : ObjCContainerStub(
stubGenerator,
container,
isMeta = false
) {
private val metaClassStub =
object : ObjCContainerStub(stubGenerator, container, isMeta = true) {}
override fun generate(context: StubGenerationContext) =
metaClassStub.generate(context) + "" + super.generate(context)
}
class ObjCProtocolStub(stubGenerator: StubGenerator, protocol: ObjCProtocol) :
ObjCClassOrProtocolStub(stubGenerator, protocol)
class ObjCClassStub(stubGenerator: StubGenerator, private val clazz: ObjCClass) :
ObjCClassOrProtocolStub(stubGenerator, clazz) {
override fun generateBody(context: StubGenerationContext) =
sequenceOf( "companion object : ${clazz.kotlinName}Meta() {}") +
super.generateBody(context)
}
class ObjCPropertyStub(
val stubGenerator: StubGenerator, val property: ObjCProperty, val clazz: ObjCClassOrProtocol
) : KotlinStub {
override fun generate(context: StubGenerationContext): Sequence<String> {
val type = property.getType(clazz)
val kotlinType = stubGenerator.mirror(type).argType
val kind = if (property.setter == null) "val" else "var"
val modifiers = if (clazz is ObjCClass) "" else "final "
val result = mutableListOf(
"$modifiers$kind ${property.name.asSimpleName()}: $kotlinType",
" get() = ${property.getter.kotlinName.asSimpleName()}()"
)
property.setter?.let {
result.add(" set(value) = ${it.kotlinName.asSimpleName()}(value)")
}
return result.asSequence()
}
}
val ObjCClassOrProtocol.kotlinName: String get() = when (this) {
is ObjCClass -> this.name
is ObjCProtocol -> "${this.name}Protocol"
}
private val ObjCClass.baseClassName: String
get() = baseClass?.name ?: "ObjCObject"
private fun Parameter.getTypeStringRepresentation() =
(if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation()
private fun ObjCMethod.getSelfTypeStringRepresentation() = if (this.nsConsumesSelf) {
"__attribute__((ns_consumed)) id"
} else {
"id"
}
val ObjCMethod.cAttributes get() = if (this.nsReturnsRetained) {
"__attribute__((ns_returns_retained)) "
} else {
""
}
private fun NativeCodeBuilder.genMethodImp(
stubGenerator: StubGenerator,
nativeBacked: NativeBacked,
method: ObjCMethod,
container: ObjCClassOrProtocol
): String {
val returnType = method.getReturnType(container)
val cReturnType = returnType.getStringRepresentation()
val bridgeArguments = mutableListOf<TypedNativeValue>()
val parameters = mutableListOf<Pair<String, String>>()
parameters.add("self" to method.getSelfTypeStringRepresentation())
val receiverType = ObjCIdType(ObjCPointer.Nullability.NonNull, protocols = emptyList())
bridgeArguments.add(TypedNativeValue(receiverType, "self"))
parameters.add("_cmd" to "SEL")
method.parameters.forEachIndexed { index, parameter ->
val name = "p$index"
parameters.add(name to parameter.getTypeStringRepresentation())
bridgeArguments.add(TypedNativeValue(parameter.type, name))
}
val functionName = "knimi_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId()
val functionAttr = method.cAttributes
out("$cReturnType $functionName(${parameters.joinToString { it.second + " " + it.first }}) $functionAttr{")
val callExpr = stubGenerator.mappingBridgeGenerator.nativeToKotlin(
this,
nativeBacked,
returnType,
bridgeArguments
) { kotlinValues ->
val kotlinReceiverType = if (method.isClass) "${container.kotlinName}Meta" else container.kotlinName
val kotlinRawReceiver = kotlinValues.first()
val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<${kotlinReceiverType.asSimpleName()}>()"
val namedArguments = kotlinValues.drop(1).zip(method.getKotlinParameterNames()) { value, name ->
"${name.asSimpleName()} = $value"
}
"${kotlinReceiver}.${method.kotlinName.asSimpleName()}(${namedArguments.joinToString()})"
}
if (returnType.unwrapTypedefs() is VoidType) {
out(" $callExpr;")
} else {
out(" return $callExpr;")
}
out("}")
return functionName
}
@@ -0,0 +1,93 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
/**
* The type which has exact counterparts on both Kotlin and native side and can be directly passed through bridges.
*/
enum class BridgedType(val kotlinType: String) {
BYTE("Byte"),
SHORT("Short"),
INT("Int"),
LONG("Long"),
FLOAT("Float"),
DOUBLE("Double"),
NATIVE_PTR("NativePtr"),
OBJC_POINTER("NativePtr"),
VOID("Unit")
}
data class BridgeTypedKotlinValue(val type: BridgedType, val value: KotlinExpression)
data class BridgeTypedNativeValue(val type: BridgedType, val value: NativeExpression)
/**
* The entity which depends on native bridges.
*/
interface NativeBacked
/**
* Generates simple bridges between Kotlin and native, passing [BridgedType] values.
*/
interface SimpleBridgeGenerator {
/**
* Generates the expression to convert given Kotlin values to native counterparts, pass through the bridge,
* use inside the native code produced by [block] and then return the result back.
*
* @param block produces native code lines into the builder and returns the expression to be used as the result.
*/
fun kotlinToNative(
nativeBacked: NativeBacked,
returnType: BridgedType,
kotlinValues: List<BridgeTypedKotlinValue>,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression
/**
* Generates the expression to convert given native values to Kotlin counterparts, pass through the bridge,
* use inside the Kotlin code produced by [block] and then return the result back.
*/
fun nativeToKotlin(
nativeBacked: NativeBacked,
returnType: BridgedType,
nativeValues: List<BridgeTypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression
fun insertNativeBridge(
nativeBacked: NativeBacked,
kotlinLines: List<String>,
nativeLines: List<String>
)
/**
* Prepares all requested native bridges.
*/
fun prepare(): NativeBridges
}
interface NativeBridges {
/**
* @return `true` iff given entity is supported by these bridges,
* i.e. all bridges it depends on can be successfully generated.
*/
fun isSupported(nativeBacked: NativeBacked): Boolean
val kotlinLines: Sequence<String>
val nativeLines: Sequence<String>
}
@@ -0,0 +1,219 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.NativeLibrary
import org.jetbrains.kotlin.native.interop.indexer.mapFragmentIsCompilable
class SimpleBridgeGeneratorImpl(
private val platform: KotlinPlatform,
private val pkgName: String,
private val jvmFileClassName: String,
private val libraryForCStubs: NativeLibrary
) : SimpleBridgeGenerator {
private var nextUniqueId = 0
private val BridgedType.nativeType: String get() = when (platform) {
KotlinPlatform.JVM -> when (this) {
BridgedType.BYTE -> "jbyte"
BridgedType.SHORT -> "jshort"
BridgedType.INT -> "jint"
BridgedType.LONG -> "jlong"
BridgedType.FLOAT -> "jfloat"
BridgedType.DOUBLE -> "jdouble"
BridgedType.NATIVE_PTR -> "jlong"
BridgedType.OBJC_POINTER -> TODO()
BridgedType.VOID -> "void"
}
KotlinPlatform.NATIVE -> when (this) {
BridgedType.BYTE -> "int8_t"
BridgedType.SHORT -> "int16_t"
BridgedType.INT -> "int32_t"
BridgedType.LONG -> "int64_t"
BridgedType.FLOAT -> "float"
BridgedType.DOUBLE -> "double"
BridgedType.NATIVE_PTR -> "void*"
BridgedType.OBJC_POINTER -> "id"
BridgedType.VOID -> "void"
}
}
private inner class NativeBridge(val kotlinLines: List<String>, val nativeLines: List<String>)
override fun kotlinToNative(
nativeBacked: NativeBacked,
returnType: BridgedType,
kotlinValues: List<BridgeTypedKotlinValue>,
block: NativeCodeBuilder.(arguments: List<NativeExpression>) -> NativeExpression
): KotlinExpression {
val kotlinLines = mutableListOf<String>()
val nativeLines = mutableListOf<String>()
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
val kotlinParameters = kotlinValues.withIndex().joinToString {
"p${it.index}: ${it.value.type.kotlinType}"
}
val callExpr = "$kotlinFunctionName(${kotlinValues.joinToString { it.value }})"
val cFunctionParameters = when (platform) {
KotlinPlatform.JVM -> mutableListOf(
"jniEnv" to "JNIEnv*",
"jclss" to "jclass"
)
KotlinPlatform.NATIVE -> mutableListOf()
}
kotlinValues.withIndex().mapTo(cFunctionParameters) {
"p${it.index}" to it.value.type.nativeType
}
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
val cReturnType = returnType.nativeType
val cFunctionHeader = when (platform) {
KotlinPlatform.JVM -> {
val funcFullName = buildString {
if (pkgName.isNotEmpty()) {
append(pkgName)
append('.')
}
append(jvmFileClassName)
append('.')
append(kotlinFunctionName)
}
val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024")
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
}
KotlinPlatform.NATIVE -> {
val functionName = pkgName.replace('.', '_') + "_$kotlinFunctionName"
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
"$cReturnType $functionName ($joinedCParameters)"
}
}
nativeLines.add(cFunctionHeader + " {")
buildNativeCodeLines {
val cExpr = block(cFunctionParameters.takeLast(kotlinValues.size).map { (name, _) -> name })
if (returnType != BridgedType.VOID) {
out("return ($cReturnType)$cExpr;")
}
}.forEach {
nativeLines.add(" $it")
}
nativeLines.add("}")
kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): ${returnType.kotlinType}")
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
nativeBridges.add(nativeBacked to nativeBridge)
return callExpr
}
override fun nativeToKotlin(
nativeBacked: NativeBacked,
returnType: BridgedType,
nativeValues: List<BridgeTypedNativeValue>,
block: KotlinCodeBuilder.(arguments: List<KotlinExpression>) -> KotlinExpression
): NativeExpression {
if (platform != KotlinPlatform.NATIVE) TODO()
val kotlinLines = mutableListOf<String>()
val nativeLines = mutableListOf<String>()
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
val kotlinParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.kotlinType
}
val joinedKotlinParameters = kotlinParameters.joinToString { "${it.first}: ${it.second}" }
val cFunctionParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.nativeType
}
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
val cReturnType = returnType.nativeType
val symbolName = pkgName.replace('.', '_') + "_$kotlinFunctionName"
kotlinLines.add("@konan.internal.ExportForCppRuntime(${symbolName.quoteAsKotlinLiteral()})")
val cFunctionHeader = "$cReturnType $symbolName($joinedCParameters)"
nativeLines.add("$cFunctionHeader;")
kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): ${returnType.kotlinType} {")
buildKotlinCodeLines {
var kotlinExpr = block(kotlinParameters.map { (name, _) -> name })
if (returnType == BridgedType.OBJC_POINTER) {
// The Kotlin code may lose the ownership on this pointer after returning from the bridge,
// so retain the pointer and autorelease it:
kotlinExpr = "objc_retainAutoreleaseReturnValue($kotlinExpr)"
// (Objective-C does the same for returned pointers).
}
out("return $kotlinExpr")
}.forEach {
kotlinLines.add(" $it")
}
kotlinLines.add("}")
insertNativeBridge(nativeBacked, kotlinLines, nativeLines)
return "$symbolName(${nativeValues.joinToString { it.value }})"
}
override fun insertNativeBridge(nativeBacked: NativeBacked, kotlinLines: List<String>, nativeLines: List<String>) {
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
nativeBridges.add(nativeBacked to nativeBridge)
}
private val nativeBridges = mutableListOf<Pair<NativeBacked, NativeBridge>>()
override fun prepare(): NativeBridges {
val includedBridges = mutableListOf<NativeBridge>()
val excludedClients = mutableSetOf<NativeBacked>()
nativeBridges.map { it.second.nativeLines }
.mapFragmentIsCompilable(libraryForCStubs)
.forEachIndexed { index, isCompilable ->
if (isCompilable) {
includedBridges.add(nativeBridges[index].second)
} else {
excludedClients.add(nativeBridges[index].first)
}
}
// TODO: exclude unused bridges.
return object : NativeBridges {
override val kotlinLines: Sequence<String>
get() = includedBridges.asSequence().flatMap { it.kotlinLines.asSequence() }
override val nativeLines: Sequence<String>
get() = includedBridges.asSequence().flatMap { it.nativeLines.asSequence() }
override fun isSupported(nativeBacked: NativeBacked): Boolean =
nativeBacked !in excludedClients
}
}
}
@@ -0,0 +1,66 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
val EnumDef.isAnonymous: Boolean
get() = spelling.contains("(anonymous ") // TODO: it is a hack
/**
* Returns the expression which could be used for this type in C code.
* Note: the resulting string doesn't exactly represent this type, but it is enough for current purposes.
*
* TODO: use libclang to implement?
*/
fun Type.getStringRepresentation(): String = when (this) {
is VoidType -> "void"
is CharType -> "char"
is BoolType -> "BOOL"
is IntegerType -> this.spelling
is FloatingType -> this.spelling
is PointerType, is ArrayType -> "void*"
is RecordType -> this.decl.spelling
is EnumType -> if (this.def.isAnonymous) {
this.def.baseType.getStringRepresentation()
} else {
this.def.spelling
}
is Typedef -> this.def.aliased.getStringRepresentation()
is ObjCPointer -> when (this) {
is ObjCIdType -> "id$protocolQualifier"
is ObjCClassPointer -> "Class$protocolQualifier"
is ObjCObjectPointer -> "${def.name}$protocolQualifier*"
is ObjCInstanceType -> TODO(this.toString()) // Must have already been handled.
}
else -> throw kotlin.NotImplementedError()
}
private val ObjCQualifiedPointer.protocolQualifier: String
get() = if (this.protocols.isEmpty()) "" else " <${protocols.joinToString { it.name }}>"
tailrec fun Type.unwrapTypedefs(): Type = if (this is Typedef) {
this.def.aliased.unwrapTypedefs()
} else {
this
}
@@ -324,6 +324,19 @@ private fun downloadDependencies(dependenciesRoot: String, target: String, konan
maybeExecuteHelper(dependenciesRoot, konanProperties, dependencyList)
}
private fun selectNativeLanguage(config: Properties): Language {
val languages = mapOf(
"C" to Language.C,
"Objective-C" to Language.OBJECTIVE_C
)
val language = config.getProperty("language") ?: return Language.C
return languages[language] ?:
error("Unexpected language '$language'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
}
private fun processLib(konanHome: String,
substitutions: Map<String, String>,
args: Map<String, List<String>>) {
@@ -363,9 +376,24 @@ private fun processLib(konanHome: String,
val defaultOpts = konanProperties.defaultCompilerOpts(target, dependencies)
val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders
val compilerOpts = config.getSpaceSeparated("compilerOpts") + defaultOpts + additionalCompilerOpts
val language = selectNativeLanguage(config)
val compilerOpts: List<String> = mutableListOf<String>().apply {
addAll(config.getSpaceSeparated("compilerOpts"))
addAll(defaultOpts)
addAll(additionalCompilerOpts)
addAll(when (language) {
Language.C -> emptyList()
Language.OBJECTIVE_C -> {
// "Objective-C" within interop means "Objective-C with ARC":
listOf("-fobjc-arc")
// Using this flag here has two effects:
// 1. The headers are parsed with ARC enabled, thus the API is visible correctly.
// 2. The generated Objective-C stubs are compiled with ARC enabled, so reference counting
// calls are inserted automatically.
}
})
}
val compiler = "clang"
val language = Language.C
val excludeSystemLibs = config.getProperty("excludeSystemLibs")?.toBoolean() ?: false
val excludeDependentModules = config.getProperty("excludeDependentModules")?.toBoolean() ?: false
@@ -422,7 +450,7 @@ private fun processLib(konanHome: String,
outKtFile.parentFile.mkdirs()
File(nativeLibsDir).mkdirs()
val outCFile = File("$nativeLibsDir/$libName.c") // TODO: select the better location.
val outCFile = File("$nativeLibsDir/$libName.${language.sourceFileExtension}") // TODO: select the better location.
outKtFile.bufferedWriter().use { ktFile ->
outCFile.bufferedWriter().use { cFile ->