Implement very basic Objective-C interop
Also refactor StubGenerator
This commit is contained in:
committed by
SvyatoslavScherbina
parent
a38835cb46
commit
d3185e3e19
+386
-34
@@ -25,19 +25,47 @@ private class StructDeclImpl(spelling: String) : StructDecl(spelling) {
|
||||
override var def: StructDefImpl? = null
|
||||
}
|
||||
|
||||
private class StructDefImpl(size: Long, align: Int, decl: StructDecl, hasNaturalLayout: Boolean) :
|
||||
StructDef(size, align, decl, hasNaturalLayout) {
|
||||
private class StructDefImpl(
|
||||
size: Long, align: Int, decl: StructDecl,
|
||||
hasNaturalLayout: Boolean, hasUnalignedFields: Boolean
|
||||
) : StructDef(
|
||||
size, align, decl,
|
||||
hasNaturalLayout = hasNaturalLayout, hasUnalignedFields = hasUnalignedFields
|
||||
) {
|
||||
|
||||
override val fields = mutableListOf<Field>()
|
||||
}
|
||||
|
||||
private class EnumDefImpl(spelling: String, type: PrimitiveType) : EnumDef(spelling, type) {
|
||||
private class EnumDefImpl(spelling: String, type: Type) : EnumDef(spelling, type) {
|
||||
override val constants = mutableListOf<EnumConstant>()
|
||||
}
|
||||
|
||||
private interface ObjCClassOrProtocolImpl {
|
||||
val protocols: MutableList<ObjCProtocol>
|
||||
val methods: MutableList<ObjCMethod>
|
||||
val properties: MutableList<ObjCProperty>
|
||||
}
|
||||
|
||||
private class ObjCProtocolImpl(name: String) : ObjCProtocol(name), ObjCClassOrProtocolImpl {
|
||||
override val protocols = mutableListOf<ObjCProtocol>()
|
||||
override val methods = mutableListOf<ObjCMethod>()
|
||||
override val properties = mutableListOf<ObjCProperty>()
|
||||
}
|
||||
|
||||
private class ObjCClassImpl(name: String) : ObjCClass(name), ObjCClassOrProtocolImpl {
|
||||
override val protocols = mutableListOf<ObjCProtocol>()
|
||||
override val methods = mutableListOf<ObjCMethod>()
|
||||
override val properties = mutableListOf<ObjCProperty>()
|
||||
override var baseClass: ObjCClass? = null
|
||||
}
|
||||
|
||||
internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
|
||||
private data class DeclarationID(val usr: String)
|
||||
private sealed class DeclarationID {
|
||||
data class USR(val usr: String) : DeclarationID()
|
||||
object VaListTag : DeclarationID()
|
||||
object BuiltinVaList : DeclarationID()
|
||||
}
|
||||
|
||||
private val structById = mutableMapOf<DeclarationID, StructDeclImpl>()
|
||||
|
||||
@@ -49,21 +77,39 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
override val enums: List<EnumDef>
|
||||
get() = enumById.values.toList()
|
||||
|
||||
private val objCClassesByName = mutableMapOf<String, ObjCClassImpl>()
|
||||
|
||||
override val objCClasses: List<ObjCClass> get() = objCClassesByName.values.toList()
|
||||
|
||||
private val objCProtocolsByName = mutableMapOf<String, ObjCProtocolImpl>()
|
||||
|
||||
override val objCProtocols: List<ObjCProtocol> get() = objCProtocolsByName.values.toList()
|
||||
|
||||
private val typedefById = mutableMapOf<DeclarationID, TypedefDef>()
|
||||
|
||||
override val typedefs: List<TypedefDef>
|
||||
get() = typedefById.values.toList()
|
||||
|
||||
val functionByName = mutableMapOf<String, FunctionDecl>()
|
||||
private val functionById = mutableMapOf<DeclarationID, FunctionDecl>()
|
||||
|
||||
override val functions: List<FunctionDecl>
|
||||
get() = functionByName.values.toList()
|
||||
get() = functionById.values.toList()
|
||||
|
||||
override val macroConstants = mutableListOf<ConstantDef>()
|
||||
|
||||
private fun getDeclarationId(cursor: CValue<CXCursor>): DeclarationID {
|
||||
val usr = clang_getCursorUSR(cursor).convertAndDispose()
|
||||
return DeclarationID(usr)
|
||||
if (usr == "") {
|
||||
val kind = cursor.kind
|
||||
val spelling = getCursorSpelling(cursor)
|
||||
return when (kind to spelling) {
|
||||
CXCursorKind.CXCursor_StructDecl to "__va_list_tag" -> DeclarationID.VaListTag
|
||||
CXCursorKind.CXCursor_TypedefDecl to "__builtin_va_list" -> DeclarationID.BuiltinVaList
|
||||
else -> error(spelling)
|
||||
}
|
||||
}
|
||||
|
||||
return DeclarationID.USR(usr)
|
||||
}
|
||||
|
||||
private fun getStructDeclAt(cursor: CValue<CXCursor>): StructDeclImpl {
|
||||
@@ -91,8 +137,13 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
val type = clang_getCursorType(cursor)
|
||||
val size = clang_Type_getSizeOf(type)
|
||||
val align = clang_Type_getAlignOf(type).toInt()
|
||||
val hasNaturalLayout = structHasNaturalLayout(cursor)
|
||||
val structDef = StructDefImpl(size, align, structDecl, hasNaturalLayout)
|
||||
|
||||
val structDef = StructDefImpl(
|
||||
size, align, structDecl,
|
||||
hasNaturalLayout = structHasNaturalLayout(cursor),
|
||||
hasUnalignedFields = structHasUnalignedFields(cursor)
|
||||
)
|
||||
|
||||
structDecl.def = structDef
|
||||
|
||||
visitChildren(cursor) { childCursor: CValue<CXCursor>, _: CValue<CXCursor> ->
|
||||
@@ -117,7 +168,7 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
val cursorType = clang_getCursorType(cursor)
|
||||
val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose()
|
||||
|
||||
val baseType = convertType(clang_getEnumDeclIntegerType(cursor)) as PrimitiveType
|
||||
val baseType = convertType(clang_getEnumDeclIntegerType(cursor))
|
||||
|
||||
val enumDef = EnumDefImpl(typeSpelling, baseType)
|
||||
|
||||
@@ -137,11 +188,112 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getObjCCategoryClassCursor(cursor: CValue<CXCursor>): CValue<CXCursor> {
|
||||
assert(cursor.kind == CXCursorKind.CXCursor_ObjCCategoryDecl)
|
||||
var classRef: CValue<CXCursor>? = null
|
||||
visitChildren(cursor) { child, _ ->
|
||||
if (child.kind == CXCursorKind.CXCursor_ObjCClassRef) {
|
||||
classRef = child
|
||||
CXChildVisitResult.CXChildVisit_Break
|
||||
} else {
|
||||
CXChildVisitResult.CXChildVisit_Continue
|
||||
}
|
||||
}
|
||||
|
||||
return clang_getCursorReferenced(classRef!!).apply {
|
||||
assert(this.kind == CXCursorKind.CXCursor_ObjCInterfaceDecl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getObjCClassAt(cursor: CValue<CXCursor>): ObjCClassImpl {
|
||||
assert(cursor.kind == CXCursorKind.CXCursor_ObjCInterfaceDecl) { cursor.kind }
|
||||
|
||||
val name = clang_getCursorDisplayName(cursor).convertAndDispose()
|
||||
|
||||
objCClassesByName[name]?.let { return it }
|
||||
|
||||
val result = ObjCClassImpl(name)
|
||||
objCClassesByName[name] = result
|
||||
|
||||
addChildrenToClassOrProtocol(cursor, result)
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
private fun getObjCProtocolAt(cursor: CValue<CXCursor>): ObjCProtocolImpl? {
|
||||
assert(cursor.kind == CXCursorKind.CXCursor_ObjCProtocolDecl) { cursor.kind }
|
||||
if (clang_isCursorDefinition(cursor) == 0) {
|
||||
val definition = clang_getCursorDefinition(cursor)
|
||||
if (clang_isCursorDefinition(cursor) == 0) return null
|
||||
return getObjCProtocolAt(definition)
|
||||
}
|
||||
|
||||
val name = clang_getCursorDisplayName(cursor).convertAndDispose()
|
||||
|
||||
objCProtocolsByName[name]?.let { return it }
|
||||
|
||||
val result = ObjCProtocolImpl(name)
|
||||
objCProtocolsByName[name] = result
|
||||
|
||||
addChildrenToClassOrProtocol(cursor, result)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun addChildrenToClassOrProtocol(cursor: CValue<CXCursor>, result: ObjCClassOrProtocolImpl) {
|
||||
visitChildren(cursor) { child, _ ->
|
||||
when (child.kind) {
|
||||
CXCursorKind.CXCursor_ObjCSuperClassRef -> {
|
||||
assert(cursor.kind == CXCursorKind.CXCursor_ObjCInterfaceDecl)
|
||||
result as ObjCClassImpl
|
||||
|
||||
assert(result.baseClass == null)
|
||||
result.baseClass = getObjCClassAt(clang_getCursorReferenced(child))
|
||||
}
|
||||
CXCursorKind.CXCursor_ObjCProtocolRef -> {
|
||||
getObjCProtocolAt(clang_getCursorReferenced(child))?.let {
|
||||
if (it !in result.protocols) {
|
||||
result.protocols.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
CXCursorKind.CXCursor_ObjCClassMethodDecl, CXCursorKind.CXCursor_ObjCInstanceMethodDecl -> {
|
||||
getObjCMethod(child)?.let { method ->
|
||||
result.methods.removeAll { method.replaces(it) }
|
||||
result.methods.add(method)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
CXChildVisitResult.CXChildVisit_Continue
|
||||
}
|
||||
}
|
||||
|
||||
fun getTypedef(type: CValue<CXType>): Type {
|
||||
val declCursor = clang_getTypeDeclaration(type)
|
||||
val name = getCursorSpelling(declCursor)
|
||||
|
||||
val underlying = convertType(clang_getTypedefDeclUnderlyingType(declCursor))
|
||||
if (clang_getCursorLexicalParent(declCursor).kind != CXCursorKind.CXCursor_TranslationUnit) {
|
||||
// Objective-C type parameters are represented as non-top-level typedefs.
|
||||
// Erase for now:
|
||||
return underlying
|
||||
}
|
||||
|
||||
if (library.language == Language.OBJECTIVE_C) {
|
||||
if (name == "BOOL" || name == "Boolean") {
|
||||
assert(clang_Type_getSizeOf(type) == 1L)
|
||||
return BoolType
|
||||
}
|
||||
|
||||
if (underlying is ObjCPointer && (name == "Class" || name == "id") ||
|
||||
underlying is PointerType && name == "SEL") {
|
||||
|
||||
// Ignore implicit Objective-C typedefs:
|
||||
return underlying
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ((underlying is RecordType && underlying.decl.spelling.split(' ').last() == name) ||
|
||||
(underlying is EnumType && underlying.def.spelling.split(' ').last() == name)) {
|
||||
@@ -189,10 +341,36 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertCursorType(cursor: CValue<CXCursor>) =
|
||||
convertType(clang_getCursorType(cursor))
|
||||
/**
|
||||
* Computes [StructDef.hasUnalignedFields] property.
|
||||
*/
|
||||
fun structHasUnalignedFields(structDefCursor: CValue<CXCursor>): Boolean {
|
||||
var hasUnalignedFields = false
|
||||
visitChildren(structDefCursor) { child, _ ->
|
||||
if (clang_getCursorKind(child) == CXCursorKind.CXCursor_FieldDecl &&
|
||||
clang_Cursor_isBitField(child) == 0 &&
|
||||
clang_Cursor_getOffsetOfField(child) %
|
||||
(clang_Type_getAlignOf(clang_getCursorType(child)) * 8) != 0L) {
|
||||
|
||||
fun convertType(type: CValue<CXType>): Type {
|
||||
hasUnalignedFields = true
|
||||
CXChildVisitResult.CXChildVisit_Break
|
||||
} else {
|
||||
CXChildVisitResult.CXChildVisit_Continue
|
||||
}
|
||||
}
|
||||
|
||||
return hasUnalignedFields
|
||||
}
|
||||
|
||||
private fun convertCursorType(cursor: CValue<CXCursor>) =
|
||||
convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor))
|
||||
|
||||
private inline fun objCType(supplier: () -> ObjCPointer) = when (library.language) {
|
||||
Language.C -> UnsupportedType
|
||||
Language.OBJECTIVE_C -> supplier()
|
||||
}
|
||||
|
||||
fun convertType(type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>? = null): Type {
|
||||
val primitiveType = convertUnqualifiedPrimitiveType(type)
|
||||
if (primitiveType != UnsupportedType) {
|
||||
return primitiveType
|
||||
@@ -217,7 +395,17 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
|
||||
CXType_Void -> VoidType
|
||||
|
||||
CXType_Typedef -> getTypedef(type)
|
||||
CXType_Typedef -> {
|
||||
val declCursor = clang_getTypeDeclaration(type)
|
||||
val declSpelling = getCursorSpelling(declCursor)
|
||||
val underlying = convertType(clang_getTypedefDeclUnderlyingType(declCursor))
|
||||
when {
|
||||
declSpelling == "instancetype" && underlying is ObjCPointer ->
|
||||
ObjCInstanceType(getNullability(type, typeAttributes))
|
||||
|
||||
else -> getTypedef(type)
|
||||
}
|
||||
}
|
||||
|
||||
CXType_Record -> RecordType(getStructDeclAt(clang_getTypeDeclaration(type)))
|
||||
CXType_Enum -> EnumType(getEnumDefAt(clang_getTypeDeclaration(type)))
|
||||
@@ -245,16 +433,58 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
convertFunctionType(type)
|
||||
}
|
||||
|
||||
CXType_ObjCObjectPointer -> objCType {
|
||||
val declaration = clang_getTypeDeclaration(clang_getPointeeType(type))
|
||||
val declarationKind = declaration.kind
|
||||
val nullability = getNullability(type, typeAttributes)
|
||||
when (declarationKind) {
|
||||
CXCursorKind.CXCursor_NoDeclFound -> ObjCIdType(nullability, getProtocols(type))
|
||||
|
||||
CXCursorKind.CXCursor_ObjCInterfaceDecl ->
|
||||
ObjCObjectPointer(getObjCClassAt(declaration), nullability, getProtocols(type))
|
||||
|
||||
else -> TODO(declarationKind.toString())
|
||||
}
|
||||
}
|
||||
|
||||
CXType_ObjCId -> objCType { ObjCIdType(getNullability(type, typeAttributes), getProtocols(type)) }
|
||||
|
||||
CXType_ObjCClass -> objCType { ObjCClassPointer(getNullability(type, typeAttributes), getProtocols(type)) }
|
||||
|
||||
CXType_ObjCSel -> PointerType(VoidType)
|
||||
|
||||
CXType_BlockPointer -> objCType { ObjCIdType(getNullability(type, typeAttributes), getProtocols(type)) }
|
||||
|
||||
else -> UnsupportedType
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNullability(
|
||||
type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>?
|
||||
): ObjCPointer.Nullability {
|
||||
|
||||
if (typeAttributes == null) return ObjCPointer.Nullability.Unspecified
|
||||
|
||||
return when (clang_Type_getNullabilityKind(type, typeAttributes)) {
|
||||
CXNullabilityKind.CXNullabilityKind_Nullable -> ObjCPointer.Nullability.Nullable
|
||||
CXNullabilityKind.CXNullabilityKind_NonNull -> ObjCPointer.Nullability.NonNull
|
||||
CXNullabilityKind.CXNullabilityKind_Unspecified -> ObjCPointer.Nullability.Unspecified
|
||||
}
|
||||
}
|
||||
|
||||
private fun getProtocols(type: CValue<CXType>): List<ObjCProtocol> {
|
||||
val num = clang_Type_getNumProtocols(type)
|
||||
return (0 until num).mapNotNull { index ->
|
||||
getObjCProtocolAt(clang_Type_getProtocol(type, index))
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertFunctionType(type: CValue<CXType>): Type {
|
||||
val kind = type.kind
|
||||
assert (kind == CXType_Unexposed || kind == CXType_FunctionProto)
|
||||
|
||||
return if (clang_isFunctionTypeVariadic(type) != 0) {
|
||||
UnsupportedType
|
||||
VoidType // make this function pointer opaque.
|
||||
} else {
|
||||
val returnType = convertType(clang_getResultType(type))
|
||||
val numArgs = clang_getNumArgTypes(type)
|
||||
@@ -286,38 +516,160 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
}
|
||||
|
||||
CXIdxEntity_Function -> {
|
||||
val name = entityName!!
|
||||
val returnType = convertType(clang_getCursorResultType(cursor))
|
||||
val argNum = clang_Cursor_getNumArguments(cursor)
|
||||
val args = (0 .. argNum - 1).map {
|
||||
val argCursor = clang_Cursor_getArgument(cursor, it)
|
||||
val argName = getCursorSpelling(argCursor)
|
||||
val type = convertCursorType(argCursor)
|
||||
Parameter(argName, type)
|
||||
if (isAvailable(cursor)) {
|
||||
functionById[getDeclarationId(cursor)] = getFunction(cursor)
|
||||
}
|
||||
|
||||
val binaryName = when (library.language) {
|
||||
Language.C -> clang_Cursor_getMangling(cursor).convertAndDispose()
|
||||
}
|
||||
|
||||
val definitionCursor = clang_getCursorDefinition(cursor)
|
||||
val isDefined = (clang_Cursor_isNull(definitionCursor) == 0)
|
||||
|
||||
val isVararg = clang_Cursor_isVariadic(cursor) != 0
|
||||
|
||||
functionByName[name] = FunctionDecl(name, args, returnType, binaryName, isDefined, isVararg)
|
||||
}
|
||||
|
||||
CXIdxEntity_Enum -> {
|
||||
getEnumDefAt(cursor)
|
||||
}
|
||||
|
||||
CXIdxEntity_ObjCClass -> {
|
||||
if (isAvailable(cursor)) {
|
||||
getObjCClassAt(clang_getCursorReferenced(cursor))
|
||||
}
|
||||
}
|
||||
|
||||
CXIdxEntity_ObjCCategory -> {
|
||||
val classCursor = getObjCCategoryClassCursor(cursor)
|
||||
if (isAvailable(classCursor)) {
|
||||
val objCClass = getObjCClassAt(classCursor)
|
||||
addChildrenToClassOrProtocol(cursor, objCClass)
|
||||
}
|
||||
}
|
||||
|
||||
CXIdxEntity_ObjCProtocol -> {
|
||||
if (isAvailable(cursor)) {
|
||||
getObjCProtocolAt(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
CXIdxEntity_ObjCProperty -> {
|
||||
val container = clang_getCursorSemanticParent(cursor)
|
||||
if (isAvailable(cursor) && isAvailable(container)) {
|
||||
val propertyInfo = clang_index_getObjCPropertyDeclInfo(info.ptr)!!.pointed
|
||||
val getter = getObjCMethod(propertyInfo.getter!!.pointed.cursor.readValue())
|
||||
val setter = propertyInfo.setter?.let {
|
||||
getObjCMethod(it.pointed.cursor.readValue())
|
||||
}
|
||||
|
||||
if (getter != null) {
|
||||
val property = ObjCProperty(entityName!!, getter, setter)
|
||||
val classOrProtocol: ObjCClassOrProtocolImpl? = when (container.kind) {
|
||||
CXCursorKind.CXCursor_ObjCCategoryDecl -> {
|
||||
val classCursor = getObjCCategoryClassCursor(container)
|
||||
if (isAvailable(classCursor)) {
|
||||
getObjCClassAt(classCursor)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
CXCursorKind.CXCursor_ObjCInterfaceDecl -> getObjCClassAt(container)
|
||||
CXCursorKind.CXCursor_ObjCProtocolDecl -> getObjCProtocolAt(container)!!
|
||||
else -> error(container.kind)
|
||||
}
|
||||
|
||||
if (classOrProtocol != null) {
|
||||
classOrProtocol.properties.removeAll { property.replaces(it) }
|
||||
classOrProtocol.properties.add(property)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Ignore declaration.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFunction(cursor: CValue<CXCursor>): FunctionDecl {
|
||||
val name = clang_getCursorSpelling(cursor).convertAndDispose()
|
||||
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
|
||||
|
||||
val parameters = getFunctionParameters(cursor)
|
||||
|
||||
val binaryName = when (library.language) {
|
||||
Language.C, Language.OBJECTIVE_C -> clang_Cursor_getMangling(cursor).convertAndDispose()
|
||||
}
|
||||
|
||||
val definitionCursor = clang_getCursorDefinition(cursor)
|
||||
val isDefined = (clang_Cursor_isNull(definitionCursor) == 0)
|
||||
|
||||
val isVararg = clang_Cursor_isVariadic(cursor) != 0
|
||||
|
||||
return FunctionDecl(name, parameters, returnType, binaryName, isDefined, isVararg)
|
||||
}
|
||||
|
||||
private fun getObjCMethod(cursor: CValue<CXCursor>): ObjCMethod? {
|
||||
if (!isAvailable(cursor)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val selector = clang_getCursorDisplayName(cursor).convertAndDispose()
|
||||
|
||||
// Ignore some very special methods:
|
||||
when (selector) {
|
||||
"dealloc", "retain", "release", "autorelease", "retainCount", "self" -> return null
|
||||
}
|
||||
|
||||
val encoding = clang_getDeclObjCTypeEncoding(cursor).convertAndDispose()
|
||||
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
|
||||
val parameters = getFunctionParameters(cursor)
|
||||
|
||||
val isClass = when (cursor.kind) {
|
||||
CXCursorKind.CXCursor_ObjCClassMethodDecl -> true
|
||||
CXCursorKind.CXCursor_ObjCInstanceMethodDecl -> false
|
||||
else -> error(cursor.kind)
|
||||
}
|
||||
|
||||
return ObjCMethod(selector, encoding, parameters, returnType,
|
||||
isClass = isClass,
|
||||
nsConsumesSelf = hasAttribute(cursor, NS_CONSUMES_SELF),
|
||||
nsReturnsRetained = hasAttribute(cursor, NS_RETURNS_RETAINED),
|
||||
isOptional = (clang_Cursor_isObjCOptional(cursor) != 0),
|
||||
isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0))
|
||||
}
|
||||
|
||||
// TODO: unavailable declarations should be imported as deprecated.
|
||||
private fun isAvailable(cursor: CValue<CXCursor>): Boolean = when (clang_getCursorAvailability(cursor)) {
|
||||
CXAvailabilityKind.CXAvailability_Available,
|
||||
CXAvailabilityKind.CXAvailability_Deprecated -> true
|
||||
|
||||
CXAvailabilityKind.CXAvailability_NotAvailable,
|
||||
CXAvailabilityKind.CXAvailability_NotAccessible -> false
|
||||
}
|
||||
|
||||
private fun getFunctionParameters(cursor: CValue<CXCursor>): List<Parameter> {
|
||||
val argNum = clang_Cursor_getNumArguments(cursor)
|
||||
val args = (0..argNum - 1).map {
|
||||
val argCursor = clang_Cursor_getArgument(cursor, it)
|
||||
val argName = getCursorSpelling(argCursor)
|
||||
val type = convertCursorType(argCursor)
|
||||
Parameter(argName, type,
|
||||
nsConsumed = hasAttribute(argCursor, NS_CONSUMED))
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
private val NS_CONSUMED = "ns_consumed"
|
||||
private val NS_CONSUMES_SELF = "ns_consumes_self"
|
||||
private val NS_RETURNS_RETAINED = "ns_returns_retained"
|
||||
|
||||
private fun hasAttribute(cursor: CValue<CXCursor>, name: String): Boolean {
|
||||
var result = false
|
||||
visitChildren(cursor) { child, _ ->
|
||||
if (clang_isAttribute(child.kind) != 0 && clang_Cursor_getAttributeSpelling(child)?.toKString() == name) {
|
||||
result = true
|
||||
CXChildVisitResult.CXChildVisit_Break
|
||||
} else {
|
||||
CXChildVisitResult.CXChildVisit_Continue
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex {
|
||||
|
||||
+2
-2
@@ -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:
|
||||
|
||||
+74
-5
@@ -16,8 +16,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.indexer
|
||||
|
||||
enum class Language {
|
||||
C
|
||||
enum class Language(val sourceFileExtension: String) {
|
||||
C("c"),
|
||||
OBJECTIVE_C("m")
|
||||
}
|
||||
|
||||
data class NativeLibrary(val includes: List<String>,
|
||||
@@ -39,6 +40,8 @@ fun buildNativeIndex(library: NativeLibrary): NativeIndex = buildNativeIndexImpl
|
||||
abstract class NativeIndex {
|
||||
abstract val structs: List<StructDecl>
|
||||
abstract val enums: List<EnumDef>
|
||||
abstract val objCClasses: List<ObjCClass>
|
||||
abstract val objCProtocols: List<ObjCProtocol>
|
||||
abstract val typedefs: List<TypedefDef>
|
||||
abstract val functions: List<FunctionDecl>
|
||||
abstract val macroConstants: List<ConstantDef>
|
||||
@@ -65,7 +68,8 @@ abstract class StructDecl(val spelling: String) {
|
||||
*/
|
||||
abstract class StructDef(val size: Long, val align: Int,
|
||||
val decl: StructDecl,
|
||||
val hasNaturalLayout: Boolean) {
|
||||
val hasNaturalLayout: Boolean,
|
||||
val hasUnalignedFields: Boolean) {
|
||||
|
||||
abstract val fields: List<Field>
|
||||
}
|
||||
@@ -78,15 +82,48 @@ class EnumConstant(val name: String, val value: Long, val isExplicitlyDefined: B
|
||||
/**
|
||||
* C enum definition.
|
||||
*/
|
||||
abstract class EnumDef(val spelling: String, val baseType: PrimitiveType) {
|
||||
abstract class EnumDef(val spelling: String, val baseType: Type) {
|
||||
|
||||
abstract val constants: List<EnumConstant>
|
||||
}
|
||||
|
||||
sealed class ObjCClassOrProtocol(val name: String) {
|
||||
abstract val protocols: List<ObjCProtocol>
|
||||
abstract val methods: List<ObjCMethod>
|
||||
abstract val properties: List<ObjCProperty>
|
||||
}
|
||||
|
||||
data class ObjCMethod(
|
||||
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
|
||||
val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
|
||||
val isOptional: Boolean, val isInit: Boolean
|
||||
) {
|
||||
|
||||
fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType
|
||||
|
||||
fun getReturnType(container: ObjCClassOrProtocol): Type = if (returnType is ObjCInstanceType) {
|
||||
when (container) {
|
||||
is ObjCClass -> ObjCObjectPointer(container, returnType.nullability, protocols = emptyList())
|
||||
is ObjCProtocol -> ObjCIdType(returnType.nullability, protocols = listOf(container))
|
||||
}
|
||||
} else {
|
||||
returnType
|
||||
}
|
||||
}
|
||||
|
||||
data class ObjCProperty(val name: String, val getter: ObjCMethod, val setter: ObjCMethod?) {
|
||||
fun getType(container: ObjCClassOrProtocol): Type = getter.getReturnType(container)
|
||||
}
|
||||
|
||||
abstract class ObjCClass(name: String) : ObjCClassOrProtocol(name) {
|
||||
abstract val baseClass: ObjCClass?
|
||||
}
|
||||
abstract class ObjCProtocol(name: String) : ObjCClassOrProtocol(name)
|
||||
|
||||
/**
|
||||
* C function parameter.
|
||||
*/
|
||||
class Parameter(val name: String?, val type: Type)
|
||||
class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
|
||||
|
||||
/**
|
||||
* C function declaration.
|
||||
@@ -117,6 +154,8 @@ interface PrimitiveType : Type
|
||||
|
||||
object CharType : PrimitiveType
|
||||
|
||||
object BoolType : PrimitiveType
|
||||
|
||||
data class IntegerType(val size: Int, val isSigned: Boolean, val spelling: String) : PrimitiveType
|
||||
|
||||
// TODO: floating type is not actually defined entirely by its size.
|
||||
@@ -142,4 +181,34 @@ data class IncompleteArrayType(override val elemType: Type) : ArrayType
|
||||
|
||||
data class Typedef(val def: TypedefDef) : Type
|
||||
|
||||
sealed class ObjCPointer : Type {
|
||||
enum class Nullability {
|
||||
Nullable, NonNull, Unspecified
|
||||
}
|
||||
|
||||
abstract val nullability: Nullability
|
||||
}
|
||||
|
||||
sealed class ObjCQualifiedPointer : ObjCPointer() {
|
||||
abstract val protocols: List<ObjCProtocol>
|
||||
}
|
||||
|
||||
data class ObjCObjectPointer(
|
||||
val def: ObjCClass,
|
||||
override val nullability: Nullability,
|
||||
override val protocols: List<ObjCProtocol>
|
||||
) : ObjCQualifiedPointer()
|
||||
|
||||
data class ObjCClassPointer(
|
||||
override val nullability: Nullability,
|
||||
override val protocols: List<ObjCProtocol>
|
||||
) : ObjCQualifiedPointer()
|
||||
|
||||
data class ObjCIdType(
|
||||
override val nullability: Nullability,
|
||||
override val protocols: List<ObjCProtocol>
|
||||
) : ObjCQualifiedPointer()
|
||||
|
||||
data class ObjCInstanceType(override val nullability: Nullability) : ObjCPointer()
|
||||
|
||||
object UnsupportedType : Type
|
||||
+8
-5
@@ -176,10 +176,7 @@ internal fun Appendable.appendPreamble(library: NativeLibrary) = this.apply {
|
||||
* Creates temporary source file which includes the library.
|
||||
*/
|
||||
internal fun NativeLibrary.createTempSource(): File {
|
||||
val suffix = when (language) {
|
||||
Language.C -> ".c"
|
||||
}
|
||||
val result = createTempFile(suffix = suffix)
|
||||
val result = createTempFile(suffix = ".${language.sourceFileExtension}")
|
||||
result.deleteOnExit()
|
||||
|
||||
result.bufferedWriter().use { writer ->
|
||||
@@ -478,4 +475,10 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat
|
||||
result.add(mainFile!!)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fun ObjCMethod.replaces(other: ObjCMethod): Boolean =
|
||||
this.isClass == other.isClass && this.selector == other.selector
|
||||
|
||||
fun ObjCProperty.replaces(other: ObjCProperty): Boolean =
|
||||
this.getter.replaces(other.getter)
|
||||
|
||||
@@ -379,22 +379,7 @@ private class Struct(val size: Long, val align: Int, elementTypes: List<CType<*>
|
||||
) {
|
||||
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).readValue<CStructVar>(size, align)
|
||||
|
||||
override fun write(location: NativePtr, value: CValue<*>) {
|
||||
// TODO: probably CValue must be redesigned.
|
||||
val fakePlacement = object : NativePlacement {
|
||||
var used = false
|
||||
override fun alloc(size: Long, align: Int): NativePointed {
|
||||
assert(!used)
|
||||
assert (size == this@Struct.size)
|
||||
assert (align == this@Struct.align)
|
||||
used = true
|
||||
return interpretPointed<ByteVar>(location)
|
||||
}
|
||||
}
|
||||
|
||||
value.getPointer(fakePlacement)
|
||||
assert (fakePlacement.used)
|
||||
}
|
||||
override fun write(location: NativePtr, value: CValue<*>) = value.write(location)
|
||||
}
|
||||
|
||||
private class CEnumType(private val rawValueCType: CType<Number>) : CType<CEnum>(rawValueCType.ffiType) {
|
||||
|
||||
@@ -67,6 +67,25 @@ abstract class CValuesRef<T : CPointed> {
|
||||
abstract fun getPointer(placement: NativePlacement): CPointer<T>
|
||||
}
|
||||
|
||||
inline fun <T : CPointed, R> CValuesRef<T>?.usePointer(block: (CPointer<T>?) -> R): R {
|
||||
val allocated: Boolean
|
||||
val pointer: CPointer<T>? = if (this is CPointer<T>?) {
|
||||
allocated = false
|
||||
this
|
||||
} else {
|
||||
allocated = true
|
||||
this!!.getPointer(nativeHeap)
|
||||
}
|
||||
|
||||
return try {
|
||||
block(pointer)
|
||||
} finally {
|
||||
if (allocated) {
|
||||
nativeHeap.free(pointer.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The (possibly empty) sequence of immutable C values.
|
||||
* It is self-contained and doesn't depend on native memory.
|
||||
@@ -241,6 +260,11 @@ abstract class CEnumVar : CPrimitiveVar()
|
||||
// generics below are used for typedef support
|
||||
// these classes are not supposed to be used directly, instead the typealiases are provided.
|
||||
|
||||
@Suppress("FINAL_UPPER_BOUND")
|
||||
class BooleanVarOf<T : Boolean>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(1)
|
||||
}
|
||||
|
||||
@Suppress("FINAL_UPPER_BOUND")
|
||||
class ByteVarOf<T : Byte>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(1)
|
||||
@@ -271,6 +295,7 @@ class DoubleVarOf<T : Double>(override val rawPtr: NativePtr) : CPrimitiveVar()
|
||||
companion object : Type(8)
|
||||
}
|
||||
|
||||
typealias BooleanVar = BooleanVarOf<Boolean>
|
||||
typealias ByteVar = ByteVarOf<Byte>
|
||||
typealias ShortVar = ShortVarOf<Short>
|
||||
typealias IntVar = IntVarOf<Int>
|
||||
@@ -278,6 +303,20 @@ typealias LongVar = LongVarOf<Long>
|
||||
typealias FloatVar = FloatVarOf<Float>
|
||||
typealias DoubleVar = DoubleVarOf<Double>
|
||||
|
||||
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
|
||||
var <T : Boolean> BooleanVarOf<T>.value: T
|
||||
get() {
|
||||
val byte = nativeMemUtils.getByte(this)
|
||||
return byte.toBoolean() as T
|
||||
}
|
||||
set(value) = nativeMemUtils.putByte(this, value.toByte())
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Boolean.toByte(): Byte = if (this) 1 else 0
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Byte.toBoolean() = (this - 0 != 0)
|
||||
|
||||
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
|
||||
var <T : Byte> ByteVarOf<T>.value: T
|
||||
get() = nativeMemUtils.getByte(this) as T
|
||||
|
||||
@@ -218,6 +218,21 @@ fun <T : CVariable> CPointed.readValue(size: Long, align: Int): CValue<T> {
|
||||
// TODO: find better name.
|
||||
inline fun <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(sizeOf<T>(), alignOf<T>())
|
||||
|
||||
fun CValue<*>.write(location: NativePtr) {
|
||||
// TODO: probably CValue must be redesigned.
|
||||
val fakePlacement = object : NativePlacement {
|
||||
var used = false
|
||||
override fun alloc(size: Long, align: Int): NativePointed {
|
||||
assert(!used)
|
||||
used = true
|
||||
return interpretPointed<ByteVar>(location)
|
||||
}
|
||||
}
|
||||
|
||||
this.getPointer(fakePlacement)
|
||||
assert(fakePlacement.used)
|
||||
}
|
||||
|
||||
// TODO: optimize
|
||||
fun <T : CVariable> CValues<T>.getBytes(): ByteArray = memScoped {
|
||||
val result = ByteArray(size)
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package kotlinx.cinterop
|
||||
|
||||
interface ObjCObject
|
||||
interface ObjCClass : ObjCObject
|
||||
typealias ObjCObjectMeta = ObjCClass
|
||||
|
||||
abstract class ObjCObjectBase protected constructor() : ObjCObject {
|
||||
final override fun equals(other: Any?): Boolean = TODO()
|
||||
final override fun hashCode(): Int = TODO()
|
||||
final override fun toString(): String = TODO()
|
||||
}
|
||||
|
||||
abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {}
|
||||
|
||||
external fun optional(): Nothing
|
||||
|
||||
/**
|
||||
* The runtime representation of any [ObjCObject].
|
||||
*/
|
||||
@ExportTypeInfo("theObjCPointerHolderTypeInfo")
|
||||
class ObjCPointerHolder(inline val rawPtr: NativePtr) {
|
||||
init {
|
||||
assert(rawPtr != nativeNullPtr)
|
||||
objc_retain(rawPtr)
|
||||
}
|
||||
|
||||
final override fun equals(other: Any?): Boolean = TODO()
|
||||
final override fun hashCode(): Int = TODO()
|
||||
final override fun toString(): String = TODO()
|
||||
}
|
||||
|
||||
@konan.internal.Intrinsic
|
||||
@konan.internal.ExportForCompiler
|
||||
private external fun ObjCObject.initFromPtr(ptr: NativePtr)
|
||||
|
||||
@konan.internal.ExportForCompiler
|
||||
private fun ObjCObject.initFrom(other: ObjCObject?) = this.initFromPtr(other!!.rawPtr)
|
||||
|
||||
fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) // TODO: make private
|
||||
|
||||
inline fun <T : ObjCObject?> interpretObjCPointerOrNull(rawPtr: NativePtr): T? = if (rawPtr != nativeNullPtr) {
|
||||
ObjCPointerHolder(rawPtr).uncheckedCast<T>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
inline fun <T : ObjCObject?> interpretObjCPointer(rawPtr: NativePtr): T? = interpretObjCPointerOrNull<T>(rawPtr)!!
|
||||
|
||||
inline val ObjCObject.rawPtr: NativePtr get() = (this.uncheckedCast<ObjCPointerHolder>()).rawPtr
|
||||
inline val ObjCObject?.rawPtr: NativePtr get() = if (this != null) {
|
||||
(this.uncheckedCast<ObjCPointerHolder>()).rawPtr
|
||||
} else {
|
||||
nativeNullPtr
|
||||
}
|
||||
|
||||
class ObjCObjectVar<T : ObjCObject?>(override val rawPtr: NativePtr) : CVariable {
|
||||
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
|
||||
}
|
||||
|
||||
class ObjCStringVarOf<T : String?>(override val rawPtr: NativePtr) : CVariable {
|
||||
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
|
||||
}
|
||||
|
||||
var <T : String?> ObjCStringVarOf<T>.value: T
|
||||
get() = TODO()
|
||||
set(value) = TODO()
|
||||
|
||||
@konan.internal.Intrinsic external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer?
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ExternalObjCClass()
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCMethod(val selector: String, val bridge: String)
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCBridge(val selector: String, val encoding: String, val imp: String)
|
||||
|
||||
@Target(AnnotationTarget.CONSTRUCTOR)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCConstructor(val initSelector: String)
|
||||
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class InteropStubs()
|
||||
|
||||
@konan.internal.ExportForCompiler
|
||||
private fun <T : ObjCObject> allocObjCObject(clazz: NativePtr): T {
|
||||
val rawResult = objc_allocWithZone(clazz)
|
||||
if (rawResult == nativeNullPtr) {
|
||||
throw OutOfMemoryError("Unable to allocate Objective-C object")
|
||||
}
|
||||
|
||||
val result = interpretObjCPointerOrNull<T>(rawResult)!!
|
||||
// `objc_allocWithZone` returns retained pointer. Balance it:
|
||||
objc_release(rawResult)
|
||||
// TODO: do not retain this pointer in `interpretObjCPointerOrNull` instead.
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@konan.internal.Intrinsic
|
||||
@konan.internal.ExportForCompiler
|
||||
private external fun <T : ObjCObject> getObjCClass(): NativePtr
|
||||
|
||||
@konan.internal.Intrinsic external fun getMessenger(superClass: NativePtr): COpaquePointer?
|
||||
@konan.internal.Intrinsic external fun getMessengerLU(superClass: NativePtr): COpaquePointer?
|
||||
|
||||
// Konan runtme:
|
||||
|
||||
@SymbolName("CreateNSStringFromKString")
|
||||
external fun CreateNSStringFromKString(str: String?): NativePtr
|
||||
|
||||
@SymbolName("CreateKStringFromNSString")
|
||||
external fun CreateKStringFromNSString(ptr: NativePtr): String?
|
||||
|
||||
// Objective-C runtime:
|
||||
|
||||
@SymbolName("objc_retainAutoreleaseReturnValue")
|
||||
external fun objc_retainAutoreleaseReturnValue(ptr: NativePtr): NativePtr
|
||||
|
||||
@SymbolName("Kotlin_objc_autoreleasePoolPush")
|
||||
external fun objc_autoreleasePoolPush(): NativePtr
|
||||
|
||||
@SymbolName("Kotlin_objc_autoreleasePoolPop")
|
||||
external fun objc_autoreleasePoolPop(ptr: NativePtr)
|
||||
|
||||
@SymbolName("Kotlin_objc_allocWithZone")
|
||||
private external fun objc_allocWithZone(clazz: NativePtr): NativePtr
|
||||
|
||||
@SymbolName("Kotlin_objc_retain")
|
||||
external fun objc_retain(ptr: NativePtr): NativePtr
|
||||
|
||||
@SymbolName("Kotlin_objc_release")
|
||||
external fun objc_release(ptr: NativePtr)
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package kotlinx.cinterop
|
||||
|
||||
inline fun <R> autoreleasepool(block: () -> R): R {
|
||||
val pool = objc_autoreleasePoolPush()
|
||||
return try {
|
||||
block()
|
||||
} finally {
|
||||
objc_autoreleasePoolPop(pool)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: null checks
|
||||
var <T : ObjCObject?> ObjCObjectVar<T>.value: T
|
||||
get() = interpretObjCPointerOrNull<T>(nativeMemUtils.getNativePtr(this)).uncheckedCast<T>()
|
||||
set(value) = nativeMemUtils.putNativePtr(this, value.rawPtr)
|
||||
@@ -66,6 +66,11 @@ private tailrec fun convertArgument(
|
||||
|
||||
is CEnum -> convertArgument(argument.value, isVariadic, location, additionalPlacement)
|
||||
|
||||
is ObjCPointerHolder -> {
|
||||
location.reinterpret<COpaquePointerVar>()[0] = interpretCPointer(argument.rawPtr)
|
||||
FFI_TYPE_KIND_POINTER
|
||||
}
|
||||
|
||||
else -> throw Error("unsupported argument: $argument")
|
||||
}
|
||||
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
class NativeCodeBuilder {
|
||||
val lines = mutableListOf<String>()
|
||||
|
||||
fun out(line: String): Unit {
|
||||
lines.add(line)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun buildNativeCodeLines(block: NativeCodeBuilder.() -> Unit): List<String> {
|
||||
val builder = NativeCodeBuilder()
|
||||
builder.block()
|
||||
return builder.lines
|
||||
}
|
||||
|
||||
class KotlinCodeBuilder {
|
||||
private val lines = mutableListOf<String>()
|
||||
|
||||
private val freeStack = mutableListOf<String>()
|
||||
private val nesting get() = freeStack.size
|
||||
|
||||
fun out(line: String) {
|
||||
lines.add(" ".repeat(nesting) + line)
|
||||
}
|
||||
|
||||
fun pushBlock(line: String, free: String = "") {
|
||||
out(line)
|
||||
freeStack.add(free)
|
||||
}
|
||||
|
||||
private fun popBlocks() {
|
||||
while (freeStack.isNotEmpty()) {
|
||||
val free = freeStack.last()
|
||||
freeStack.removeAt(freeStack.lastIndex)
|
||||
out("} $free".trim())
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): List<String> {
|
||||
this.popBlocks()
|
||||
val result = this.lines.toList()
|
||||
this.lines.clear()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
inline fun buildKotlinCodeLines(block: KotlinCodeBuilder.() -> Unit): List<String> {
|
||||
val builder = KotlinCodeBuilder()
|
||||
builder.block()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
interface StubGenerationContext {
|
||||
val nativeBridges: NativeBridges
|
||||
fun addTopLevelDeclaration(lines: List<String>)
|
||||
}
|
||||
|
||||
interface KotlinStub {
|
||||
fun generate(context: StubGenerationContext): Sequence<String>
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
val kotlinKeywords = setOf(
|
||||
"as", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in",
|
||||
"interface", "is", "null", "object", "package", "return", "super", "this", "throw",
|
||||
"true", "try", "typealias", "val", "var", "when", "while"
|
||||
)
|
||||
|
||||
/**
|
||||
* The expression written in native language.
|
||||
*/
|
||||
typealias NativeExpression = String
|
||||
|
||||
/**
|
||||
* The expression written in Kotlin.
|
||||
*/
|
||||
typealias KotlinExpression = String
|
||||
|
||||
/**
|
||||
* For this identifier constructs the string to be parsed by Kotlin as `SimpleName`
|
||||
* defined [here](https://kotlinlang.org/docs/reference/grammar.html#SimpleName).
|
||||
*/
|
||||
fun String.asSimpleName(): String = if (this in kotlinKeywords) {
|
||||
"`$this`"
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expression to be parsed by Kotlin as string literal with given contents,
|
||||
* i.e. transforms `foo$bar` to `"foo\$bar"`.
|
||||
*/
|
||||
fun String.quoteAsKotlinLiteral(): KotlinExpression {
|
||||
val sb = StringBuilder()
|
||||
sb.append('"')
|
||||
|
||||
this.forEach { c ->
|
||||
val escaped = when (c) {
|
||||
in 'a' .. 'z', in 'A' .. 'Z', in '0' .. '9', '_', '@', ':', '{', '}', '=', '[', ']', '^', '#', '*' -> c.toString()
|
||||
'$' -> "\\$"
|
||||
else -> "\\u" + "%04X".format(c.toInt()) // TODO: improve result readability by preserving more characters.
|
||||
}
|
||||
sb.append(escaped)
|
||||
}
|
||||
|
||||
sb.append('"')
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun block(header: String, lines: Iterable<String>) = block(header, lines.asSequence())
|
||||
|
||||
fun block(header: String, lines: Sequence<String>) =
|
||||
sequenceOf("$header {") +
|
||||
lines.map { " $it" } +
|
||||
sequenceOf("}")
|
||||
|
||||
val annotationForUnableToImport
|
||||
get() = "@Deprecated(${"Unable to import this declaration".quoteAsKotlinLiteral()}, level = DeprecationLevel.ERROR)"
|
||||
|
||||
fun String.applyToStrings(vararg arguments: String) =
|
||||
"${this}(${arguments.joinToString { it.quoteAsKotlinLiteral() }})"
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
import org.jetbrains.kotlin.native.interop.indexer.Type
|
||||
|
||||
data class TypedKotlinValue(val type: Type, val value: KotlinExpression)
|
||||
data class TypedNativeValue(val type: Type, val value: NativeExpression)
|
||||
|
||||
/**
|
||||
* Generates bridges between Kotlin and native, passing arbitrary native-typed values.
|
||||
*
|
||||
* It does the same as [SimpleBridgeGenerator] except that it supports any native types, e.g. struct values.
|
||||
*/
|
||||
interface MappingBridgeGenerator {
|
||||
fun kotlinToNative(
|
||||
builder: KotlinCodeBuilder,
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: Type,
|
||||
kotlinValues: List<TypedKotlinValue>,
|
||||
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
|
||||
): KotlinExpression
|
||||
|
||||
fun nativeToKotlin(
|
||||
builder: NativeCodeBuilder,
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: Type,
|
||||
nativeValues: List<TypedNativeValue>,
|
||||
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
|
||||
): NativeExpression
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
import org.jetbrains.kotlin.native.interop.indexer.RecordType
|
||||
import org.jetbrains.kotlin.native.interop.indexer.Type
|
||||
import org.jetbrains.kotlin.native.interop.indexer.VoidType
|
||||
|
||||
/**
|
||||
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
|
||||
* maps the type using [mirror].
|
||||
*/
|
||||
class MappingBridgeGeneratorImpl(
|
||||
val declarationMapper: DeclarationMapper,
|
||||
val simpleBridgeGenerator: SimpleBridgeGenerator
|
||||
) : MappingBridgeGenerator {
|
||||
|
||||
override fun kotlinToNative(
|
||||
builder: KotlinCodeBuilder,
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: Type,
|
||||
kotlinValues: List<TypedKotlinValue>,
|
||||
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
|
||||
): KotlinExpression {
|
||||
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
|
||||
|
||||
kotlinValues.forEachIndexed { index, (type, value) ->
|
||||
if (type.unwrapTypedefs() is RecordType) {
|
||||
val tmpVarName = "kni$index"
|
||||
builder.pushBlock("$value.usePointer { $tmpVarName ->")
|
||||
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawValue"))
|
||||
} else {
|
||||
val info = mirror(declarationMapper, type).info
|
||||
bridgeArguments.add(BridgeTypedKotlinValue(info.bridgedType, info.argToBridged(value)))
|
||||
}
|
||||
}
|
||||
|
||||
val unwrappedReturnType = returnType.unwrapTypedefs()
|
||||
val kniRetVal = "kniRetVal"
|
||||
val bridgeReturnType = when (unwrappedReturnType) {
|
||||
VoidType -> BridgedType.VOID
|
||||
is RecordType -> {
|
||||
val mirror = mirror(declarationMapper, returnType)
|
||||
val tmpVarName = kniRetVal
|
||||
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedTypeName}>()")
|
||||
builder.pushBlock("try {", free = "finally { nativeHeap.free($tmpVarName) }")
|
||||
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawPtr"))
|
||||
BridgedType.VOID
|
||||
}
|
||||
else -> {
|
||||
val mirror = mirror(declarationMapper, returnType)
|
||||
mirror.info.bridgedType
|
||||
}
|
||||
}
|
||||
|
||||
val callExpr = simpleBridgeGenerator.kotlinToNative(
|
||||
nativeBacked, bridgeReturnType, bridgeArguments
|
||||
) { bridgeNativeValues ->
|
||||
|
||||
val nativeValues = mutableListOf<String>()
|
||||
kotlinValues.forEachIndexed { index, (type, _) ->
|
||||
val unwrappedType = type.unwrapTypedefs()
|
||||
if (unwrappedType is RecordType) {
|
||||
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
|
||||
} else {
|
||||
nativeValues.add(mirror(declarationMapper, type).info.cFromBridged(bridgeNativeValues[index]))
|
||||
}
|
||||
}
|
||||
|
||||
val nativeResult = block(nativeValues)
|
||||
|
||||
when (unwrappedReturnType) {
|
||||
is VoidType -> {
|
||||
out(nativeResult + ";")
|
||||
""
|
||||
}
|
||||
is RecordType -> {
|
||||
out("*(${unwrappedReturnType.decl.spelling}*)${bridgeNativeValues.last()} = $nativeResult;")
|
||||
""
|
||||
}
|
||||
else -> {
|
||||
nativeResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = when (unwrappedReturnType) {
|
||||
is VoidType -> callExpr
|
||||
is RecordType -> {
|
||||
builder.out(callExpr)
|
||||
"$kniRetVal.readValue()"
|
||||
}
|
||||
else -> {
|
||||
val mirror = mirror(declarationMapper, returnType)
|
||||
mirror.info.argFromBridged(callExpr)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun nativeToKotlin(
|
||||
builder: NativeCodeBuilder,
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: Type,
|
||||
nativeValues: List<TypedNativeValue>,
|
||||
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
|
||||
): NativeExpression {
|
||||
|
||||
val bridgeArguments = mutableListOf<BridgeTypedNativeValue>()
|
||||
|
||||
nativeValues.forEachIndexed { index, (type, value) ->
|
||||
val bridgeArgument = if (type.unwrapTypedefs() is RecordType) {
|
||||
BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$value")
|
||||
} else {
|
||||
val info = mirror(declarationMapper, type).info
|
||||
BridgeTypedNativeValue(info.bridgedType, value)
|
||||
}
|
||||
bridgeArguments.add(bridgeArgument)
|
||||
}
|
||||
|
||||
val unwrappedReturnType = returnType.unwrapTypedefs()
|
||||
val kniRetVal = "kniRetVal"
|
||||
val bridgeReturnType = when (unwrappedReturnType) {
|
||||
VoidType -> BridgedType.VOID
|
||||
is RecordType -> {
|
||||
val tmpVarName = kniRetVal
|
||||
builder.out("${unwrappedReturnType.decl.spelling} $tmpVarName;")
|
||||
bridgeArguments.add(BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$tmpVarName"))
|
||||
BridgedType.VOID
|
||||
}
|
||||
else -> {
|
||||
val mirror = mirror(declarationMapper, returnType)
|
||||
mirror.info.bridgedType
|
||||
}
|
||||
}
|
||||
|
||||
val callExpr = simpleBridgeGenerator.nativeToKotlin(
|
||||
nativeBacked,
|
||||
bridgeReturnType,
|
||||
bridgeArguments
|
||||
) { bridgeKotlinValues ->
|
||||
val kotlinValues = mutableListOf<String>()
|
||||
nativeValues.forEachIndexed { index, (type, _) ->
|
||||
val mirror = mirror(declarationMapper, type)
|
||||
if (type.unwrapTypedefs() is RecordType) {
|
||||
kotlinValues.add(
|
||||
"interpretPointed<${mirror.pointedTypeName}>(${bridgeKotlinValues[index]}).readValue()"
|
||||
)
|
||||
} else {
|
||||
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index]))
|
||||
}
|
||||
}
|
||||
|
||||
val kotlinResult = block(kotlinValues)
|
||||
when (unwrappedReturnType) {
|
||||
is RecordType -> {
|
||||
"$kotlinResult.write(${bridgeKotlinValues.last()})"
|
||||
}
|
||||
is VoidType -> {
|
||||
kotlinResult
|
||||
}
|
||||
else -> {
|
||||
mirror(declarationMapper, returnType).info.argToBridged(kotlinResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = when (unwrappedReturnType) {
|
||||
is VoidType -> callExpr
|
||||
is RecordType -> {
|
||||
builder.out("$callExpr;")
|
||||
kniRetVal
|
||||
}
|
||||
else -> {
|
||||
mirror(declarationMapper, returnType).info.cFromBridged(callExpr)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
+334
@@ -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
|
||||
}
|
||||
|
||||
+498
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator
|
||||
import org.jetbrains.kotlin.native.interop.indexer.*
|
||||
|
||||
private fun ObjCMethod.getKotlinParameterNames(): List<String> {
|
||||
val selectorParts = this.selector.split(":")
|
||||
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
// The names of all parameters except first must depend only on the selector:
|
||||
this.parameters.forEachIndexed { index, parameter ->
|
||||
if (index > 0) {
|
||||
var name = selectorParts[index]
|
||||
if (name.isEmpty()) {
|
||||
name = "_$index"
|
||||
}
|
||||
|
||||
while (name in result) {
|
||||
name = "_$name"
|
||||
}
|
||||
|
||||
result.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
this.parameters.firstOrNull()?.let {
|
||||
var name = it.name ?: ""
|
||||
if (name.isEmpty()) {
|
||||
name = "arg"
|
||||
}
|
||||
while (name in result) {
|
||||
name = "_$name"
|
||||
}
|
||||
result.add(0, name)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
class ObjCMethodStub(stubGenerator: StubGenerator,
|
||||
val method: ObjCMethod,
|
||||
private val container: ObjCClassOrProtocol) : KotlinStub, NativeBacked {
|
||||
|
||||
override fun generate(context: StubGenerationContext): Sequence<String> =
|
||||
if (context.nativeBridges.isSupported(this)) {
|
||||
val result = mutableListOf<String>()
|
||||
result.add("@ObjCMethod".applyToStrings(method.selector, bridgeName))
|
||||
result.add(header)
|
||||
|
||||
if (method.isInit && container is ObjCClass) {
|
||||
result.add("")
|
||||
result.add("@ObjCConstructor".applyToStrings(method.selector))
|
||||
result.add("constructor($joinedKotlinParameters) {}")
|
||||
}
|
||||
|
||||
context.addTopLevelDeclaration(
|
||||
listOf("@konan.internal.ExportForCompiler",
|
||||
"@ObjCBridge".applyToStrings(method.selector, method.encoding, implementationTemplate))
|
||||
+ block(bridgeHeader, bodyLines)
|
||||
)
|
||||
|
||||
result.asSequence()
|
||||
} else {
|
||||
sequenceOf(
|
||||
annotationForUnableToImport,
|
||||
header
|
||||
)
|
||||
}
|
||||
|
||||
private val bodyLines: List<String>
|
||||
private val joinedKotlinParameters: String
|
||||
private val header: String
|
||||
private val implementationTemplate: String
|
||||
private val bridgeName: String
|
||||
private val bridgeHeader: String
|
||||
|
||||
private val isOverride = method.isOverride(container)
|
||||
|
||||
init {
|
||||
val bodyGenerator = KotlinCodeBuilder()
|
||||
|
||||
val kotlinParameters = mutableListOf<Pair<String, String>>()
|
||||
val kotlinObjCBridgeParameters = mutableListOf<Pair<String, String>>()
|
||||
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
|
||||
|
||||
val kniReceiverParameter = "kniR"
|
||||
val kniSuperClassParameter = "kniSC"
|
||||
|
||||
val voidPtr = PointerType(VoidType)
|
||||
|
||||
val returnType = method.getReturnType(container)
|
||||
|
||||
val messengerGetter = if (returnType.isLargeOrUnaligned()) "getMessengerLU" else "getMessenger"
|
||||
|
||||
kotlinObjCBridgeParameters.add(kniSuperClassParameter to "NativePtr")
|
||||
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)"))
|
||||
|
||||
if (method.nsConsumesSelf) {
|
||||
// TODO: do this later due to possible exceptions
|
||||
bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr)")
|
||||
}
|
||||
|
||||
kotlinObjCBridgeParameters.add(kniReceiverParameter to "ObjCObject")
|
||||
nativeBridgeArguments.add(
|
||||
TypedKotlinValue(voidPtr,
|
||||
"getReceiverOrSuper($kniReceiverParameter.rawPtr, $kniSuperClassParameter)"))
|
||||
|
||||
val kotlinParameterNames = method.getKotlinParameterNames()
|
||||
|
||||
method.parameters.forEachIndexed { index, it ->
|
||||
val name = kotlinParameterNames[index]
|
||||
|
||||
val kotlinType = stubGenerator.mirror(it.type).argType
|
||||
kotlinParameters.add(name to kotlinType)
|
||||
|
||||
kotlinObjCBridgeParameters.add(name to kotlinType)
|
||||
nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName()))
|
||||
}
|
||||
|
||||
val kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
|
||||
"Unit"
|
||||
} else {
|
||||
stubGenerator.mirror(returnType).argType
|
||||
}
|
||||
|
||||
val result = stubGenerator.mappingBridgeGenerator.kotlinToNative(
|
||||
bodyGenerator,
|
||||
this@ObjCMethodStub,
|
||||
returnType,
|
||||
nativeBridgeArguments
|
||||
) { nativeValues ->
|
||||
val selector = "@selector(${method.selector})"
|
||||
val messengerParameterTypes = mutableListOf<String>()
|
||||
messengerParameterTypes.add("void*")
|
||||
messengerParameterTypes.add("SEL")
|
||||
method.parameters.forEach {
|
||||
messengerParameterTypes.add(it.getTypeStringRepresentation())
|
||||
}
|
||||
|
||||
val messengerReturnType = returnType.getStringRepresentation()
|
||||
|
||||
val messengerType =
|
||||
"$messengerReturnType (* ${method.cAttributes}) (${messengerParameterTypes.joinToString()})"
|
||||
|
||||
val messenger = "(($messengerType) ${nativeValues.first()})"
|
||||
|
||||
val messengerArguments = listOf(nativeValues[1]) + selector + nativeValues.drop(2)
|
||||
|
||||
"$messenger(${messengerArguments.joinToString()})"
|
||||
}
|
||||
bodyGenerator.out("return $result")
|
||||
|
||||
this.implementationTemplate = genImplementationTemplate(stubGenerator)
|
||||
this.bodyLines = bodyGenerator.build()
|
||||
|
||||
bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}"
|
||||
|
||||
this.bridgeHeader = "internal fun $bridgeName(" +
|
||||
"${kotlinObjCBridgeParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" }})" +
|
||||
": $kotlinReturnType"
|
||||
|
||||
this.joinedKotlinParameters = kotlinParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" }
|
||||
|
||||
this.header = buildString {
|
||||
if (container is ObjCClass) append("external ")
|
||||
if (isOverride) {
|
||||
append("override ")
|
||||
} else if (container is ObjCClass) {
|
||||
append("open ")
|
||||
}
|
||||
|
||||
append("fun ${method.kotlinName.asSimpleName()}($joinedKotlinParameters): $kotlinReturnType")
|
||||
|
||||
if (container is ObjCProtocol && method.isOptional) append(" = optional()")
|
||||
}
|
||||
}
|
||||
|
||||
private fun genImplementationTemplate(stubGenerator: StubGenerator): String {
|
||||
val codeBuilder = NativeCodeBuilder()
|
||||
|
||||
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
|
||||
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private fun Type.isLargeOrUnaligned(): Boolean {
|
||||
val unwrappedType = this.unwrapTypedefs()
|
||||
return when (unwrappedType) {
|
||||
is RecordType -> unwrappedType.decl.def!!.size > 16 || this.hasUnalignedMembers()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun Type.hasUnalignedMembers(): Boolean = when (this) {
|
||||
is Typedef -> this.def.aliased.hasUnalignedMembers()
|
||||
is RecordType -> this.decl.def!!.let { def ->
|
||||
def.hasUnalignedFields || // Check members of fields too:
|
||||
def.fields.any { it.type.hasUnalignedMembers() }
|
||||
}
|
||||
is ArrayType -> this.elemType.hasUnalignedMembers()
|
||||
else -> false
|
||||
|
||||
// TODO: should the recursive checks be made in indexer when computing `hasUnalignedFields`?
|
||||
}
|
||||
|
||||
private val ObjCMethod.kotlinName: String get() = selector.split(":").first()
|
||||
|
||||
private val ObjCClassOrProtocol.protocolsWithSupers: Sequence<ObjCProtocol>
|
||||
get() = this.protocols.asSequence().flatMap { sequenceOf(it) + it.protocolsWithSupers }
|
||||
|
||||
private val ObjCClassOrProtocol.immediateSuperTypes: Sequence<ObjCClassOrProtocol>
|
||||
get() {
|
||||
val baseClass = (this as? ObjCClass)?.baseClass
|
||||
if (baseClass != null) {
|
||||
return sequenceOf(baseClass) + this.protocols.asSequence()
|
||||
}
|
||||
|
||||
return this.protocols.asSequence()
|
||||
}
|
||||
|
||||
private val ObjCClassOrProtocol.selfAndSuperTypes: Sequence<ObjCClassOrProtocol>
|
||||
get() = sequenceOf(this) + this.superTypes
|
||||
|
||||
private val ObjCClassOrProtocol.superTypes: Sequence<ObjCClassOrProtocol>
|
||||
get() = this.immediateSuperTypes.flatMap { it.selfAndSuperTypes }.distinct()
|
||||
|
||||
private fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence<ObjCMethod> =
|
||||
this.methods.asSequence().filter { it.isClass == isClass }
|
||||
|
||||
private fun ObjCClassOrProtocol.inheritedMethods(isClass: Boolean): Sequence<ObjCMethod> =
|
||||
this.superTypes.flatMap { it.declaredMethods(isClass) }.distinctBy { it.selector }
|
||||
|
||||
private fun ObjCClassOrProtocol.methodsWithInherited(isClass: Boolean): Sequence<ObjCMethod> =
|
||||
this.selfAndSuperTypes.flatMap { it.declaredMethods(isClass) }.distinctBy { it.selector }
|
||||
|
||||
private fun ObjCMethod.isOverride(container: ObjCClassOrProtocol): Boolean =
|
||||
container.superTypes.any { superType -> superType.methods.any(this::replaces) }
|
||||
|
||||
abstract class ObjCContainerStub(stubGenerator: StubGenerator,
|
||||
private val container: ObjCClassOrProtocol,
|
||||
private val isMeta: Boolean) : KotlinStub {
|
||||
|
||||
private val methods: List<ObjCMethod>
|
||||
|
||||
init {
|
||||
val superMethods = container.inheritedMethods(isMeta)
|
||||
|
||||
// Add all methods declared in the class or protocol:
|
||||
var methods = container.declaredMethods(isMeta)
|
||||
|
||||
// Exclude those which are identically declared in super types:
|
||||
methods -= superMethods
|
||||
|
||||
// Add some special methods from super types:
|
||||
methods += superMethods.filter { it.returnsInstancetype() || it.isInit }
|
||||
|
||||
// Add methods from adopted protocols that must be implemented according to Kotlin rules:
|
||||
if (container is ObjCClass) {
|
||||
methods += container.protocolsWithSupers.flatMap { it.declaredMethods(isMeta) }.filter { !it.isOptional }
|
||||
}
|
||||
|
||||
// Add methods inherited from multiple supertypes that must be defined according to Kotlin rules:
|
||||
methods += container.immediateSuperTypes
|
||||
.flatMap { superType ->
|
||||
val methodsWithInherited = superType.methodsWithInherited(isMeta)
|
||||
// Select only those which are represented as non-abstract in Kotlin:
|
||||
when (superType) {
|
||||
is ObjCClass -> methodsWithInherited
|
||||
is ObjCProtocol -> methodsWithInherited.filter { it.isOptional }
|
||||
}
|
||||
}
|
||||
.groupBy { it.selector }
|
||||
.mapNotNull { (_, inheritedMethods) -> if (inheritedMethods.size > 1) inheritedMethods.first() else null }
|
||||
|
||||
this.methods = methods.distinctBy { it.selector }.toList()
|
||||
}
|
||||
|
||||
private val methodStubs = methods.map {
|
||||
ObjCMethodStub(stubGenerator, it, container)
|
||||
}
|
||||
|
||||
private val properties: List<ObjCProperty>
|
||||
|
||||
init {
|
||||
val superProperties = container.superTypes.flatMap { it.properties.asSequence() }
|
||||
|
||||
this.properties = container.properties.filter {
|
||||
it.getter.isClass == isMeta &&
|
||||
// Select only properties that don't override anything:
|
||||
superProperties.none(it::replaces)
|
||||
}
|
||||
}
|
||||
|
||||
val propertyStubs = properties.map {
|
||||
ObjCPropertyStub(stubGenerator, it, container)
|
||||
}
|
||||
|
||||
private val classHeader: String
|
||||
|
||||
init {
|
||||
val nameSuffix = if (isMeta) "Meta" else ""
|
||||
|
||||
val supers = mutableListOf<String>()
|
||||
|
||||
if (container is ObjCClass) {
|
||||
supers.add(container.baseClassName)
|
||||
}
|
||||
container.protocols.forEach {
|
||||
supers.add(it.kotlinName)
|
||||
}
|
||||
|
||||
if (supers.isEmpty()) {
|
||||
assert(container is ObjCProtocol)
|
||||
supers.add("ObjCObject")
|
||||
}
|
||||
|
||||
val keywords = when (container) {
|
||||
is ObjCClass -> "open class"
|
||||
is ObjCProtocol -> "interface"
|
||||
}
|
||||
|
||||
val supersString = supers.joinToString { "$it$nameSuffix".asSimpleName() }
|
||||
val name = "${container.kotlinName}$nameSuffix".asSimpleName()
|
||||
this.classHeader = "@ExternalObjCClass $keywords $name : $supersString"
|
||||
}
|
||||
|
||||
open fun generateBody(context: StubGenerationContext): Sequence<String> {
|
||||
var result = (propertyStubs.asSequence() + methodStubs.asSequence())
|
||||
.flatMap { sequenceOf("") + it.generate(context) }
|
||||
|
||||
if (container is ObjCClass && methodStubs.none {
|
||||
it.method.isInit && it.method.parameters.isEmpty() && context.nativeBridges.isSupported(it)
|
||||
}) {
|
||||
// Always generate default constructor.
|
||||
// If it is not produced for an init method, then include it manually:
|
||||
result += sequenceOf("", "protected constructor() {}")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun generate(context: StubGenerationContext): Sequence<String> = block(classHeader, generateBody(context))
|
||||
}
|
||||
|
||||
open class ObjCClassOrProtocolStub(
|
||||
stubGenerator: StubGenerator,
|
||||
private val container: ObjCClassOrProtocol
|
||||
) : ObjCContainerStub(
|
||||
stubGenerator,
|
||||
container,
|
||||
isMeta = false
|
||||
) {
|
||||
private val metaClassStub =
|
||||
object : ObjCContainerStub(stubGenerator, container, isMeta = true) {}
|
||||
|
||||
override fun generate(context: StubGenerationContext) =
|
||||
metaClassStub.generate(context) + "" + super.generate(context)
|
||||
}
|
||||
|
||||
class ObjCProtocolStub(stubGenerator: StubGenerator, protocol: ObjCProtocol) :
|
||||
ObjCClassOrProtocolStub(stubGenerator, protocol)
|
||||
|
||||
class ObjCClassStub(stubGenerator: StubGenerator, private val clazz: ObjCClass) :
|
||||
ObjCClassOrProtocolStub(stubGenerator, clazz) {
|
||||
|
||||
override fun generateBody(context: StubGenerationContext) =
|
||||
sequenceOf( "companion object : ${clazz.kotlinName}Meta() {}") +
|
||||
super.generateBody(context)
|
||||
}
|
||||
|
||||
class ObjCPropertyStub(
|
||||
val stubGenerator: StubGenerator, val property: ObjCProperty, val clazz: ObjCClassOrProtocol
|
||||
) : KotlinStub {
|
||||
|
||||
override fun generate(context: StubGenerationContext): Sequence<String> {
|
||||
val type = property.getType(clazz)
|
||||
|
||||
val kotlinType = stubGenerator.mirror(type).argType
|
||||
|
||||
val kind = if (property.setter == null) "val" else "var"
|
||||
val modifiers = if (clazz is ObjCClass) "" else "final "
|
||||
val result = mutableListOf(
|
||||
"$modifiers$kind ${property.name.asSimpleName()}: $kotlinType",
|
||||
" get() = ${property.getter.kotlinName.asSimpleName()}()"
|
||||
)
|
||||
|
||||
property.setter?.let {
|
||||
result.add(" set(value) = ${it.kotlinName.asSimpleName()}(value)")
|
||||
}
|
||||
|
||||
return result.asSequence()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val ObjCClassOrProtocol.kotlinName: String get() = when (this) {
|
||||
is ObjCClass -> this.name
|
||||
is ObjCProtocol -> "${this.name}Protocol"
|
||||
}
|
||||
|
||||
private val ObjCClass.baseClassName: String
|
||||
get() = baseClass?.name ?: "ObjCObject"
|
||||
|
||||
private fun Parameter.getTypeStringRepresentation() =
|
||||
(if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation()
|
||||
|
||||
private fun ObjCMethod.getSelfTypeStringRepresentation() = if (this.nsConsumesSelf) {
|
||||
"__attribute__((ns_consumed)) id"
|
||||
} else {
|
||||
"id"
|
||||
}
|
||||
|
||||
val ObjCMethod.cAttributes get() = if (this.nsReturnsRetained) {
|
||||
"__attribute__((ns_returns_retained)) "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
private fun NativeCodeBuilder.genMethodImp(
|
||||
stubGenerator: StubGenerator,
|
||||
nativeBacked: NativeBacked,
|
||||
method: ObjCMethod,
|
||||
container: ObjCClassOrProtocol
|
||||
): String {
|
||||
|
||||
val returnType = method.getReturnType(container)
|
||||
val cReturnType = returnType.getStringRepresentation()
|
||||
|
||||
val bridgeArguments = mutableListOf<TypedNativeValue>()
|
||||
|
||||
val parameters = mutableListOf<Pair<String, String>>()
|
||||
|
||||
parameters.add("self" to method.getSelfTypeStringRepresentation())
|
||||
|
||||
val receiverType = ObjCIdType(ObjCPointer.Nullability.NonNull, protocols = emptyList())
|
||||
bridgeArguments.add(TypedNativeValue(receiverType, "self"))
|
||||
|
||||
parameters.add("_cmd" to "SEL")
|
||||
|
||||
method.parameters.forEachIndexed { index, parameter ->
|
||||
val name = "p$index"
|
||||
parameters.add(name to parameter.getTypeStringRepresentation())
|
||||
bridgeArguments.add(TypedNativeValue(parameter.type, name))
|
||||
}
|
||||
|
||||
val functionName = "knimi_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId()
|
||||
val functionAttr = method.cAttributes
|
||||
|
||||
out("$cReturnType $functionName(${parameters.joinToString { it.second + " " + it.first }}) $functionAttr{")
|
||||
|
||||
val callExpr = stubGenerator.mappingBridgeGenerator.nativeToKotlin(
|
||||
this,
|
||||
nativeBacked,
|
||||
returnType,
|
||||
bridgeArguments
|
||||
) { kotlinValues ->
|
||||
|
||||
val kotlinReceiverType = if (method.isClass) "${container.kotlinName}Meta" else container.kotlinName
|
||||
val kotlinRawReceiver = kotlinValues.first()
|
||||
val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<${kotlinReceiverType.asSimpleName()}>()"
|
||||
|
||||
val namedArguments = kotlinValues.drop(1).zip(method.getKotlinParameterNames()) { value, name ->
|
||||
"${name.asSimpleName()} = $value"
|
||||
}
|
||||
|
||||
"${kotlinReceiver}.${method.kotlinName.asSimpleName()}(${namedArguments.joinToString()})"
|
||||
}
|
||||
|
||||
if (returnType.unwrapTypedefs() is VoidType) {
|
||||
out(" $callExpr;")
|
||||
} else {
|
||||
out(" return $callExpr;")
|
||||
}
|
||||
|
||||
out("}")
|
||||
|
||||
return functionName
|
||||
}
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
/**
|
||||
* The type which has exact counterparts on both Kotlin and native side and can be directly passed through bridges.
|
||||
*/
|
||||
enum class BridgedType(val kotlinType: String) {
|
||||
BYTE("Byte"),
|
||||
SHORT("Short"),
|
||||
INT("Int"),
|
||||
LONG("Long"),
|
||||
FLOAT("Float"),
|
||||
DOUBLE("Double"),
|
||||
NATIVE_PTR("NativePtr"),
|
||||
OBJC_POINTER("NativePtr"),
|
||||
VOID("Unit")
|
||||
}
|
||||
|
||||
data class BridgeTypedKotlinValue(val type: BridgedType, val value: KotlinExpression)
|
||||
data class BridgeTypedNativeValue(val type: BridgedType, val value: NativeExpression)
|
||||
|
||||
/**
|
||||
* The entity which depends on native bridges.
|
||||
*/
|
||||
interface NativeBacked
|
||||
|
||||
/**
|
||||
* Generates simple bridges between Kotlin and native, passing [BridgedType] values.
|
||||
*/
|
||||
interface SimpleBridgeGenerator {
|
||||
|
||||
/**
|
||||
* Generates the expression to convert given Kotlin values to native counterparts, pass through the bridge,
|
||||
* use inside the native code produced by [block] and then return the result back.
|
||||
*
|
||||
* @param block produces native code lines into the builder and returns the expression to be used as the result.
|
||||
*/
|
||||
fun kotlinToNative(
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: BridgedType,
|
||||
kotlinValues: List<BridgeTypedKotlinValue>,
|
||||
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
|
||||
): KotlinExpression
|
||||
|
||||
/**
|
||||
* Generates the expression to convert given native values to Kotlin counterparts, pass through the bridge,
|
||||
* use inside the Kotlin code produced by [block] and then return the result back.
|
||||
*/
|
||||
fun nativeToKotlin(
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: BridgedType,
|
||||
nativeValues: List<BridgeTypedNativeValue>,
|
||||
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
|
||||
): NativeExpression
|
||||
|
||||
fun insertNativeBridge(
|
||||
nativeBacked: NativeBacked,
|
||||
kotlinLines: List<String>,
|
||||
nativeLines: List<String>
|
||||
)
|
||||
|
||||
/**
|
||||
* Prepares all requested native bridges.
|
||||
*/
|
||||
fun prepare(): NativeBridges
|
||||
}
|
||||
|
||||
interface NativeBridges {
|
||||
/**
|
||||
* @return `true` iff given entity is supported by these bridges,
|
||||
* i.e. all bridges it depends on can be successfully generated.
|
||||
*/
|
||||
fun isSupported(nativeBacked: NativeBacked): Boolean
|
||||
|
||||
val kotlinLines: Sequence<String>
|
||||
val nativeLines: Sequence<String>
|
||||
}
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
|
||||
import org.jetbrains.kotlin.native.interop.indexer.NativeLibrary
|
||||
import org.jetbrains.kotlin.native.interop.indexer.mapFragmentIsCompilable
|
||||
|
||||
class SimpleBridgeGeneratorImpl(
|
||||
private val platform: KotlinPlatform,
|
||||
private val pkgName: String,
|
||||
private val jvmFileClassName: String,
|
||||
private val libraryForCStubs: NativeLibrary
|
||||
) : SimpleBridgeGenerator {
|
||||
|
||||
private var nextUniqueId = 0
|
||||
|
||||
private val BridgedType.nativeType: String get() = when (platform) {
|
||||
KotlinPlatform.JVM -> when (this) {
|
||||
BridgedType.BYTE -> "jbyte"
|
||||
BridgedType.SHORT -> "jshort"
|
||||
BridgedType.INT -> "jint"
|
||||
BridgedType.LONG -> "jlong"
|
||||
BridgedType.FLOAT -> "jfloat"
|
||||
BridgedType.DOUBLE -> "jdouble"
|
||||
BridgedType.NATIVE_PTR -> "jlong"
|
||||
BridgedType.OBJC_POINTER -> TODO()
|
||||
BridgedType.VOID -> "void"
|
||||
}
|
||||
KotlinPlatform.NATIVE -> when (this) {
|
||||
BridgedType.BYTE -> "int8_t"
|
||||
BridgedType.SHORT -> "int16_t"
|
||||
BridgedType.INT -> "int32_t"
|
||||
BridgedType.LONG -> "int64_t"
|
||||
BridgedType.FLOAT -> "float"
|
||||
BridgedType.DOUBLE -> "double"
|
||||
BridgedType.NATIVE_PTR -> "void*"
|
||||
BridgedType.OBJC_POINTER -> "id"
|
||||
BridgedType.VOID -> "void"
|
||||
}
|
||||
}
|
||||
|
||||
private inner class NativeBridge(val kotlinLines: List<String>, val nativeLines: List<String>)
|
||||
|
||||
override fun kotlinToNative(
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: BridgedType,
|
||||
kotlinValues: List<BridgeTypedKotlinValue>,
|
||||
block: NativeCodeBuilder.(arguments: List<NativeExpression>) -> NativeExpression
|
||||
): KotlinExpression {
|
||||
|
||||
val kotlinLines = mutableListOf<String>()
|
||||
val nativeLines = mutableListOf<String>()
|
||||
|
||||
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
|
||||
val kotlinParameters = kotlinValues.withIndex().joinToString {
|
||||
"p${it.index}: ${it.value.type.kotlinType}"
|
||||
}
|
||||
|
||||
val callExpr = "$kotlinFunctionName(${kotlinValues.joinToString { it.value }})"
|
||||
|
||||
val cFunctionParameters = when (platform) {
|
||||
KotlinPlatform.JVM -> mutableListOf(
|
||||
"jniEnv" to "JNIEnv*",
|
||||
"jclss" to "jclass"
|
||||
)
|
||||
KotlinPlatform.NATIVE -> mutableListOf()
|
||||
}
|
||||
|
||||
kotlinValues.withIndex().mapTo(cFunctionParameters) {
|
||||
"p${it.index}" to it.value.type.nativeType
|
||||
}
|
||||
|
||||
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
|
||||
val cReturnType = returnType.nativeType
|
||||
|
||||
val cFunctionHeader = when (platform) {
|
||||
KotlinPlatform.JVM -> {
|
||||
val funcFullName = buildString {
|
||||
if (pkgName.isNotEmpty()) {
|
||||
append(pkgName)
|
||||
append('.')
|
||||
}
|
||||
append(jvmFileClassName)
|
||||
append('.')
|
||||
append(kotlinFunctionName)
|
||||
}
|
||||
|
||||
val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024")
|
||||
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
|
||||
}
|
||||
KotlinPlatform.NATIVE -> {
|
||||
val functionName = pkgName.replace('.', '_') + "_$kotlinFunctionName"
|
||||
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
|
||||
"$cReturnType $functionName ($joinedCParameters)"
|
||||
}
|
||||
}
|
||||
nativeLines.add(cFunctionHeader + " {")
|
||||
|
||||
buildNativeCodeLines {
|
||||
val cExpr = block(cFunctionParameters.takeLast(kotlinValues.size).map { (name, _) -> name })
|
||||
if (returnType != BridgedType.VOID) {
|
||||
out("return ($cReturnType)$cExpr;")
|
||||
}
|
||||
}.forEach {
|
||||
nativeLines.add(" $it")
|
||||
}
|
||||
|
||||
nativeLines.add("}")
|
||||
kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): ${returnType.kotlinType}")
|
||||
|
||||
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
|
||||
nativeBridges.add(nativeBacked to nativeBridge)
|
||||
|
||||
return callExpr
|
||||
}
|
||||
|
||||
override fun nativeToKotlin(
|
||||
nativeBacked: NativeBacked,
|
||||
returnType: BridgedType,
|
||||
nativeValues: List<BridgeTypedNativeValue>,
|
||||
block: KotlinCodeBuilder.(arguments: List<KotlinExpression>) -> KotlinExpression
|
||||
): NativeExpression {
|
||||
|
||||
if (platform != KotlinPlatform.NATIVE) TODO()
|
||||
|
||||
val kotlinLines = mutableListOf<String>()
|
||||
val nativeLines = mutableListOf<String>()
|
||||
|
||||
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
|
||||
val kotlinParameters = nativeValues.withIndex().map {
|
||||
"p${it.index}" to it.value.type.kotlinType
|
||||
}
|
||||
val joinedKotlinParameters = kotlinParameters.joinToString { "${it.first}: ${it.second}" }
|
||||
|
||||
val cFunctionParameters = nativeValues.withIndex().map {
|
||||
"p${it.index}" to it.value.type.nativeType
|
||||
}
|
||||
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
|
||||
val cReturnType = returnType.nativeType
|
||||
|
||||
val symbolName = pkgName.replace('.', '_') + "_$kotlinFunctionName"
|
||||
kotlinLines.add("@konan.internal.ExportForCppRuntime(${symbolName.quoteAsKotlinLiteral()})")
|
||||
val cFunctionHeader = "$cReturnType $symbolName($joinedCParameters)"
|
||||
|
||||
nativeLines.add("$cFunctionHeader;")
|
||||
kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): ${returnType.kotlinType} {")
|
||||
|
||||
buildKotlinCodeLines {
|
||||
var kotlinExpr = block(kotlinParameters.map { (name, _) -> name })
|
||||
if (returnType == BridgedType.OBJC_POINTER) {
|
||||
// The Kotlin code may lose the ownership on this pointer after returning from the bridge,
|
||||
// so retain the pointer and autorelease it:
|
||||
kotlinExpr = "objc_retainAutoreleaseReturnValue($kotlinExpr)"
|
||||
// (Objective-C does the same for returned pointers).
|
||||
}
|
||||
out("return $kotlinExpr")
|
||||
}.forEach {
|
||||
kotlinLines.add(" $it")
|
||||
}
|
||||
|
||||
kotlinLines.add("}")
|
||||
|
||||
insertNativeBridge(nativeBacked, kotlinLines, nativeLines)
|
||||
|
||||
return "$symbolName(${nativeValues.joinToString { it.value }})"
|
||||
|
||||
}
|
||||
|
||||
override fun insertNativeBridge(nativeBacked: NativeBacked, kotlinLines: List<String>, nativeLines: List<String>) {
|
||||
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
|
||||
nativeBridges.add(nativeBacked to nativeBridge)
|
||||
}
|
||||
|
||||
private val nativeBridges = mutableListOf<Pair<NativeBacked, NativeBridge>>()
|
||||
|
||||
override fun prepare(): NativeBridges {
|
||||
val includedBridges = mutableListOf<NativeBridge>()
|
||||
val excludedClients = mutableSetOf<NativeBacked>()
|
||||
|
||||
nativeBridges.map { it.second.nativeLines }
|
||||
.mapFragmentIsCompilable(libraryForCStubs)
|
||||
.forEachIndexed { index, isCompilable ->
|
||||
if (isCompilable) {
|
||||
includedBridges.add(nativeBridges[index].second)
|
||||
} else {
|
||||
excludedClients.add(nativeBridges[index].first)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: exclude unused bridges.
|
||||
|
||||
return object : NativeBridges {
|
||||
|
||||
override val kotlinLines: Sequence<String>
|
||||
get() = includedBridges.asSequence().flatMap { it.kotlinLines.asSequence() }
|
||||
|
||||
override val nativeLines: Sequence<String>
|
||||
get() = includedBridges.asSequence().flatMap { it.nativeLines.asSequence() }
|
||||
|
||||
override fun isSupported(nativeBacked: NativeBacked): Boolean =
|
||||
nativeBacked !in excludedClients
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -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
|
||||
}
|
||||
+166
-807
File diff suppressed because it is too large
Load Diff
+31
-3
@@ -324,6 +324,19 @@ private fun downloadDependencies(dependenciesRoot: String, target: String, konan
|
||||
maybeExecuteHelper(dependenciesRoot, konanProperties, dependencyList)
|
||||
}
|
||||
|
||||
private fun selectNativeLanguage(config: Properties): Language {
|
||||
val languages = mapOf(
|
||||
"C" to Language.C,
|
||||
"Objective-C" to Language.OBJECTIVE_C
|
||||
)
|
||||
|
||||
val language = config.getProperty("language") ?: return Language.C
|
||||
|
||||
return languages[language] ?:
|
||||
error("Unexpected language '$language'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
|
||||
}
|
||||
|
||||
|
||||
private fun processLib(konanHome: String,
|
||||
substitutions: Map<String, String>,
|
||||
args: Map<String, List<String>>) {
|
||||
@@ -363,9 +376,24 @@ private fun processLib(konanHome: String,
|
||||
|
||||
val defaultOpts = konanProperties.defaultCompilerOpts(target, dependencies)
|
||||
val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders
|
||||
val compilerOpts = config.getSpaceSeparated("compilerOpts") + defaultOpts + additionalCompilerOpts
|
||||
val language = selectNativeLanguage(config)
|
||||
val compilerOpts: List<String> = mutableListOf<String>().apply {
|
||||
addAll(config.getSpaceSeparated("compilerOpts"))
|
||||
addAll(defaultOpts)
|
||||
addAll(additionalCompilerOpts)
|
||||
addAll(when (language) {
|
||||
Language.C -> emptyList()
|
||||
Language.OBJECTIVE_C -> {
|
||||
// "Objective-C" within interop means "Objective-C with ARC":
|
||||
listOf("-fobjc-arc")
|
||||
// Using this flag here has two effects:
|
||||
// 1. The headers are parsed with ARC enabled, thus the API is visible correctly.
|
||||
// 2. The generated Objective-C stubs are compiled with ARC enabled, so reference counting
|
||||
// calls are inserted automatically.
|
||||
}
|
||||
})
|
||||
}
|
||||
val compiler = "clang"
|
||||
val language = Language.C
|
||||
val excludeSystemLibs = config.getProperty("excludeSystemLibs")?.toBoolean() ?: false
|
||||
val excludeDependentModules = config.getProperty("excludeDependentModules")?.toBoolean() ?: false
|
||||
|
||||
@@ -422,7 +450,7 @@ private fun processLib(konanHome: String,
|
||||
outKtFile.parentFile.mkdirs()
|
||||
|
||||
File(nativeLibsDir).mkdirs()
|
||||
val outCFile = File("$nativeLibsDir/$libName.c") // TODO: select the better location.
|
||||
val outCFile = File("$nativeLibsDir/$libName.${language.sourceFileExtension}") // TODO: select the better location.
|
||||
|
||||
outKtFile.bufferedWriter().use { ktFile ->
|
||||
outCFile.bufferedWriter().use { cFile ->
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.backend.konan.ObjCOverridabilityCondition
|
||||
+26
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -100,6 +101,31 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
|
||||
val signExtend = packageScope.getContributedFunctions("signExtend").single()
|
||||
|
||||
val narrow = packageScope.getContributedFunctions("narrow").single()
|
||||
|
||||
val objCObject = packageScope.getContributedClassifier("ObjCObject") as ClassDescriptor
|
||||
val objCPointerHolder = packageScope.getContributedClassifier("ObjCPointerHolder") as ClassDescriptor
|
||||
|
||||
val objCPointerHolderValue = objCPointerHolder.unsubstitutedMemberScope
|
||||
.getContributedDescriptors().filterIsInstance<PropertyDescriptor>().single()
|
||||
|
||||
val objCObjectInitFromPtr = packageScope.getContributedFunctions("initFromPtr").single()
|
||||
val objCObjectInitFrom = packageScope.getContributedFunctions("initFrom").single()
|
||||
|
||||
val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single()
|
||||
|
||||
val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single()
|
||||
|
||||
val objCObjectRawPtr = packageScope.getContributedVariables("rawPtr").single {
|
||||
val extensionReceiverType = it.extensionReceiverParameter?.type
|
||||
extensionReceiverType != null && !extensionReceiverType.isMarkedNullable &&
|
||||
TypeUtils.getClassDescriptor(extensionReceiverType) == objCObject
|
||||
}
|
||||
|
||||
val getObjCReceiverOrSuper = packageScope.getContributedFunctions("getReceiverOrSuper").single()
|
||||
|
||||
val getObjCMessenger = packageScope.getContributedFunctions("getMessenger").single()
|
||||
val getObjCMessengerLU = packageScope.getContributedFunctions("getMessengerLU").single()
|
||||
|
||||
}
|
||||
|
||||
private fun MemberScope.getContributedVariables(name: String) =
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||
|
||||
private val interopPackageName = InteropBuiltIns.FqNames.packageName
|
||||
private val objCObjectFqName = interopPackageName.child(Name.identifier("ObjCObject"))
|
||||
private val objCClassFqName = interopPackageName.child(Name.identifier("ObjCClass"))
|
||||
private val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass"))
|
||||
private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod"))
|
||||
private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge"))
|
||||
|
||||
fun ClassDescriptor.isObjCClass(): Boolean =
|
||||
this.getAllSuperClassifiers().any { it.fqNameSafe == objCObjectFqName } &&
|
||||
this.containingDeclaration.fqNameSafe != interopPackageName
|
||||
|
||||
fun ClassDescriptor.isExternalObjCClass(): Boolean = this.isObjCClass() &&
|
||||
this.parentsWithSelf.filterIsInstance<ClassDescriptor>().any {
|
||||
it.annotations.findAnnotation(externalObjCClassFqName) != null
|
||||
}
|
||||
|
||||
fun ClassDescriptor.isObjCMetaClass(): Boolean = this.getAllSuperClassifiers().any {
|
||||
it.fqNameSafe == objCClassFqName
|
||||
}
|
||||
|
||||
fun FunctionDescriptor.isObjCClassMethod() =
|
||||
this.containingDeclaration.let { it is ClassDescriptor && it.isObjCClass() }
|
||||
|
||||
fun FunctionDescriptor.isExternalObjCClassMethod() =
|
||||
this.containingDeclaration.let { it is ClassDescriptor && it.isExternalObjCClass() }
|
||||
|
||||
// Special case: methods from Kotlin Objective-C classes can be called virtually from bridges.
|
||||
fun FunctionDescriptor.canObjCClassMethodBeCalledVirtually(overriddenDescriptor: FunctionDescriptor) =
|
||||
overriddenDescriptor.isOverridable && this.kind.isReal && !this.isExternalObjCClassMethod()
|
||||
|
||||
fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass()
|
||||
|
||||
data class ObjCMethodInfo(val descriptor: FunctionDescriptor,
|
||||
val bridge: FunctionDescriptor,
|
||||
val selector: String,
|
||||
val encoding: String,
|
||||
val imp: String)
|
||||
|
||||
private fun CallableDescriptor.getBridgeAnnotation() =
|
||||
this.annotations.findAnnotation(objCBridgeFqName)
|
||||
|
||||
private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
|
||||
assert (this.kind.isReal)
|
||||
|
||||
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
|
||||
val packageFragment = this.parents.filterIsInstance<PackageFragmentDescriptor>().single()
|
||||
|
||||
val bridgeName = methodAnnotation.getStringValue("bridge")
|
||||
|
||||
val bridge = packageFragment.getMemberScope()
|
||||
.getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND)
|
||||
.single()
|
||||
|
||||
val bridgeAnnotation = bridge.getBridgeAnnotation()!!
|
||||
return ObjCMethodInfo(
|
||||
descriptor = this,
|
||||
bridge = bridge,
|
||||
selector = methodAnnotation.getStringValue("selector"),
|
||||
encoding = bridgeAnnotation.getStringValue("encoding"),
|
||||
imp = bridgeAnnotation.getStringValue("imp")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onlyExternal indicates whether to accept overriding methods from Kotlin classes
|
||||
*/
|
||||
private fun FunctionDescriptor.getObjCMethodInfo(onlyExternal: Boolean): ObjCMethodInfo? {
|
||||
if (this.kind.isReal) {
|
||||
this.decodeObjCMethodAnnotation()?.let { return it }
|
||||
|
||||
if (onlyExternal) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return this.overriddenDescriptors.asSequence().mapNotNull { it.getObjCMethodInfo(onlyExternal) }.firstOrNull()
|
||||
}
|
||||
|
||||
fun FunctionDescriptor.getExternalObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = true)
|
||||
|
||||
fun FunctionDescriptor.getObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = false)
|
||||
|
||||
/**
|
||||
* Describes method overriding rules for Objective-C methods.
|
||||
*
|
||||
* This class is applied at [org.jetbrains.kotlin.resolve.OverridingUtil] as configured with
|
||||
* `META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition` resource.
|
||||
*/
|
||||
class ObjCOverridabilityCondition : ExternalOverridabilityCondition {
|
||||
|
||||
override fun getContract() = ExternalOverridabilityCondition.Contract.BOTH
|
||||
|
||||
override fun isOverridable(
|
||||
superDescriptor: CallableDescriptor,
|
||||
subDescriptor: CallableDescriptor,
|
||||
subClassDescriptor: ClassDescriptor?
|
||||
): ExternalOverridabilityCondition.Result {
|
||||
|
||||
assert(superDescriptor.name == subDescriptor.name)
|
||||
|
||||
val superClass = superDescriptor.containingDeclaration as? ClassDescriptor
|
||||
if (superClass == null || !superClass.isObjCClass()) {
|
||||
return ExternalOverridabilityCondition.Result.UNKNOWN
|
||||
}
|
||||
|
||||
return if (areSelectorsEqual(superDescriptor, subDescriptor)) {
|
||||
ExternalOverridabilityCondition.Result.OVERRIDABLE
|
||||
} else {
|
||||
ExternalOverridabilityCondition.Result.INCOMPATIBLE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun areSelectorsEqual(first: CallableDescriptor, second: CallableDescriptor): Boolean {
|
||||
// The original Objective-C method selector is represented as
|
||||
// function name and parameter names (except first).
|
||||
|
||||
if (first.valueParameters.size != second.valueParameters.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
first.valueParameters.forEachIndexed { index, parameter ->
|
||||
if (index > 0 && parameter.name != second.valueParameters[index].name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
+9
-3
@@ -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
|
||||
|
||||
+7
@@ -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
|
||||
}
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
+14
-2
@@ -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()
|
||||
|
||||
+27
@@ -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)
|
||||
|
||||
+3
@@ -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"
|
||||
|
||||
+167
-10
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
@@ -156,6 +157,8 @@ internal fun verifyModule(llvmModule: LLVMModuleRef, current: String = "") {
|
||||
internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
|
||||
val generator = RTTIGenerator(context)
|
||||
|
||||
val kotlinObjCClassInfoGenerator = KotlinObjCClassInfoGenerator(context)
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
@@ -163,12 +166,17 @@ internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
|
||||
override fun visitClass(declaration: IrClass) {
|
||||
super.visitClass(declaration)
|
||||
|
||||
if (declaration.descriptor.isIntrinsic) {
|
||||
val descriptor = declaration.descriptor
|
||||
if (descriptor.isIntrinsic) {
|
||||
// do not generate any code for intrinsic classes as they require special handling
|
||||
return
|
||||
}
|
||||
|
||||
generator.generate(declaration.descriptor)
|
||||
generator.generate(descriptor)
|
||||
|
||||
if (descriptor.isKotlinObjCClass()) {
|
||||
kotlinObjCClassInfoGenerator.generate(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -734,7 +742,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
functionGenerationContext.condBr(condition, bbExit, bbInit)
|
||||
|
||||
functionGenerationContext.positionAtEnd(bbInit)
|
||||
val typeInfo = codegen.typeInfoValue(value.descriptor)
|
||||
val typeInfo = typeInfoForAllocation(value.descriptor)
|
||||
val initFunction = value.descriptor.constructors.first { it.valueParameters.size == 0 }
|
||||
val ctor = codegen.llvmFunction(initFunction)
|
||||
val args = listOf(objectPtr, typeInfo, ctor)
|
||||
@@ -1350,13 +1358,32 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
private fun fieldPtrOfClass(thisPtr: LLVMValueRef, value: PropertyDescriptor): LLVMValueRef {
|
||||
val fieldInfo = context.llvmDeclarations.forField(value)
|
||||
|
||||
val classDescriptor = value.containingDeclaration as ClassDescriptor
|
||||
val typePtr = pointerType(fieldInfo.classBodyType)
|
||||
val objectPtr = LLVMBuildGEP(functionGenerationContext.builder, thisPtr, cValuesOf(kImmOne), 1, "")
|
||||
val typedObjPtr = functionGenerationContext.bitcast(typePtr, objectPtr!!)
|
||||
val fieldPtr = LLVMBuildStructGEP(functionGenerationContext.builder, typedObjPtr, fieldInfo.index, "")
|
||||
|
||||
val bodyPtr = getObjectBodyPtr(classDescriptor, thisPtr)
|
||||
|
||||
val typedBodyPtr = functionGenerationContext.bitcast(typePtr, bodyPtr)
|
||||
val fieldPtr = LLVMBuildStructGEP(functionGenerationContext.builder, typedBodyPtr, fieldInfo.index, "")
|
||||
return fieldPtr!!
|
||||
}
|
||||
|
||||
private fun getObjectBodyPtr(classDescriptor: ClassDescriptor, objectPtr: LLVMValueRef): LLVMValueRef {
|
||||
return if (classDescriptor.isObjCClass()) {
|
||||
assert(classDescriptor.isKotlinObjCClass())
|
||||
|
||||
val objCPtr = callDirect(context.interopBuiltIns.objCPointerHolderValue.getter!!,
|
||||
listOf(objectPtr), Lifetime.IRRELEVANT)
|
||||
|
||||
val objCDeclarations = context.llvmDeclarations.forClass(classDescriptor).objCDeclarations!!
|
||||
val bodyOffset = functionGenerationContext.load(objCDeclarations.bodyOffsetGlobal.llvmGlobal)
|
||||
|
||||
functionGenerationContext.gep(objCPtr, bodyOffset)
|
||||
} else {
|
||||
LLVMBuildGEP(functionGenerationContext.builder, objectPtr, cValuesOf(kImmOne), 1, "")!!
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
private fun evaluateConst(value: IrConst<*>): LLVMValueRef {
|
||||
@@ -1871,7 +1898,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
functionGenerationContext.allocArray(codegen.typeInfoValue(constructedClass), count = kImmZero,
|
||||
lifetime = resultLifetime(callee))
|
||||
} else {
|
||||
functionGenerationContext.allocInstance(codegen.typeInfoValue(constructedClass), resultLifetime(callee))
|
||||
functionGenerationContext.allocInstance(typeInfoForAllocation(constructedClass), resultLifetime(callee))
|
||||
}
|
||||
evaluateSimpleFunctionCall(callee.descriptor,
|
||||
listOf(thisValue) + args, Lifetime.IRRELEVANT /* constructor doesn't return anything */)
|
||||
@@ -1879,6 +1906,15 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
}
|
||||
}
|
||||
|
||||
private fun typeInfoForAllocation(constructedClass: ClassDescriptor): LLVMValueRef {
|
||||
val descriptorForTypeInfo = if (constructedClass.isObjCClass()) {
|
||||
context.interopBuiltIns.objCPointerHolder
|
||||
} else {
|
||||
constructedClass
|
||||
}
|
||||
return codegen.typeInfoValue(descriptorForTypeInfo)
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
private fun evaluateIntrinsicCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
|
||||
@@ -1934,10 +1970,122 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
LLVMBuildSExt(functionGenerationContext.builder, intPtrValue, resultType, "")!!
|
||||
}
|
||||
}
|
||||
interop.objCObjectInitFromPtr -> {
|
||||
genObjCObjectInitFromPtr(args)
|
||||
}
|
||||
|
||||
interop.getObjCReceiverOrSuper -> {
|
||||
genGetObjCReceiverOrSuper(args)
|
||||
}
|
||||
|
||||
interop.getObjCClass -> {
|
||||
val typeArgument = callee.getTypeArgument(descriptor.typeParameters.single())
|
||||
val classDescriptor = TypeUtils.getClassDescriptor(typeArgument!!)!!
|
||||
genGetObjCClass(classDescriptor)
|
||||
}
|
||||
|
||||
interop.getObjCMessenger -> {
|
||||
genGetObjCMessenger(args, isLU = false)
|
||||
}
|
||||
interop.getObjCMessengerLU -> {
|
||||
genGetObjCMessenger(args, isLU = true)
|
||||
}
|
||||
|
||||
else -> TODO(callee.descriptor.original.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun genGetObjCClass(classDescriptor: ClassDescriptor): LLVMValueRef {
|
||||
assert(!classDescriptor.isInterface)
|
||||
|
||||
return if (classDescriptor.isExternalObjCClass()) {
|
||||
val lookUpFunction = context.llvm.externalFunction("objc_lookUpClass",
|
||||
functionType(int8TypePtr, false, int8TypePtr))
|
||||
|
||||
call(lookUpFunction,
|
||||
listOf(codegen.staticData.cStringLiteral(classDescriptor.name.asString()).llvm))
|
||||
} else {
|
||||
val objCDeclarations = context.llvmDeclarations.forClass(classDescriptor).objCDeclarations!!
|
||||
val classPointerGlobal = objCDeclarations.classPointerGlobal.llvmGlobal
|
||||
val gen = functionGenerationContext
|
||||
|
||||
val storedClass = gen.load(classPointerGlobal)
|
||||
|
||||
val storedClassIsNotNull = gen.icmpNe(storedClass, kNullInt8Ptr)
|
||||
|
||||
return gen.ifThenElse(storedClassIsNotNull, storedClass) {
|
||||
val newClass = call(context.llvm.createKotlinObjCClass,
|
||||
listOf(objCDeclarations.classInfoGlobal.llvmGlobal))
|
||||
|
||||
gen.store(newClass, classPointerGlobal)
|
||||
newClass
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun genGetObjCMessenger(args: List<LLVMValueRef>, isLU: Boolean): LLVMValueRef {
|
||||
val gen = functionGenerationContext
|
||||
|
||||
// 'LU' means "large or unaligned".
|
||||
|
||||
// objc_msgSend*_stret functions must be used when return value is returned through memory
|
||||
// pointed by implicit argument, which is passed on the register that would otherwise be used for receiver.
|
||||
// On aarch64 it is never the case, since such implicit argument gets passed on x8.
|
||||
// On x86_64 it is the case if the return value takes more than 16 bytes or is the structure with
|
||||
// unaligned fields (there are some complicated exceptions currently ignored). The latter condition
|
||||
// is "encoded" by stub generator by emitting either `getMessenger` or `getMessengerLU` intrinsic call.
|
||||
val isStret = when (context.config.targetManager.target) {
|
||||
KonanTarget.MACBOOK, KonanTarget.IPHONE_SIM -> isLU // x86_64
|
||||
KonanTarget.IPHONE -> false // aarch64
|
||||
else -> TODO()
|
||||
}
|
||||
|
||||
val messengerNameSuffix = if (isStret) "_stret" else ""
|
||||
|
||||
val functionType = functionType(int8TypePtr, true, int8TypePtr, int8TypePtr)
|
||||
|
||||
val normalMessenger = context.llvm.externalFunction("objc_msgSend$messengerNameSuffix", functionType)
|
||||
val superMessenger = context.llvm.externalFunction("objc_msgSendSuper$messengerNameSuffix", functionType)
|
||||
|
||||
val superClass = args.single()
|
||||
val messenger = LLVMBuildSelect(gen.builder,
|
||||
If = gen.icmpEq(superClass, kNullInt8Ptr),
|
||||
Then = normalMessenger,
|
||||
Else = superMessenger,
|
||||
Name = ""
|
||||
)!!
|
||||
|
||||
return gen.bitcast(int8TypePtr, messenger)
|
||||
}
|
||||
|
||||
private fun genObjCObjectInitFromPtr(args: List<LLVMValueRef>): LLVMValueRef {
|
||||
return callDirect(context.interopBuiltIns.objCPointerHolder.unsubstitutedPrimaryConstructor!!,
|
||||
args, Lifetime.IRRELEVANT)
|
||||
}
|
||||
|
||||
private fun genGetObjCReceiverOrSuper(args: List<LLVMValueRef>): LLVMValueRef {
|
||||
val gen = functionGenerationContext
|
||||
|
||||
assert(args.size == 2)
|
||||
val receiver = args[0]
|
||||
val superClass = args[1]
|
||||
|
||||
val superClassIsNull = gen.icmpEq(superClass, kNullInt8Ptr)
|
||||
|
||||
return gen.ifThenElse(superClassIsNull, receiver) {
|
||||
val structType = structType(kInt8Ptr, kInt8Ptr)
|
||||
val ptr = gen.alloca(structType)
|
||||
gen.store(receiver,
|
||||
LLVMBuildGEP(gen.builder, ptr, cValuesOf(kImmZero, kImmZero), 2, "")!!)
|
||||
|
||||
gen.store(superClass,
|
||||
LLVMBuildGEP(gen.builder, ptr, cValuesOf(kImmZero, kImmOne), 2, "")!!)
|
||||
|
||||
gen.bitcast(int8TypePtr, ptr)
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
private val kImmZero = LLVMConstInt(LLVMInt32Type(), 0, 1)!!
|
||||
private val kImmOne = LLVMConstInt(LLVMInt32Type(), 1, 1)!!
|
||||
@@ -2008,11 +2156,16 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
fun callVirtual(descriptor: FunctionDescriptor, args: List<LLVMValueRef>,
|
||||
resultLifetime: Lifetime): LLVMValueRef {
|
||||
assert(LLVMTypeOf(args[0]) == codegen.kObjHeaderPtr)
|
||||
val typeInfoPtrPtr = LLVMBuildStructGEP(functionGenerationContext.builder, args[0], 0 /* type_info */, "")!!
|
||||
val typeInfoPtr = functionGenerationContext.load(typeInfoPtrPtr)
|
||||
assert (typeInfoPtr.type == codegen.kTypeInfoPtr)
|
||||
|
||||
val owner = descriptor.containingDeclaration as ClassDescriptor
|
||||
|
||||
val typeInfoPtr: LLVMValueRef = if (owner.isObjCClass()) {
|
||||
call(context.llvm.getObjCKotlinTypeInfo, listOf(args.first()))
|
||||
} else {
|
||||
val typeInfoPtrPtr = LLVMBuildStructGEP(functionGenerationContext.builder, args[0], 0 /* type_info */, "")!!
|
||||
functionGenerationContext.load(typeInfoPtrPtr)
|
||||
}
|
||||
assert (typeInfoPtr.type == codegen.kTypeInfoPtr)
|
||||
val llvmMethod = if (!owner.isInterface) {
|
||||
// If this is a virtual method of the class - we can call via vtable.
|
||||
val index = context.getVtableBuilder(owner).vtableIndex(descriptor)
|
||||
@@ -2068,6 +2221,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
val constructedClass = functionGenerationContext.constructedClass!!
|
||||
val thisPtr = currentCodeContext.genGetValue(constructedClass.thisAsReceiverParameter)
|
||||
|
||||
if (constructedClass.isObjCClass()) {
|
||||
return codegen.theUnitInstanceRef.llvm
|
||||
}
|
||||
|
||||
val thisPtrArgType = codegen.getLLVMType(descriptor.allParameters[0].type)
|
||||
val thisPtrArg = if (thisPtr.type == thisPtrArgType) {
|
||||
thisPtr
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import llvm.LLVMStoreSizeOfType
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.ObjCMethodInfo
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods
|
||||
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.isFinalClass
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
|
||||
|
||||
internal class KotlinObjCClassInfoGenerator(override val context: Context) : ContextUtils {
|
||||
fun generate(descriptor: ClassDescriptor) {
|
||||
assert(descriptor.isFinalClass)
|
||||
|
||||
val objCLLvmDeclarations = context.llvmDeclarations.forClass(descriptor).objCDeclarations!!
|
||||
|
||||
val instanceMethods = descriptor.generateMethodDescs()
|
||||
|
||||
val companionObjectDescriptor = descriptor.companionObjectDescriptor
|
||||
val classMethods = companionObjectDescriptor?.generateMethodDescs() ?: emptyList()
|
||||
|
||||
val superclassName = descriptor.getSuperClassNotAny()!!.name.asString()
|
||||
val protocolNames = descriptor.getSuperInterfaces().map { it.name.asString().removeSuffix("Protocol") }
|
||||
|
||||
val bodySize =
|
||||
LLVMStoreSizeOfType(llvmTargetData, context.llvmDeclarations.forClass(descriptor).bodyType).toInt()
|
||||
|
||||
val className = if (descriptor.isExported()) {
|
||||
staticData.cStringLiteral(descriptor.fqNameSafe.asString())
|
||||
} else {
|
||||
NullPointer(int8Type) // Generate as anonymous.
|
||||
}
|
||||
|
||||
val info = Struct(runtime.kotlinObjCClassInfo,
|
||||
className,
|
||||
|
||||
staticData.cStringLiteral(superclassName),
|
||||
staticData.placeGlobalConstArray("", int8TypePtr,
|
||||
protocolNames.map { staticData.cStringLiteral(it) } + NullPointer(int8Type)),
|
||||
|
||||
staticData.placeGlobalConstArray("", runtime.objCMethodDescription, instanceMethods),
|
||||
Int32(instanceMethods.size),
|
||||
|
||||
staticData.placeGlobalConstArray("", runtime.objCMethodDescription, classMethods),
|
||||
Int32(classMethods.size),
|
||||
|
||||
Int32(bodySize),
|
||||
objCLLvmDeclarations.bodyOffsetGlobal.pointer,
|
||||
|
||||
descriptor.typeInfoPtr,
|
||||
companionObjectDescriptor?.typeInfoPtr ?: NullPointer(runtime.typeInfoType),
|
||||
|
||||
objCLLvmDeclarations.classPointerGlobal.pointer
|
||||
)
|
||||
|
||||
objCLLvmDeclarations.classInfoGlobal.setInitializer(info)
|
||||
|
||||
objCLLvmDeclarations.classPointerGlobal.setInitializer(NullPointer(int8Type))
|
||||
objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0))
|
||||
}
|
||||
|
||||
private fun generateMethodDesc(info: ObjCMethodInfo): ConstValue {
|
||||
return Struct(runtime.objCMethodDescription,
|
||||
staticData.cStringLiteral(info.selector),
|
||||
staticData.cStringLiteral(info.encoding),
|
||||
constPointer(context.llvm.externalFunction(info.imp, functionType(voidType))).bitcast(int8TypePtr)
|
||||
)
|
||||
}
|
||||
|
||||
private fun ClassDescriptor.generateMethodDescs(): List<ConstValue> =
|
||||
this.unsubstitutedMemberScope.contributedMethods.filter {
|
||||
it.kind.isReal && it !is ConstructorDescriptor
|
||||
}.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) }
|
||||
}
|
||||
+37
-3
@@ -21,6 +21,7 @@ import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||
import org.jetbrains.kotlin.backend.konan.isKotlinObjCClass
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
@@ -75,10 +76,17 @@ internal class ClassLlvmDeclarations(
|
||||
val fields: List<PropertyDescriptor>, // TODO: it is not an LLVM declaration.
|
||||
val typeInfoGlobal: StaticData.Global,
|
||||
val typeInfo: ConstPointer,
|
||||
val singletonDeclarations: SingletonLlvmDeclarations?)
|
||||
val singletonDeclarations: SingletonLlvmDeclarations?,
|
||||
val objCDeclarations: KotlinObjCClassLlvmDeclarations?)
|
||||
|
||||
internal class SingletonLlvmDeclarations(val instanceFieldRef: LLVMValueRef)
|
||||
|
||||
internal class KotlinObjCClassLlvmDeclarations(
|
||||
val classPointerGlobal: StaticData.Global,
|
||||
val classInfoGlobal: StaticData.Global,
|
||||
val bodyOffsetGlobal: StaticData.Global
|
||||
)
|
||||
|
||||
internal class FunctionLlvmDeclarations(val llvmFunction: LLVMValueRef)
|
||||
|
||||
internal class FieldLlvmDeclarations(val index: Int, val classBodyType: LLVMTypeRef)
|
||||
@@ -236,7 +244,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
"ktype:$internalName"
|
||||
}
|
||||
|
||||
if (!descriptor.isAbstract()) {
|
||||
if (descriptor.typeInfoHasVtableAttached) {
|
||||
// Create the special global consisting of TypeInfo and vtable.
|
||||
|
||||
val typeInfoGlobalName = "ktypeglobal:$internalName"
|
||||
@@ -273,7 +281,14 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
null
|
||||
}
|
||||
|
||||
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, typeInfoPtr, singletonDeclarations)
|
||||
val objCDeclarations = if (descriptor.isKotlinObjCClass()) {
|
||||
createKotlinObjCClassDeclarations(descriptor)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, typeInfoPtr,
|
||||
singletonDeclarations, objCDeclarations)
|
||||
}
|
||||
|
||||
private fun createSingletonDeclarations(
|
||||
@@ -299,6 +314,23 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
return SingletonLlvmDeclarations(instanceFieldRef)
|
||||
}
|
||||
|
||||
private fun createKotlinObjCClassDeclarations(descriptor: ClassDescriptor): KotlinObjCClassLlvmDeclarations {
|
||||
val internalName = qualifyInternalName(descriptor)
|
||||
|
||||
val classPointerGlobal = staticData.createGlobal(int8TypePtr, "kobjcclassptr:$internalName")
|
||||
|
||||
val classInfoGlobal = staticData.createGlobal(
|
||||
context.llvm.runtime.kotlinObjCClassInfo,
|
||||
"kobjcclassinfo:$internalName"
|
||||
).apply {
|
||||
setConstant(true)
|
||||
}
|
||||
|
||||
val bodyOffsetGlobal = staticData.createGlobal(int32Type, "kobjcbodyoffs:$internalName")
|
||||
|
||||
return KotlinObjCClassLlvmDeclarations(classPointerGlobal, classInfoGlobal, bodyOffsetGlobal)
|
||||
}
|
||||
|
||||
override fun visitField(declaration: IrField) {
|
||||
super.visitField(declaration)
|
||||
|
||||
@@ -330,6 +362,8 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
override fun visitFunction(declaration: IrFunction) {
|
||||
super.visitFunction(declaration)
|
||||
|
||||
if (!declaration.descriptor.kind.isReal) return
|
||||
|
||||
val descriptor = declaration.descriptor
|
||||
val llvmFunctionType = getLlvmFunctionType(descriptor)
|
||||
|
||||
|
||||
+10
-2
@@ -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)
|
||||
}
|
||||
|
||||
+3
@@ -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") }
|
||||
}
|
||||
|
||||
+4
@@ -152,6 +152,10 @@ internal class StaticData(override val context: Context): ContextUtils {
|
||||
}
|
||||
|
||||
private val stringLiterals = mutableMapOf<String, ConstPointer>()
|
||||
private val cStringLiterals = mutableMapOf<String, ConstPointer>()
|
||||
|
||||
fun cStringLiteral(value: String) =
|
||||
cStringLiterals.getOrPut(value) { placeCStringLiteral(value) }
|
||||
|
||||
fun kotlinStringLiteral(type: KotlinType, value: IrConst<String>) =
|
||||
stringLiterals.getOrPut(value.value) { createKotlinStringLiteral(type, value) }
|
||||
|
||||
+6
@@ -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)
|
||||
}
|
||||
+257
-15
@@ -20,40 +20,282 @@ import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
|
||||
import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer
|
||||
import org.jetbrains.kotlin.backend.common.lower.at
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.ValueType
|
||||
import org.jetbrains.kotlin.backend.konan.isRepresentedAs
|
||||
import org.jetbrains.kotlin.backend.konan.reportCompilationError
|
||||
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
||||
import org.jetbrains.kotlin.backend.common.peek
|
||||
import org.jetbrains.kotlin.backend.common.pop
|
||||
import org.jetbrains.kotlin.backend.common.push
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilder
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.getArguments
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.OverridingUtil
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||
|
||||
internal class InteropLoweringPart1(val context: Context) : IrElementTransformerVoid(), FileLoweringPass {
|
||||
internal class InteropLoweringPart1(val context: Context) : IrBuildingTransformer(context), FileLoweringPass {
|
||||
|
||||
private val symbols get() = context.ir.symbols
|
||||
private val symbolTable get() = symbols.symbolTable
|
||||
|
||||
lateinit var currentFile: IrFile
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
currentFile = irFile
|
||||
irFile.transformChildrenVoid(this)
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callAlloc(classSymbol: IrClassSymbol): IrExpression {
|
||||
return irCall(symbols.interopAllocObjCObject, listOf(classSymbol.descriptor.defaultType)).apply {
|
||||
putValueArgument(0, getObjCClass(classSymbol))
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.getObjCClass(classSymbol: IrClassSymbol): IrExpression {
|
||||
val classDescriptor = classSymbol.descriptor
|
||||
assert(!classDescriptor.isObjCMetaClass())
|
||||
|
||||
if (classDescriptor.isExternalObjCClass()) {
|
||||
val companionObject = classDescriptor.companionObjectDescriptor!!
|
||||
if (companionObject.unsubstitutedPrimaryConstructor != scope.scopeOwner) {
|
||||
// Optimization: get class pointer from companion object thus avoiding lookup by name.
|
||||
return irCall(symbols.interopObjCObjectRawValueGetter).apply {
|
||||
extensionReceiver = irGetObject(symbolTable.referenceClass(companionObject))
|
||||
}
|
||||
}
|
||||
}
|
||||
return irCall(symbols.interopGetObjCClass, listOf(classDescriptor.defaultType))
|
||||
}
|
||||
|
||||
private val outerClasses = mutableListOf<IrClass>()
|
||||
|
||||
override fun visitClass(declaration: IrClass): IrStatement {
|
||||
if (declaration.descriptor.isKotlinObjCClass()) {
|
||||
checkKotlinObjCClass(declaration)
|
||||
}
|
||||
|
||||
outerClasses.push(declaration)
|
||||
try {
|
||||
return super.visitClass(declaration)
|
||||
} finally {
|
||||
outerClasses.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkKotlinObjCClass(irClass: IrClass) {
|
||||
val kind = irClass.descriptor.kind
|
||||
if (kind != ClassKind.CLASS && kind != ClassKind.OBJECT) {
|
||||
context.reportCompilationError(
|
||||
"Only classes are supported as subtypes of Objective-C types",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
if (!irClass.descriptor.isFinalClass) {
|
||||
context.reportCompilationError(
|
||||
"Non-final Kotlin subclasses of Objective-C classes are not yet supported",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
var hasObjCClassSupertype = false
|
||||
irClass.descriptor.defaultType.constructor.supertypes.forEach {
|
||||
val descriptor = it.constructor.declarationDescriptor as ClassDescriptor
|
||||
if (!descriptor.isObjCClass()) {
|
||||
context.reportCompilationError(
|
||||
"Mixing Kotlin and Objective-C supertypes is not supported",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
if (descriptor.kind == ClassKind.CLASS) {
|
||||
hasObjCClassSupertype = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasObjCClassSupertype) {
|
||||
context.reportCompilationError(
|
||||
"Kotlin implementation of Objective-C protocol must have Objective-C superclass (e.g. NSObject)",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
|
||||
expression.transformChildrenVoid()
|
||||
|
||||
builder.at(expression)
|
||||
|
||||
val constructedClass = outerClasses.peek()!!
|
||||
val constructedClassDescriptor = constructedClass.descriptor
|
||||
|
||||
if (!constructedClassDescriptor.isObjCClass()) {
|
||||
return expression
|
||||
}
|
||||
|
||||
constructedClassDescriptor.containingDeclaration.let { classContainer ->
|
||||
if (classContainer is ClassDescriptor && classContainer.isObjCClass() &&
|
||||
constructedClassDescriptor == classContainer.companionObjectDescriptor) {
|
||||
|
||||
val outerConstructedClass = outerClasses[outerClasses.lastIndex - 1]
|
||||
assert (outerConstructedClass.descriptor == classContainer)
|
||||
|
||||
assert(expression.getArguments().isEmpty())
|
||||
|
||||
return builder.irBlock(expression) {
|
||||
+expression // Required for the IR to be valid, will be ignored in codegen.
|
||||
+irCall(symbols.interopObjCObjectInitFromPtr).apply {
|
||||
extensionReceiver = irGet(constructedClass.thisReceiver!!.symbol)
|
||||
putValueArgument(0, getObjCClass(outerConstructedClass.symbol))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!constructedClassDescriptor.isExternalObjCClass() &&
|
||||
expression.descriptor.constructedClass.isExternalObjCClass()) {
|
||||
|
||||
// Calling super constructor from Kotlin Objective-C class.
|
||||
|
||||
assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass)
|
||||
|
||||
val initMethod = getObjCInitMethod(expression.descriptor)!!
|
||||
val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
|
||||
|
||||
assert(expression.dispatchReceiver == null)
|
||||
assert(expression.extensionReceiver == null)
|
||||
|
||||
val initCall = builder.genLoweredObjCMethodCall(
|
||||
initMethodInfo,
|
||||
superQualifier = symbolTable.referenceClass(expression.descriptor.constructedClass),
|
||||
receiver = builder.callAlloc(constructedClass.symbol),
|
||||
arguments = initMethod.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
)
|
||||
|
||||
val superConstructor = symbolTable.referenceConstructor(
|
||||
expression.descriptor.constructedClass.constructors.single { it.valueParameters.size == 0 }
|
||||
)
|
||||
|
||||
return builder.irBlock(expression) {
|
||||
// Required for the IR to be valid, will be ignored in codegen:
|
||||
+IrDelegatingConstructorCallImpl(startOffset, endOffset, superConstructor, superConstructor.descriptor)
|
||||
|
||||
+irCall(symbols.interopObjCObjectInitFrom).apply {
|
||||
extensionReceiver = irGet(constructedClass.thisReceiver!!.symbol)
|
||||
putValueArgument(0, initCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.genLoweredObjCMethodCall(info: ObjCMethodInfo, superQualifier: IrClassSymbol?,
|
||||
receiver: IrExpression, arguments: List<IrExpression>): IrExpression {
|
||||
|
||||
val superClass = superQualifier?.let { getObjCClass(it) } ?:
|
||||
irCall(symbols.getNativeNullPtr)
|
||||
|
||||
val bridge = symbolTable.referenceSimpleFunction(info.bridge)
|
||||
return irCall(bridge).apply {
|
||||
putValueArgument(0, superClass)
|
||||
putValueArgument(1, receiver)
|
||||
|
||||
assert(arguments.size + 2 == info.bridge.valueParameters.size)
|
||||
arguments.forEachIndexed { index, argument ->
|
||||
putValueArgument(index + 2, argument)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getObjCInitMethod(descriptor: ConstructorDescriptor): FunctionDescriptor? {
|
||||
return descriptor.annotations.findAnnotation(FqName("kotlinx.cinterop.ObjCConstructor"))?.let {
|
||||
val initSelector = it.getStringValue("initSelector")
|
||||
descriptor.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
|
||||
.filterIsInstance<FunctionDescriptor>()
|
||||
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
expression.transformChildrenVoid()
|
||||
|
||||
return when (expression.descriptor.original) {
|
||||
val descriptor = expression.descriptor.original
|
||||
|
||||
if (descriptor is ConstructorDescriptor) {
|
||||
val initMethod = getObjCInitMethod(descriptor)
|
||||
|
||||
if (initMethod != null) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
assert(expression.extensionReceiver == null)
|
||||
assert(expression.dispatchReceiver == null)
|
||||
|
||||
builder.at(expression)
|
||||
|
||||
return builder.genLoweredObjCMethodCall(
|
||||
initMethod.getExternalObjCMethodInfo()!!,
|
||||
superQualifier = null,
|
||||
receiver = builder.callAlloc(symbolTable.referenceClass(descriptor.constructedClass)),
|
||||
arguments = arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
descriptor.getExternalObjCMethodInfo()?.let { methodInfo ->
|
||||
val isInteropStubsFile =
|
||||
currentFile.fileAnnotations.any { it.fqName == FqName("kotlinx.cinterop.InteropStubs") }
|
||||
|
||||
// Special case: bridge from Objective-C method implementation template to Kotlin method;
|
||||
// handled in CodeGeneratorVisitor.callVirtual.
|
||||
val useKotlinDispatch = isInteropStubsFile &&
|
||||
builder.scope.scopeOwner.annotations.hasAnnotation(FqName("konan.internal.ExportForCppRuntime"))
|
||||
|
||||
if (!useKotlinDispatch) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
assert(expression.extensionReceiver == null)
|
||||
|
||||
if (expression.superQualifier?.isObjCMetaClass() == true) {
|
||||
context.reportCompilationError(
|
||||
"Super calls to Objective-C meta classes are not supported yet",
|
||||
currentFile, expression
|
||||
)
|
||||
}
|
||||
|
||||
if (expression.superQualifier?.isInterface == true) {
|
||||
context.reportCompilationError(
|
||||
"Super calls to Objective-C protocols are not allowed",
|
||||
currentFile, expression
|
||||
)
|
||||
}
|
||||
|
||||
builder.at(expression)
|
||||
return builder.genLoweredObjCMethodCall(
|
||||
methodInfo,
|
||||
superQualifier = expression.superQualifierSymbol,
|
||||
receiver = expression.dispatchReceiver!!,
|
||||
arguments = arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return when (descriptor) {
|
||||
context.interopBuiltIns.typeOf -> {
|
||||
val typeArgument = expression.getSingleTypeArgument()
|
||||
val classDescriptor = TypeUtils.getClassDescriptor(typeArgument)
|
||||
@@ -65,8 +307,8 @@ internal class InteropLoweringPart1(val context: Context) : IrElementTransformer
|
||||
error("native variable class $classDescriptor must have the companion object")
|
||||
|
||||
IrGetObjectValueImpl(
|
||||
expression.startOffset, expression.endOffset,
|
||||
companionObjectDescriptor.defaultType, companionObjectDescriptor
|
||||
expression.startOffset, expression.endOffset, companionObjectDescriptor.defaultType,
|
||||
symbolTable.referenceClass(companionObjectDescriptor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1995,6 +1995,13 @@ kotlinNativeInterop {
|
||||
linkerOpts '-framework', 'OpenGL', '-framework', 'GLUT'
|
||||
flavor 'native'
|
||||
}
|
||||
|
||||
objcSmoke {
|
||||
defFile 'interop/objc/objcSmoke.def'
|
||||
headers "$projectDir/interop/objc/smoke.h"
|
||||
linkerOpts "-L$buildDir", "-lobjcsmoke"
|
||||
flavor 'native'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2057,4 +2064,19 @@ if (isMac()) {
|
||||
source = "interop/basics/opengl_teapot.kt"
|
||||
interop = 'opengl'
|
||||
}
|
||||
|
||||
task interop_objc_smoke(type: RunInteropKonanTest) {
|
||||
goldValue = "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\nDeallocated\nDeallocated\n"
|
||||
|
||||
source = "interop/objc/smoke.kt"
|
||||
interop = 'objcSmoke'
|
||||
|
||||
doFirst {
|
||||
execClang {
|
||||
args "$projectDir/interop/objc/smoke.m"
|
||||
args "-lobjc", '-fobjc-arc'
|
||||
args '-fPIC', '-shared', '-o', "$buildDir/libobjcsmoke.dylib"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/smoke.h
|
||||
@@ -0,0 +1,24 @@
|
||||
#import <objc/NSObject.h>
|
||||
|
||||
@protocol Printer
|
||||
@required
|
||||
-(void)print:(const char*)string;
|
||||
@end;
|
||||
|
||||
@interface Foo : NSObject
|
||||
@property NSString* name;
|
||||
-(void)hello;
|
||||
-(void)helloWithPrinter:(id <Printer>)printer;
|
||||
@end;
|
||||
|
||||
@protocol MutablePair
|
||||
@required
|
||||
@property (readonly) int first;
|
||||
@property (readonly) int second;
|
||||
|
||||
-(void)update:(int)index add:(int)delta;
|
||||
-(void)update:(int)index sub:(int)delta;
|
||||
|
||||
@end;
|
||||
|
||||
void replacePairElements(id <MutablePair> pair, int first, int second);
|
||||
@@ -0,0 +1,55 @@
|
||||
import kotlinx.cinterop.*
|
||||
import objcSmoke.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
autoreleasepool {
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
val foo = Foo()
|
||||
foo.hello()
|
||||
foo.name = "everybody"
|
||||
foo.helloWithPrinter(object : NSObject(), PrinterProtocol {
|
||||
override fun print(string: CPointer<ByteVar>?) {
|
||||
println("Kotlin says: " + string?.toKString())
|
||||
}
|
||||
})
|
||||
|
||||
Bar().hello()
|
||||
|
||||
val pair = MutablePairImpl(42, 17)
|
||||
replacePairElements(pair, 1, 2)
|
||||
pair.swap()
|
||||
println("${pair.first}, ${pair.second}")
|
||||
}
|
||||
|
||||
fun MutablePairProtocol.swap() {
|
||||
update(0, add = second)
|
||||
update(1, sub = first)
|
||||
update(0, add = second)
|
||||
update(1, sub = second*2)
|
||||
}
|
||||
|
||||
class Bar : Foo() {
|
||||
override fun helloWithPrinter(printer: PrinterProtocol) = memScoped {
|
||||
printer.print("Hello from Kotlin".cstr.getPointer(memScope))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CONFLICTING_OVERLOADS")
|
||||
class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol {
|
||||
private var elements = intArrayOf(first, second)
|
||||
|
||||
override fun first() = elements.first()
|
||||
override fun second() = elements.last()
|
||||
|
||||
override fun update(index: Int, add: Int) {
|
||||
elements[index] += add
|
||||
}
|
||||
|
||||
override fun update(index: Int, sub: Int) {
|
||||
elements[index] -= sub
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#import <stdio.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import "smoke.h"
|
||||
|
||||
@interface CPrinter : NSObject <Printer>
|
||||
-(void)print:(const char*)string;
|
||||
@end;
|
||||
|
||||
@implementation CPrinter
|
||||
-(void)print:(const char*)string {
|
||||
printf("%s\n", string);
|
||||
fflush(stdout);
|
||||
}
|
||||
@end;
|
||||
|
||||
@implementation Foo
|
||||
|
||||
@synthesize name;
|
||||
|
||||
-(instancetype)init {
|
||||
if (self = [super init]) {
|
||||
self.name = @"World";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)hello {
|
||||
CPrinter* printer = [[CPrinter alloc] init];
|
||||
[self helloWithPrinter:printer];
|
||||
}
|
||||
|
||||
-(void)helloWithPrinter:(id <Printer>)printer {
|
||||
NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name];
|
||||
[printer print:message.UTF8String];
|
||||
}
|
||||
|
||||
-(void)dealloc {
|
||||
printf("Deallocated\n");
|
||||
}
|
||||
|
||||
@end;
|
||||
|
||||
void replacePairElements(id <MutablePair> pair, int first, int second) {
|
||||
[pair update:0 add:(first - pair.first)];
|
||||
[pair update:1 sub:(pair.second - second)];
|
||||
}
|
||||
@@ -124,7 +124,10 @@ class CompileCppToBitcode extends DefaultTask {
|
||||
args "-I$headersDir"
|
||||
|
||||
args '-c', '-emit-llvm'
|
||||
args project.fileTree(srcDir).include('**/*.cpp')
|
||||
args project.fileTree(srcDir) {
|
||||
include('**/*.cpp')
|
||||
include('**/*.mm') // Objective-C++
|
||||
}
|
||||
}
|
||||
|
||||
project.exec {
|
||||
|
||||
@@ -350,6 +350,31 @@ ContainerHeader* AllocContainer(size_t size) {
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
void objc_release(void* ptr);
|
||||
}
|
||||
|
||||
inline void runDeallocationHooks(ObjHeader* obj) {
|
||||
#if KONAN_OBJC_INTEROP
|
||||
if (obj->type_info() == theObjCPointerHolderTypeInfo) {
|
||||
void* objcPtr = *reinterpret_cast<void**>(obj + 1); // TODO: use more reliable layout description
|
||||
objc_release(objcPtr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void DeinitInstanceBodyImpl(const TypeInfo* typeInfo, void* body) {
|
||||
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
|
||||
ObjHeader** location = reinterpret_cast<ObjHeader**>(
|
||||
reinterpret_cast<uintptr_t>(body) + typeInfo->objOffsets_[index]);
|
||||
UpdateRef(location, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DeinitInstanceBody(const TypeInfo* typeInfo, void* body) {
|
||||
DeinitInstanceBodyImpl(typeInfo, body);
|
||||
}
|
||||
|
||||
void FreeContainer(ContainerHeader* header) {
|
||||
RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed");
|
||||
#if TRACE_MEMORY
|
||||
@@ -366,13 +391,12 @@ void FreeContainer(ContainerHeader* header) {
|
||||
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
|
||||
|
||||
for (int index = 0; index < header->objectCount_; index++) {
|
||||
runDeallocationHooks(obj);
|
||||
|
||||
const TypeInfo* typeInfo = obj->type_info();
|
||||
|
||||
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
|
||||
ObjHeader** location = reinterpret_cast<ObjHeader**>(
|
||||
reinterpret_cast<uintptr_t>(obj + 1) + typeInfo->objOffsets_[index]);
|
||||
UpdateRef(location, nullptr);
|
||||
}
|
||||
DeinitInstanceBodyImpl(typeInfo, reinterpret_cast<void*>(obj + 1));
|
||||
|
||||
// Object arrays are *special*.
|
||||
if (typeInfo == theArrayTypeInfo) {
|
||||
ArrayHeader* array = obj->array();
|
||||
@@ -399,6 +423,15 @@ void FreeContainerNoRef(ContainerHeader* header) {
|
||||
#if USE_GC
|
||||
removeFreeable(memoryState, header);
|
||||
#endif
|
||||
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
|
||||
|
||||
for (int index = 0; index < header->objectCount_; index++) {
|
||||
runDeallocationHooks(obj);
|
||||
|
||||
obj = reinterpret_cast<ObjHeader*>(
|
||||
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
|
||||
}
|
||||
|
||||
memoryState->allocCount--;
|
||||
konanFreeMemory(header);
|
||||
}
|
||||
|
||||
@@ -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*));
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#if KONAN_OBJC_INTEROP
|
||||
|
||||
#include <objc/objc.h>
|
||||
#include <objc/runtime.h>
|
||||
#include <objc/message.h>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include "Memory.h"
|
||||
#include "Natives.h"
|
||||
#include "Utils.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct KotlinClassData {
|
||||
const TypeInfo* typeInfo;
|
||||
int32_t bodyOffset;
|
||||
};
|
||||
|
||||
static inline struct KotlinClassData* GetKotlinClassData(Class clazz) {
|
||||
void* ivars = object_getIndexedIvars(reinterpret_cast<id>(clazz));
|
||||
return static_cast<struct KotlinClassData*>(ivars);
|
||||
}
|
||||
|
||||
static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) {
|
||||
GetKotlinClassData(clazz)->typeInfo = typeInfo;
|
||||
}
|
||||
|
||||
const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) {
|
||||
void* objcPtr = *reinterpret_cast<void * const *>(obj + 1); // TODO: use more reliable layout description
|
||||
Class clazz = object_getClass(reinterpret_cast<id>(objcPtr));
|
||||
return GetKotlinClassData(clazz)->typeInfo;
|
||||
}
|
||||
|
||||
id objc_msgSendSuper2(struct objc_super *super, SEL op, ...);
|
||||
|
||||
static void DeallocImp(id self, SEL _cmd) {
|
||||
// TODO: doesn't support overriding Kotlin classes.
|
||||
Class clazz = object_getClass(self);
|
||||
RuntimeAssert(clazz != nullptr, "Must not be null");
|
||||
|
||||
struct KotlinClassData* classData = GetKotlinClassData(clazz);
|
||||
void* body = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(self) + classData->bodyOffset);
|
||||
|
||||
const TypeInfo* typeInfo = classData->typeInfo;
|
||||
DeinitInstanceBody(typeInfo, body);
|
||||
|
||||
// Call super.dealloc:
|
||||
struct objc_super s = {self, clazz};
|
||||
auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2);
|
||||
messenger(&s, _cmd);
|
||||
}
|
||||
|
||||
static void AddDeallocMethod(Class clazz) {
|
||||
Class nsObjectClass = objc_getClass("NSObject");
|
||||
RuntimeAssert(nsObjectClass != nullptr, "NSObject class not found");
|
||||
|
||||
SEL deallocSelector = sel_registerName("dealloc");
|
||||
Method nsObjectDeallocMethod = class_getInstanceMethod(nsObjectClass, deallocSelector);
|
||||
RuntimeAssert(nsObjectDeallocMethod != nullptr, "[NSObject dealloc] method not found");
|
||||
|
||||
const char* nsObjectDeallocMethodTypeEncoding = method_getTypeEncoding(nsObjectDeallocMethod);
|
||||
RuntimeAssert(nsObjectDeallocMethodTypeEncoding != nullptr, "[NSObject dealloc] method has no encoding provided");
|
||||
|
||||
// TODO: something of the above can be cached.
|
||||
|
||||
BOOL added = class_addMethod(clazz, deallocSelector, (IMP)DeallocImp, nsObjectDeallocMethodTypeEncoding);
|
||||
RuntimeAssert(added, "Unable to add dealloc method to Objective-C class");
|
||||
}
|
||||
|
||||
struct ObjCMethodDescription {
|
||||
const char* selector;
|
||||
const char* encoding;
|
||||
const void* imp;
|
||||
};
|
||||
|
||||
struct KotlinObjCClassInfo {
|
||||
const char* name;
|
||||
|
||||
const char* superclassName;
|
||||
const char** protocolNames;
|
||||
|
||||
const struct ObjCMethodDescription* instanceMethods;
|
||||
int32_t instanceMethodsNum;
|
||||
|
||||
const struct ObjCMethodDescription* classMethods;
|
||||
int32_t classMethodsNum;
|
||||
|
||||
int32_t bodySize;
|
||||
int32_t* bodyOffset;
|
||||
|
||||
const TypeInfo* typeInfo;
|
||||
const TypeInfo* metaTypeInfo;
|
||||
|
||||
void** createdClass;
|
||||
};
|
||||
|
||||
static void AddMethods(Class clazz, const struct ObjCMethodDescription* methods, int32_t methodsNum) {
|
||||
for (int32_t i = 0; i < methodsNum; ++i) {
|
||||
const struct ObjCMethodDescription* method = &methods[i];
|
||||
BOOL added = class_addMethod(clazz, sel_registerName(method->selector), (IMP)method->imp, method->encoding);
|
||||
RuntimeAssert(added == YES, "Unable to add method to Objective-C class");
|
||||
}
|
||||
}
|
||||
|
||||
static SimpleMutex classCreationMutex;
|
||||
static int anonymousClassNextId = 0;
|
||||
|
||||
void* CreateKotlinObjCClass(const KotlinObjCClassInfo* info) {
|
||||
LockGuard<SimpleMutex> lockGuard(classCreationMutex);
|
||||
|
||||
void* createdClass = *info->createdClass;
|
||||
if (createdClass != nullptr) {
|
||||
return createdClass;
|
||||
}
|
||||
|
||||
char classNameBuffer[64];
|
||||
const char* className = info->name;
|
||||
if (className == nullptr) {
|
||||
snprintf(classNameBuffer, sizeof(classNameBuffer), "kobjc%d", anonymousClassNextId++);
|
||||
className = classNameBuffer;
|
||||
}
|
||||
|
||||
Class superclass = objc_getClass(info->superclassName);
|
||||
Class newClass = objc_allocateClassPair(superclass, className, sizeof(struct KotlinClassData));
|
||||
RuntimeAssert(newClass != nullptr, "Failed to allocate Objective-C class");
|
||||
|
||||
Class newMetaclass = object_getClass(reinterpret_cast<id>(newClass));
|
||||
|
||||
for (size_t i = 0;; ++i) {
|
||||
const char* protocolName = info->protocolNames[i];
|
||||
if (protocolName == nullptr) break;
|
||||
Protocol* proto = objc_getProtocol(protocolName);
|
||||
if (proto != nullptr) {
|
||||
BOOL added = class_addProtocol(newClass, proto);
|
||||
RuntimeAssert(added == YES, "Unable to add protocol to Objective-C class");
|
||||
added = class_addProtocol(newMetaclass, proto);
|
||||
RuntimeAssert(added == YES, "Unable to add protocol to Objective-C metaclass");
|
||||
}
|
||||
}
|
||||
|
||||
AddDeallocMethod(newClass);
|
||||
|
||||
AddMethods(newClass, info->instanceMethods, info->instanceMethodsNum);
|
||||
AddMethods(newMetaclass, info->classMethods, info->classMethodsNum);
|
||||
|
||||
SetKotlinTypeInfo(newClass, info->typeInfo);
|
||||
SetKotlinTypeInfo(newMetaclass, info->metaTypeInfo);
|
||||
|
||||
char bodyTypeEncoding[16];
|
||||
snprintf(bodyTypeEncoding, sizeof(bodyTypeEncoding), "[%dc]", info->bodySize);
|
||||
BOOL added = class_addIvar(newClass, "kotlinBody", info->bodySize, /* log2(align) = */ 3, bodyTypeEncoding);
|
||||
RuntimeAssert(added == YES, "Unable to add ivar to Objective-C class");
|
||||
|
||||
objc_registerClassPair(newClass);
|
||||
|
||||
Ivar body = class_getInstanceVariable(newClass, "kotlinBody");
|
||||
RuntimeAssert(body != nullptr, "Unable to get ivar added to Objective-C class");
|
||||
int32_t offset = (int32_t)ivar_getOffset(body);
|
||||
GetKotlinClassData(newClass)->bodyOffset = offset;
|
||||
*info->bodyOffset = offset;
|
||||
|
||||
*info->createdClass = newClass;
|
||||
return newClass;
|
||||
}
|
||||
|
||||
void* objc_autoreleasePoolPush();
|
||||
void objc_autoreleasePoolPop(void* ptr);
|
||||
id objc_allocWithZone(Class clazz);
|
||||
id objc_retain(id ptr);
|
||||
void objc_release(id ptr);
|
||||
|
||||
void* Kotlin_objc_autoreleasePoolPush() {
|
||||
return objc_autoreleasePoolPush();
|
||||
}
|
||||
|
||||
void Kotlin_objc_autoreleasePoolPop(void* ptr) {
|
||||
objc_autoreleasePoolPop(ptr);
|
||||
}
|
||||
|
||||
id Kotlin_objc_allocWithZone(Class clazz) {
|
||||
return objc_allocWithZone(clazz);
|
||||
}
|
||||
|
||||
id Kotlin_objc_retain(id ptr) {
|
||||
return objc_retain(ptr);
|
||||
}
|
||||
|
||||
void Kotlin_objc_release(id ptr) {
|
||||
objc_release(ptr);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#else // KONAN_OBJC_INTEROP
|
||||
|
||||
#include "Assert.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void* Kotlin_objc_autoreleasePoolPush() {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Kotlin_objc_autoreleasePoolPop(void* ptr) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
}
|
||||
|
||||
void* Kotlin_objc_allocWithZone(void* clazz) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* Kotlin_objc_retain(void* ptr) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Kotlin_objc_release(void* ptr) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // KONAN_OBJC_INTEROP
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Natives.h"
|
||||
|
||||
#if KONAN_OBJC_INTEROP
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
namespace {
|
||||
Class nsStringClass = nullptr;
|
||||
|
||||
Class getNSStringClass() {
|
||||
Class result = nsStringClass;
|
||||
if (result == nullptr) {
|
||||
// Lookup dynamically to avoid direct reference to Foundation:
|
||||
result = objc_getClass("NSString");
|
||||
RuntimeAssert(result != nullptr, "NSString class not found");
|
||||
nsStringClass = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
NSString* CreateNSStringFromKString(const ArrayHeader* str) {
|
||||
if (str == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const KChar* utf16Chars = CharArrayAddressOfElementAt(str, 0);
|
||||
|
||||
NSString* result = [[[getNSStringClass() alloc] initWithBytes:utf16Chars
|
||||
length:str->count_*sizeof(KChar)
|
||||
encoding:NSUTF16LittleEndianStringEncoding] autorelease];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
OBJ_GETTER(CreateKStringFromNSString, NSString* str) {
|
||||
if (str == nullptr) {
|
||||
RETURN_OBJ(nullptr);
|
||||
}
|
||||
|
||||
size_t length = [str length];
|
||||
NSRange range = {0, length};
|
||||
ArrayHeader* result = AllocArrayInstance(theStringTypeInfo, length, OBJ_RESULT)->array();
|
||||
KChar* rawResult = CharArrayAddressOfElementAt(result, 0);
|
||||
|
||||
[str getCharacters:rawResult range:range];
|
||||
|
||||
RETURN_OBJ(result->obj());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#else // KONAN_OBJC_INTEROP
|
||||
|
||||
extern "C" {
|
||||
|
||||
void* CreateNSStringFromKString(const ArrayHeader* str) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OBJ_GETTER(CreateKStringFromNSString, void* str) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
RETURN_OBJ(nullptr);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // KONAN_OBJC_INTEROP
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include "Assert.h"
|
||||
|
||||
class SimpleMutex {
|
||||
private:
|
||||
int32_t atomicInt = 0;
|
||||
|
||||
public:
|
||||
void lock() {
|
||||
while (!__sync_bool_compare_and_swap(&atomicInt, 0, 1)) {
|
||||
// TODO: yield.
|
||||
}
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
if (!__sync_bool_compare_and_swap(&atomicInt, 1, 0)) {
|
||||
RuntimeAssert(false, "Unable to unlock");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: use std::lock_guard instead?
|
||||
template <class Mutex>
|
||||
class LockGuard {
|
||||
public:
|
||||
explicit LockGuard(Mutex& mutex_) : mutex(mutex_) {
|
||||
mutex.lock();
|
||||
}
|
||||
|
||||
~LockGuard() {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
Mutex& mutex;
|
||||
|
||||
LockGuard(const LockGuard&) = delete;
|
||||
LockGuard& operator=(const LockGuard&) = delete;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
apply plugin: 'konan'
|
||||
|
||||
konanInterop {
|
||||
objc {
|
||||
target "macbook"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
konanArtifacts {
|
||||
Window {
|
||||
useInterop 'objc'
|
||||
target "macbook"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,59 @@
|
||||
import objc.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
autoreleasepool {
|
||||
runApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runApp() {
|
||||
val app = NSApplication.sharedApplication()
|
||||
|
||||
app.delegate = MyAppDelegate()
|
||||
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
|
||||
app.activateIgnoringOtherApps(true)
|
||||
|
||||
app.run()
|
||||
}
|
||||
|
||||
private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
|
||||
private val window: NSWindow
|
||||
|
||||
init {
|
||||
val mainDisplayRect = NSScreen.mainScreen()!!.frame
|
||||
val windowRect = mainDisplayRect.useContents {
|
||||
NSMakeRect(
|
||||
origin.x + size.width * 0.25,
|
||||
origin.y + size.height * 0.25,
|
||||
size.width * 0.5,
|
||||
size.height * 0.5
|
||||
)
|
||||
}
|
||||
|
||||
val windowStyle = NSWindowStyleMaskTitled or NSWindowStyleMaskMiniaturizable or
|
||||
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
|
||||
|
||||
window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
|
||||
title = "Окошко Konan"
|
||||
opaque = true
|
||||
hasShadow = true
|
||||
preferredBackingLocation = NSWindowBackingLocationVideoMemory
|
||||
hidesOnDeactivate = false
|
||||
backgroundColor = NSColor.whiteColor()
|
||||
releasedWhenClosed = false
|
||||
|
||||
delegate = object : NSObject(), NSWindowDelegateProtocol {
|
||||
override fun windowShouldClose(sender: ObjCObject): Boolean {
|
||||
NSApplication.sharedApplication().stop(this)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun applicationWillFinishLaunching(notification: NSNotification) {
|
||||
window.makeKeyAndOrderFront(this)
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,5 @@ include ':concurrent'
|
||||
// So temporary switching off for now, as it breaks the build
|
||||
// of other samples if SDK is not present.
|
||||
//include ':androidNativeActivity'
|
||||
include ':objc'
|
||||
includeBuild '../'
|
||||
|
||||
@@ -43,13 +43,16 @@ class ClangTarget(val target: KonanTarget, konanProperties: KonanProperties) {
|
||||
"-DUSE_GCC_UNWIND=1", "-DUSE_PE_COFF_SYMBOLS=1", "-DKONAN_WINDOWS=1")
|
||||
|
||||
KonanTarget.MACBOOK ->
|
||||
listOf("--sysroot=$sysRoot", "-mmacosx-version-min=10.11", "-DKONAN_OSX=1")
|
||||
listOf("--sysroot=$sysRoot", "-mmacosx-version-min=10.11", "-DKONAN_OSX=1",
|
||||
"-DKONAN_OBJC_INTEROP=1")
|
||||
|
||||
KonanTarget.IPHONE ->
|
||||
listOf("-stdlib=libc++", "-arch", "arm64", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0")
|
||||
listOf("-stdlib=libc++", "-arch", "arm64", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0",
|
||||
"-DKONAN_OBJC_INTEROP=1")
|
||||
|
||||
KonanTarget.IPHONE_SIM ->
|
||||
listOf("-stdlib=libc++", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0")
|
||||
listOf("-stdlib=libc++", "-isysroot", "$sysRoot", "-miphoneos-version-min=8.0.0",
|
||||
"-DKONAN_OBJC_INTEROP=1")
|
||||
|
||||
KonanTarget.ANDROID_ARM32 ->
|
||||
listOf("-target", targetArg!!,
|
||||
|
||||
Reference in New Issue
Block a user