From 77897ba712f3b5af02efc1e72288251202001f1a Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 19 Nov 2018 17:04:50 +0300 Subject: [PATCH] Make Objective-C objects shareable in Kotlin --- .../kotlin/kotlinx/cinterop/ObjectiveCImpl.kt | 1 + .../descriptors/LegacyDescriptorUtils.kt | 6 +++-- .../backend/konan/llvm/RTTIGenerator.kt | 9 +++++--- .../objcexport/ObjCExportCodeGenerator.kt | 3 ++- .../tests/framework/values/values.kt | 4 ++++ .../tests/framework/values/values.swift | 22 +++++++++++++++++++ runtime/src/main/cpp/ObjCExport.mm | 7 +++++- 7 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index ff9841a7099..57afe9c726b 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -26,6 +26,7 @@ interface ObjCClassOf : ObjCClass // TODO: T should be added to typealias ObjCObjectMeta = ObjCClass @ExportTypeInfo("theForeignObjCObjectTypeInfo") +@kotlin.native.internal.Frozen internal open class ForeignObjCObject : kotlin.native.internal.ObjCObjectWrapper abstract class ObjCObjectBase protected constructor() : ObjCObject { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt index af9ba7cb74d..cf9c0778d89 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/LegacyDescriptorUtils.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.descriptors import org.jetbrains.kotlin.backend.common.atMostOne import org.jetbrains.kotlin.backend.konan.binaryTypeIsReference +import org.jetbrains.kotlin.backend.konan.isObjCClass import org.jetbrains.kotlin.backend.konan.serialization.isExported import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor import org.jetbrains.kotlin.builtins.getFunctionalClassKind @@ -241,8 +242,9 @@ private val frozenAnnotation = FqName("kotlin.native.internal.Frozen") internal val DeclarationDescriptor.isFrozen: Boolean get() = this.annotations.hasAnnotation(frozenAnnotation) || - // RTTI is used for non-reference type box: - this is org.jetbrains.kotlin.descriptors.ClassDescriptor && !this.defaultType.binaryTypeIsReference() + (this is org.jetbrains.kotlin.descriptors.ClassDescriptor + // RTTI is used for non-reference type box or Objective-C object wrapper: + && (!this.defaultType.binaryTypeIsReference() || this.isObjCClass())) internal val FunctionDescriptor.isIntrinsic: Boolean get() = this.annotations.hasAnnotation(intrinsicAnnotation) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 00eea3d7ca0..5db5250d77a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -19,7 +19,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { private fun flagsFromClass(classDescriptor: ClassDescriptor): Int { var result = 0 if (classDescriptor.isFrozen) - result = result or 1 /* TF_IMMUTABLE */ + result = result or TF_IMMUTABLE return result } @@ -291,7 +291,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { // TODO: extract more code common with generate(). fun generateSyntheticInterfaceImpl( descriptor: ClassDescriptor, - methodImpls: Map + methodImpls: Map, + immutable: Boolean = false ): ConstPointer { assert(descriptor.isInterface) @@ -346,7 +347,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { fields = fieldsPtr, fieldsCount = fieldsCount, packageName = reflectionInfo.packageName, relativeName = reflectionInfo.relativeName, - flags = flagsFromClass(descriptor), + flags = flagsFromClass(descriptor) or (if (immutable) TF_IMMUTABLE else 0), extendedInfo = NullPointer(runtime.extendedTypeInfoType), writableTypeInfo = writableTypeInfo ), vtable) @@ -376,3 +377,5 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { } } } + +private const val TF_IMMUTABLE = 1 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 34aa31c5219..4c949feb776 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -403,7 +403,8 @@ private fun ObjCExportCodeGenerator.generateKotlinFunctionAdapterToBlock(numberO return rttiGenerator.generateSyntheticInterfaceImpl( irInterface, - mapOf(invokeMethod to invokeImpl) + mapOf(invokeMethod to invokeImpl), + immutable = true ) } diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index f3cd7e72a15..cf28bcd85fa 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -8,6 +8,7 @@ package conversions +import kotlin.native.concurrent.isFrozen import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -236,3 +237,6 @@ fun IC2?.getValueOrNull2() = this?.value fun IC3.getValue3() = value fun IC3?.getValueOrNull3() = this?.value + +fun isFrozen(obj: Any): Boolean = obj.isFrozen +fun kotlinLambda(block: (Any) -> Any): Any = block diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index 6938cdcc4ed..f0d34742546 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -420,6 +420,27 @@ func testInlineClasses() throws { try assertEquals(actual: ValuesKt.getValueOrNull3(ic3N), expected: nil) } +class TestSharedIImpl : NSObject, I { + func iFun() -> String { + return "TestSharedIImpl::iFun" + } +} + +func testShared() throws { + func assertFrozen(_ obj: AnyObject) throws { + try assertTrue(ValuesKt.isFrozen(obj: obj), "isFrozen(\(obj))") + } + + func assertNotFrozen(_ obj: AnyObject) throws { + try assertFalse(ValuesKt.isFrozen(obj: obj), "isFrozen(\(obj))") + } + + try assertFrozen(NSObject()) + try assertFrozen(TestSharedIImpl()) + try assertFrozen(ValuesKt.kotlinLambda(block: { return $0 }) as AnyObject) + try assertNotFrozen(FinalClassExtOpen()) +} + // -------- Execution of the test -------- class ValuesTests : TestProvider { @@ -453,6 +474,7 @@ class ValuesTests : TestProvider { TestCase(name: "TestDataClass", method: withAutorelease(testDataClass)), TestCase(name: "TestCompanionObj", method: withAutorelease(testCompanionObj)), TestCase(name: "TestInlineClasses", method: withAutorelease(testInlineClasses)), + TestCase(name: "TestShared", method: withAutorelease(testShared)), ] } } diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 45f7821b9e6..90a6d319d36 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -669,11 +669,16 @@ static const TypeInfo* createTypeInfo( TypeInfo* result = (TypeInfo*)konanAllocMemory(sizeof(TypeInfo) + vtable.size() * sizeof(void*)); result->typeInfo_ = result; + result->flags_ = 0; + MakeGlobalHash(nullptr, 0, &result->name_); result->instanceSize_ = superType->instanceSize_; result->superType_ = superType; result->objOffsets_ = superType->objOffsets_; - result->objOffsetsCount_ = superType->objOffsetsCount_; + result->objOffsetsCount_ = superType->objOffsetsCount_; // So TF_IMMUTABLE can also be inherited: + if ((superType->flags_ & TF_IMMUTABLE) != 0) { + result->flags_ |= TF_IMMUTABLE; + } KStdVector implementedInterfaces( superType->implementedInterfaces_, superType->implementedInterfaces_ + superType->implementedInterfacesCount_