From d3185e3e197ac8ea452e7c4e39c304ef54133e8a Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 17 Jul 2017 14:34:09 +0300 Subject: [PATCH] Implement very basic Objective-C interop Also refactor StubGenerator --- .../kotlin/native/interop/indexer/Indexer.kt | 420 +++++++- .../native/interop/indexer/MacroConstants.kt | 4 +- .../native/interop/indexer/NativeIndex.kt | 79 +- .../kotlin/native/interop/indexer/Utils.kt | 13 +- .../kotlin/kotlinx/cinterop/JvmCallbacks.kt | 17 +- .../src/main/kotlin/kotlinx/cinterop/Types.kt | 39 + .../src/main/kotlin/kotlinx/cinterop/Utils.kt | 15 + .../kotlin/kotlinx/cinterop/ObjectiveCImpl.kt | 156 +++ .../kotlinx/cinterop/ObjectiveCUtils.kt | 31 + .../native/kotlin/kotlinx/cinterop/Varargs.kt | 5 + .../kotlin/native/interop/gen/CodeBuilders.kt | 77 ++ .../kotlin/native/interop/gen/CodeUtils.kt | 77 ++ .../interop/gen/MappingBridgeGenerator.kt | 45 + .../interop/gen/MappingBridgeGeneratorImpl.kt | 196 ++++ .../kotlin/native/interop/gen/Mappings.kt | 334 ++++++ .../kotlin/native/interop/gen/ObjCStubs.kt | 498 +++++++++ .../interop/gen/SimpleBridgeGenerator.kt | 93 ++ .../interop/gen/SimpleBridgeGeneratorImpl.kt | 219 ++++ .../kotlin/native/interop/gen/TypeUtils.kt | 66 ++ .../native/interop/gen/jvm/StubGenerator.kt | 973 +++--------------- .../kotlin/native/interop/gen/jvm/main.kt | 34 +- backend.native/build.gradle | 4 +- ...in.resolve.ExternalOverridabilityCondition | 1 + .../kotlin/backend/konan/InteropUtils.kt | 26 + .../kotlin/backend/konan/ObjCInterop.kt | 156 +++ .../konan/descriptors/ClassVtablesBuilder.kt | 12 +- .../konan/descriptors/DescriptorUtils.kt | 7 + .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 12 + .../backend/konan/llvm/BinaryInterface.kt | 16 +- .../backend/konan/llvm/CodeGenerator.kt | 27 + .../kotlin/backend/konan/llvm/ContextUtils.kt | 3 + .../kotlin/backend/konan/llvm/IrToBitcode.kt | 177 +++- .../llvm/KotlinObjCClassInfoGenerator.kt | 94 ++ .../backend/konan/llvm/LlvmDeclarations.kt | 40 +- .../backend/konan/llvm/RTTIGenerator.kt | 12 +- .../kotlin/backend/konan/llvm/Runtime.kt | 3 + .../kotlin/backend/konan/llvm/StaticData.kt | 4 + .../backend/konan/llvm/StaticDataUtils.kt | 6 + .../backend/konan/lower/InteropLowering.kt | 272 ++++- backend.native/konan.properties | 6 +- backend.native/tests/build.gradle | 22 + .../tests/interop/objc/objcSmoke.def | 2 + backend.native/tests/interop/objc/smoke.h | 24 + backend.native/tests/interop/objc/smoke.kt | 55 + backend.native/tests/interop/objc/smoke.m | 46 + .../jetbrains/kotlin/CompileToBitcode.groovy | 5 +- runtime/src/main/cpp/Memory.cpp | 43 +- runtime/src/main/cpp/Memory.h | 1 + runtime/src/main/cpp/ObjCInterop.cpp | 241 +++++ runtime/src/main/cpp/ObjCInteropUtils.mm | 88 ++ runtime/src/main/cpp/Types.h | 1 + runtime/src/main/cpp/Utils.h | 55 + samples/objc/build.gradle | 15 + samples/objc/src/main/c_interop/objc.def | 4 + samples/objc/src/main/kotlin/Window.kt | 59 ++ samples/settings.gradle | 1 + .../kotlin/konan/target/ClangArgs.kt | 9 +- 57 files changed, 4020 insertions(+), 920 deletions(-) create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGenerator.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt create mode 100644 backend.native/compiler/ir/backend.native/resources/META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt create mode 100644 backend.native/tests/interop/objc/objcSmoke.def create mode 100644 backend.native/tests/interop/objc/smoke.h create mode 100644 backend.native/tests/interop/objc/smoke.kt create mode 100644 backend.native/tests/interop/objc/smoke.m create mode 100644 runtime/src/main/cpp/ObjCInterop.cpp create mode 100644 runtime/src/main/cpp/ObjCInteropUtils.mm create mode 100644 runtime/src/main/cpp/Utils.h create mode 100644 samples/objc/build.gradle create mode 100644 samples/objc/src/main/c_interop/objc.def create mode 100644 samples/objc/src/main/kotlin/Window.kt diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index dd8e46170f5..a74844a0e62 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -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() } -private class EnumDefImpl(spelling: String, type: PrimitiveType) : EnumDef(spelling, type) { +private class EnumDefImpl(spelling: String, type: Type) : EnumDef(spelling, type) { override val constants = mutableListOf() } +private interface ObjCClassOrProtocolImpl { + val protocols: MutableList + val methods: MutableList + val properties: MutableList +} + +private class ObjCProtocolImpl(name: String) : ObjCProtocol(name), ObjCClassOrProtocolImpl { + override val protocols = mutableListOf() + override val methods = mutableListOf() + override val properties = mutableListOf() +} + +private class ObjCClassImpl(name: String) : ObjCClass(name), ObjCClassOrProtocolImpl { + override val protocols = mutableListOf() + override val methods = mutableListOf() + override val properties = mutableListOf() + 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() @@ -49,21 +77,39 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { override val enums: List get() = enumById.values.toList() + private val objCClassesByName = mutableMapOf() + + override val objCClasses: List get() = objCClassesByName.values.toList() + + private val objCProtocolsByName = mutableMapOf() + + override val objCProtocols: List get() = objCProtocolsByName.values.toList() + private val typedefById = mutableMapOf() override val typedefs: List get() = typedefById.values.toList() - val functionByName = mutableMapOf() + private val functionById = mutableMapOf() override val functions: List - get() = functionByName.values.toList() + get() = functionById.values.toList() override val macroConstants = mutableListOf() private fun getDeclarationId(cursor: CValue): 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): 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, _: CValue -> @@ -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): CValue { + assert(cursor.kind == CXCursorKind.CXCursor_ObjCCategoryDecl) + var classRef: CValue? = 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): 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): 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, 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): 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) = - convertType(clang_getCursorType(cursor)) + /** + * Computes [StructDef.hasUnalignedFields] property. + */ + fun structHasUnalignedFields(structDefCursor: CValue): 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): Type { + hasUnalignedFields = true + CXChildVisitResult.CXChildVisit_Break + } else { + CXChildVisitResult.CXChildVisit_Continue + } + } + + return hasUnalignedFields + } + + private fun convertCursorType(cursor: CValue) = + 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, typeAttributes: CValue? = 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, typeAttributes: CValue? + ): 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): List { + val num = clang_Type_getNumProtocols(type) + return (0 until num).mapNotNull { index -> + getObjCProtocolAt(clang_Type_getProtocol(type, index)) + } + } + private fun convertFunctionType(type: CValue): 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): 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): 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): Boolean = when (clang_getCursorAvailability(cursor)) { + CXAvailabilityKind.CXAvailability_Available, + CXAvailabilityKind.CXAvailability_Deprecated -> true + + CXAvailabilityKind.CXAvailability_NotAvailable, + CXAvailabilityKind.CXAvailability_NotAccessible -> false + } + + private fun getFunctionParameters(cursor: CValue): List { + 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, 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 { diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt index bca8f42c865..df2bd6e4e87 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt @@ -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: diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index b154089fdea..056384fecd4 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -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, @@ -39,6 +40,8 @@ fun buildNativeIndex(library: NativeLibrary): NativeIndex = buildNativeIndexImpl abstract class NativeIndex { abstract val structs: List abstract val enums: List + abstract val objCClasses: List + abstract val objCProtocols: List abstract val typedefs: List abstract val functions: List abstract val macroConstants: List @@ -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 } @@ -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 } +sealed class ObjCClassOrProtocol(val name: String) { + abstract val protocols: List + abstract val methods: List + abstract val properties: List +} + +data class ObjCMethod( + val selector: String, val encoding: String, val parameters: List, 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 +} + +data class ObjCObjectPointer( + val def: ObjCClass, + override val nullability: Nullability, + override val protocols: List +) : ObjCQualifiedPointer() + +data class ObjCClassPointer( + override val nullability: Nullability, + override val protocols: List +) : ObjCQualifiedPointer() + +data class ObjCIdType( + override val nullability: Nullability, + override val protocols: List +) : ObjCQualifiedPointer() + +data class ObjCInstanceType(override val nullability: Nullability) : ObjCPointer() + object UnsupportedType : Type \ No newline at end of file diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt index 80d5c30a3ad..ee81d679162 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -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 -} \ No newline at end of file +} + +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) diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt index 54acd06848a..c7310a0ad90 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt @@ -379,22 +379,7 @@ private class Struct(val size: Long, val align: Int, elementTypes: List ) { override fun read(location: NativePtr) = interpretPointed(location).readValue(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(location) - } - } - - value.getPointer(fakePlacement) - assert (fakePlacement.used) - } + override fun write(location: NativePtr, value: CValue<*>) = value.write(location) } private class CEnumType(private val rawValueCType: CType) : CType(rawValueCType.ffiType) { diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt index aa4429042ed..1833fc7f73d 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt @@ -67,6 +67,25 @@ abstract class CValuesRef { abstract fun getPointer(placement: NativePlacement): CPointer } +inline fun CValuesRef?.usePointer(block: (CPointer?) -> R): R { + val allocated: Boolean + val pointer: CPointer? = if (this is CPointer?) { + 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(override val rawPtr: NativePtr) : CPrimitiveVar() { + companion object : Type(1) +} + @Suppress("FINAL_UPPER_BOUND") class ByteVarOf(override val rawPtr: NativePtr) : CPrimitiveVar() { companion object : Type(1) @@ -271,6 +295,7 @@ class DoubleVarOf(override val rawPtr: NativePtr) : CPrimitiveVar() companion object : Type(8) } +typealias BooleanVar = BooleanVarOf typealias ByteVar = ByteVarOf typealias ShortVar = ShortVarOf typealias IntVar = IntVarOf @@ -278,6 +303,20 @@ typealias LongVar = LongVarOf typealias FloatVar = FloatVarOf typealias DoubleVar = DoubleVarOf +@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST") +var BooleanVarOf.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 ByteVarOf.value: T get() = nativeMemUtils.getByte(this) as T diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index 3ed3774c6fe..2f849d72468 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -218,6 +218,21 @@ fun CPointed.readValue(size: Long, align: Int): CValue { // TODO: find better name. inline fun T.readValue(): CValue = this.readValue(sizeOf(), alignOf()) +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(location) + } + } + + this.getPointer(fakePlacement) + assert(fakePlacement.used) +} + // TODO: optimize fun CValues.getBytes(): ByteArray = memScoped { val result = ByteArray(size) diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt new file mode 100644 index 00000000000..814d25200e6 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -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 Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) // TODO: make private + +inline fun interpretObjCPointerOrNull(rawPtr: NativePtr): T? = if (rawPtr != nativeNullPtr) { + ObjCPointerHolder(rawPtr).uncheckedCast() +} else { + null +} + +inline fun interpretObjCPointer(rawPtr: NativePtr): T? = interpretObjCPointerOrNull(rawPtr)!! + +inline val ObjCObject.rawPtr: NativePtr get() = (this.uncheckedCast()).rawPtr +inline val ObjCObject?.rawPtr: NativePtr get() = if (this != null) { + (this.uncheckedCast()).rawPtr +} else { + nativeNullPtr +} + +class ObjCObjectVar(override val rawPtr: NativePtr) : CVariable { + companion object : CVariable.Type(pointerSize.toLong(), pointerSize) +} + +class ObjCStringVarOf(override val rawPtr: NativePtr) : CVariable { + companion object : CVariable.Type(pointerSize.toLong(), pointerSize) +} + +var ObjCStringVarOf.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 allocObjCObject(clazz: NativePtr): T { + val rawResult = objc_allocWithZone(clazz) + if (rawResult == nativeNullPtr) { + throw OutOfMemoryError("Unable to allocate Objective-C object") + } + + val result = interpretObjCPointerOrNull(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 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) diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt new file mode 100644 index 00000000000..b6a027d2f89 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt @@ -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 autoreleasepool(block: () -> R): R { + val pool = objc_autoreleasePoolPush() + return try { + block() + } finally { + objc_autoreleasePoolPop(pool) + } +} + +// TODO: null checks +var ObjCObjectVar.value: T + get() = interpretObjCPointerOrNull(nativeMemUtils.getNativePtr(this)).uncheckedCast() + set(value) = nativeMemUtils.putNativePtr(this, value.rawPtr) \ No newline at end of file diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt index 3593284bfa9..0d6e57632c9 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt @@ -66,6 +66,11 @@ private tailrec fun convertArgument( is CEnum -> convertArgument(argument.value, isVariadic, location, additionalPlacement) + is ObjCPointerHolder -> { + location.reinterpret()[0] = interpretCPointer(argument.rawPtr) + FFI_TYPE_KIND_POINTER + } + else -> throw Error("unsupported argument: $argument") } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt new file mode 100644 index 00000000000..27535c63f75 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt @@ -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() + + fun out(line: String): Unit { + lines.add(line) + } +} + +inline fun buildNativeCodeLines(block: NativeCodeBuilder.() -> Unit): List { + val builder = NativeCodeBuilder() + builder.block() + return builder.lines +} + +class KotlinCodeBuilder { + private val lines = mutableListOf() + + private val freeStack = mutableListOf() + 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 { + this.popBlocks() + val result = this.lines.toList() + this.lines.clear() + return result + } +} + +inline fun buildKotlinCodeLines(block: KotlinCodeBuilder.() -> Unit): List { + val builder = KotlinCodeBuilder() + builder.block() + return builder.build() +} + +interface StubGenerationContext { + val nativeBridges: NativeBridges + fun addTopLevelDeclaration(lines: List) +} + +interface KotlinStub { + fun generate(context: StubGenerationContext): Sequence +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt new file mode 100644 index 00000000000..d4e6af89042 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt @@ -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) = block(header, lines.asSequence()) + +fun block(header: String, lines: Sequence) = + 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() }})" diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGenerator.kt new file mode 100644 index 00000000000..aeb786557d2 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGenerator.kt @@ -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, + block: NativeCodeBuilder.(nativeValues: List) -> NativeExpression + ): KotlinExpression + + fun nativeToKotlin( + builder: NativeCodeBuilder, + nativeBacked: NativeBacked, + returnType: Type, + nativeValues: List, + block: KotlinCodeBuilder.(kotlinValues: List) -> KotlinExpression + ): NativeExpression +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt new file mode 100644 index 00000000000..6259f82b506 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt @@ -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, + block: NativeCodeBuilder.(nativeValues: List) -> NativeExpression + ): KotlinExpression { + val bridgeArguments = mutableListOf() + + 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() + 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, + block: KotlinCodeBuilder.(kotlinValues: List) -> KotlinExpression + ): NativeExpression { + + val bridgeArguments = mutableListOf() + + 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() + 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 + } +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt new file mode 100644 index 00000000000..1bf8f1e98f8 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt @@ -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 +} + diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt new file mode 100644 index 00000000000..21aa13c0d0c --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -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 { + val selectorParts = this.selector.split(":") + + val result = mutableListOf() + + // 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 = + if (context.nativeBridges.isSupported(this)) { + val result = mutableListOf() + 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 + 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>() + val kotlinObjCBridgeParameters = mutableListOf>() + val nativeBridgeArguments = mutableListOf() + + 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() + 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 + get() = this.protocols.asSequence().flatMap { sequenceOf(it) + it.protocolsWithSupers } + +private val ObjCClassOrProtocol.immediateSuperTypes: Sequence + 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 + get() = sequenceOf(this) + this.superTypes + +private val ObjCClassOrProtocol.superTypes: Sequence + get() = this.immediateSuperTypes.flatMap { it.selfAndSuperTypes }.distinct() + +private fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence = + this.methods.asSequence().filter { it.isClass == isClass } + +private fun ObjCClassOrProtocol.inheritedMethods(isClass: Boolean): Sequence = + this.superTypes.flatMap { it.declaredMethods(isClass) }.distinctBy { it.selector } + +private fun ObjCClassOrProtocol.methodsWithInherited(isClass: Boolean): Sequence = + 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 + + 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 + + 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() + + 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 { + 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 = 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 { + 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() + + val parameters = mutableListOf>() + + 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 +} + diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt new file mode 100644 index 00000000000..5d308f254f0 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt @@ -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, + block: NativeCodeBuilder.(nativeValues: List) -> 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, + block: KotlinCodeBuilder.(kotlinValues: List) -> KotlinExpression + ): NativeExpression + + fun insertNativeBridge( + nativeBacked: NativeBacked, + kotlinLines: List, + nativeLines: List + ) + + /** + * 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 + val nativeLines: Sequence +} + diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt new file mode 100644 index 00000000000..06164e6658d --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt @@ -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, val nativeLines: List) + + override fun kotlinToNative( + nativeBacked: NativeBacked, + returnType: BridgedType, + kotlinValues: List, + block: NativeCodeBuilder.(arguments: List) -> NativeExpression + ): KotlinExpression { + + val kotlinLines = mutableListOf() + val nativeLines = mutableListOf() + + 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, + block: KotlinCodeBuilder.(arguments: List) -> KotlinExpression + ): NativeExpression { + + if (platform != KotlinPlatform.NATIVE) TODO() + + val kotlinLines = mutableListOf() + val nativeLines = mutableListOf() + + 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, nativeLines: List) { + val nativeBridge = NativeBridge(kotlinLines, nativeLines) + nativeBridges.add(nativeBacked to nativeBridge) + } + + private val nativeBridges = mutableListOf>() + + override fun prepare(): NativeBridges { + val includedBridges = mutableListOf() + val excludedClients = mutableSetOf() + + 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 + get() = includedBridges.asSequence().flatMap { it.kotlinLines.asSequence() } + + override val nativeLines: Sequence + get() = includedBridges.asSequence().flatMap { it.nativeLines.asSequence() } + + override fun isSupported(nativeBacked: NativeBacked): Boolean = + nativeBacked !in excludedClients + } + } +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt new file mode 100644 index 00000000000..dae4149d302 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt @@ -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 +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 259dfcecd34..0db7e8fde1f 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.native.interop.gen.jvm +import org.jetbrains.kotlin.native.interop.gen.* import org.jetbrains.kotlin.native.interop.indexer.* import java.lang.IllegalStateException @@ -24,10 +25,6 @@ enum class KotlinPlatform { NATIVE } -private class StubsFragment(val kotlinStubLines: List, val nativeStubLines: List = emptyList()) - -// TODO: mostly rename 'jni' to 'c'. - class StubGenerator( val nativeIndex: NativeIndex, val configuration: InteropConfiguration, @@ -36,6 +33,9 @@ class StubGenerator( val verbose: Boolean = false, val platform: KotlinPlatform = KotlinPlatform.JVM) { + private var theCounter = 0 + fun nextUniqueId() = theCounter++ + private fun log(message: String) { if (verbose) { println(message) @@ -54,26 +54,8 @@ class StubGenerator( val excludedFunctions: Set get() = configuration.excludedFunctions - val keywords = 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" - ) - val platformWStringTypes = setOf("LPCWSTR") - /** - * 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 { - return if (this in keywords) { - "`$this`" - } else { - this - } - } - /** * The names that should not be used for struct classes to prevent name clashes */ @@ -110,9 +92,6 @@ class StubGenerator( return if (strippedCName !in forbiddenStructNames) strippedCName else (strippedCName + "Struct") } - val EnumDef.isAnonymous: Boolean - get() = spelling.contains("(anonymous ") // TODO: it is a hack - /** * Indicates whether this enum should be represented as Kotlin enum. */ @@ -149,9 +128,15 @@ class StubGenerator( spelling } - val RecordType.kotlinName: String - get() = decl.kotlinName + val declarationMapper = object : DeclarationMapper { + override fun getKotlinNameForPointed(structDecl: StructDecl): String = structDecl.kotlinName + override fun isMappedToStrict(enumDef: EnumDef): Boolean = enumDef.isStrictEnum + + override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName + } + + fun mirror(type: Type): TypeMirror = mirror(declarationMapper, type) val functionsToBind = nativeIndex.functions.filter { it.name !in excludedFunctions } @@ -185,9 +170,11 @@ class StubGenerator( return withOutput({ appendable.appendln(it) }, action) } - private fun generateKotlinFragmentBy(block: () -> Unit): StubsFragment { + private fun generateKotlinFragmentBy(block: () -> Unit): KotlinStub { val lines = generateLinesBy(block) - return StubsFragment(kotlinStubLines = lines) + return object : KotlinStub { + override fun generate(context: StubGenerationContext) = lines.asSequence() + } } private fun indent(action: () -> R): R { @@ -204,301 +191,6 @@ class StubGenerator( return res } - /** - * Returns the expression which could be used for this type in C code. - * - * TODO: use libclang to implement: - */ - fun Type.getStringRepresentation(): String { - return when (this) { - is VoidType -> "void" - is CharType -> "char" - is IntegerType -> this.spelling - is FloatingType -> this.spelling - - is PointerType -> { - val pointeeType = this.pointeeType - if (pointeeType is FunctionType) { - "void*" // TODO - } else { - pointeeType.getStringRepresentation() + "*" - } - } - - is RecordType -> this.decl.spelling - - is ArrayType -> "void*" // TODO - - is EnumType -> if (this.def.isAnonymous) { - this.def.baseType.getStringRepresentation() - } else { - this.def.spelling - } - - is Typedef -> this.def.name - - else -> throw kotlin.NotImplementedError() - } - } - - val PrimitiveType.kotlinType: String - get() = when (this) { - is CharType -> "Byte" - - // 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() - } - - /** - * Describes the Kotlin types used to represent some C type. - */ - private 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) "?" 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() = pointedTypeName - } - } - - /** - * Describes various type conversions for [TypeMirror]. - */ - private sealed class TypeInfo { - /** - * The conversion from [TypeMirror.argType] to [jniType]. - */ - abstract fun argToJni(name: String): String - - /** - * The conversion from [jniType] to [TypeMirror.argType]. - */ - abstract fun argFromJni(name: String): String - - /** - * The type to be used for passing [TypeMirror.argType] through JNI. - */ - abstract val jniType: String - - /** - * 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 jniType: String, val varTypeName: String) : TypeInfo() { - - override fun argToJni(name: String) = name - override fun argFromJni(name: String) = name - - override fun constructPointedType(valueType: String) = "${varTypeName}Of<$valueType>" - } - - class Enum(val className: String, val baseType: String) : TypeInfo() { - override fun argToJni(name: String) = "$name.value" - - override fun argFromJni(name: String) = "$className.byValue($name)" - - override val jniType: String - get() = baseType - - override fun constructPointedType(valueType: String) = "$className.Var" // TODO: improve - - } - - class Pointer(val pointee: String) : TypeInfo() { - override fun argToJni(name: String) = "$name.rawValue" - - override fun argFromJni(name: String) = "interpretCPointer<$pointee>($name)" - - override val jniType: String - get() = "NativePtr" - - override fun constructPointedType(valueType: String) = "CPointerVarOf<$valueType>" - } - - class ByRef(val pointed: String) : TypeInfo() { - override fun argToJni(name: String) = "$name.rawPtr" - - override fun argFromJni(name: String) = "interpretPointed<$pointed>($name)" - - override val jniType: String - get() = "NativePtr" - - override fun constructPointedType(valueType: String): String { - // TODO: this method must not exist - throw UnsupportedOperationException() - } - } - } - - private fun mirror(type: PrimitiveType): TypeMirror.ByValue { - val varTypeName = when (type) { - is CharType -> "ByteVar" - 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 = TypeInfo.Primitive(type.kotlinType, 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) - } - - private fun mirror(type: Type): TypeMirror = when (type) { - is PrimitiveType -> mirror(type) - - is RecordType -> byRefTypeMirror(type.kotlinName.asSimpleName()) - - is EnumType -> when { - type.def.isStrictEnum -> { - val classSimpleName = type.def.kotlinName.asSimpleName() - val info = TypeInfo.Enum(classSimpleName, type.def.baseType.kotlinType) - TypeMirror.ByValue("$classSimpleName.Var", info, classSimpleName) - } - !type.def.isAnonymous -> { - val baseTypeMirror = mirror(type.def.baseType) - val name = type.def.kotlinName - TypeMirror.ByValue("${name}Var", baseTypeMirror.info, name.asSimpleName()) - } - else -> mirror(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(pointeeType) - } else { - val pointeeMirror = mirror(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(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(type)}>") - - is Typedef -> { - val baseType = mirror(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) - } - - } - - else -> TODO(type.toString()) - } - - /** - * Describes how to represent in Kotlin the value to be passed to native code. - * - * @param kotlinType the name of Kotlin type to be used for this value in Kotlin. - * @param kotlinConv the function such that `kotlinConv(name)` converts variable `name` to pass it into JNI stub - * @param memScoped the conversion allocates memory and should be placed into `memScoped {}` block - * @param kotlinJniBridgeType the name of Kotlin type to be used for this value in JNI stub - */ - class OutValueBinding(val kotlinType: String, - val kotlinConv: ((String) -> String)? = null, - val memScoped: Boolean = false, - val kotlinJniBridgeType: String = kotlinType) - - /** - * Describes how to represent in Kotlin the value to be received from native code. - * - * @param kotlinJniBridgeType the name of Kotlin type to be used for this value in JNI stub - * @param conv the function such that `conv(name)` converts variable `name` received from JNI stub to `kotlinType`. - * @param kotlinType the name of Kotlin type to be used for this value in Kotlin. - */ - class InValueBinding(val kotlinJniBridgeType: String, - val conv: ((String) -> String) = { it }, - val kotlinType: String = kotlinJniBridgeType) - - /** - * Constructs [OutValueBinding] for the value of given C type. - */ - fun getOutValueBinding(type: Type): OutValueBinding { - if (type.unwrapTypedefs() is RecordType) { - // TODO: this case should probably be handled more generally. - val typeMirror = mirror(type) - return OutValueBinding( - kotlinType = "CValue<${typeMirror.pointedTypeName}>", - kotlinConv = { name -> "$name.getPointer(memScope).rawValue" }, // TODO: eliminate this copying - memScoped = true, - kotlinJniBridgeType = "NativePtr" - ) - } - - val mirror = mirror(type) - - return OutValueBinding( - kotlinType = mirror.argType, - kotlinConv = mirror.info::argToJni, - kotlinJniBridgeType = mirror.info.jniType - ) - } - fun representCFunctionParameterAsValuesRef(type: Type): Type? { val pointeeType = when (type) { is PointerType -> type.pointeeType @@ -537,91 +229,6 @@ class StubGenerator( // We take this approach as generic 'const short*' shall not be used as String. fun representCFunctionParameterAsWString(type: Type)= type.isAliasOf(platformWStringTypes) - fun getCFunctionParamBinding(type: Type): OutValueBinding { - if (representCFunctionParameterAsString(type)) { - return OutValueBinding( - kotlinType = "String?", // TODO: mention the C type (e.g. with annotation). - kotlinConv = { name -> "$name?.cstr?.getPointer(memScope).rawValue" }, - memScoped = true, - kotlinJniBridgeType = "NativePtr" - ) - } - - if (representCFunctionParameterAsWString(type)) { - return OutValueBinding( - kotlinType = "String?", // TODO: mention the C type (e.g. with annotation). - kotlinConv = { name -> "$name?.wcstr?.getPointer(memScope).rawValue" }, - memScoped = true, - kotlinJniBridgeType = "NativePtr" - ) - } - - representCFunctionParameterAsValuesRef(type)?.let { - val pointeeMirror = mirror(it) - return OutValueBinding( - kotlinType = "CValuesRef<${pointeeMirror.pointedTypeName}>?", - kotlinConv = { name -> "$name?.getPointer(memScope).rawValue" }, - memScoped = true, - kotlinJniBridgeType = "NativePtr" - ) - } - - return getOutValueBinding(type) - } - - fun getCallbackRetValBinding(type: Type): OutValueBinding { - if (type.unwrapTypedefs() is VoidType) { - return OutValueBinding( - kotlinType = "Unit", - kotlinConv = { throw UnsupportedOperationException() }, - kotlinJniBridgeType = "void" - ) - } - - return getOutValueBinding(type) - } - - /** - * Constructs [InValueBinding] for the value of given C type. - */ - fun getInValueBinding(type: Type): InValueBinding { - if (type.unwrapTypedefs() is RecordType) { - val typeMirror = mirror(type) - return InValueBinding( - kotlinJniBridgeType = "NativePtr", - conv = { name -> "interpretPointed<${typeMirror.pointedTypeName}>($name).readValue()" }, - kotlinType = "CValue<${typeMirror.pointedTypeName}>" - ) - } - - val mirror = mirror(type) - - return InValueBinding( - kotlinJniBridgeType = mirror.info.jniType, - conv = mirror.info::argFromJni, - kotlinType = mirror.argType - ) - } - - fun getCFunctionRetValBinding(func: FunctionDecl): InValueBinding { - when { - func.returnsVoid() -> return InValueBinding("Unit") - } - - return getInValueBinding(func.returnType) - } - - fun getCallbackParamBinding(type: Type): InValueBinding { - return getInValueBinding(type) - } - - private fun getKotlinFunctionType(type: FunctionType): String { - return "(" + - type.parameterTypes.map { getCallbackParamBinding(it).kotlinType }.joinToString(", ") + - ") -> " + - getCallbackRetValBinding(type.returnType).kotlinType - } - private fun getArrayLength(type: ArrayType): Long { val unwrappedElementType = type.elemType.unwrapTypedefs() val elementLength = if (unwrappedElementType is ArrayType) { @@ -722,6 +329,7 @@ class StubGenerator( } val baseTypeMirror = mirror(e.baseType) + val baseKotlinType = baseTypeMirror.argType val canonicalsByValue = e.constants .groupingBy { it.value } @@ -735,7 +343,7 @@ class StubGenerator( val (canonicalConstants, aliasConstants) = e.constants.partition { canonicalsByValue[it.value] == it } - block("enum class ${e.kotlinName.asSimpleName()}(override val value: ${e.baseType.kotlinType}) : CEnum") { + block("enum class ${e.kotlinName.asSimpleName()}(override val value: $baseKotlinType) : CEnum") { canonicalConstants.forEach { out("${it.name.asSimpleName()}(${it.value}),") } @@ -748,7 +356,7 @@ class StubGenerator( } if (aliasConstants.isNotEmpty()) out("") - out("fun byValue(value: ${e.baseType.kotlinType}) = " + + out("fun byValue(value: $baseKotlinType) = " + "${e.kotlinName.asSimpleName()}.values().find { it.value == value }!!") } out("") @@ -777,12 +385,13 @@ class StubGenerator( val typeName: String + val baseKotlinType = mirror(e.baseType).argType if (e.isAnonymous) { if (constants.isNotEmpty()) { out("// ${e.spelling}:") } - typeName = e.baseType.kotlinType + typeName = baseKotlinType } else { val typeMirror = mirror(EnumType(e)) if (typeMirror !is TypeMirror.ByValue) { @@ -792,7 +401,7 @@ class StubGenerator( // Generate as typedef: val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueTypeName) out("typealias ${typeMirror.pointedTypeName} = $varTypeName") - out("typealias ${typeMirror.valueTypeName} = ${e.baseType.kotlinType}") + out("typealias ${typeMirror.valueTypeName} = $baseKotlinType") if (constants.isNotEmpty()) { out("") @@ -802,7 +411,8 @@ class StubGenerator( } for (constant in constants) { - out("val ${constant.name.asSimpleName()}: $typeName = ${constant.value}") + val literal = integerLiteral(e.baseType, constant.value) ?: continue + out("val ${constant.name.asSimpleName()}: $typeName = $literal") } } @@ -823,198 +433,133 @@ class StubGenerator( } } - private fun generateStubsForFunction(func: FunctionDecl): StubsFragment { - val kotlinStubLines = generateLinesBy { - generateKotlinBindingMethod(func) - if (func.requiresKotlinAdapter()) { - out("") - generateKotlinExternalMethod(func) - } - } - - val nativeStubLines = generateLinesBy { - generateCJniFunction(func) - } - - return StubsFragment(kotlinStubLines, nativeStubLines) - } - - private fun generateStubsForFunctions(functions: List): List { + private fun generateStubsForFunctions(functions: List): List { val stubs = functions.mapNotNull { try { - generateStubsForFunction(it) + KotlinFunctionStub(it) } catch (e: Throwable) { log("Warning: cannot generate stubs for function ${it.name}") null } } - return stubs.map { it.nativeStubLines } - .mapFragmentIsCompilable(libraryForCStubs) - .mapIndexedNotNull { index, stubIsCompilable -> - if (stubIsCompilable) { - stubs[index] - } else { - null - } - } + return stubs } private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg && // Neither takes nor returns structs by value: !this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType }) - /** - * Constructs [InValueBinding] for return value of Kotlin binding for given C function. - */ - private fun retValBinding(func: FunctionDecl) = getCFunctionRetValBinding(func) - private fun FunctionDecl.returnsRecord(): Boolean = this.returnType.unwrapTypedefs() is RecordType private fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType - /** - * Constructs [OutValueBinding]s for parameters of Kotlin binding for given C function. - */ - private fun paramBindings(func: FunctionDecl): Array { - val paramBindings = func.parameters.map { param -> - getCFunctionParamBinding(param.type) - }.toMutableList() + private inner class KotlinFunctionStub(val func: FunctionDecl) : KotlinStub, NativeBacked { + override fun generate(context: StubGenerationContext): Sequence = + if (context.nativeBridges.isSupported(this)) { + block(header, bodyLines) + } else { + sequenceOf( + annotationForUnableToImport, + "external $header" + ) + } - if (func.returnsRecord()) { - paramBindings.add(OutValueBinding( - kotlinType = "should not be used", - kotlinConv = { throw UnsupportedOperationException() }, - kotlinJniBridgeType = "NativePtr" - )) - } + private val header: String + private val bodyLines: List - return paramBindings.toTypedArray() - } + init { + // TODO: support dumpShims + val kotlinParameters = mutableListOf>() + val bodyGenerator = KotlinCodeBuilder() + val bridgeArguments = mutableListOf() - /** - * Returns names for parameters of Kotlin binding for given C function. - */ - private fun paramNames(func: FunctionDecl): Array { - val paramNames = func.parameters.mapIndexed { i: Int, parameter: Parameter -> - val name = parameter.name - if (name != null && name != "") { - name - } else { - "arg$i" + func.parameters.forEachIndexed { index, parameter -> + val parameterName = parameter.name.let { + if (it == null || it.isEmpty()) { + "arg$index" + } else { + it.asSimpleName() + } + } + + val tmpVarName = "kniTmp$index" + + val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type) + + val bridgeArgument = if (representCFunctionParameterAsString(parameter.type)) { + kotlinParameters.add(parameterName to "String?") + bodyGenerator.pushBlock("$parameterName?.cstr.usePointer { $tmpVarName ->") + tmpVarName + } else if (representCFunctionParameterAsWString(parameter.type)) { + kotlinParameters.add(parameterName to "String?") + bodyGenerator.pushBlock("$parameterName?.wcstr.usePointer { $tmpVarName ->") + tmpVarName + } else if (representAsValuesRef != null) { + kotlinParameters.add(parameterName to "CValuesRef<${mirror(representAsValuesRef).pointedTypeName}>?") + bodyGenerator.pushBlock("$parameterName.usePointer { $tmpVarName ->") + tmpVarName + } else { + val mirror = mirror(parameter.type) + kotlinParameters.add(parameterName to mirror.argType) + parameterName + } + + bridgeArguments.add(TypedKotlinValue(parameter.type, bridgeArgument)) } - }.toMutableList() - if (func.returnsRecord()) { - paramNames.add("retValPlacement") - } - - return paramNames.toTypedArray() - } - - /** - * Produces to [out] the definition of Kotlin binding for given C function. - */ - private fun generateKotlinBindingMethod(func: FunctionDecl) { - val paramNames = paramNames(func).toList().let { - if (func.returnsRecord()) { - // The last parameter should be present only in C adapter. - it.dropLast(1) + if (!func.generateAsFfiVarargs()) { + val result = mappingBridgeGenerator.kotlinToNative( + bodyGenerator, + this, + func.returnType, + bridgeArguments + ) { nativeValues -> + "${func.name}(${nativeValues.joinToString()})" + } + bodyGenerator.out("return $result") } else { - it - } - } - val paramBindings = paramBindings(func) - val retValBinding = retValBinding(func) + val returnTypeKind = getFfiTypeKind(func.returnType) - val args = paramNames.mapIndexed { i: Int, name: String -> - "${name.asSimpleName()}: " + paramBindings[i].kotlinType - }.joinToString(", ") + kotlinParameters.add("vararg variadicArguments" to "Any?") + bodyGenerator.pushBlock("memScoped {") - if (func.generateAsFfiVarargs()) { - val returnTypeKind = getFfiTypeKind(func.returnType) + val resultVar = "kniResult" - val variadicParameter = "vararg variadicArguments: Any?" - val allParameters = if (args.isEmpty()) variadicParameter else "$args, $variadicParameter" - val header = "fun ${func.name.asSimpleName()}($allParameters): ${retValBinding.kotlinType} = memScoped" - val returnValueMirror = mirror(func.returnType) - block(header) { val resultPtr = if (!func.returnsVoid()) { - val returnType = returnValueMirror.pointedTypeName - out("val resultVar = allocFfiReturnValueBuffer<$returnType>(typeOf<$returnType>())") - "resultVar.rawPtr" + val returnType = mirror(func.returnType).pointedTypeName + bodyGenerator.out("val $resultVar = allocFfiReturnValueBuffer<$returnType>(typeOf<$returnType>())") + "$resultVar.rawPtr" } else { "nativeNullPtr" } - val fixedArguments = paramNames.joinToString(", ") { it.asSimpleName() } - out("callWithVarargs(${func.kotlinExternalName}(), $resultPtr, $returnTypeKind, " + + val fixedArguments = bridgeArguments.joinToString(", ") { it.value } + + val functionPtr = simpleBridgeGenerator.kotlinToNative( + this, + BridgedType.NATIVE_PTR, + emptyList() + ) { + func.name + } + + bodyGenerator.out("callWithVarargs($functionPtr, $resultPtr, $returnTypeKind, " + "arrayOf($fixedArguments), variadicArguments, memScope)") if (!func.returnsVoid()) { - out("resultVar.value") + bodyGenerator.out("return $resultVar.value") } } - return - } - val header = "fun ${func.name.asSimpleName()}($args): ${retValBinding.kotlinType}" - - if (!func.requiresKotlinAdapter()) { - assert(platform == KotlinPlatform.NATIVE) - out(func.symbolNameAnnotation) - out("external $header") - return - } - - fun generateBody(memScoped: Boolean) { - val arguments = paramNames.mapIndexed { i: Int, name: String -> - val binding = paramBindings[i] - val externalParamName: String - - if (binding.kotlinConv != null) { - externalParamName = "_$name" - out("val $externalParamName = " + binding.kotlinConv.invoke(name.asSimpleName())) - } else { - externalParamName = name.asSimpleName() - } - - externalParamName - - }.toMutableList() - - if (func.returnsRecord()) { - val retValMirror = mirror(func.returnType) - arguments.add("alloc<${retValMirror.pointedTypeName}>().rawPtr") - } - - val callee = func.kotlinExternalName - out("val res = $callee(" + arguments.joinToString(", ") + ")") - - val result = retValBinding.conv("res") - if (dumpShims) { - val returnValueRepresentation = result - out("print(\"\${${returnValueRepresentation}}\\t= \")") - out("print(\"${func.name}( \")") - val argsRepresentation = paramNames.map{"\${${it}}"}.joinToString(", ") - out("print(\"${argsRepresentation}\")") - out("println(\")\")") - } - - if (memScoped) { - out(result) + val returnType = if (func.returnsVoid()) { + "Unit" } else { - out("return " + result) + mirror(func.returnType).argType } - } - block(header) { - val memScoped = paramBindings.any { it.memScoped } || func.returnsRecord() - if (memScoped) { - block("return memScoped") { - generateBody(true) - } - } else { - generateBody(false) - } + val joinedKotlinParameters = kotlinParameters.joinToString { (name, type) -> "$name: $type" } + this.header = "fun ${func.name}($joinedKotlinParameters): $returnType" + + this.bodyLines = bodyGenerator.build() } } @@ -1040,49 +585,6 @@ class StubGenerator( } } - /** - * Returns the expression to be parsed by Kotlin as string literal with given contents, - * i.e. transforms `foo$bar` to `"foo\$bar"`. - */ - private fun String.quoteAsKotlinLiteral(): String { - val sb = StringBuilder() - sb.append('"') - - this.forEach { c -> - val escaped = when (c) { - in 'a' .. 'z', in 'A' .. 'Z', in '0' .. '9', '_' -> c.toString() - '$' -> "\\$" - // TODO: improve result readability by preserving more characters. - else -> "\\u" + "%04X".format(c.toInt()) - } - sb.append(escaped) - } - - sb.append('"') - return sb.toString() - } - - /** - * Returns `true` iff the function binding for Kotlin Native - * requires non-trivial Kotlin adapter to convert arguments. - */ - private fun FunctionDecl.requiresKotlinAdapter(): Boolean { - // TODO: restore this optimization after refactoring stub generator. - return true - } - - /** - * Returns `true` iff the function binding for Kotlin Native - * requires non-trivial C adapter to convert arguments. - */ - private fun FunctionDecl.requiresCAdapter(): Boolean { - if (platform != KotlinPlatform.NATIVE) { - return true - } - - return true - } - private fun integerLiteral(type: Type, value: Long): String? { if (value == Long.MIN_VALUE) { return "${value + 1} - 1" // Workaround for "The value is out of range" compile error. @@ -1143,71 +645,19 @@ class StubGenerator( out("val ${constant.name.asSimpleName()}: $kotlinType = $literal") } - /** - * The name of C adapter to be used for the function binding for Kotlin Native. - */ - private val FunctionDecl.cStubName: String - get() { - require(platform == KotlinPlatform.NATIVE) - return pkgName.replace('.', '_') + "_kni_" + this.name - } - - private val FunctionDecl.kotlinExternalName: String - get() { - require(this.requiresKotlinAdapter()) - return "kni_$name" - } - - /** - * The annotation for an external function on Kotlin Native - * to bind it to either the C function itself or the C adapter. - */ - private val FunctionDecl.symbolNameAnnotation: String - get() { - require(platform == KotlinPlatform.NATIVE) - val symbolName = if (requiresCAdapter()) { - cStubName - } else { - // `@SymbolName` annotation on Kotlin Native defines the LLVM function name - // which is assumed to match the C function name. - // However on some platforms (e.g. macOS) C function names are being mangled. - // Using `0x01` prefix prevents LLVM from mangling the name. - "\u0001${binaryName}" - } - return "@SymbolName(${symbolName.quoteAsKotlinLiteral()})" - } - - /** - * Produces to [out] the definition of Kotlin JNI function used in binding for given C function. - */ - private fun generateKotlinExternalMethod(func: FunctionDecl) { - if (func.generateAsFfiVarargs()) { - assert (platform == KotlinPlatform.NATIVE) - // The C stub simply returns pointer to the function: - out(func.symbolNameAnnotation) - out("private external fun ${func.kotlinExternalName}(): NativePtr") - return - } - - val paramNames = paramNames(func) - val paramBindings = paramBindings(func) - val retValBinding = retValBinding(func) - - val args = paramNames.mapIndexed { i: Int, name: String -> - "${name.asSimpleName()}: " + paramBindings[i].kotlinJniBridgeType - }.joinToString(", ") - - if (platform == KotlinPlatform.NATIVE) { - out(func.symbolNameAnnotation) - } - out("private external fun ${func.kotlinExternalName}($args): ${retValBinding.kotlinJniBridgeType}") - } - - private fun generateStubs(): List { - val stubs = mutableListOf() + private fun generateStubs(): List { + val stubs = mutableListOf() stubs.addAll(generateStubsForFunctions(functionsToBind)) + nativeIndex.objCProtocols.mapTo(stubs) { + ObjCProtocolStub(this, it) + } + + nativeIndex.objCClasses.mapTo(stubs) { + ObjCClassStub(this, it) + } + nativeIndex.macroConstants.forEach { try { stubs.add( @@ -1254,11 +704,26 @@ class StubGenerator( /** * Produces to [out] the contents of file with Kotlin bindings. */ - private fun generateKotlinFile(stubs: List) { + private fun generateKotlinFile(nativeBridges: NativeBridges, stubs: List) { if (platform == KotlinPlatform.JVM) { out("@file:JvmName(${jvmFileClassName.quoteAsKotlinLiteral()})") } - out("@file:Suppress(\"UNUSED_EXPRESSION\", \"UNUSED_VARIABLE\")") + if (platform == KotlinPlatform.NATIVE) { + out("@file:kotlinx.cinterop.InteropStubs") + } + + val suppress = mutableListOf("UNUSED_VARIABLE", "UNUSED_EXPRESSION").apply { + if (configuration.library.language == Language.OBJECTIVE_C) { + add("CONFLICTING_OVERLOADS") + add("RETURN_TYPE_MISMATCH_ON_INHERITANCE") + add("RETURN_TYPE_MISMATCH_ON_OVERRIDE") + add("WRONG_MODIFIER_CONTAINING_DECLARATION") // For `final val` in interface. + add("PARAMETER_NAME_CHANGED_ON_OVERRIDE") + add("UNUSED_PARAMETER") // For constructors. + } + } + + out("@file:Suppress(${suppress.joinToString { it.quoteAsKotlinLiteral() }})") if (pkgName != "") { out("package $pkgName") out("") @@ -1269,52 +734,27 @@ class StubGenerator( out("import kotlinx.cinterop.*") out("") + val context = object : StubGenerationContext { + val topLevelDeclarationLines = mutableListOf() + + override val nativeBridges: NativeBridges get() = nativeBridges + override fun addTopLevelDeclaration(lines: List) { + topLevelDeclarationLines.addAll(lines) + } + } + stubs.forEach { - it.kotlinStubLines.forEach { out(it) } + it.generate(context).forEach(out) out("") } + context.topLevelDeclarationLines.forEach(out) + nativeBridges.kotlinLines.forEach(out) if (platform == KotlinPlatform.JVM) { out("private val loadLibrary = System.loadLibrary(\"$libName\")") } } - /** - * Returns the C type to be used for value of given Kotlin type in JNI function implementation. - */ - fun getCBridgeType(kotlinJniBridgeType: String): String { - return when (platform) { - KotlinPlatform.JVM -> getCJniBridgeType(kotlinJniBridgeType) - KotlinPlatform.NATIVE -> getCNativeBridgeType(kotlinJniBridgeType) - } - } - - fun getCNativeBridgeType(kotlinJniBridgeType: String) = when (kotlinJniBridgeType) { - "Unit" -> "void" - "Byte" -> "int8_t" - "Short" -> "int16_t" - "Int" -> "int32_t" - "Long" -> "int64_t" - "Float" -> "float" - "Double" -> "double" // TODO: float32_t, float64_t? - "NativePtr" -> "void*" - else -> TODO(kotlinJniBridgeType) - } - - /** - * Returns the C type to be used for value of given Kotlin type in JNI function implementation. - */ - fun getCJniBridgeType(kotlinJniBridgeType: String) = when (kotlinJniBridgeType) { - "Unit" -> "void" - "Byte" -> "jbyte" - "Short" -> "jshort" - "Int" -> "jint" - "Long", "NativePtr" -> "jlong" - "Float" -> "jfloat" - "Double" -> "jdouble" - else -> throw NotImplementedError(kotlinJniBridgeType) - } - val libraryForCStubs = configuration.library.copy( includes = mutableListOf().apply { add("stdint.h") @@ -1334,22 +774,16 @@ class StubGenerator( ) /** - * Produces to [out] the contents of C source file to be compiled into JNI lib used for Kotlin bindings impl. + * Produces to [out] the contents of C source file to be compiled into native lib used for Kotlin bindings impl. */ - private fun generateCFile(stubs: List, entryPoint: String?) { + private fun generateCFile(bridges: NativeBridges, entryPoint: String?) { libraryForCStubs.preambleLines.forEach { out(it) } out("") - stubs.forEach { - val lines = it.nativeStubLines - if (lines.isNotEmpty()) { - lines.forEach { - out(it) - } - out("") - } + bridges.nativeLines.forEach { + out(it) } if (entryPoint != null) { @@ -1362,99 +796,24 @@ class StubGenerator( } } - private tailrec fun Type.unwrapTypedefs(): Type = if (this is Typedef) { - this.def.aliased.unwrapTypedefs() - } else { - this - } - - /** - * Produces to [out] the implementation of JNI function used in Kotlin binding for given C function. - */ - private fun generateCJniFunction(func: FunctionDecl) { - if (func.generateAsFfiVarargs()) { - assert (platform == KotlinPlatform.NATIVE) - // The C stub simply returns pointer to the function: - out("void* ${func.cStubName}() { return ${func.name}; }") - return - } - - val paramNames = paramNames(func) - val paramBindings = paramBindings(func) - val retValBinding = retValBinding(func) - - val parameters = paramBindings - .map { getCBridgeType(it.kotlinJniBridgeType) } - .mapIndexed { i, type -> "$type ${paramNames[i]}" } - - val cReturnType = getCBridgeType(retValBinding.kotlinJniBridgeType) - - val funcDecl = when (platform) { - KotlinPlatform.JVM -> { - val joinedParameters = if (!parameters.isEmpty()) { - parameters.joinToString(separator = ", ", prefix = ", ") - } else { - "" - } - - val funcFullName = buildString { - if (pkgName.isNotEmpty()) { - append(pkgName) - append('.') - } - append(jvmFileClassName) - append('.') - append(func.kotlinExternalName) - } - - val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024") - "JNIEXPORT $cReturnType JNICALL $functionName (JNIEnv *jniEnv, jclass jclss$joinedParameters)" - } - KotlinPlatform.NATIVE -> { - val joinedParameters = parameters.joinToString(", ") - val functionName = func.cStubName - "$cReturnType $functionName ($joinedParameters)" - } - } - - val arguments = func.parameters.mapIndexed { i, parameter -> - val cType = parameter.type.getStringRepresentation() - val name = paramNames[i] - val unwrappedType = parameter.type.unwrapTypedefs() - when (unwrappedType) { - is RecordType -> "*($cType*)$name" - is ArrayType -> { - val pointerCType = PointerType(unwrappedType.elemType).getStringRepresentation() - "($pointerCType)$name" - } - else -> "($cType)$name" - } - }.joinToString(", ") - - val callExpr = "${func.name}($arguments)" - - block(funcDecl) { - - if (cReturnType == "void") { - out("$callExpr;") - } else if (func.returnsRecord()) { - out("*(${func.returnType.getStringRepresentation()}*)retValPlacement = $callExpr;") - out("return ($cReturnType) retValPlacement;") - } else { - out("return ($cReturnType) ($callExpr);") - } - } - } - fun generateFiles(ktFile: Appendable, cFile: Appendable, entryPoint: String?) { val stubs = generateStubs() + val nativeBridges = simpleBridgeGenerator.prepare() + withOutput(cFile) { - generateCFile(stubs, entryPoint) + generateCFile(nativeBridges, entryPoint) } withOutput(ktFile) { - generateKotlinFile(stubs) + generateKotlinFile(nativeBridges, stubs) } } + + val simpleBridgeGenerator: SimpleBridgeGenerator = + SimpleBridgeGeneratorImpl(platform, pkgName, jvmFileClassName, libraryForCStubs) + + val mappingBridgeGenerator: MappingBridgeGenerator = + MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator) + } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index 439fe366c4d..306306273d6 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -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, args: Map>) { @@ -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 = mutableListOf().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 -> diff --git a/backend.native/build.gradle b/backend.native/build.gradle index a1cff7f9435..a9c7e8c7362 100644 --- a/backend.native/build.gradle +++ b/backend.native/build.gradle @@ -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' } diff --git a/backend.native/compiler/ir/backend.native/resources/META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition b/backend.native/compiler/ir/backend.native/resources/META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition new file mode 100644 index 00000000000..d558692ac29 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/resources/META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition @@ -0,0 +1 @@ +org.jetbrains.kotlin.backend.konan.ObjCOverridabilityCondition diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index 33c3bbfa69e..96d8bf04070 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -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().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) = diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt new file mode 100644 index 00000000000..64274a1c156 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt @@ -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().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().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 + } + +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt index b74cb748f8c..7f19b0a318d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt @@ -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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 1ad8840294e..d27d8ee2248 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -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 +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index be5eb18047a..1eff4d2982a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -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 { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index c5dba4fcf8b..5a9085a5303 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -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() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index 1be283f5b73..1aa9a07dfb4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -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) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 62d10fcc708..d3708ed430c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -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" diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index e726bfa227e..f7a338b9848 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -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): LLVMValueRef { @@ -1871,7 +1898,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map): LLVMValueRef { @@ -1934,10 +1970,122 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { + 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, 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 { + return callDirect(context.interopBuiltIns.objCPointerHolder.unsubstitutedPrimaryConstructor!!, + args, Lifetime.IRRELEVANT) + } + + private fun genGetObjCReceiverOrSuper(args: List): 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, 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 = + this.unsubstitutedMemberScope.contributedMethods.filter { + it.kind.isReal && it !is ConstructorDescriptor + }.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt index ae02071e75b..f8060207c69 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt @@ -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, // 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) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 61b28dac633..765d12cd3a4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -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) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt index 9dc284a1b9b..d1a76271728 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt @@ -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") } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt index d28c840f45c..69eb5b23b0b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt @@ -152,6 +152,10 @@ internal class StaticData(override val context: Context): ContextUtils { } private val stringLiterals = mutableMapOf() + private val cStringLiterals = mutableMapOf() + + fun cStringLiteral(value: String) = + cStringLiterals.getOrPut(value) { placeCStringLiteral(value) } fun kotlinStringLiteral(type: KotlinType, value: IrConst) = stringLiterals.getOrPut(value.value) { createKotlinStringLiteral(type, value) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticDataUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticDataUtils.kt index 6649430da85..ac3df6d8802 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticDataUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticDataUtils.kt @@ -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) +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index d433aaf802d..f46e20b84a8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -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() + + 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 { + + 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() + .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) ) } } diff --git a/backend.native/konan.properties b/backend.native/konan.properties index 14c32b1f338..42d3d465142 100644 --- a/backend.native/konan.properties +++ b/backend.native/konan.properties @@ -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 diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 016f34f9025..a83be98a576 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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" + } + } + } } diff --git a/backend.native/tests/interop/objc/objcSmoke.def b/backend.native/tests/interop/objc/objcSmoke.def new file mode 100644 index 00000000000..d19eb24c93c --- /dev/null +++ b/backend.native/tests/interop/objc/objcSmoke.def @@ -0,0 +1,2 @@ +language = Objective-C +headerFilter = **/smoke.h diff --git a/backend.native/tests/interop/objc/smoke.h b/backend.native/tests/interop/objc/smoke.h new file mode 100644 index 00000000000..c764f5f399d --- /dev/null +++ b/backend.native/tests/interop/objc/smoke.h @@ -0,0 +1,24 @@ +#import + +@protocol Printer +@required +-(void)print:(const char*)string; +@end; + +@interface Foo : NSObject +@property NSString* name; +-(void)hello; +-(void)helloWithPrinter:(id )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 pair, int first, int second); diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt new file mode 100644 index 00000000000..23f03627e45 --- /dev/null +++ b/backend.native/tests/interop/objc/smoke.kt @@ -0,0 +1,55 @@ +import kotlinx.cinterop.* +import objcSmoke.* + +fun main(args: Array) { + autoreleasepool { + run() + } +} + +fun run() { + val foo = Foo() + foo.hello() + foo.name = "everybody" + foo.helloWithPrinter(object : NSObject(), PrinterProtocol { + override fun print(string: CPointer?) { + 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 + } +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/smoke.m b/backend.native/tests/interop/objc/smoke.m new file mode 100644 index 00000000000..07f2b97de42 --- /dev/null +++ b/backend.native/tests/interop/objc/smoke.m @@ -0,0 +1,46 @@ +#import +#import +#import "smoke.h" + +@interface CPrinter : NSObject +-(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 { + NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name]; + [printer print:message.UTF8String]; +} + +-(void)dealloc { + printf("Deallocated\n"); +} + +@end; + +void replacePairElements(id pair, int first, int second) { + [pair update:0 add:(first - pair.first)]; + [pair update:1 sub:(pair.second - second)]; +} diff --git a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/CompileToBitcode.groovy b/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/CompileToBitcode.groovy index 67fc0b567ce..6eb7f60875b 100644 --- a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/CompileToBitcode.groovy +++ b/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/CompileToBitcode.groovy @@ -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 { diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index bd773932f61..94774fed9ed 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -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(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( + reinterpret_cast(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(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( - reinterpret_cast(obj + 1) + typeInfo->objOffsets_[index]); - UpdateRef(location, nullptr); - } + DeinitInstanceBodyImpl(typeInfo, reinterpret_cast(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(header + 1); + + for (int index = 0; index < header->objectCount_; index++) { + runDeallocationHooks(obj); + + obj = reinterpret_cast( + reinterpret_cast(obj) + objectSize(obj)); + } + memoryState->allocCount--; konanFreeMemory(header); } diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 62a54a9a059..39be02acaf1 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -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*)); diff --git a/runtime/src/main/cpp/ObjCInterop.cpp b/runtime/src/main/cpp/ObjCInterop.cpp new file mode 100644 index 00000000000..c052004d7c2 --- /dev/null +++ b/runtime/src/main/cpp/ObjCInterop.cpp @@ -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 +#include +#include +#include +#include +#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(clazz)); + return static_cast(ivars); +} + +static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) { + GetKotlinClassData(clazz)->typeInfo = typeInfo; +} + +const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) { + void* objcPtr = *reinterpret_cast(obj + 1); // TODO: use more reliable layout description + Class clazz = object_getClass(reinterpret_cast(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(reinterpret_cast(self) + classData->bodyOffset); + + const TypeInfo* typeInfo = classData->typeInfo; + DeinitInstanceBody(typeInfo, body); + + // Call super.dealloc: + struct objc_super s = {self, clazz}; + auto messenger = reinterpret_cast(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 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(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 diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm new file mode 100644 index 00000000000..c3c23d0cac9 --- /dev/null +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -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 +#import + +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 \ No newline at end of file diff --git a/runtime/src/main/cpp/Types.h b/runtime/src/main/cpp/Types.h index 60d299d1be8..3292edb5263 100644 --- a/runtime/src/main/cpp/Types.h +++ b/runtime/src/main/cpp/Types.h @@ -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); diff --git a/runtime/src/main/cpp/Utils.h b/runtime/src/main/cpp/Utils.h new file mode 100644 index 00000000000..d019a860578 --- /dev/null +++ b/runtime/src/main/cpp/Utils.h @@ -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 +#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 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; +}; diff --git a/samples/objc/build.gradle b/samples/objc/build.gradle new file mode 100644 index 00000000000..d71a9558557 --- /dev/null +++ b/samples/objc/build.gradle @@ -0,0 +1,15 @@ +apply plugin: 'konan' + +konanInterop { + objc { + target "macbook" + } +} + + +konanArtifacts { + Window { + useInterop 'objc' + target "macbook" + } +} diff --git a/samples/objc/src/main/c_interop/objc.def b/samples/objc/src/main/c_interop/objc.def new file mode 100644 index 00000000000..c1c50548269 --- /dev/null +++ b/samples/objc/src/main/c_interop/objc.def @@ -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 diff --git a/samples/objc/src/main/kotlin/Window.kt b/samples/objc/src/main/kotlin/Window.kt new file mode 100644 index 00000000000..b1e0e3eb09a --- /dev/null +++ b/samples/objc/src/main/kotlin/Window.kt @@ -0,0 +1,59 @@ +import objc.* +import kotlinx.cinterop.* + +fun main(args: Array) { + 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) + } +} diff --git a/samples/settings.gradle b/samples/settings.gradle index af2363f660e..ead16853dbc 100644 --- a/samples/settings.gradle +++ b/samples/settings.gradle @@ -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 '../' diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt index b30d18be3d8..087238830b8 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt @@ -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!!,