diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 15c080fe1a8..6aa8d93ccbc 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -22,44 +22,14 @@ interface ObjCObject interface ObjCClass : ObjCObject typealias ObjCObjectMeta = ObjCClass -abstract class ObjCObjectBase protected constructor() : ObjCObject { - final override fun equals(other: Any?): Boolean { - val thisAny: Any = this - return thisAny.equals(other) // Call it virtually because ObjCObjectBase is a fake type. - } - final override fun hashCode(): Int = ObjCHashCode(this.rawPtr()) - final override fun toString(): String = ObjCToString(this.rawPtr()) -} +@ExportTypeInfo("theForeignObjCObjectTypeInfo") +internal open class ForeignObjCObject +abstract class ObjCObjectBase protected constructor() : ObjCObject abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {} fun optional(): Nothing = throw RuntimeException("Do not call me!!!") -/** - * 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 = - if (other is ObjCPointerHolder) { - ObjCEquals(this.rawPtr, other.rawPtr) - } else { - other == this - } - - final override fun hashCode(): Int = ObjCHashCode(this.rawPtr) - final override fun toString(): String = ObjCToString(this.rawPtr) -} - -@konan.internal.Intrinsic -@konan.internal.ExportForCompiler -private external fun ObjCObject.initFromPtr(ptr: NativePtr) - @konan.internal.Intrinsic external fun T.initBy(constructorCall: T): T @@ -72,19 +42,13 @@ private fun ObjCObjectBase.superInitCheck(superInitCallResult: ObjCObject?) { throw UnsupportedOperationException("Super initializer has replaced object") } +@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING) fun Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) // TODO: make private -inline fun interpretObjCPointerOrNull(rawPtr: NativePtr): T? = if (rawPtr != nativeNullPtr) { - ObjCPointerHolder(rawPtr).uncheckedCast() -} else { - null -} +@SymbolName("Kotlin_Interop_refFromObjC") +external fun interpretObjCPointerOrNull(rawPtr: NativePtr): T? -inline fun interpretObjCPointer(rawPtr: NativePtr): T = if (rawPtr != nativeNullPtr) { - ObjCPointerHolder(rawPtr).uncheckedCast() -} else { - throw NullPointerException() -} +inline fun interpretObjCPointer(rawPtr: NativePtr): T = interpretObjCPointerOrNull(rawPtr)!! @SymbolName("Kotlin_Interop_refToObjC") external fun ObjCObject?.rawPtr(): NativePtr @@ -119,7 +83,7 @@ typealias ObjCBlockVar = ObjCNotImplementedVar @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) -annotation class ExternalObjCClass() +annotation class ExternalObjCClass(val protocolGetter: String = "") @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.BINARY) @@ -155,18 +119,15 @@ private fun getObjCClassByName(name: NativePtr): NativePtr { } @konan.internal.ExportForCompiler -private fun allocObjCObject(clazz: NativePtr): T { +private fun allocObjCObject(clazz: NativePtr): NativePtr { val rawResult = objc_allocWithZone(clazz) if (rawResult == nativeNullPtr) { throw OutOfMemoryError("Unable to allocate Objective-C object") } - val result = interpretObjCPointerOrNull(rawResult)!! - // `objc_allocWithZone` returns retained pointer. Balance it: - objc_release(rawResult) - // TODO: do not retain this pointer in `interpretObjCPointerOrNull` instead. + // Note: `objc_allocWithZone` returns retained pointer, and thus it must be balanced by the caller. - return result + return rawResult } @konan.internal.Intrinsic diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt index cae9babcfbf..fac6866b526 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt @@ -25,12 +25,14 @@ inline fun autoreleasepool(block: () -> R): R { } } -// FIXME: implement a checked cast instead. -fun ObjCObject.reinterpret() = this.uncheckedCast() +@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING) +fun ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast() // TODO: null checks var ObjCObjectVar.value: T - get() = interpretObjCPointerOrNull(nativeMemUtils.getNativePtr(this)).uncheckedCast() + @Suppress("DEPRECATION") get() = + interpretObjCPointerOrNull(nativeMemUtils.getNativePtr(this)).uncheckedCast() + set(value) = nativeMemUtils.putNativePtr(this, value.rawPtr()) /** diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt index 20d50b041e3..2f858ea29f9 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt @@ -92,8 +92,8 @@ private tailrec fun convertArgument( is CEnum -> convertArgument(argument.value, isVariadic, location, additionalPlacement) - is ObjCPointerHolder -> { - location.reinterpret()[0] = interpretCPointer(argument.rawPtr) + is ForeignObjCObject -> { + location.reinterpret()[0] = interpretCPointer((argument as ObjCObject).rawPtr()) FFI_TYPE_KIND_POINTER } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt index a91f1bc0335..d7efb18ed85 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt @@ -470,8 +470,23 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when else -> TODO(type.toString()) } +internal tailrec fun ObjCClass.isNSStringOrSubclass(): Boolean = when (this.name) { + "NSMutableString", // fast path and handling for forward declarations. + "NSString" -> true + else -> { + val baseClass = this.baseClass + if (baseClass != null) { + baseClass.isNSStringOrSubclass() + } else { + false + } + } +} + +internal fun ObjCClass.isNSStringSubclass(): Boolean = this.baseClass?.isNSStringOrSubclass() == true + private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPointer): TypeMirror.ByValue { - if (type is ObjCObjectPointer && type.def.name == "NSString") { + if (type is ObjCObjectPointer && type.def.isNSStringOrSubclass()) { val info = TypeInfo.NSString(type) return objCMirror(KotlinTypes.string, info, type.isNullable) } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index fe76b0a5a3e..60be4a823eb 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -112,13 +112,13 @@ class ObjCMethodStub(stubGenerator: StubGenerator, if (method.nsConsumesSelf) { // TODO: do this later due to possible exceptions - bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr())") + bodyGenerator.out("objc_retain($kniReceiverParameter)") } - kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.objCObject.type) + kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.nativePtr) nativeBridgeArguments.add( TypedKotlinValue(voidPtr, - "getReceiverOrSuper($kniReceiverParameter.rawPtr(), $kniSuperClassParameter)")) + "getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)")) val kotlinParameterNames = method.getKotlinParameterNames() @@ -289,11 +289,16 @@ private fun ObjCMethod.isOverride(container: ObjCClassOrProtocol): Boolean = abstract class ObjCContainerStub(stubGenerator: StubGenerator, private val container: ObjCClassOrProtocol, - private val isMeta: Boolean) : KotlinStub { + protected val metaContainerStub: ObjCContainerStub? +) : KotlinStub { + + private val isMeta: Boolean get() = metaContainerStub == null private val methods: List private val properties: List + private val protocolGetter: String? + init { val superMethods = container.inheritedMethods(isMeta) @@ -376,7 +381,26 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator, val supersString = supers.joinToString { it.render(stubGenerator.kotlinFile) } val classifier = stubGenerator.declarationMapper.getKotlinClassFor(container, isMeta) val name = stubGenerator.kotlinFile.declare(classifier) - this.classHeader = "@ExternalObjCClass $keywords $name : $supersString" + + val externalObjCClassAnnotationName = "@ExternalObjCClass" + val externalObjCClassAnnotation: String + + if (container is ObjCProtocol) { + protocolGetter = if (metaContainerStub != null) { + metaContainerStub.protocolGetter!! + } else { + val nativeBacked = object : NativeBacked {} + // TODO: handle the case when protocol getter stub can't be compiled. + genProtocolGetter(stubGenerator, nativeBacked, container) + } + + externalObjCClassAnnotation = externalObjCClassAnnotationName.applyToStrings(protocolGetter) + } else { + protocolGetter = null + externalObjCClassAnnotation = externalObjCClassAnnotationName + } + + this.classHeader = "$externalObjCClassAnnotation $keywords $name : $supersString" } open fun generateBody(context: StubGenerationContext): Sequence { @@ -403,13 +427,10 @@ open class ObjCClassOrProtocolStub( ) : ObjCContainerStub( stubGenerator, container, - isMeta = false + metaContainerStub = object : ObjCContainerStub(stubGenerator, container, metaContainerStub = null) {} ) { - private val metaClassStub = - object : ObjCContainerStub(stubGenerator, container, isMeta = true) {} - override fun generate(context: StubGenerationContext) = - metaClassStub.generate(context) + "" + super.generate(context) + metaContainerStub!!.generate(context) + "" + super.generate(context) } class ObjCProtocolStub(stubGenerator: StubGenerator, protocol: ObjCProtocol) : @@ -485,11 +506,11 @@ class ObjCPropertyStub( } val result = mutableListOf( "$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType", - " get() = ${getterStub.bridgeName}(nativeNullPtr, this)" + " get() = ${getterStub.bridgeName}(nativeNullPtr, this.rawPtr())" ) property.setter?.let { - result.add(" set(value) = ${setterStub!!.bridgeName}(nativeNullPtr, this, value)") + result.add(" set(value) = ${setterStub!!.bridgeName}(nativeNullPtr, this.rawPtr(), value)") } return result.asSequence() @@ -585,3 +606,22 @@ private fun NativeCodeBuilder.genMethodImp( return functionName } +private fun genProtocolGetter( + stubGenerator: StubGenerator, + nativeBacked: NativeBacked, + protocol: ObjCProtocol +): String { + val functionName = "kniprot_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId() + + val builder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope) + + with(builder) { + out("Protocol* $functionName() {") + out(" return @protocol(${protocol.name});") + out("}") + } + + stubGenerator.simpleBridgeGenerator.insertNativeBridge(nativeBacked, emptyList(), builder.lines) + + return functionName +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 23067a45455..0fceca95bb4 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -802,12 +802,12 @@ class StubGenerator( } nativeIndex.objCClasses.forEach { - if (!it.isForwardDeclaration) { + if (!it.isForwardDeclaration && !it.isNSStringSubclass()) { stubs.add(ObjCClassStub(this, it)) } } - nativeIndex.objCCategories.mapTo(stubs) { + nativeIndex.objCCategories.filter { !it.clazz.isNSStringSubclass() }.mapTo(stubs) { ObjCCategoryStub(this, it) } @@ -886,6 +886,7 @@ class StubGenerator( add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties. add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions. add("REDUNDANT_NULLABLE") // This warning appears due to Obj-C typedef nullability incomplete support. + add("DEPRECATION") // For uncheckedCast. } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index 33c6d1b4de8..88e083aa1d2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -69,7 +69,7 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val objCNamesProtocols = objCNames.child(Name.identifier("protocols")) } - private val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope + val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope val getPointerSize = packageScope.getContributedFunctions("getPointerSize").single() @@ -161,12 +161,6 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { }.toMap() val objCObject = packageScope.getContributedClass("ObjCObject") - val objCPointerHolder = packageScope.getContributedClass("ObjCPointerHolder") - - val objCPointerHolderValue = objCPointerHolder.unsubstitutedMemberScope - .getContributedDescriptors().filterIsInstance().single() - - val objCObjectInitFromPtr = packageScope.getContributedFunctions("initFromPtr").single() val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt index 86de13698cd..fa88bb450c5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt @@ -25,11 +25,12 @@ import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.typeUtil.supertypes 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")) +internal val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass")) private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod")) private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge")) @@ -38,10 +39,7 @@ fun ClassDescriptor.isObjCClass(): Boolean = this.containingDeclaration.fqNameSafe != interopPackageName fun KotlinType.isObjCObjectType(): Boolean = - TypeUtils.getClassDescriptor(this) - ?.getAllSuperClassifiers() - ?.any { it.fqNameSafe == objCObjectFqName } - ?: false + (this.supertypes() + this).any { TypeUtils.getClassDescriptor(it)?.fqNameSafe == objCObjectFqName } fun ClassDescriptor.isExternalObjCClass(): Boolean = this.isObjCClass() && this.parentsWithSelf.filterIsInstance().any { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt index 5de2d1a3f47..0a72b5555f6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt @@ -27,6 +27,7 @@ internal fun CommonBackendContext.reportCompilationError(message: String, irFile internal fun CommonBackendContext.reportCompilationError(message: String) { report(null, null, message, true) + throw KonanCompilationException() } internal fun CommonBackendContext.reportCompilationWarning(message: String) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 4ad8f2b879d..a1fc80489a5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -108,10 +108,13 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject) - val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass) + val interopObjCRelease = symbolTable.referenceSimpleFunction( + context.interopBuiltIns.packageScope + .getContributedFunctions(Name.identifier("objc_release"), NoLookupLocation.FROM_BACKEND) + .single() + ) - val interopObjCObjectInitFromPtr = - symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitFromPtr) + val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass) val interopObjCObjectSuperInitCheck = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectSuperInitCheck) @@ -132,12 +135,6 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym val interopCreateNSStringFromKString = symbolTable.referenceSimpleFunction(context.interopBuiltIns.CreateNSStringFromKString) - val objCPointerHolder = symbolTable.referenceClass(context.interopBuiltIns.objCPointerHolder) - val objCPointerHolderValueGetter = - symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCPointerHolderValue.getter!!) - - val allocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject) - val objCExportTrapOnUndeclaredException = symbolTable.referenceSimpleFunction(context.builtIns.konanInternal.getContributedFunctions( Name.identifier("trapOnUndeclaredException"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt index fc0027c9b98..7a4c16bec17 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt @@ -47,6 +47,7 @@ internal fun IrFunction.isObjCClassMethod() = this.descriptor.isObjCClassMethod( internal fun IrFunction.canObjCClassMethodBeCalledVirtually(overridden: IrFunction) = this.descriptor.canObjCClassMethodBeCalledVirtually(overridden.descriptor) internal fun IrClass.isObjCClass() = this.descriptor.isObjCClass() +internal fun IrClass.isObjCMetaClass() = this.descriptor.isObjCMetaClass() internal fun IrFunction.isExternalObjCClassMethod() = this.descriptor.isExternalObjCClassMethod() internal val IrDeclaration.llvmSymbolOrigin get() = this.descriptor.llvmSymbolOrigin diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index 7cdb8b57496..e75e2be415b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -24,6 +24,8 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.backend.konan.descriptors.stdlibModule import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.backend.konan.irasdescriptors.* +import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator +import org.jetbrains.kotlin.konan.target.KonanTarget internal class CodeGenerator(override val context: Context) : ContextUtils { @@ -85,18 +87,19 @@ internal class CodeGenerator(override val context: Context) : ContextUtils { } fun typeInfoForAllocation(constructedClass: ClassDescriptor): LLVMValueRef { - val descriptorForTypeInfo = if (constructedClass.isObjCClass()) { - context.ir.symbols.objCPointerHolder.owner - } else { - constructedClass - } - return typeInfoValue(descriptorForTypeInfo) + assert(!constructedClass.isObjCClass()) + return typeInfoValue(constructedClass) } fun generateLocationInfo(locationInfo: LocationInfo): DILocationRef? { return LLVMCreateLocation(LLVMGetModuleContext(context.llvmModule), locationInfo.line, locationInfo.line, locationInfo.scope) } + val objCDataGenerator = when (context.config.target) { + KonanTarget.IOS_ARM64, KonanTarget.IOS_X64, KonanTarget.MACOS_X64 -> ObjCDataGenerator(this) + else -> null + } + } internal sealed class ExceptionHandler { @@ -430,6 +433,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return LLVMBlockAddress(function, bbLabel)!! } + fun not(arg: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildNot(builder, arg, name)!! fun and(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildAnd(builder, arg0, arg1, name)!! fun or(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildOr(builder, arg0, arg1, name)!! fun xor(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildXor(builder, arg0, arg1, name)!! @@ -613,7 +617,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, val owner = descriptor.containingDeclaration as ClassDescriptor - val typeInfoPtr: LLVMValueRef = if (owner.isObjCClass()) { + val typeInfoPtr: LLVMValueRef = if (descriptor.getObjCMethodInfo() != null) { call(context.llvm.getObjCKotlinTypeInfo, listOf(receiver)) } else { val typeInfoOrMetaPtr = structGep(receiver, 0 /* typeInfoOrMeta_ */) @@ -655,6 +659,20 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return codegen.theUnitInstanceRef.llvm } + if (descriptor.isCompanion) { + val parent = descriptor.parent as IrClass + if (parent.isObjCClass()) { + // TODO: cache it too. + + return call( + codegen.llvmFunction(context.ir.symbols.interopInterpretObjCPointer.owner), + listOf(getObjCClass(parent, exceptionHandler)), + Lifetime.GLOBAL, + exceptionHandler + ) + } + } + val objectPtr = codegen.getObjectInstanceStorage(descriptor, shared) val bbCurrent = currentBlock val bbInit= basicBlock("label_init", locationInfo) @@ -708,6 +726,53 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, ) } + // TODO: get rid of exceptionHandler argument by ensuring that all called functions are non-throwing. + fun getObjCClass(irClass: IrClass, exceptionHandler: ExceptionHandler): LLVMValueRef { + assert(!irClass.isInterface) + + return if (irClass.isExternalObjCClass()) { + context.llvm.imports.add(irClass.llvmSymbolOrigin) + + if (irClass.isObjCMetaClass()) { + val name = irClass.name.asString().removeSuffix("Meta") + + val objCClass = load(codegen.objCDataGenerator!!.genClassRef(name).llvm) + + val getClass = context.llvm.externalFunction( + "object_getClass", + functionType(int8TypePtr, false, int8TypePtr), + origin = context.standardLlvmSymbolsOrigin + ) + + call(getClass, listOf(objCClass), exceptionHandler = exceptionHandler) + } else { + load(codegen.objCDataGenerator!!.genClassRef(irClass.name.asString()).llvm) + } + } else { + if (irClass.isObjCMetaClass()) { + error("type-checking against Kotlin classes inheriting Objective-C meta-classes isn't supported yet") + } + + val objCDeclarations = context.llvmDeclarations.forClass(irClass).objCDeclarations!! + val classPointerGlobal = objCDeclarations.classPointerGlobal.llvmGlobal + + val storedClass = this.load(classPointerGlobal) + + val storedClassIsNotNull = this.icmpNe(storedClass, kNullInt8Ptr) + + return this.ifThenElse(storedClassIsNotNull, storedClass) { + val newClass = call( + context.llvm.createKotlinObjCClass, + listOf(objCDeclarations.classInfoGlobal.llvmGlobal), + exceptionHandler = exceptionHandler + ) + + this.store(newClass, classPointerGlobal) + newClass + } + } + } + fun resetDebugLocation() { if (!context.shouldContainDebugInfo()) return currentPositionHolder.resetBuilderDebugLocation() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 9f6edf7832e..dd40e9c6ee7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -417,8 +417,12 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") } val missingInitImp by lazy { importRtFunction("MissingInitImp") } + val Kotlin_Interop_DoesObjectConformToProtocol by lazyRtFunction + val Kotlin_Interop_IsObjectKindOfClass by lazyRtFunction + val Kotlin_ObjCExport_refToObjC by lazyRtFunction val Kotlin_ObjCExport_refFromObjC by lazyRtFunction + val Kotlin_ObjCExport_CreateNSStringFromKString by lazyRtFunction val Kotlin_Interop_CreateNSArrayFromKList by lazyRtFunction val Kotlin_Interop_CreateNSMutableArrayFromKList by lazyRtFunction val Kotlin_Interop_CreateNSSetFromKSet by lazyRtFunction @@ -431,15 +435,6 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val Kotlin_ObjCExport_RethrowExceptionAsNSError by lazyRtFunction val Kotlin_ObjCExport_RethrowNSErrorAsException by lazyRtFunction - val objCExportEnabled = if (context.config.produce.isNativeBinary) { - // Note: this defines the global declared in runtime (if any). - staticData.placeGlobal("objCExportEnabled", Int8(0), isExported = true).also { - it.setConstant(true) - } - } else { - null - } - val tlsMode by lazy { when (target) { KonanTarget.WASM32, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index cd4be13a132..926fd410586 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -340,7 +340,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { - genObjCObjectInitFromPtr(args) - } - interop.getObjCReceiverOrSuper -> { genGetObjCReceiverOrSuper(args) } @@ -2226,36 +2300,24 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map, isLU: Boolean): LLVMValueRef { @@ -2302,11 +2364,6 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map): LLVMValueRef { - return callDirect(context.ir.symbols.objCPointerHolder.owner.constructors.single(), - args, Lifetime.IRRELEVANT) - } - private fun genGetObjCReceiverOrSuper(args: List): LLVMValueRef { val gen = functionGenerationContext diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt index 481c85272db..ee12ef45828 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.* internal open class ObjCCodeGenerator(val codegen: CodeGenerator) { val context = codegen.context - val dataGenerator = ObjCDataGenerator(codegen) + val dataGenerator = codegen.objCDataGenerator!! fun FunctionGenerationContext.genSelector(selector: String): LLVMValueRef { val selectorRef = dataGenerator.genSelectorRef(selector) 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 079aaed92b8..bf511b9a1eb 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 @@ -216,10 +216,6 @@ internal class ObjCExportCodeGenerator( emitSortedAdapters(placedClassAdapters, "Kotlin_ObjCExport_sortedClassAdapters") emitSortedAdapters(placedInterfaceAdapters, "Kotlin_ObjCExport_sortedProtocolAdapters") - - context.llvm.objCExportEnabled!!.setInitializer(Int8(1)) - - dataGenerator.finishModule() // TODO: move to appropriate place. } private val impType = pointerType(functionType(int8TypePtr, true, int8TypePtr, int8TypePtr)) @@ -408,7 +404,7 @@ private fun ObjCExportCodeGenerator.emitKotlinFunctionAdaptersToBlock() { private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() { setObjCExportTypeInfo( context.builtIns.string, - constPointer(codegen.llvmFunction(context.ir.symbols.interopCreateNSStringFromKString.owner)) + constPointer(context.llvm.Kotlin_ObjCExport_CreateNSStringFromKString) ) setObjCExportTypeInfo( @@ -445,11 +441,6 @@ private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() { emitBoxConverter(it) } - setObjCExportTypeInfo( - context.interopBuiltIns.objCPointerHolder, - constPointer(codegen.llvmFunction(context.ir.symbols.objCPointerHolderValueGetter.owner)) - ) - emitFunctionConverters() emitKotlinFunctionAdaptersToBlock() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 0167dac293f..fe6ea96cc9d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -23,9 +23,10 @@ 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.allOverriddenDescriptors import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName +import org.jetbrains.kotlin.backend.konan.irasdescriptors.isReal import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl @@ -80,16 +81,6 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme 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)) } @@ -349,6 +340,29 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme ) } + val methodsOfAny = + context.ir.symbols.any.owner.declarations.filterIsInstance().toSet() + + irClass.declarations.filterIsInstance().filter { it.isReal }.forEach { method -> + val overriddenMethodOfAny = method.allOverriddenDescriptors.firstOrNull { + it in methodsOfAny + } + + if (overriddenMethodOfAny != null) { + val correspondingObjCMethod = when (method.name.asString()) { + "toString" -> "'description'" + "hashCode" -> "'hash'" + "equals" -> "'isEqual:'" + else -> "corresponding Objective-C method" + } + + context.report( + method, + "can't override '${method.name}', override $correspondingObjCMethod instead", + isError = true + ) + } + } } override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression { @@ -367,18 +381,10 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme if (classContainer is ClassDescriptor && classContainer.isObjCClass() && constructedClassDescriptor == classContainer.companionObjectDescriptor) { - val outerConstructedClass = outerClasses[outerClasses.lastIndex - 1] - assert (outerConstructedClass.descriptor == classContainer) + // Note: it is actually not used; getting values of such objects is handled by code generator + // in [FunctionGenerationContext.getObjectValue]. - 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)) - } - } + return expression } } @@ -398,7 +404,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme val initCall = builder.genLoweredObjCMethodCall( initMethodInfo, superQualifier = symbolTable.referenceClass(expression.descriptor.constructedClass), - receiver = builder.irGet(constructedClass.thisReceiver!!.symbol), + receiver = builder.getRawPtr(builder.irGet(constructedClass.thisReceiver!!.symbol)), arguments = initMethod.valueParameters.map { expression.getValueArgument(it)!! } ) @@ -451,14 +457,21 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme 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 - ) + return builder.irBlock(expression) { + val allocated = irTemporaryVar(callAlloc(symbolTable.referenceClass(descriptor.constructedClass))) + val result = irTemporaryVar( + genLoweredObjCMethodCall( + initMethod.getExternalObjCMethodInfo()!!, + superQualifier = null, + receiver = irGet(allocated.symbol), + arguments = arguments + ) + ) + +irCall(symbols.interopObjCRelease).apply { + putValueArgument(0, irGet(allocated.symbol)) // Balance pointer retained by alloc. + } + +irGet(result.symbol) + } } } @@ -493,7 +506,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme return builder.genLoweredObjCMethodCall( methodInfo, superQualifier = expression.superQualifierSymbol, - receiver = expression.dispatchReceiver ?: expression.extensionReceiver!!, + receiver = builder.getRawPtr(expression.dispatchReceiver ?: expression.extensionReceiver!!), arguments = arguments ) } @@ -519,6 +532,11 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme else -> expression } } + + private fun IrBuilderWithScope.getRawPtr(receiver: IrExpression) = + irCall(symbols.interopObjCObjectRawValueGetter).apply { + extensionReceiver = receiver + } } /** diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt index e9a8539a96e..201add23f73 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt @@ -16,10 +16,12 @@ package org.jetbrains.kotlin.backend.konan.objcexport -import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments +import org.jetbrains.kotlin.backend.konan.isNativeBinary import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.target.CompilerOutputKind @@ -29,19 +31,51 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.isSubpackageOf -internal class ObjCExport(val context: Context) { +internal class ObjCExport(val codegen: CodeGenerator) { + val context get() = codegen.context private val target get() = context.config.target - internal fun produceObjCFramework() { - if (context.config.produce != CompilerOutputKind.FRAMEWORK) return + internal fun produce() { + if (target != KonanTarget.IOS_ARM64 && target != KonanTarget.IOS_X64 && target != KonanTarget.MACOS_X64) return - val headerGenerator = ObjCExportHeaderGenerator(context) + if (!context.config.produce.isNativeBinary) return // TODO: emit RTTI to the same modules as classes belong to. + + val objCCodeGenerator: ObjCExportCodeGenerator + val generatedClasses: Set + val topLevelDeclarations: Map> + + if (context.config.produce == CompilerOutputKind.FRAMEWORK) { + val headerGenerator = ObjCExportHeaderGenerator(context) + produceFrameworkSpecific(headerGenerator) + + generatedClasses = headerGenerator.generatedClasses + topLevelDeclarations = headerGenerator.topLevel + objCCodeGenerator = ObjCExportCodeGenerator(codegen, headerGenerator.namer, headerGenerator.mapper) + } else { + // TODO: refactor ObjCExport* to handle this case on a general basis. + val mapper = object : ObjCExportMapper() { + override fun getCategoryMembersFor(descriptor: ClassDescriptor): List = + emptyList() + + override fun isSpecialMapped(descriptor: ClassDescriptor): Boolean = + error("shouldn't reach here") + + } + + val namer = ObjCExportNamer(context, mapper) + objCCodeGenerator = ObjCExportCodeGenerator(codegen, namer, mapper) + + generatedClasses = emptySet() + topLevelDeclarations = emptyMap() + } + + objCCodeGenerator.emitRtti(generatedClasses = generatedClasses, topLevel = topLevelDeclarations) + } + + private fun produceFrameworkSpecific(headerGenerator: ObjCExportHeaderGenerator) { headerGenerator.translateModule() - val namer = headerGenerator.namer - val mapper = headerGenerator.mapper - val framework = File(context.config.outputFile) val frameworkContents = when (target) { KonanTarget.IOS_ARM64, KonanTarget.IOS_X64 -> framework @@ -79,9 +113,6 @@ internal class ObjCExport(val context: Context) { framework.child(child).createAsSymlink("Versions/Current/$child") } } - - val objCCodeGenerator = ObjCExportCodeGenerator(CodeGenerator(context), namer, mapper) - objCCodeGenerator.emitRtti(headerGenerator.generatedClasses, headerGenerator.topLevel) } private fun emitInfoPlist(frameworkContents: File, name: String) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt index 74316933f59..e300f74ba24 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt @@ -17,6 +17,8 @@ package org.jetbrains.kotlin.ir.util import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.konan.KonanBackendContext +import org.jetbrains.kotlin.backend.konan.KonanCompilationException import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations @@ -358,7 +360,7 @@ tailrec fun IrDeclaration.getContainingFile(): IrFile? { } } -fun CommonBackendContext.report(declaration: IrDeclaration, message: String, isError: Boolean) { +internal fun KonanBackendContext.report(declaration: IrDeclaration, message: String, isError: Boolean) { val irFile = declaration.getContainingFile() this.report( declaration, @@ -371,4 +373,5 @@ fun CommonBackendContext.report(declaration: IrDeclaration, message: String, isE }, isError ) + if (isError) throw KonanCompilationException() } diff --git a/backend.native/tests/interop/objc/objcSmoke.def b/backend.native/tests/interop/objc/objcSmoke.def index d19eb24c93c..7d28f8646c4 100644 --- a/backend.native/tests/interop/objc/objcSmoke.def +++ b/backend.native/tests/interop/objc/objcSmoke.def @@ -1,2 +1,3 @@ language = Objective-C -headerFilter = **/smoke.h +headers = Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h +headerFilter = **/smoke.h Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 5255b144b35..193232fb742 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -1,5 +1,6 @@ import kotlinx.cinterop.* import objcSmoke.* +import kotlin.test.* fun main(args: Array) { autoreleasepool { @@ -8,6 +9,9 @@ fun main(args: Array) { } fun run() { + testTypeOps() + testConversions() + println( getSupplier( invoke1(42) { it * 2 } @@ -44,7 +48,7 @@ fun run() { } // hashCode (directly): - if (foo.hashCode() == foo.hash().toInt()) { + if (foo.hashCode() == foo.hash().let { it.toInt() xor (it shr 32).toInt() }) { // toString (virtually): println(listOf(foo, pair).map { it.toString() }.min() == foo.description()) } @@ -94,4 +98,57 @@ class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol override fun update(index: Int, sub: Int) { elements[index] -= sub } -} \ No newline at end of file +} + +fun testTypeOps() { + assertTrue(99.asAny() is NSNumber) + assertTrue(null.asAny() is NSNumber?) + assertFalse(null.asAny() is NSNumber) + assertFalse("".asAny() is NSNumber) + assertTrue("bar".asAny() is NSString) + + assertTrue(Foo.asAny() is FooMeta) + assertTrue(Foo.asAny() is NSObjectMeta) + assertTrue(Foo.asAny() is NSObject) + assertFalse(Foo.asAny() is Foo) + assertTrue(NSString.asAny() is NSCopyingProtocolMeta) + assertFalse(NSString.asAny() is NSCopyingProtocol) + assertTrue(NSValue.asAny() is NSObjectProtocolMeta) + assertFalse(NSValue.asAny() is NSObjectProtocol) // Must be true, but not implemented properly yet. + + assertEquals(3, ("foo" as NSString).length()) + assertEquals(4, ((1..4).joinToString("") as NSString).length()) + assertEquals(2, (listOf(0, 1) as NSArray).count()) + assertEquals(42L, (42 as NSNumber).longLongValue()) + + assertFails { "bar" as NSNumber } + assertFails { 42 as NSArray } + assertFails { listOf(1) as NSString } + assertFails { NSObject() as Bar } + assertFails { NSObject() as NSValue } + + MutablePairImpl(1, 2).asAny() as MutablePairProtocol + assertFails { MutablePairImpl(1, 2).asAny() as Foo } +} + +fun testConversions() { + testMethodsOfAny(emptyList(), NSArray()) + testMethodsOfAny(listOf(1, "foo"), nsArrayOf(1, "foo")) + testMethodsOfAny(42, NSNumber.numberWithInt(42), 17) +} + +fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: Any = Any()) { + assertEquals(kotlinObject.hashCode(), equalNsObject.hashCode()) + assertEquals(kotlinObject.toString(), equalNsObject.toString()) + assertEquals(kotlinObject, equalNsObject) + assertEquals(equalNsObject, kotlinObject) + assertNotEquals(equalNsObject, otherObject) +} + +fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply { + elements.forEach { + this.addObject(it as ObjCObject) + } +} + +fun Any?.asAny(): Any? = this diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 4fe3209fdb3..4fe869b02eb 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -418,12 +418,6 @@ inline void runDeallocationHooks(ObjHeader* obj) { if (obj->has_meta_object()) { ObjHeader::destroyMetaObject(&obj->typeInfoOrMeta_); } -#if KONAN_OBJC_INTEROP - if (obj->type_info() == theObjCPointerHolderTypeInfo) { - void* objcPtr = *reinterpret_cast(obj + 1); // TODO: use more reliable layout description - objc_release(objcPtr); - } -#endif } inline void runDeallocationHooks(ContainerHeader* container) { @@ -828,7 +822,7 @@ void ObjHeader::destroyMetaObject(TypeInfo** location) { } #ifdef KONAN_OBJC_INTEROP - Kotlin_ObjCExport_releaseAssociatedObject(meta->associatedObject); + Kotlin_ObjCExport_releaseAssociatedObject(meta->associatedObject_); #endif konanFreeMemory(meta); diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index aa4dcc73dd2..7b5c1ceff95 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -246,7 +246,7 @@ struct MetaObjHeader { ObjHeader* counter_; #ifdef KONAN_OBJC_INTEROP - void* associatedObject; + void* associatedObject_; #endif }; diff --git a/runtime/src/main/cpp/ObjCExport.h b/runtime/src/main/cpp/ObjCExport.h index b392fda7d0f..c681409dbe4 100644 --- a/runtime/src/main/cpp/ObjCExport.h +++ b/runtime/src/main/cpp/ObjCExport.h @@ -12,11 +12,12 @@ extern "C" id objc_retain(id self); extern "C" void objc_release(id self); inline static id GetAssociatedObject(ObjHeader* obj) { - return (id)obj->meta_object()->associatedObject; + return (id)obj->meta_object()->associatedObject_; } +// Note: this function shall not be used on shared objects. inline static void SetAssociatedObject(ObjHeader* obj, id value) { - obj->meta_object()->associatedObject = (void*)value; + obj->meta_object()->associatedObject_ = (void*)value; } extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj); diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 6f14fa701cd..26bec276f4e 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -16,6 +16,7 @@ #import "Types.h" #import "Memory.h" +#include "Natives.h" #if KONAN_OBJC_INTEROP @@ -36,9 +37,6 @@ #import "Utils.h" #import "Exceptions.h" -// Note: defined by a compiler-generated bitcode. -extern "C" const uint8_t objCExportEnabled; - struct ObjCToKotlinMethodAdapter { const char* selector; const char* encoding; @@ -111,6 +109,22 @@ inline static OBJ_GETTER(AllocInstanceWithAssociatedObject, const TypeInfo* type static Class getOrCreateClass(const TypeInfo* typeInfo); static void initializeClass(Class clazz); +extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject); + +static inline id AtomicSetAssociatedObject(ObjHeader* obj, id associatedObject) { + if (!obj->container()->permanentOrFrozen()) { + SetAssociatedObject(obj, associatedObject); + return associatedObject; + } else { + void* old = __sync_val_compare_and_swap(&obj->meta_object()->associatedObject_, nullptr, (void*)associatedObject); + if (old == nullptr) { + return associatedObject; + } else { + Kotlin_ObjCExport_releaseAssociatedObject((void*)associatedObject); + return (id)old; + } + } +} @interface KotlinBase : NSObject @end; @@ -154,13 +168,14 @@ static void initializeClass(Class clazz); KotlinBase* result = [super allocWithZone:nil]; // TODO: should we call NSObject.init ? UpdateRef(&result->kotlinObj, obj); + [result autorelease]; if (!obj->permanent()) { - SetAssociatedObject(obj, result); + return AtomicSetAssociatedObject(obj, result); + } else { + // TODO: permanent objects should probably be supported as custom types. + return result; } - // TODO: permanent objects should probably be supported as custom types. - - return [result autorelease]; } -(instancetype)retain { @@ -194,7 +209,7 @@ static void initializeClass(Class clazz); @end; -extern "C" void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject) { +extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject) { if (associatedObject != nullptr) { [((id)associatedObject) releaseAsAssociatedObject]; } @@ -210,6 +225,27 @@ extern "C" id Kotlin_ObjCExport_convertUnit(ObjHeader* unitInstance) { return instance; } +extern "C" id objc_retainBlock(id self); +extern "C" id objc_retainAutoreleaseReturnValue(id self); + +extern "C" id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str) { + KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0); + auto numBytes = str->array()->count_ * sizeof(KChar); + + if (str->permanent()) { + return [[[NSString alloc] initWithBytesNoCopy:utf16Chars + length:numBytes + encoding:NSUTF16LittleEndianStringEncoding + freeWhenDone:NO] autorelease]; + } else { + // TODO: consider making NSString subclass to avoid copying here. + NSString* result = [[NSString alloc] initWithBytes:utf16Chars + length:numBytes + encoding:NSUTF16LittleEndianStringEncoding]; + + return objc_retainAutoreleaseReturnValue(AtomicSetAssociatedObject(str, result)); + } +} static const ObjCTypeAdapter* findAdapterByName( const char* name, const ObjCTypeAdapter** sortedAdapters, @@ -315,9 +351,6 @@ static void initializeClass(Class clazz) { @interface NSObject (NSObjectToKotlin) @end; -extern "C" id objc_retainBlock(id self); -extern "C" id objc_retainAutoreleaseReturnValue(id self); - @implementation NSObject (NSObjectToKotlin) -(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { const TypeInfo* typeInfo = getOrCreateTypeInfo(object_getClass(self)); @@ -494,16 +527,19 @@ static const TypeInfo* getFunctionTypeInfoForBlock(id block) { static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj); -extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) { +template +static ALWAYS_INLINE id Kotlin_ObjCExport_refToObjCImpl(ObjHeader* obj) { if (obj == nullptr) return nullptr; if (obj->has_meta_object()) { id associatedObject = GetAssociatedObject(obj); if (associatedObject != nullptr) { - return objc_retainAutoreleaseReturnValue(associatedObject); + return retainAutorelease ? objc_retainAutoreleaseReturnValue(associatedObject) : associatedObject; } } + // TODO: propagate [retainAutorelease] to the code below. + convertReferenceToObjC converter = (convertReferenceToObjC)obj->type_info()->writableInfo_->objCExport.convert; if (converter != nullptr) { return converter(obj); @@ -512,14 +548,18 @@ extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) { return Kotlin_ObjCExport_refToObjC_slowpath(obj); } +extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) { + // TODO: in some cases (e.g. when converting a bridge argument) performing retain-autorelease is not necessary. + return Kotlin_ObjCExport_refToObjCImpl(obj); +} + extern "C" ALWAYS_INLINE id Kotlin_Interop_refToObjC(ObjHeader* obj) { - if (obj == nullptr) { - return nullptr; - } else if (!objCExportEnabled || obj->type_info() == theObjCPointerHolderTypeInfo) { - return *reinterpret_cast(obj + 1); // First field. - } else { - return Kotlin_ObjCExport_refToObjC(obj); - } + return Kotlin_ObjCExport_refToObjCImpl(obj); +} + +extern "C" ALWAYS_INLINE OBJ_GETTER(Kotlin_Interop_refFromObjC, id obj) { + // TODO: consider removing this function. + RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, obj); } extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj) { @@ -679,6 +719,16 @@ static const TypeInfo* getMostSpecificKotlinClass(const TypeInfo* typeInfo) { return result; } +static int getVtableSize(const TypeInfo* typeInfo) { + for (const TypeInfo* current = typeInfo; current != nullptr; current = current->superType_) { + auto typeAdapter = getTypeAdapter(current); + if (typeAdapter != nullptr) return typeAdapter->kotlinVtableSize; + } + + RuntimeAssert(false, ""); + return -1; +} + static void insertOrReplace(KStdVector& methodTable, MethodNameHash nameSignature, void* entryPoint) { MethodTableRecord record = {nameSignature, entryPoint}; @@ -704,7 +754,7 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType) { const ObjCTypeAdapter* superTypeAdapter = getTypeAdapter(superType); const void * const * superVtable = nullptr; - int superVtableSize = getTypeAdapter(getMostSpecificKotlinClass(superType))->kotlinVtableSize; + int superVtableSize = getVtableSize(superType); const MethodTableRecord* superMethodTable = nullptr; int superMethodTableSize = 0; @@ -802,7 +852,7 @@ static const TypeInfo* getOrCreateTypeInfo(Class clazz) { Class superClass = class_getSuperclass(clazz); const TypeInfo* superType = superClass == nullptr ? - theAnyTypeInfo : + theForeignObjCObjectTypeInfo : getOrCreateTypeInfo(superClass); LockGuard lockGuard(typeInfoCreationMutex); @@ -920,9 +970,14 @@ static void checkLoadedOnce() { #else -extern "C" ALWAYS_INLINE id Kotlin_Interop_refToObjC(ObjHeader* obj) { +extern "C" ALWAYS_INLINE void* Kotlin_Interop_refToObjC(ObjHeader* obj) { RuntimeAssert(false, "Unavailable operation"); return nullptr; } +extern "C" ALWAYS_INLINE OBJ_GETTER(Kotlin_Interop_refFromObjC, void* obj) { + RuntimeAssert(false, "Unavailable operation"); + RETURN_OBJ(nullptr); +} + #endif // KONAN_OBJC_INTEROP diff --git a/runtime/src/main/cpp/ObjCInterop.cpp b/runtime/src/main/cpp/ObjCInterop.cpp index 54cb40d1333..7a63ed284c8 100644 --- a/runtime/src/main/cpp/ObjCInterop.cpp +++ b/runtime/src/main/cpp/ObjCInterop.cpp @@ -43,10 +43,12 @@ static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) { GetKotlinClassData(clazz)->typeInfo = typeInfo; } -const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) RUNTIME_NOTHROW; +const TypeInfo* GetObjCKotlinTypeInfo(ObjHeader* obj) RUNTIME_NOTHROW; -const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) { - void* objcPtr = *reinterpret_cast(obj + 1); // TODO: use more reliable layout description +const TypeInfo* GetObjCKotlinTypeInfo(ObjHeader* obj) { + RuntimeAssert(obj->has_meta_object(), ""); + void* objcPtr = obj->meta_object()->associatedObject_; + RuntimeAssert(objcPtr != nullptr, ""); Class clazz = object_getClass(reinterpret_cast(objcPtr)); return GetKotlinClassData(clazz)->typeInfo; } diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm index c1d0f7decb3..40f283b1f76 100644 --- a/runtime/src/main/cpp/ObjCInteropUtils.mm +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -44,18 +44,22 @@ namespace { extern "C" { -id Kotlin_Interop_CreateNSStringFromKString(const ObjHeader* str) { +id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str); + +id Kotlin_Interop_CreateNSStringFromKString(ObjHeader* str) { + // Note: this function is just a bit specialized [Kotlin_Interop_refToObjC]. if (str == nullptr) { return nullptr; } - const KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0); + if (str->has_meta_object()) { + void* associatedObject = str->meta_object()->associatedObject_; + if (associatedObject != nullptr) { + return (id)associatedObject; + } + } - NSString* result = [[[getNSStringClass() alloc] initWithBytes:utf16Chars - length:str->array()->count_*sizeof(KChar) - encoding:NSUTF16LittleEndianStringEncoding] autorelease]; - - return result; + return Kotlin_ObjCExport_CreateNSStringFromKString(str); } OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str) { @@ -78,7 +82,7 @@ OBJ_GETTER(Kotlin_Interop_ObjCToString, id ptr) { } KInt Kotlin_Interop_ObjCHashCode(id ptr) { - KLong hash = ptr.hash; + uint64_t hash = ptr.hash; return (KInt)(hash ^ (hash >> 32)); } @@ -149,6 +153,18 @@ KRef Kotlin_Interop_unwrapKotlinObjectHolder(id holder) { return [((KotlinObjectHolder*)holder) ref]; } +KBoolean Kotlin_Interop_DoesObjectConformToProtocol(id obj, void* prot, BOOL isMeta) { + BOOL objectIsClass = class_isMetaClass(object_getClass(obj)); + if ((isMeta && !objectIsClass) || (!isMeta && objectIsClass)) return false; + // TODO: handle root classes properly. + + return [((id)obj) conformsToProtocol:(Protocol*)prot]; +} + +KBoolean Kotlin_Interop_IsObjectKindOfClass(id obj, void* cls) { + return [((id)obj) isKindOfClass:(Class)cls]; +} + } // extern "C" #else // KONAN_OBJC_INTEROP diff --git a/runtime/src/main/cpp/Types.h b/runtime/src/main/cpp/Types.h index 850f19ef586..8355e42a411 100644 --- a/runtime/src/main/cpp/Types.h +++ b/runtime/src/main/cpp/Types.h @@ -83,7 +83,7 @@ extern const TypeInfo* theBooleanArrayTypeInfo; extern const TypeInfo* theStringTypeInfo; extern const TypeInfo* theThrowableTypeInfo; extern const TypeInfo* theUnitTypeInfo; -extern const TypeInfo* theObjCPointerHolderTypeInfo; +extern const TypeInfo* theForeignObjCObjectTypeInfo; KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE; void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);