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 ->
+3 -1
View File
@@ -61,6 +61,7 @@ sourceSets {
srcDir 'compiler/ir/backend.native/src/'
srcDir "${rootProject.projectDir}/shared/src/main/kotlin"
}
resources.srcDir 'compiler/ir/backend.native/resources/'
}
cli_bc {
java.srcDir 'cli.bc/src'
@@ -213,7 +214,8 @@ task jars(type: Jar) {
from 'build/classes/cli_bc',
'build/classes/compiler',
'build/classes/hashInteropStubs',
'build/classes/llvmInteropStubs'
'build/classes/llvmInteropStubs',
'build/resources/compiler'
dependsOn 'build', ':runtime:hostRuntime', 'external_jars'
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.backend.konan.ObjCOverridabilityCondition
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -100,6 +101,31 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val signExtend = packageScope.getContributedFunctions("signExtend").single()
val narrow = packageScope.getContributedFunctions("narrow").single()
val objCObject = packageScope.getContributedClassifier("ObjCObject") as ClassDescriptor
val objCPointerHolder = packageScope.getContributedClassifier("ObjCPointerHolder") as ClassDescriptor
val objCPointerHolderValue = objCPointerHolder.unsubstitutedMemberScope
.getContributedDescriptors().filterIsInstance<PropertyDescriptor>().single()
val objCObjectInitFromPtr = packageScope.getContributedFunctions("initFromPtr").single()
val objCObjectInitFrom = packageScope.getContributedFunctions("initFrom").single()
val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single()
val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single()
val objCObjectRawPtr = packageScope.getContributedVariables("rawPtr").single {
val extensionReceiverType = it.extensionReceiverParameter?.type
extensionReceiverType != null && !extensionReceiverType.isMarkedNullable &&
TypeUtils.getClassDescriptor(extensionReceiverType) == objCObject
}
val getObjCReceiverOrSuper = packageScope.getContributedFunctions("getReceiverOrSuper").single()
val getObjCMessenger = packageScope.getContributedFunctions("getMessenger").single()
val getObjCMessengerLU = packageScope.getContributedFunctions("getMessengerLU").single()
}
private fun MemberScope.getContributedVariables(name: String) =
@@ -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.
*/
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
import org.jetbrains.kotlin.resolve.descriptorUtil.*
private val interopPackageName = InteropBuiltIns.FqNames.packageName
private val objCObjectFqName = interopPackageName.child(Name.identifier("ObjCObject"))
private val objCClassFqName = interopPackageName.child(Name.identifier("ObjCClass"))
private val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass"))
private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod"))
private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge"))
fun ClassDescriptor.isObjCClass(): Boolean =
this.getAllSuperClassifiers().any { it.fqNameSafe == objCObjectFqName } &&
this.containingDeclaration.fqNameSafe != interopPackageName
fun ClassDescriptor.isExternalObjCClass(): Boolean = this.isObjCClass() &&
this.parentsWithSelf.filterIsInstance<ClassDescriptor>().any {
it.annotations.findAnnotation(externalObjCClassFqName) != null
}
fun ClassDescriptor.isObjCMetaClass(): Boolean = this.getAllSuperClassifiers().any {
it.fqNameSafe == objCClassFqName
}
fun FunctionDescriptor.isObjCClassMethod() =
this.containingDeclaration.let { it is ClassDescriptor && it.isObjCClass() }
fun FunctionDescriptor.isExternalObjCClassMethod() =
this.containingDeclaration.let { it is ClassDescriptor && it.isExternalObjCClass() }
// Special case: methods from Kotlin Objective-C classes can be called virtually from bridges.
fun FunctionDescriptor.canObjCClassMethodBeCalledVirtually(overriddenDescriptor: FunctionDescriptor) =
overriddenDescriptor.isOverridable && this.kind.isReal && !this.isExternalObjCClassMethod()
fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass()
data class ObjCMethodInfo(val descriptor: FunctionDescriptor,
val bridge: FunctionDescriptor,
val selector: String,
val encoding: String,
val imp: String)
private fun CallableDescriptor.getBridgeAnnotation() =
this.annotations.findAnnotation(objCBridgeFqName)
private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
assert (this.kind.isReal)
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
val packageFragment = this.parents.filterIsInstance<PackageFragmentDescriptor>().single()
val bridgeName = methodAnnotation.getStringValue("bridge")
val bridge = packageFragment.getMemberScope()
.getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND)
.single()
val bridgeAnnotation = bridge.getBridgeAnnotation()!!
return ObjCMethodInfo(
descriptor = this,
bridge = bridge,
selector = methodAnnotation.getStringValue("selector"),
encoding = bridgeAnnotation.getStringValue("encoding"),
imp = bridgeAnnotation.getStringValue("imp")
)
}
/**
* @param onlyExternal indicates whether to accept overriding methods from Kotlin classes
*/
private fun FunctionDescriptor.getObjCMethodInfo(onlyExternal: Boolean): ObjCMethodInfo? {
if (this.kind.isReal) {
this.decodeObjCMethodAnnotation()?.let { return it }
if (onlyExternal) {
return null
}
}
return this.overriddenDescriptors.asSequence().mapNotNull { it.getObjCMethodInfo(onlyExternal) }.firstOrNull()
}
fun FunctionDescriptor.getExternalObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = true)
fun FunctionDescriptor.getObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = false)
/**
* Describes method overriding rules for Objective-C methods.
*
* This class is applied at [org.jetbrains.kotlin.resolve.OverridingUtil] as configured with
* `META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition` resource.
*/
class ObjCOverridabilityCondition : ExternalOverridabilityCondition {
override fun getContract() = ExternalOverridabilityCondition.Contract.BOTH
override fun isOverridable(
superDescriptor: CallableDescriptor,
subDescriptor: CallableDescriptor,
subClassDescriptor: ClassDescriptor?
): ExternalOverridabilityCondition.Result {
assert(superDescriptor.name == subDescriptor.name)
val superClass = superDescriptor.containingDeclaration as? ClassDescriptor
if (superClass == null || !superClass.isObjCClass()) {
return ExternalOverridabilityCondition.Result.UNKNOWN
}
return if (areSelectorsEqual(superDescriptor, subDescriptor)) {
ExternalOverridabilityCondition.Result.OVERRIDABLE
} else {
ExternalOverridabilityCondition.Result.INCOMPATIBLE
}
}
private fun areSelectorsEqual(first: CallableDescriptor, second: CallableDescriptor): Boolean {
// The original Objective-C method selector is represented as
// function name and parameter names (except first).
if (first.valueParameters.size != second.valueParameters.size) {
return false
}
first.valueParameters.forEachIndexed { index, parameter ->
if (index > 0 && parameter.name != second.valueParameters[index].name) {
return false
}
}
return true
}
}
@@ -16,7 +16,7 @@
package org.jetbrains.kotlin.backend.konan.descriptors
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.llvm.functionName
import org.jetbrains.kotlin.backend.konan.llvm.localHash
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
@@ -35,9 +35,15 @@ internal class OverriddenFunctionDescriptor(val descriptor: FunctionDescriptor,
get() = descriptor.target.bridgeDirectionsTo(overriddenDescriptor)
val canBeCalledVirtually: Boolean
get() {
if (overriddenDescriptor.isObjCClassMethod()) {
return descriptor.canObjCClassMethodBeCalledVirtually(this.overriddenDescriptor)
}
// We check that either method is open, or one of declarations it overrides is open.
get() = overriddenDescriptor.isOverridable
|| DescriptorUtils.getAllOverriddenDeclarations(overriddenDescriptor).any { it.isOverridable }
return (overriddenDescriptor.isOverridable
|| DescriptorUtils.getAllOverriddenDeclarations(overriddenDescriptor).any { it.isOverridable })
}
val inheritsBridge: Boolean
get() = !descriptor.kind.isReal
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.builtins.getFunctionalClassKind
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -308,3 +309,9 @@ internal val FunctionDescriptor.needsInlining: Boolean
internal val FunctionDescriptor.needsSerializedIr: Boolean
get() = (this.needsInlining && this.isExported())
fun AnnotationDescriptor.getStringValue(name: String): String {
val constantValue = this.allValueArguments.entries.single {
it.key.asString() == name
}.value
return constantValue.value as String
}
@@ -49,6 +49,18 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym
val interopCPointerGetRawValue = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cPointerGetRawValue)
val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject)
val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass)
val interopObjCObjectInitFromPtr =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitFromPtr)
val interopObjCObjectInitFrom = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitFrom)
val interopObjCObjectRawValueGetter =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr.getter!!)
val getNativeNullPtr = symbolTable.referenceSimpleFunction(context.builtIns.getNativeNullPtr)
val boxFunctions = ValueType.values().associate {
@@ -18,7 +18,10 @@ package org.jetbrains.kotlin.backend.konan.llvm
import llvm.LLVMTypeRef
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract
import org.jetbrains.kotlin.backend.konan.descriptors.isUnit
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
import org.jetbrains.kotlin.backend.konan.isExternalObjCClass
import org.jetbrains.kotlin.backend.konan.isValueType
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
@@ -148,8 +151,14 @@ private val FunctionDescriptor.signature: String
// TODO: rename to indicate that it has signature included
internal val FunctionDescriptor.functionName: String
get() = with(this.original) { // basic support for generics
"$name$signature"
get() {
with(this.original) { // basic support for generics
this.getObjCMethodInfo()?.let {
return "objc:${it.selector}"
}
return "$name$signature"
}
}
internal val FunctionDescriptor.symbolName: String
@@ -228,3 +237,6 @@ internal val ClassDescriptor.objectInstanceFieldSymbolName: String
return "kobjref:$fqNameSafe"
}
internal val ClassDescriptor.typeInfoHasVtableAttached: Boolean
get() = !this.isAbstract() && !this.isExternalObjCClass()
@@ -376,6 +376,33 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
return LLVMBuildLandingPad(builder, landingpadType, personalityFunction, numClauses, name)!!
}
inline fun ifThenElse(
condition: LLVMValueRef,
thenValue: LLVMValueRef,
elseBlock: () -> LLVMValueRef
): LLVMValueRef {
val resultType = thenValue.type
val bbExit = basicBlock(locationInfo = position())
val resultPhi = appendingTo(bbExit) {
phi(resultType)
}
val bbElse = basicBlock(locationInfo = position())
condBr(condition, bbExit, bbElse)
assignPhis(resultPhi to thenValue)
appendingTo(bbElse) {
val elseValue = elseBlock()
br(bbExit)
assignPhis(resultPhi to elseValue)
}
positionAtEnd(bbExit)
return resultPhi
}
internal fun debugLocation(locationInfo: LocationInfo): DILocationRef? {
if (!context.shouldContainDebugInfo()) return null
update(currentBlock, locationInfo)
@@ -326,6 +326,9 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val throwExceptionFunction = importRtFunction("ThrowException")
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
private val personalityFunctionName = when (context.config.targetManager.target) {
KonanTarget.MINGW -> "__gxx_personality_seh0"
else -> "__gxx_personality_v0"
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
@@ -156,6 +157,8 @@ internal fun verifyModule(llvmModule: LLVMModuleRef, current: String = "") {
internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
val generator = RTTIGenerator(context)
val kotlinObjCClassInfoGenerator = KotlinObjCClassInfoGenerator(context)
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
@@ -163,12 +166,17 @@ internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
override fun visitClass(declaration: IrClass) {
super.visitClass(declaration)
if (declaration.descriptor.isIntrinsic) {
val descriptor = declaration.descriptor
if (descriptor.isIntrinsic) {
// do not generate any code for intrinsic classes as they require special handling
return
}
generator.generate(declaration.descriptor)
generator.generate(descriptor)
if (descriptor.isKotlinObjCClass()) {
kotlinObjCClassInfoGenerator.generate(descriptor)
}
}
}
@@ -734,7 +742,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
functionGenerationContext.condBr(condition, bbExit, bbInit)
functionGenerationContext.positionAtEnd(bbInit)
val typeInfo = codegen.typeInfoValue(value.descriptor)
val typeInfo = typeInfoForAllocation(value.descriptor)
val initFunction = value.descriptor.constructors.first { it.valueParameters.size == 0 }
val ctor = codegen.llvmFunction(initFunction)
val args = listOf(objectPtr, typeInfo, ctor)
@@ -1350,13 +1358,32 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
private fun fieldPtrOfClass(thisPtr: LLVMValueRef, value: PropertyDescriptor): LLVMValueRef {
val fieldInfo = context.llvmDeclarations.forField(value)
val classDescriptor = value.containingDeclaration as ClassDescriptor
val typePtr = pointerType(fieldInfo.classBodyType)
val objectPtr = LLVMBuildGEP(functionGenerationContext.builder, thisPtr, cValuesOf(kImmOne), 1, "")
val typedObjPtr = functionGenerationContext.bitcast(typePtr, objectPtr!!)
val fieldPtr = LLVMBuildStructGEP(functionGenerationContext.builder, typedObjPtr, fieldInfo.index, "")
val bodyPtr = getObjectBodyPtr(classDescriptor, thisPtr)
val typedBodyPtr = functionGenerationContext.bitcast(typePtr, bodyPtr)
val fieldPtr = LLVMBuildStructGEP(functionGenerationContext.builder, typedBodyPtr, fieldInfo.index, "")
return fieldPtr!!
}
private fun getObjectBodyPtr(classDescriptor: ClassDescriptor, objectPtr: LLVMValueRef): LLVMValueRef {
return if (classDescriptor.isObjCClass()) {
assert(classDescriptor.isKotlinObjCClass())
val objCPtr = callDirect(context.interopBuiltIns.objCPointerHolderValue.getter!!,
listOf(objectPtr), Lifetime.IRRELEVANT)
val objCDeclarations = context.llvmDeclarations.forClass(classDescriptor).objCDeclarations!!
val bodyOffset = functionGenerationContext.load(objCDeclarations.bodyOffsetGlobal.llvmGlobal)
functionGenerationContext.gep(objCPtr, bodyOffset)
} else {
LLVMBuildGEP(functionGenerationContext.builder, objectPtr, cValuesOf(kImmOne), 1, "")!!
}
}
//-------------------------------------------------------------------------//
private fun evaluateConst(value: IrConst<*>): LLVMValueRef {
@@ -1871,7 +1898,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
functionGenerationContext.allocArray(codegen.typeInfoValue(constructedClass), count = kImmZero,
lifetime = resultLifetime(callee))
} else {
functionGenerationContext.allocInstance(codegen.typeInfoValue(constructedClass), resultLifetime(callee))
functionGenerationContext.allocInstance(typeInfoForAllocation(constructedClass), resultLifetime(callee))
}
evaluateSimpleFunctionCall(callee.descriptor,
listOf(thisValue) + args, Lifetime.IRRELEVANT /* constructor doesn't return anything */)
@@ -1879,6 +1906,15 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
}
private fun typeInfoForAllocation(constructedClass: ClassDescriptor): LLVMValueRef {
val descriptorForTypeInfo = if (constructedClass.isObjCClass()) {
context.interopBuiltIns.objCPointerHolder
} else {
constructedClass
}
return codegen.typeInfoValue(descriptorForTypeInfo)
}
//-------------------------------------------------------------------------//
private fun evaluateIntrinsicCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
@@ -1934,10 +1970,122 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
LLVMBuildSExt(functionGenerationContext.builder, intPtrValue, resultType, "")!!
}
}
interop.objCObjectInitFromPtr -> {
genObjCObjectInitFromPtr(args)
}
interop.getObjCReceiverOrSuper -> {
genGetObjCReceiverOrSuper(args)
}
interop.getObjCClass -> {
val typeArgument = callee.getTypeArgument(descriptor.typeParameters.single())
val classDescriptor = TypeUtils.getClassDescriptor(typeArgument!!)!!
genGetObjCClass(classDescriptor)
}
interop.getObjCMessenger -> {
genGetObjCMessenger(args, isLU = false)
}
interop.getObjCMessengerLU -> {
genGetObjCMessenger(args, isLU = true)
}
else -> TODO(callee.descriptor.original.toString())
}
}
private fun genGetObjCClass(classDescriptor: ClassDescriptor): LLVMValueRef {
assert(!classDescriptor.isInterface)
return if (classDescriptor.isExternalObjCClass()) {
val lookUpFunction = context.llvm.externalFunction("objc_lookUpClass",
functionType(int8TypePtr, false, int8TypePtr))
call(lookUpFunction,
listOf(codegen.staticData.cStringLiteral(classDescriptor.name.asString()).llvm))
} else {
val objCDeclarations = context.llvmDeclarations.forClass(classDescriptor).objCDeclarations!!
val classPointerGlobal = objCDeclarations.classPointerGlobal.llvmGlobal
val gen = functionGenerationContext
val storedClass = gen.load(classPointerGlobal)
val storedClassIsNotNull = gen.icmpNe(storedClass, kNullInt8Ptr)
return gen.ifThenElse(storedClassIsNotNull, storedClass) {
val newClass = call(context.llvm.createKotlinObjCClass,
listOf(objCDeclarations.classInfoGlobal.llvmGlobal))
gen.store(newClass, classPointerGlobal)
newClass
}
}
}
private fun genGetObjCMessenger(args: List<LLVMValueRef>, isLU: Boolean): LLVMValueRef {
val gen = functionGenerationContext
// 'LU' means "large or unaligned".
// objc_msgSend*_stret functions must be used when return value is returned through memory
// pointed by implicit argument, which is passed on the register that would otherwise be used for receiver.
// On aarch64 it is never the case, since such implicit argument gets passed on x8.
// On x86_64 it is the case if the return value takes more than 16 bytes or is the structure with
// unaligned fields (there are some complicated exceptions currently ignored). The latter condition
// is "encoded" by stub generator by emitting either `getMessenger` or `getMessengerLU` intrinsic call.
val isStret = when (context.config.targetManager.target) {
KonanTarget.MACBOOK, KonanTarget.IPHONE_SIM -> isLU // x86_64
KonanTarget.IPHONE -> false // aarch64
else -> TODO()
}
val messengerNameSuffix = if (isStret) "_stret" else ""
val functionType = functionType(int8TypePtr, true, int8TypePtr, int8TypePtr)
val normalMessenger = context.llvm.externalFunction("objc_msgSend$messengerNameSuffix", functionType)
val superMessenger = context.llvm.externalFunction("objc_msgSendSuper$messengerNameSuffix", functionType)
val superClass = args.single()
val messenger = LLVMBuildSelect(gen.builder,
If = gen.icmpEq(superClass, kNullInt8Ptr),
Then = normalMessenger,
Else = superMessenger,
Name = ""
)!!
return gen.bitcast(int8TypePtr, messenger)
}
private fun genObjCObjectInitFromPtr(args: List<LLVMValueRef>): LLVMValueRef {
return callDirect(context.interopBuiltIns.objCPointerHolder.unsubstitutedPrimaryConstructor!!,
args, Lifetime.IRRELEVANT)
}
private fun genGetObjCReceiverOrSuper(args: List<LLVMValueRef>): LLVMValueRef {
val gen = functionGenerationContext
assert(args.size == 2)
val receiver = args[0]
val superClass = args[1]
val superClassIsNull = gen.icmpEq(superClass, kNullInt8Ptr)
return gen.ifThenElse(superClassIsNull, receiver) {
val structType = structType(kInt8Ptr, kInt8Ptr)
val ptr = gen.alloca(structType)
gen.store(receiver,
LLVMBuildGEP(gen.builder, ptr, cValuesOf(kImmZero, kImmZero), 2, "")!!)
gen.store(superClass,
LLVMBuildGEP(gen.builder, ptr, cValuesOf(kImmZero, kImmOne), 2, "")!!)
gen.bitcast(int8TypePtr, ptr)
}
}
//-------------------------------------------------------------------------//
private val kImmZero = LLVMConstInt(LLVMInt32Type(), 0, 1)!!
private val kImmOne = LLVMConstInt(LLVMInt32Type(), 1, 1)!!
@@ -2008,11 +2156,16 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
fun callVirtual(descriptor: FunctionDescriptor, args: List<LLVMValueRef>,
resultLifetime: Lifetime): LLVMValueRef {
assert(LLVMTypeOf(args[0]) == codegen.kObjHeaderPtr)
val typeInfoPtrPtr = LLVMBuildStructGEP(functionGenerationContext.builder, args[0], 0 /* type_info */, "")!!
val typeInfoPtr = functionGenerationContext.load(typeInfoPtrPtr)
assert (typeInfoPtr.type == codegen.kTypeInfoPtr)
val owner = descriptor.containingDeclaration as ClassDescriptor
val typeInfoPtr: LLVMValueRef = if (owner.isObjCClass()) {
call(context.llvm.getObjCKotlinTypeInfo, listOf(args.first()))
} else {
val typeInfoPtrPtr = LLVMBuildStructGEP(functionGenerationContext.builder, args[0], 0 /* type_info */, "")!!
functionGenerationContext.load(typeInfoPtrPtr)
}
assert (typeInfoPtr.type == codegen.kTypeInfoPtr)
val llvmMethod = if (!owner.isInterface) {
// If this is a virtual method of the class - we can call via vtable.
val index = context.getVtableBuilder(owner).vtableIndex(descriptor)
@@ -2068,6 +2221,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val constructedClass = functionGenerationContext.constructedClass!!
val thisPtr = currentCodeContext.genGetValue(constructedClass.thisAsReceiverParameter)
if (constructedClass.isObjCClass()) {
return codegen.theUnitInstanceRef.llvm
}
val thisPtrArgType = codegen.getLLVMType(descriptor.allParameters[0].type)
val thisPtrArg = if (thisPtr.type == thisPtrArgType) {
thisPtr
@@ -0,0 +1,94 @@
/*
* 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.backend.konan.llvm
import llvm.LLVMStoreSizeOfType
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.ObjCMethodInfo
import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.isFinalClass
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
internal class KotlinObjCClassInfoGenerator(override val context: Context) : ContextUtils {
fun generate(descriptor: ClassDescriptor) {
assert(descriptor.isFinalClass)
val objCLLvmDeclarations = context.llvmDeclarations.forClass(descriptor).objCDeclarations!!
val instanceMethods = descriptor.generateMethodDescs()
val companionObjectDescriptor = descriptor.companionObjectDescriptor
val classMethods = companionObjectDescriptor?.generateMethodDescs() ?: emptyList()
val superclassName = descriptor.getSuperClassNotAny()!!.name.asString()
val protocolNames = descriptor.getSuperInterfaces().map { it.name.asString().removeSuffix("Protocol") }
val bodySize =
LLVMStoreSizeOfType(llvmTargetData, context.llvmDeclarations.forClass(descriptor).bodyType).toInt()
val className = if (descriptor.isExported()) {
staticData.cStringLiteral(descriptor.fqNameSafe.asString())
} else {
NullPointer(int8Type) // Generate as anonymous.
}
val info = Struct(runtime.kotlinObjCClassInfo,
className,
staticData.cStringLiteral(superclassName),
staticData.placeGlobalConstArray("", int8TypePtr,
protocolNames.map { staticData.cStringLiteral(it) } + NullPointer(int8Type)),
staticData.placeGlobalConstArray("", runtime.objCMethodDescription, instanceMethods),
Int32(instanceMethods.size),
staticData.placeGlobalConstArray("", runtime.objCMethodDescription, classMethods),
Int32(classMethods.size),
Int32(bodySize),
objCLLvmDeclarations.bodyOffsetGlobal.pointer,
descriptor.typeInfoPtr,
companionObjectDescriptor?.typeInfoPtr ?: NullPointer(runtime.typeInfoType),
objCLLvmDeclarations.classPointerGlobal.pointer
)
objCLLvmDeclarations.classInfoGlobal.setInitializer(info)
objCLLvmDeclarations.classPointerGlobal.setInitializer(NullPointer(int8Type))
objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0))
}
private fun generateMethodDesc(info: ObjCMethodInfo): ConstValue {
return Struct(runtime.objCMethodDescription,
staticData.cStringLiteral(info.selector),
staticData.cStringLiteral(info.encoding),
constPointer(context.llvm.externalFunction(info.imp, functionType(voidType))).bitcast(int8TypePtr)
)
}
private fun ClassDescriptor.generateMethodDescs(): List<ConstValue> =
this.unsubstitutedMemberScope.contributedMethods.filter {
it.kind.isReal && it !is ConstructorDescriptor
}.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) }
}
@@ -21,6 +21,7 @@ import llvm.*
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.isKotlinObjCClass
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.serialization.deserialization.descriptors.*
import org.jetbrains.kotlin.ir.IrElement
@@ -75,10 +76,17 @@ internal class ClassLlvmDeclarations(
val fields: List<PropertyDescriptor>, // TODO: it is not an LLVM declaration.
val typeInfoGlobal: StaticData.Global,
val typeInfo: ConstPointer,
val singletonDeclarations: SingletonLlvmDeclarations?)
val singletonDeclarations: SingletonLlvmDeclarations?,
val objCDeclarations: KotlinObjCClassLlvmDeclarations?)
internal class SingletonLlvmDeclarations(val instanceFieldRef: LLVMValueRef)
internal class KotlinObjCClassLlvmDeclarations(
val classPointerGlobal: StaticData.Global,
val classInfoGlobal: StaticData.Global,
val bodyOffsetGlobal: StaticData.Global
)
internal class FunctionLlvmDeclarations(val llvmFunction: LLVMValueRef)
internal class FieldLlvmDeclarations(val index: Int, val classBodyType: LLVMTypeRef)
@@ -236,7 +244,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
"ktype:$internalName"
}
if (!descriptor.isAbstract()) {
if (descriptor.typeInfoHasVtableAttached) {
// Create the special global consisting of TypeInfo and vtable.
val typeInfoGlobalName = "ktypeglobal:$internalName"
@@ -273,7 +281,14 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
null
}
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, typeInfoPtr, singletonDeclarations)
val objCDeclarations = if (descriptor.isKotlinObjCClass()) {
createKotlinObjCClassDeclarations(descriptor)
} else {
null
}
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, typeInfoPtr,
singletonDeclarations, objCDeclarations)
}
private fun createSingletonDeclarations(
@@ -299,6 +314,23 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
return SingletonLlvmDeclarations(instanceFieldRef)
}
private fun createKotlinObjCClassDeclarations(descriptor: ClassDescriptor): KotlinObjCClassLlvmDeclarations {
val internalName = qualifyInternalName(descriptor)
val classPointerGlobal = staticData.createGlobal(int8TypePtr, "kobjcclassptr:$internalName")
val classInfoGlobal = staticData.createGlobal(
context.llvm.runtime.kotlinObjCClassInfo,
"kobjcclassinfo:$internalName"
).apply {
setConstant(true)
}
val bodyOffsetGlobal = staticData.createGlobal(int32Type, "kobjcbodyoffs:$internalName")
return KotlinObjCClassLlvmDeclarations(classPointerGlobal, classInfoGlobal, bodyOffsetGlobal)
}
override fun visitField(declaration: IrField) {
super.visitField(declaration)
@@ -330,6 +362,8 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
override fun visitFunction(declaration: IrFunction) {
super.visitFunction(declaration)
if (!declaration.descriptor.kind.isReal) return
val descriptor = declaration.descriptor
val llvmFunctionType = getLlvmFunctionType(descriptor)
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.backend.konan.llvm
import llvm.*
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.isExternalObjCClassMethod
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.FqName
@@ -166,11 +167,18 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
val typeInfoGlobalValue = if (classDesc.isAbstract()) {
val typeInfoGlobalValue = if (!classDesc.typeInfoHasVtableAttached) {
typeInfo
} else {
// TODO: compile-time resolution limits binary compatibility
val vtableEntries = context.getVtableBuilder(classDesc).vtableEntries.map { it.implementation.entryPointAddress }
val vtableEntries = context.getVtableBuilder(classDesc).vtableEntries.map {
val implementation = it.implementation
if (implementation.isExternalObjCClassMethod()) {
NullPointer(int8Type)
} else {
implementation.entryPointAddress
}
}
val vtable = ConstArray(int8TypePtr, vtableEntries)
Struct(typeInfo, vtable)
}
@@ -53,4 +53,7 @@ class Runtime(bitcodeFile: String) {
val dataLayout = LLVMGetDataLayout(llvmModule)!!.toKString()
val targetData = LLVMCreateTargetData(dataLayout)!!
val kotlinObjCClassInfo by lazy { getStructType("KotlinObjCClassInfo") }
val objCMethodDescription by lazy { getStructType("ObjCMethodDescription") }
}
@@ -152,6 +152,10 @@ internal class StaticData(override val context: Context): ContextUtils {
}
private val stringLiterals = mutableMapOf<String, ConstPointer>()
private val cStringLiterals = mutableMapOf<String, ConstPointer>()
fun cStringLiteral(value: String) =
cStringLiterals.getOrPut(value) { placeCStringLiteral(value) }
fun kotlinStringLiteral(type: KotlinType, value: IrConst<String>) =
stringLiterals.getOrPut(value.value) { createKotlinStringLiteral(type, value) }
@@ -41,3 +41,9 @@ internal fun StaticData.createAlias(name: String, aliasee: ConstPointer): ConstP
val alias = LLVMAddAlias(context.llvmModule, aliasee.llvmType, aliasee.llvm, name)!!
return constPointer(alias)
}
internal fun StaticData.placeCStringLiteral(value: String): ConstPointer {
val chars = value.toByteArray(Charsets.UTF_8).map { Int8(it) } + Int8(0)
return placeGlobalConstArray("", int8Type, chars)
}
@@ -20,40 +20,282 @@ import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.ValueType
import org.jetbrains.kotlin.backend.konan.isRepresentedAs
import org.jetbrains.kotlin.backend.konan.reportCompilationError
import org.jetbrains.kotlin.backend.common.lower.irBlock
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.descriptors.getStringValue
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.ir.builders.IrBuilder
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.getArguments
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
internal class InteropLoweringPart1(val context: Context) : IrElementTransformerVoid(), FileLoweringPass {
internal class InteropLoweringPart1(val context: Context) : IrBuildingTransformer(context), FileLoweringPass {
private val symbols get() = context.ir.symbols
private val symbolTable get() = symbols.symbolTable
lateinit var currentFile: IrFile
override fun lower(irFile: IrFile) {
currentFile = irFile
irFile.transformChildrenVoid(this)
}
private fun IrBuilderWithScope.callAlloc(classSymbol: IrClassSymbol): IrExpression {
return irCall(symbols.interopAllocObjCObject, listOf(classSymbol.descriptor.defaultType)).apply {
putValueArgument(0, getObjCClass(classSymbol))
}
}
private fun IrBuilderWithScope.getObjCClass(classSymbol: IrClassSymbol): IrExpression {
val classDescriptor = classSymbol.descriptor
assert(!classDescriptor.isObjCMetaClass())
if (classDescriptor.isExternalObjCClass()) {
val companionObject = classDescriptor.companionObjectDescriptor!!
if (companionObject.unsubstitutedPrimaryConstructor != scope.scopeOwner) {
// Optimization: get class pointer from companion object thus avoiding lookup by name.
return irCall(symbols.interopObjCObjectRawValueGetter).apply {
extensionReceiver = irGetObject(symbolTable.referenceClass(companionObject))
}
}
}
return irCall(symbols.interopGetObjCClass, listOf(classDescriptor.defaultType))
}
private val outerClasses = mutableListOf<IrClass>()
override fun visitClass(declaration: IrClass): IrStatement {
if (declaration.descriptor.isKotlinObjCClass()) {
checkKotlinObjCClass(declaration)
}
outerClasses.push(declaration)
try {
return super.visitClass(declaration)
} finally {
outerClasses.pop()
}
}
private fun checkKotlinObjCClass(irClass: IrClass) {
val kind = irClass.descriptor.kind
if (kind != ClassKind.CLASS && kind != ClassKind.OBJECT) {
context.reportCompilationError(
"Only classes are supported as subtypes of Objective-C types",
currentFile, irClass
)
}
if (!irClass.descriptor.isFinalClass) {
context.reportCompilationError(
"Non-final Kotlin subclasses of Objective-C classes are not yet supported",
currentFile, irClass
)
}
var hasObjCClassSupertype = false
irClass.descriptor.defaultType.constructor.supertypes.forEach {
val descriptor = it.constructor.declarationDescriptor as ClassDescriptor
if (!descriptor.isObjCClass()) {
context.reportCompilationError(
"Mixing Kotlin and Objective-C supertypes is not supported",
currentFile, irClass
)
}
if (descriptor.kind == ClassKind.CLASS) {
hasObjCClassSupertype = true
}
}
if (!hasObjCClassSupertype) {
context.reportCompilationError(
"Kotlin implementation of Objective-C protocol must have Objective-C superclass (e.g. NSObject)",
currentFile, irClass
)
}
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
expression.transformChildrenVoid()
builder.at(expression)
val constructedClass = outerClasses.peek()!!
val constructedClassDescriptor = constructedClass.descriptor
if (!constructedClassDescriptor.isObjCClass()) {
return expression
}
constructedClassDescriptor.containingDeclaration.let { classContainer ->
if (classContainer is ClassDescriptor && classContainer.isObjCClass() &&
constructedClassDescriptor == classContainer.companionObjectDescriptor) {
val outerConstructedClass = outerClasses[outerClasses.lastIndex - 1]
assert (outerConstructedClass.descriptor == classContainer)
assert(expression.getArguments().isEmpty())
return builder.irBlock(expression) {
+expression // Required for the IR to be valid, will be ignored in codegen.
+irCall(symbols.interopObjCObjectInitFromPtr).apply {
extensionReceiver = irGet(constructedClass.thisReceiver!!.symbol)
putValueArgument(0, getObjCClass(outerConstructedClass.symbol))
}
}
}
}
if (!constructedClassDescriptor.isExternalObjCClass() &&
expression.descriptor.constructedClass.isExternalObjCClass()) {
// Calling super constructor from Kotlin Objective-C class.
assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass)
val initMethod = getObjCInitMethod(expression.descriptor)!!
val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
assert(expression.dispatchReceiver == null)
assert(expression.extensionReceiver == null)
val initCall = builder.genLoweredObjCMethodCall(
initMethodInfo,
superQualifier = symbolTable.referenceClass(expression.descriptor.constructedClass),
receiver = builder.callAlloc(constructedClass.symbol),
arguments = initMethod.valueParameters.map { expression.getValueArgument(it)!! }
)
val superConstructor = symbolTable.referenceConstructor(
expression.descriptor.constructedClass.constructors.single { it.valueParameters.size == 0 }
)
return builder.irBlock(expression) {
// Required for the IR to be valid, will be ignored in codegen:
+IrDelegatingConstructorCallImpl(startOffset, endOffset, superConstructor, superConstructor.descriptor)
+irCall(symbols.interopObjCObjectInitFrom).apply {
extensionReceiver = irGet(constructedClass.thisReceiver!!.symbol)
putValueArgument(0, initCall)
}
}
}
return expression
}
private fun IrBuilderWithScope.genLoweredObjCMethodCall(info: ObjCMethodInfo, superQualifier: IrClassSymbol?,
receiver: IrExpression, arguments: List<IrExpression>): IrExpression {
val superClass = superQualifier?.let { getObjCClass(it) } ?:
irCall(symbols.getNativeNullPtr)
val bridge = symbolTable.referenceSimpleFunction(info.bridge)
return irCall(bridge).apply {
putValueArgument(0, superClass)
putValueArgument(1, receiver)
assert(arguments.size + 2 == info.bridge.valueParameters.size)
arguments.forEachIndexed { index, argument ->
putValueArgument(index + 2, argument)
}
}
}
private fun getObjCInitMethod(descriptor: ConstructorDescriptor): FunctionDescriptor? {
return descriptor.annotations.findAnnotation(FqName("kotlinx.cinterop.ObjCConstructor"))?.let {
val initSelector = it.getStringValue("initSelector")
descriptor.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
.filterIsInstance<FunctionDescriptor>()
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
}
}
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid()
return when (expression.descriptor.original) {
val descriptor = expression.descriptor.original
if (descriptor is ConstructorDescriptor) {
val initMethod = getObjCInitMethod(descriptor)
if (initMethod != null) {
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
assert(expression.extensionReceiver == null)
assert(expression.dispatchReceiver == null)
builder.at(expression)
return builder.genLoweredObjCMethodCall(
initMethod.getExternalObjCMethodInfo()!!,
superQualifier = null,
receiver = builder.callAlloc(symbolTable.referenceClass(descriptor.constructedClass)),
arguments = arguments
)
}
}
descriptor.getExternalObjCMethodInfo()?.let { methodInfo ->
val isInteropStubsFile =
currentFile.fileAnnotations.any { it.fqName == FqName("kotlinx.cinterop.InteropStubs") }
// Special case: bridge from Objective-C method implementation template to Kotlin method;
// handled in CodeGeneratorVisitor.callVirtual.
val useKotlinDispatch = isInteropStubsFile &&
builder.scope.scopeOwner.annotations.hasAnnotation(FqName("konan.internal.ExportForCppRuntime"))
if (!useKotlinDispatch) {
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
assert(expression.extensionReceiver == null)
if (expression.superQualifier?.isObjCMetaClass() == true) {
context.reportCompilationError(
"Super calls to Objective-C meta classes are not supported yet",
currentFile, expression
)
}
if (expression.superQualifier?.isInterface == true) {
context.reportCompilationError(
"Super calls to Objective-C protocols are not allowed",
currentFile, expression
)
}
builder.at(expression)
return builder.genLoweredObjCMethodCall(
methodInfo,
superQualifier = expression.superQualifierSymbol,
receiver = expression.dispatchReceiver!!,
arguments = arguments
)
}
}
return when (descriptor) {
context.interopBuiltIns.typeOf -> {
val typeArgument = expression.getSingleTypeArgument()
val classDescriptor = TypeUtils.getClassDescriptor(typeArgument)
@@ -65,8 +307,8 @@ internal class InteropLoweringPart1(val context: Context) : IrElementTransformer
error("native variable class $classDescriptor must have the companion object")
IrGetObjectValueImpl(
expression.startOffset, expression.endOffset,
companionObjectDescriptor.defaultType, companionObjectDescriptor
expression.startOffset, expression.endOffset, companionObjectDescriptor.defaultType,
symbolTable.referenceClass(companionObjectDescriptor)
)
}
}
+3 -3
View File
@@ -37,7 +37,7 @@ libffiDir.osx = libffi-3.2.1-2-darwin-macos
llvmLtoFlags.osx =
llvmLtoOptFlags.osx = -O3 -function-sections
llvmLtoNooptFlags.osx = -O1
linkerKonanFlags.osx = -lc++
linkerKonanFlags.osx = -lc++ -lobjc
linkerOptimizationFlags.osx = -dead_strip
linkerDebugFlags.osx = -S
osVersionMinFlagLd.osx = -macosx_version_min
@@ -63,7 +63,7 @@ llvmLtoFlags.ios =
llvmLtoOptFlags.ios = -O3 -function-sections
linkerDebugFlags.ios = -S
llvmLtoNooptFlags.ios = -O1
linkerKonanFlags.ios = -lc++
linkerKonanFlags.ios = -lc++ -lobjc
linkerOptimizationFlags.ios = -dead_strip
osVersionMinFlagLd.ios = -iphoneos_version_min
osVersionMinFlagClang.ios = -miphoneos-version-min
@@ -83,7 +83,7 @@ libffiDir.ios_sim = libffi-3.2.1-2-darwin-ios-sim
llvmLtoFlags.ios_sim =
llvmLtoOptFlags.ios_sim = -O3 -function-sections
llvmLtoNooptFlags.ios_sim = -O1
linkerKonanFlags.ios_sim = -lc++
linkerKonanFlags.ios_sim = -lc++ -lobjc
linkerOptimizationFlags.ios_sim = -dead_strip
linkerDebugFlags.ios_sim = -S
osVersionMinFlagLd.ios_sim = -ios_simulator_version_min
+22
View File
@@ -1995,6 +1995,13 @@ kotlinNativeInterop {
linkerOpts '-framework', 'OpenGL', '-framework', 'GLUT'
flavor 'native'
}
objcSmoke {
defFile 'interop/objc/objcSmoke.def'
headers "$projectDir/interop/objc/smoke.h"
linkerOpts "-L$buildDir", "-lobjcsmoke"
flavor 'native'
}
}
}
@@ -2057,4 +2064,19 @@ if (isMac()) {
source = "interop/basics/opengl_teapot.kt"
interop = 'opengl'
}
task interop_objc_smoke(type: RunInteropKonanTest) {
goldValue = "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\nDeallocated\nDeallocated\n"
source = "interop/objc/smoke.kt"
interop = 'objcSmoke'
doFirst {
execClang {
args "$projectDir/interop/objc/smoke.m"
args "-lobjc", '-fobjc-arc'
args '-fPIC', '-shared', '-o', "$buildDir/libobjcsmoke.dylib"
}
}
}
}
@@ -0,0 +1,2 @@
language = Objective-C
headerFilter = **/smoke.h
+24
View File
@@ -0,0 +1,24 @@
#import <objc/NSObject.h>
@protocol Printer
@required
-(void)print:(const char*)string;
@end;
@interface Foo : NSObject
@property NSString* name;
-(void)hello;
-(void)helloWithPrinter:(id <Printer>)printer;
@end;
@protocol MutablePair
@required
@property (readonly) int first;
@property (readonly) int second;
-(void)update:(int)index add:(int)delta;
-(void)update:(int)index sub:(int)delta;
@end;
void replacePairElements(id <MutablePair> pair, int first, int second);
@@ -0,0 +1,55 @@
import kotlinx.cinterop.*
import objcSmoke.*
fun main(args: Array<String>) {
autoreleasepool {
run()
}
}
fun run() {
val foo = Foo()
foo.hello()
foo.name = "everybody"
foo.helloWithPrinter(object : NSObject(), PrinterProtocol {
override fun print(string: CPointer<ByteVar>?) {
println("Kotlin says: " + string?.toKString())
}
})
Bar().hello()
val pair = MutablePairImpl(42, 17)
replacePairElements(pair, 1, 2)
pair.swap()
println("${pair.first}, ${pair.second}")
}
fun MutablePairProtocol.swap() {
update(0, add = second)
update(1, sub = first)
update(0, add = second)
update(1, sub = second*2)
}
class Bar : Foo() {
override fun helloWithPrinter(printer: PrinterProtocol) = memScoped {
printer.print("Hello from Kotlin".cstr.getPointer(memScope))
}
}
@Suppress("CONFLICTING_OVERLOADS")
class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol {
private var elements = intArrayOf(first, second)
override fun first() = elements.first()
override fun second() = elements.last()
override fun update(index: Int, add: Int) {
elements[index] += add
}
override fun update(index: Int, sub: Int) {
elements[index] -= sub
}
}
+46
View File
@@ -0,0 +1,46 @@
#import <stdio.h>
#import <Foundation/NSString.h>
#import "smoke.h"
@interface CPrinter : NSObject <Printer>
-(void)print:(const char*)string;
@end;
@implementation CPrinter
-(void)print:(const char*)string {
printf("%s\n", string);
fflush(stdout);
}
@end;
@implementation Foo
@synthesize name;
-(instancetype)init {
if (self = [super init]) {
self.name = @"World";
}
return self;
}
-(void)hello {
CPrinter* printer = [[CPrinter alloc] init];
[self helloWithPrinter:printer];
}
-(void)helloWithPrinter:(id <Printer>)printer {
NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name];
[printer print:message.UTF8String];
}
-(void)dealloc {
printf("Deallocated\n");
}
@end;
void replacePairElements(id <MutablePair> pair, int first, int second) {
[pair update:0 add:(first - pair.first)];
[pair update:1 sub:(pair.second - second)];
}
@@ -124,7 +124,10 @@ class CompileCppToBitcode extends DefaultTask {
args "-I$headersDir"
args '-c', '-emit-llvm'
args project.fileTree(srcDir).include('**/*.cpp')
args project.fileTree(srcDir) {
include('**/*.cpp')
include('**/*.mm') // Objective-C++
}
}
project.exec {
+38 -5
View File
@@ -350,6 +350,31 @@ ContainerHeader* AllocContainer(size_t size) {
return result;
}
extern "C" {
void objc_release(void* ptr);
}
inline void runDeallocationHooks(ObjHeader* obj) {
#if KONAN_OBJC_INTEROP
if (obj->type_info() == theObjCPointerHolderTypeInfo) {
void* objcPtr = *reinterpret_cast<void**>(obj + 1); // TODO: use more reliable layout description
objc_release(objcPtr);
}
#endif
}
static inline void DeinitInstanceBodyImpl(const TypeInfo* typeInfo, void* body) {
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
ObjHeader** location = reinterpret_cast<ObjHeader**>(
reinterpret_cast<uintptr_t>(body) + typeInfo->objOffsets_[index]);
UpdateRef(location, nullptr);
}
}
void DeinitInstanceBody(const TypeInfo* typeInfo, void* body) {
DeinitInstanceBodyImpl(typeInfo, body);
}
void FreeContainer(ContainerHeader* header) {
RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed");
#if TRACE_MEMORY
@@ -366,13 +391,12 @@ void FreeContainer(ContainerHeader* header) {
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
for (int index = 0; index < header->objectCount_; index++) {
runDeallocationHooks(obj);
const TypeInfo* typeInfo = obj->type_info();
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
ObjHeader** location = reinterpret_cast<ObjHeader**>(
reinterpret_cast<uintptr_t>(obj + 1) + typeInfo->objOffsets_[index]);
UpdateRef(location, nullptr);
}
DeinitInstanceBodyImpl(typeInfo, reinterpret_cast<void*>(obj + 1));
// Object arrays are *special*.
if (typeInfo == theArrayTypeInfo) {
ArrayHeader* array = obj->array();
@@ -399,6 +423,15 @@ void FreeContainerNoRef(ContainerHeader* header) {
#if USE_GC
removeFreeable(memoryState, header);
#endif
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
for (int index = 0; index < header->objectCount_; index++) {
runDeallocationHooks(obj);
obj = reinterpret_cast<ObjHeader*>(
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
}
memoryState->allocCount--;
konanFreeMemory(header);
}
+1
View File
@@ -322,6 +322,7 @@ void DeinitMemory(MemoryState*);
//
OBJ_GETTER(AllocInstance, const TypeInfo* type_info) RUNTIME_NOTHROW;
OBJ_GETTER(AllocArrayInstance, const TypeInfo* type_info, uint32_t elements) RUNTIME_NOTHROW;
void DeinitInstanceBody(const TypeInfo* typeInfo, void* body);
OBJ_GETTER(InitInstance, ObjHeader** location, const TypeInfo* type_info,
void (*ctor)(ObjHeader*));
+241
View File
@@ -0,0 +1,241 @@
/*
* 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.
*/
#if KONAN_OBJC_INTEROP
#include <objc/objc.h>
#include <objc/runtime.h>
#include <objc/message.h>
#include <cstdio>
#include <cstdint>
#include "Memory.h"
#include "Natives.h"
#include "Utils.h"
extern "C" {
struct KotlinClassData {
const TypeInfo* typeInfo;
int32_t bodyOffset;
};
static inline struct KotlinClassData* GetKotlinClassData(Class clazz) {
void* ivars = object_getIndexedIvars(reinterpret_cast<id>(clazz));
return static_cast<struct KotlinClassData*>(ivars);
}
static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) {
GetKotlinClassData(clazz)->typeInfo = typeInfo;
}
const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) {
void* objcPtr = *reinterpret_cast<void * const *>(obj + 1); // TODO: use more reliable layout description
Class clazz = object_getClass(reinterpret_cast<id>(objcPtr));
return GetKotlinClassData(clazz)->typeInfo;
}
id objc_msgSendSuper2(struct objc_super *super, SEL op, ...);
static void DeallocImp(id self, SEL _cmd) {
// TODO: doesn't support overriding Kotlin classes.
Class clazz = object_getClass(self);
RuntimeAssert(clazz != nullptr, "Must not be null");
struct KotlinClassData* classData = GetKotlinClassData(clazz);
void* body = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(self) + classData->bodyOffset);
const TypeInfo* typeInfo = classData->typeInfo;
DeinitInstanceBody(typeInfo, body);
// Call super.dealloc:
struct objc_super s = {self, clazz};
auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2);
messenger(&s, _cmd);
}
static void AddDeallocMethod(Class clazz) {
Class nsObjectClass = objc_getClass("NSObject");
RuntimeAssert(nsObjectClass != nullptr, "NSObject class not found");
SEL deallocSelector = sel_registerName("dealloc");
Method nsObjectDeallocMethod = class_getInstanceMethod(nsObjectClass, deallocSelector);
RuntimeAssert(nsObjectDeallocMethod != nullptr, "[NSObject dealloc] method not found");
const char* nsObjectDeallocMethodTypeEncoding = method_getTypeEncoding(nsObjectDeallocMethod);
RuntimeAssert(nsObjectDeallocMethodTypeEncoding != nullptr, "[NSObject dealloc] method has no encoding provided");
// TODO: something of the above can be cached.
BOOL added = class_addMethod(clazz, deallocSelector, (IMP)DeallocImp, nsObjectDeallocMethodTypeEncoding);
RuntimeAssert(added, "Unable to add dealloc method to Objective-C class");
}
struct ObjCMethodDescription {
const char* selector;
const char* encoding;
const void* imp;
};
struct KotlinObjCClassInfo {
const char* name;
const char* superclassName;
const char** protocolNames;
const struct ObjCMethodDescription* instanceMethods;
int32_t instanceMethodsNum;
const struct ObjCMethodDescription* classMethods;
int32_t classMethodsNum;
int32_t bodySize;
int32_t* bodyOffset;
const TypeInfo* typeInfo;
const TypeInfo* metaTypeInfo;
void** createdClass;
};
static void AddMethods(Class clazz, const struct ObjCMethodDescription* methods, int32_t methodsNum) {
for (int32_t i = 0; i < methodsNum; ++i) {
const struct ObjCMethodDescription* method = &methods[i];
BOOL added = class_addMethod(clazz, sel_registerName(method->selector), (IMP)method->imp, method->encoding);
RuntimeAssert(added == YES, "Unable to add method to Objective-C class");
}
}
static SimpleMutex classCreationMutex;
static int anonymousClassNextId = 0;
void* CreateKotlinObjCClass(const KotlinObjCClassInfo* info) {
LockGuard<SimpleMutex> lockGuard(classCreationMutex);
void* createdClass = *info->createdClass;
if (createdClass != nullptr) {
return createdClass;
}
char classNameBuffer[64];
const char* className = info->name;
if (className == nullptr) {
snprintf(classNameBuffer, sizeof(classNameBuffer), "kobjc%d", anonymousClassNextId++);
className = classNameBuffer;
}
Class superclass = objc_getClass(info->superclassName);
Class newClass = objc_allocateClassPair(superclass, className, sizeof(struct KotlinClassData));
RuntimeAssert(newClass != nullptr, "Failed to allocate Objective-C class");
Class newMetaclass = object_getClass(reinterpret_cast<id>(newClass));
for (size_t i = 0;; ++i) {
const char* protocolName = info->protocolNames[i];
if (protocolName == nullptr) break;
Protocol* proto = objc_getProtocol(protocolName);
if (proto != nullptr) {
BOOL added = class_addProtocol(newClass, proto);
RuntimeAssert(added == YES, "Unable to add protocol to Objective-C class");
added = class_addProtocol(newMetaclass, proto);
RuntimeAssert(added == YES, "Unable to add protocol to Objective-C metaclass");
}
}
AddDeallocMethod(newClass);
AddMethods(newClass, info->instanceMethods, info->instanceMethodsNum);
AddMethods(newMetaclass, info->classMethods, info->classMethodsNum);
SetKotlinTypeInfo(newClass, info->typeInfo);
SetKotlinTypeInfo(newMetaclass, info->metaTypeInfo);
char bodyTypeEncoding[16];
snprintf(bodyTypeEncoding, sizeof(bodyTypeEncoding), "[%dc]", info->bodySize);
BOOL added = class_addIvar(newClass, "kotlinBody", info->bodySize, /* log2(align) = */ 3, bodyTypeEncoding);
RuntimeAssert(added == YES, "Unable to add ivar to Objective-C class");
objc_registerClassPair(newClass);
Ivar body = class_getInstanceVariable(newClass, "kotlinBody");
RuntimeAssert(body != nullptr, "Unable to get ivar added to Objective-C class");
int32_t offset = (int32_t)ivar_getOffset(body);
GetKotlinClassData(newClass)->bodyOffset = offset;
*info->bodyOffset = offset;
*info->createdClass = newClass;
return newClass;
}
void* objc_autoreleasePoolPush();
void objc_autoreleasePoolPop(void* ptr);
id objc_allocWithZone(Class clazz);
id objc_retain(id ptr);
void objc_release(id ptr);
void* Kotlin_objc_autoreleasePoolPush() {
return objc_autoreleasePoolPush();
}
void Kotlin_objc_autoreleasePoolPop(void* ptr) {
objc_autoreleasePoolPop(ptr);
}
id Kotlin_objc_allocWithZone(Class clazz) {
return objc_allocWithZone(clazz);
}
id Kotlin_objc_retain(id ptr) {
return objc_retain(ptr);
}
void Kotlin_objc_release(id ptr) {
objc_release(ptr);
}
} // extern "C"
#else // KONAN_OBJC_INTEROP
#include "Assert.h"
extern "C" {
void* Kotlin_objc_autoreleasePoolPush() {
RuntimeAssert(false, "Objective-C interop is disabled");
return nullptr;
}
void Kotlin_objc_autoreleasePoolPop(void* ptr) {
RuntimeAssert(false, "Objective-C interop is disabled");
}
void* Kotlin_objc_allocWithZone(void* clazz) {
RuntimeAssert(false, "Objective-C interop is disabled");
return nullptr;
}
void* Kotlin_objc_retain(void* ptr) {
RuntimeAssert(false, "Objective-C interop is disabled");
return nullptr;
}
void Kotlin_objc_release(void* ptr) {
RuntimeAssert(false, "Objective-C interop is disabled");
}
} // extern "C"
#endif // KONAN_OBJC_INTEROP
+88
View File
@@ -0,0 +1,88 @@
/*
* 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.
*/
#include "Natives.h"
#if KONAN_OBJC_INTEROP
#import <objc/runtime.h>
#import <Foundation/NSString.h>
namespace {
Class nsStringClass = nullptr;
Class getNSStringClass() {
Class result = nsStringClass;
if (result == nullptr) {
// Lookup dynamically to avoid direct reference to Foundation:
result = objc_getClass("NSString");
RuntimeAssert(result != nullptr, "NSString class not found");
nsStringClass = result;
}
return result;
}
}
extern "C" {
NSString* CreateNSStringFromKString(const ArrayHeader* str) {
if (str == nullptr) {
return nullptr;
}
const KChar* utf16Chars = CharArrayAddressOfElementAt(str, 0);
NSString* result = [[[getNSStringClass() alloc] initWithBytes:utf16Chars
length:str->count_*sizeof(KChar)
encoding:NSUTF16LittleEndianStringEncoding] autorelease];
return result;
}
OBJ_GETTER(CreateKStringFromNSString, NSString* str) {
if (str == nullptr) {
RETURN_OBJ(nullptr);
}
size_t length = [str length];
NSRange range = {0, length};
ArrayHeader* result = AllocArrayInstance(theStringTypeInfo, length, OBJ_RESULT)->array();
KChar* rawResult = CharArrayAddressOfElementAt(result, 0);
[str getCharacters:rawResult range:range];
RETURN_OBJ(result->obj());
}
} // extern "C"
#else // KONAN_OBJC_INTEROP
extern "C" {
void* CreateNSStringFromKString(const ArrayHeader* str) {
RuntimeAssert(false, "Objective-C interop is disabled");
return nullptr;
}
OBJ_GETTER(CreateKStringFromNSString, void* str) {
RuntimeAssert(false, "Objective-C interop is disabled");
RETURN_OBJ(nullptr);
}
} // extern "C"
#endif // KONAN_OBJC_INTEROP
+1
View File
@@ -83,6 +83,7 @@ extern const TypeInfo* theDoubleArrayTypeInfo;
extern const TypeInfo* theBooleanArrayTypeInfo;
extern const TypeInfo* theStringTypeInfo;
extern const TypeInfo* theThrowableTypeInfo;
extern const TypeInfo* theObjCPointerHolderTypeInfo;
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);
+55
View File
@@ -0,0 +1,55 @@
/*
* 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.
*/
#include <cstdint>
#include "Assert.h"
class SimpleMutex {
private:
int32_t atomicInt = 0;
public:
void lock() {
while (!__sync_bool_compare_and_swap(&atomicInt, 0, 1)) {
// TODO: yield.
}
}
void unlock() {
if (!__sync_bool_compare_and_swap(&atomicInt, 1, 0)) {
RuntimeAssert(false, "Unable to unlock");
}
}
};
// TODO: use std::lock_guard instead?
template <class Mutex>
class LockGuard {
public:
explicit LockGuard(Mutex& mutex_) : mutex(mutex_) {
mutex.lock();
}
~LockGuard() {
mutex.unlock();
}
private:
Mutex& mutex;
LockGuard(const LockGuard&) = delete;
LockGuard& operator=(const LockGuard&) = delete;
};
+15
View File
@@ -0,0 +1,15 @@
apply plugin: 'konan'
konanInterop {
objc {
target "macbook"
}
}
konanArtifacts {
Window {
useInterop 'objc'
target "macbook"
}
}
+4
View File
@@ -0,0 +1,4 @@
headers = Foundation/Foundation.h Cocoa/Cocoa.h AppKit/NSApplication.h AppKit/NSWindow.h
headerFilter = Foundation/** Cocoa/** AppKit/**
language = Objective-C
linkerOpts = -framework AppKit
+59
View File
@@ -0,0 +1,59 @@
import objc.*
import kotlinx.cinterop.*
fun main(args: Array<String>) {
autoreleasepool {
runApp()
}
}
private fun runApp() {
val app = NSApplication.sharedApplication()
app.delegate = MyAppDelegate()
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
app.activateIgnoringOtherApps(true)
app.run()
}
private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
private val window: NSWindow
init {
val mainDisplayRect = NSScreen.mainScreen()!!.frame
val windowRect = mainDisplayRect.useContents {
NSMakeRect(
origin.x + size.width * 0.25,
origin.y + size.height * 0.25,
size.width * 0.5,
size.height * 0.5
)
}
val windowStyle = NSWindowStyleMaskTitled or NSWindowStyleMaskMiniaturizable or
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
title = "Окошко Konan"
opaque = true
hasShadow = true
preferredBackingLocation = NSWindowBackingLocationVideoMemory
hidesOnDeactivate = false
backgroundColor = NSColor.whiteColor()
releasedWhenClosed = false
delegate = object : NSObject(), NSWindowDelegateProtocol {
override fun windowShouldClose(sender: ObjCObject): Boolean {
NSApplication.sharedApplication().stop(this)
return true
}
}
}
}
override fun applicationWillFinishLaunching(notification: NSNotification) {
window.makeKeyAndOrderFront(this)
}
}
+1
View File
@@ -12,4 +12,5 @@ include ':concurrent'
// So temporary switching off for now, as it breaks the build
// of other samples if SDK is not present.
//include ':androidNativeActivity'
include ':objc'
includeBuild '../'
@@ -43,13 +43,16 @@ class ClangTarget(val target: KonanTarget, konanProperties: KonanProperties) {
"-DUSE_GCC_UNWIND=1", "-DUSE_PE_COFF_SYMBOLS=1", "-DKONAN_WINDOWS=1")
KonanTarget.MACBOOK ->
listOf("--sysroot=$sysRoot", "-mmacosx-version-min=10.11", "-DKONAN_OSX=1")
listOf("--sysroot=$sysRoot", "-mmacosx-version-min=10.11", "-DKONAN_OSX=1",
"-DKONAN_OBJC_INTEROP=1")
KonanTarget.IPHONE ->
listOf("-stdlib=libc++", "-arch", "arm64", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0")
listOf("-stdlib=libc++", "-arch", "arm64", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0",
"-DKONAN_OBJC_INTEROP=1")
KonanTarget.IPHONE_SIM ->
listOf("-stdlib=libc++", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0")
listOf("-stdlib=libc++", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0",
"-DKONAN_OBJC_INTEROP=1")
KonanTarget.ANDROID_ARM32 ->
listOf("-target", targetArg!!,