diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 2950d1431c1..eaa4d5924a3 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -42,7 +42,7 @@ fun optional(): Nothing = throw RuntimeException("Do not call me!!!") @Deprecated( "Add @OverrideInit to constructor to make it override Objective-C initializer", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) @TypedIntrinsic(IntrinsicType.OBJC_INIT_BY) external fun T.initBy(constructorCall: T): T @@ -56,8 +56,7 @@ 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 +internal fun Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) @SymbolName("Kotlin_Interop_refFromObjC") external fun interpretObjCPointerOrNull(objcPtr: NativePtr): T? @@ -93,8 +92,9 @@ var ObjCNotImplementedVar.value: T typealias ObjCStringVarOf = ObjCNotImplementedVar typealias ObjCBlockVar = ObjCNotImplementedVar -@TypedIntrinsic(IntrinsicType.OBJC_GET_RECEIVER_OR_SUPER) -external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer? +@TypedIntrinsic(IntrinsicType.OBJC_CREATE_SUPER_STRUCT) +@PublishedApi +internal external fun createObjCSuperStruct(receiver: NativePtr, superClass: NativePtr): NativePtr @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) @@ -102,11 +102,7 @@ annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryNa @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @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 = "") +annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false) @Target(AnnotationTarget.CONSTRUCTOR) @Retention(AnnotationRetention.BINARY) @@ -114,7 +110,7 @@ annotation class ObjCConstructor(val initSelector: String, val designated: Boole @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.BINARY) -annotation class ObjCFactory(val bridge: String) +annotation class ObjCFactory(val selector: String, val encoding: String, val isStret: Boolean = false) @Target(AnnotationTarget.FILE) @Retention(AnnotationRetention.BINARY) @@ -158,10 +154,13 @@ private fun allocObjCObject(clazz: NativePtr): NativePtr { @kotlin.native.internal.ExportForCompiler private external fun getObjCClass(): NativePtr +@PublishedApi @TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER) -external fun getMessenger(superClass: NativePtr): COpaquePointer? +internal external fun getMessenger(superClass: NativePtr): COpaquePointer? + +@PublishedApi @TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER_STRET) -external fun getMessengerStret(superClass: NativePtr): COpaquePointer? +internal external fun getMessengerStret(superClass: NativePtr): COpaquePointer? internal class ObjCWeakReferenceImpl : kotlin.native.ref.WeakReferenceImpl() { @@ -180,14 +179,18 @@ private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr) // Konan runtme: -@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.WARNING) +@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.ERROR) @SymbolName("Kotlin_Interop_CreateNSStringFromKString") external fun CreateNSStringFromKString(str: String?): NativePtr -@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.WARNING) +@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.ERROR) @SymbolName("Kotlin_Interop_CreateKStringFromNSString") external fun CreateKStringFromNSString(ptr: NativePtr): String? +@PublishedApi +@SymbolName("Kotlin_Interop_CreateObjCObjectHolder") +internal external fun createObjCObjectHolder(ptr: NativePtr): Any? + // Objective-C runtime: @SymbolName("objc_retainAutoreleaseReturnValue") diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt index 536aeb9d62d..b3b76ea1672 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt @@ -25,7 +25,7 @@ inline fun autoreleasepool(block: () -> R): R { } } -@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING) +@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.ERROR) fun ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast() // TODO: null checks 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 b3b2a6e0118..20109992f8f 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 @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.native.interop.gen import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator import org.jetbrains.kotlin.native.interop.indexer.* -import org.jetbrains.kotlin.utils.addIfNotNull private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List { val selectorParts = this.selector.split(":") @@ -74,12 +73,11 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator, override fun generate(context: StubGenerationContext): Sequence = if (context.nativeBridges.isSupported(this)) { val result = mutableListOf() - result.add(objCMethodAnnotation) - if (method.nsConsumesSelf) result.add("@CCall.ConsumesReceiver") - if (method.nsReturnsRetained) result.add("@CCall.ReturnsRetained") + result.addAll(objCMethodAnnotations) result.add(header) if (method.isInit) { + // TODO: generate only constructor/factory in this case. val kotlinScope = stubGenerator.kotlinFile val newParameterNames = method.getKotlinParameterNames(forConstructorOrFactory = true) @@ -132,26 +130,13 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator, } result.add("") - result.add("@ObjCFactory".applyToStrings(bridgeName)) + result.addAll(buildObjCMethodAnnotations("@ObjCFactory")) result.add("external fun $receiver.create($parameters): $returnType") } is ObjCProtocol -> {} // Nothing to do. } } - val objCBridge = "@ObjCBridge".applyToStrings( - *mutableListOf().apply { - add(method.selector) - add(method.encoding) - addIfNotNull(implementationTemplate) - }.toTypedArray() - ) - - context.addTopLevelDeclaration( - listOf("@kotlin.native.internal.ExportForCompiler", objCBridge) - + block(bridgeHeader, bodyLines) - ) - result.asSequence() } else { sequenceOf( @@ -160,48 +145,18 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator, ) } - private val bodyLines: List private val kotlinParameters: List private val kotlinReturnType: String private val header: String - private val implementationTemplate: String? - internal val bridgeName: String - private val bridgeHeader: String - internal val objCMethodAnnotation: String + internal val objCMethodAnnotations: List + private val isStret: Boolean init { - val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile) - kotlinParameters = mutableListOf() - val kotlinObjCBridgeParameters = mutableListOf() - val nativeBridgeArguments = mutableListOf() - - val kniReceiverParameter = "kniR" - val kniSuperClassParameter = "kniSC" - val kniSelectorParameter = "kniSEL" - - val voidPtr = PointerType(VoidType) val returnType = method.getReturnType(container.classOrProtocol) - val messengerGetter = - if (returnType.isStret(stubGenerator.configuration.target)) "getMessengerStret" else "getMessenger" - - kotlinObjCBridgeParameters.add(KotlinParameter(kniSuperClassParameter, KotlinTypes.nativePtr)) - nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)")) - - if (method.nsConsumesSelf) { - // TODO: do this later due to possible exceptions - bodyGenerator.out("objc_retain($kniReceiverParameter)") - } - - kotlinObjCBridgeParameters.add(KotlinParameter(kniReceiverParameter, KotlinTypes.nativePtr)) - nativeBridgeArguments.add( - TypedKotlinValue(voidPtr, - "getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)")) - - kotlinObjCBridgeParameters.add(KotlinParameter(kniSelectorParameter, KotlinTypes.cOpaquePointer)) - nativeBridgeArguments.add(TypedKotlinValue(voidPtr, kniSelectorParameter)) + isStret = returnType.isStret(stubGenerator.configuration.target) val kotlinParameterNames = method.getKotlinParameterNames() @@ -211,9 +166,6 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator, val kotlinType = stubGenerator.mirror(it.type).argType val annotatedName = if (it.nsConsumed) "@CCall.Consumed $name" else name kotlinParameters.add(KotlinParameter(annotatedName, kotlinType)) - - kotlinObjCBridgeParameters.add(KotlinParameter(name, kotlinType)) - nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName())) } this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) { @@ -222,55 +174,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator, stubGenerator.mirror(returnType).argType }.render(stubGenerator.kotlinFile) - val result = stubGenerator.mappingBridgeGenerator.kotlinToNative( - bodyGenerator, - this@ObjCMethodStub, - returnType, - nativeBridgeArguments, - independent = when (container) { - is ObjCClassOrProtocol -> true // Every proper instance has this method in its method table. - is ObjCCategory -> false // Method is contributed by native dependency. - } - ) { nativeValues -> - val messengerParameterTypes = mutableListOf() - messengerParameterTypes.add("void*") - messengerParameterTypes.add("SEL") - method.parameters.forEach { - messengerParameterTypes.add(it.getTypeStringRepresentation()) - } - - val messengerReturnType = returnType.getStringRepresentation() - - val messengerType = - "$messengerReturnType (* ${method.cAttributes}) (${messengerParameterTypes.joinToString()})" - - val messenger = "(($messengerType) ${nativeValues.first()})" - - val messengerArguments = nativeValues.drop(1) - - "$messenger(${messengerArguments.joinToString()})" - } - bodyGenerator.returnResult(result) - - this.implementationTemplate = if (needsImplementationTemplate()) { - genImplementationTemplate(stubGenerator) - } else { - null - } - - this.bodyLines = bodyGenerator.build() - - bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}" - objCMethodAnnotation = "@ObjCMethod".applyToStrings(method.selector, bridgeName) - - this.bridgeHeader = buildString { - append("internal fun ") - append(bridgeName) - append('(') - kotlinObjCBridgeParameters.renderParametersTo(stubGenerator.kotlinFile, this) - append("): ") - append(kotlinReturnType) - } + objCMethodAnnotations = buildObjCMethodAnnotations("@ObjCMethod") val joinedKotlinParameters = kotlinParameters.renderParameters(stubGenerator.kotlinFile) @@ -302,24 +206,26 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator, } } - private fun needsImplementationTemplate(): Boolean = - method.getReturnType(container.classOrProtocol).isBlockPointer() || - method.parameters.any { it.type.isBlockPointer() } + private fun buildObjCMethodAnnotations(main: String) = listOfNotNull( + buildObjCMethodAnnotation(main), + "@CCall.ConsumesReceiver".takeIf { method.nsConsumesSelf }, + "@CCall.ReturnsRetained".takeIf { method.nsReturnsRetained } + ) - private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) { - is ObjCClassOrProtocol -> { - val codeBuilder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope) - - val result = codeBuilder.genMethodImp(stubGenerator, this, method, container) - stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines) - result + private fun buildObjCMethodAnnotation(annotation: String) = buildString { + append(annotation) + append('(') + append(method.selector.quoteAsKotlinLiteral()) + append(", ") + append(method.encoding.quoteAsKotlinLiteral()) + if (isStret) { + append(", ") + append("true") } - is ObjCCategory -> "" + append(')') } } -private fun Type.isBlockPointer() = this.unwrapTypedefs() is ObjCBlockPointer - private fun deprecatedInit(className: String, initParameterNames: List, factory: Boolean): String { val replacement = if (factory) "$className.create" else className val replacementKind = if (factory) "factory method" else "constructor" @@ -720,11 +626,11 @@ class ObjCPropertyStub( } val result = mutableListOf( "$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType", - " ${getterStub.objCMethodAnnotation} external get" + " ${getterStub.objCMethodAnnotations.joinToString(" ")} external get" ) property.setter?.let { - result.add(" ${setterStub!!.objCMethodAnnotation} external set") + result.add(" ${setterStub!!.objCMethodAnnotations.joinToString(" ")} external set") } return result.asSequence() @@ -741,85 +647,6 @@ fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String { return if (isMeta) "${baseClassName}Meta" else baseClassName } -private fun Parameter.getTypeStringRepresentation() = - (if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation() - -private fun ObjCMethod.getSelfTypeStringRepresentation() = if (this.nsConsumesSelf) { - "__attribute__((ns_consumed)) id" -} else { - "id" -} - -val ObjCMethod.cAttributes get() = if (this.nsReturnsRetained) { - "__attribute__((ns_returns_retained)) " -} else { - "" -} - -private fun NativeCodeBuilder.genMethodImp( - stubGenerator: StubGenerator, - nativeBacked: NativeBacked, - method: ObjCMethod, - container: ObjCClassOrProtocol -): String { - - val returnType = method.getReturnType(container) - val cReturnType = returnType.getStringRepresentation() - - val bridgeArguments = mutableListOf() - - val parameters = mutableListOf>() - - parameters.add("self" to method.getSelfTypeStringRepresentation()) - - val receiverType = ObjCIdType(ObjCPointer.Nullability.NonNull, protocols = emptyList()) - bridgeArguments.add(TypedNativeValue(receiverType, "self")) - - parameters.add("_cmd" to "SEL") - - method.parameters.forEachIndexed { index, parameter -> - val name = "p$index" - parameters.add(name to parameter.getTypeStringRepresentation()) - bridgeArguments.add(TypedNativeValue(parameter.type, name)) - } - - val functionName = "knimi_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId() - val functionAttr = method.cAttributes - - out("$cReturnType $functionName(${parameters.joinToString { it.second + " " + it.first }}) $functionAttr{") - - val callExpr = stubGenerator.mappingBridgeGenerator.nativeToKotlin( - this, - nativeBacked, - returnType, - bridgeArguments - ) { kotlinValues -> - - val kotlinReceiverType = stubGenerator.declarationMapper - .getKotlinClassFor(container, isMeta = method.isClass) - .type.render(stubGenerator.kotlinFile) - - val kotlinRawReceiver = kotlinValues.first() - val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<$kotlinReceiverType>()" - - 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 -} - private fun genProtocolGetter( stubGenerator: StubGenerator, nativeBacked: NativeBacked, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt index 93d0001cd32..8df89b2f131 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt @@ -6,8 +6,10 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.konan.exec.Command import org.jetbrains.kotlin.konan.file.* import org.jetbrains.kotlin.konan.target.ClangArgs +import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.konan.target.KonanTarget -class CStubsManager { +class CStubsManager(private val target: KonanTarget) { fun getUniqueName(prefix: String) = "$prefix${counter++}" @@ -18,13 +20,24 @@ class CStubsManager { fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): File? { if (stubs.isEmpty()) return null - val cSource = createTempFile("cstubs", ".c").deleteOnExit() + val compilerOptions = mutableListOf() + val sourceFileExtension = when (target.family) { + Family.OSX, Family.IOS -> { + compilerOptions += "-fobjc-arc" + ".m" // TODO: consider managing C and Objective-C stubs separately. + } + else -> ".c" + } + val cSource = createTempFile("cstubs", sourceFileExtension).deleteOnExit() cSource.writeLines(stubs.flatMap { it.lines }) val bitcode = createTempFile("cstubs", ".bc").deleteOnExit() val cSourcePath = cSource.absolutePath - val clangCommand = clang.clangC(cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath) + + val clangCommand = clang.clangC(*compilerOptions.toTypedArray(), "-O2", + cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath) + val result = Command(clangCommand).getResult(withErrors = true) if (result.exitCode != 0) { reportCompilationErrors(cSourcePath, result, messageCollector, verbose) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index d89ad89e81a..3f52a987f65 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -316,7 +316,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { lateinit var bitcodeFileName: String lateinit var library: KonanLibraryWriter - val cStubsManager = CStubsManager() + val cStubsManager = CStubsManager(config.target) val coverage = CoverageManager(this) 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 c0ca2ddefc0..631e0d7502d 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 @@ -5,17 +5,13 @@ package org.jetbrains.kotlin.backend.konan -import org.jetbrains.kotlin.backend.konan.descriptors.findPackageView +import org.jetbrains.kotlin.backend.konan.descriptors.getArgumentValueOrNull import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue import org.jetbrains.kotlin.backend.konan.descriptors.getStringValueOrNull import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.ConstructorDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.types.classifierOrFail import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name @@ -35,7 +31,6 @@ internal val externalObjCClassFqName = interopPackageName.child(Name.identifier( private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod")) private val objCConstructorFqName = FqName("kotlinx.cinterop.ObjCConstructor") private val objCFactoryFqName = interopPackageName.child(Name.identifier("ObjCFactory")) -private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge")) @Deprecated("Use IR version rather than descriptor version") fun ClassDescriptor.isObjCClass(): Boolean = @@ -91,52 +86,21 @@ fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.i fun IrClass.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass() -data class ObjCMethodInfo(val bridge: FunctionDescriptor, - val selector: String, +data class ObjCMethodInfo(val selector: String, val encoding: String, - val imp: String?) - -private fun CallableDescriptor.getBridgeAnnotation() = - this.annotations.findAnnotation(objCBridgeFqName) + val isStret: Boolean) private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? { assert (this.kind.isReal) - val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null - val packageView = this.findPackageView() - - val bridgeName = methodAnnotation.getStringValue("bridge") - - return objCMethodInfoByBridge(packageView, bridgeName) + return objCMethodInfo(methodAnnotation) } -private fun objCMethodInfoByBridge(packageView: PackageViewDescriptor, bridgeName: String): ObjCMethodInfo { - val bridge = packageView.memberScope - .getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND) - .single() - - val bridgeAnnotation = bridge.getBridgeAnnotation()!! - return ObjCMethodInfo( - bridge = bridge, - selector = bridgeAnnotation.getStringValue("selector"), - encoding = bridgeAnnotation.getStringValue("encoding"), - imp = bridgeAnnotation.getStringValueOrNull("imp") - ) -} - -fun IrSimpleFunction.objCMethodArgValue(argName: String): String? { - val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null - methodAnnotation.symbol.owner.valueParameters.forEachIndexed { index, parameter -> - if (parameter.name.asString() == argName) { - val bridgeArgument = methodAnnotation.getValueArgument(index) as IrConst - return bridgeArgument.value - } - } - return null -} - -fun IrSimpleFunction.hasObjCMethodAnnotation() = - this.annotations.findAnnotation(objCMethodFqName) != null +private fun objCMethodInfo(annotation: AnnotationDescriptor) = ObjCMethodInfo( + selector = annotation.getStringValue("selector"), + encoding = annotation.getStringValue("encoding"), + isStret = annotation.getArgumentValueOrNull("isStret") ?: false +) /** * @param onlyExternal indicates whether to accept overriding methods from Kotlin classes @@ -237,15 +201,6 @@ fun ConstructorDescriptor.objCConstructorIsDesignated(): Boolean { } -fun ConstructorDescriptor.getObjCInitMethod(): FunctionDescriptor? { - return this.annotations.findAnnotation(objCConstructorFqName)?.let { - val initSelector = it.getStringValue("initSelector") - this.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence() - .filterIsInstance() - .single { it.getExternalObjCMethodInfo()?.selector == initSelector } - } -} - val IrConstructor.isObjCConstructor get() = this.descriptor.annotations.hasAnnotation(objCConstructorFqName) fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? { @@ -263,9 +218,7 @@ val IrFunction.hasObjCMethodAnnotation get() = this.descriptor.annotations.hasAn fun FunctionDescriptor.getObjCFactoryInitMethodInfo(): ObjCMethodInfo? { val factoryAnnotation = this.annotations.findAnnotation(objCFactoryFqName) ?: return null - val bridgeName = factoryAnnotation.getStringValue("bridge") - - return objCMethodInfoByBridge(this.findPackageView(), bridgeName) + return objCMethodInfo(factoryAnnotation) } fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt index 0c357eb95f9..e030b06b6fe 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt @@ -9,4 +9,5 @@ object RuntimeNames { val exportTypeInfoAnnotation = FqName("kotlin.native.internal.ExportTypeInfo") val cCall = FqName("kotlinx.cinterop.internal.CCall") val independent = FqName("kotlin.native.internal.Independent") + val filterExceptions = FqName("kotlin.native.internal.FilterExceptions") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt index 94f0fd8cbd4..c8a770b521e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt @@ -1,13 +1,18 @@ package org.jetbrains.kotlin.backend.konan.cgen +import org.jetbrains.kotlin.backend.common.descriptors.WrappedClassConstructorDescriptor +import org.jetbrains.kotlin.backend.common.descriptors.WrappedClassDescriptor import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor +import org.jetbrains.kotlin.backend.common.descriptors.WrappedValueParameterDescriptor import org.jetbrains.kotlin.backend.common.lower.at import org.jetbrains.kotlin.backend.common.lower.irNot import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType import org.jetbrains.kotlin.backend.konan.RuntimeNames import org.jetbrains.kotlin.backend.konan.descriptors.createAnnotation import org.jetbrains.kotlin.backend.konan.ir.* +import org.jetbrains.kotlin.backend.konan.isObjCMetaClass import org.jetbrains.kotlin.builtins.UnsignedType +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.annotations.Annotations @@ -16,21 +21,27 @@ import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns -import org.jetbrains.kotlin.ir.expressions.IrCall -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.IrSpreadElement -import org.jetbrains.kotlin.ir.expressions.IrVararg +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.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrConstructorSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.util.OperatorNameConventions internal interface KotlinStubs { val irBuiltIns: IrBuiltIns @@ -39,16 +50,18 @@ internal interface KotlinStubs { fun addKotlin(declaration: IrDeclaration) fun addC(lines: List) fun getUniqueCName(prefix: String): String + fun getUniqueKotlinFunctionReferenceClassName(prefix: String): String fun reportError(location: IrElement, message: String): Nothing } private class KotlinToCCallBuilder( val irBuilder: IrBuilderWithScope, - cBridgeName: String, val stubs: KotlinStubs ) { + val cBridgeName = stubs.getUniqueCName("knbridge") + val symbols: KonanSymbols get() = stubs.symbols val bridgeCallBuilder = KotlinCallBuilder(irBuilder, symbols) @@ -71,6 +84,14 @@ private fun KotlinToCCallBuilder.addArgument( parameter: IrValueParameter? ) { val argumentPassing = mapParameter(type, variadic, parameter, argument) + addArgument(argument, argumentPassing, variadic) +} + +private fun KotlinToCCallBuilder.addArgument( + argument: IrExpression, + argumentPassing: KotlinToCArgumentPassing, + variadic: Boolean +) { val cArgument = with(argumentPassing) { passValue(argument) } cCallBuilder.arguments += cArgument.expression if (!variadic) cFunctionBuilder.addParameter(cArgument.type) @@ -87,10 +108,7 @@ private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrCall) - internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean): IrExpression { require(expression.dispatchReceiver == null) - val cBridgeName = this.getUniqueCName("knbridge") - val callBuilder = KotlinToCCallBuilder(builder, cBridgeName, this) - - val cFunctionBuilder = callBuilder.cFunctionBuilder + val callBuilder = KotlinToCCallBuilder(builder, this) val callee = expression.symbol.owner as IrSimpleFunction @@ -136,15 +154,13 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit else -> error(it) } } - cFunctionBuilder.variadic = true + callBuilder.cFunctionBuilder.variadic = true } else { callBuilder.addArgument(argument!!, parameter.type, variadic = false, parameter = parameter) } } } - val cLines = mutableListOf() - val returnValuePassing = if (isInvoke) { val returnType = expression.getTypeArgument(expression.typeArgumentsCount - 1)!! mapReturnType(returnType, TypeLocation.FunctionCallResult(expression), signature = null) @@ -152,27 +168,126 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit mapReturnType(callee.returnType, TypeLocation.FunctionCallResult(expression), signature = callee) } - val result = with(returnValuePassing) { - callBuilder.returnValue(callBuilder.cCallBuilder.build(targetFunctionName)) - } + val result = callBuilder.buildCall(targetFunctionName, returnValuePassing) + + val targetFunctionVariable = CVariable(CTypes.pointer(callBuilder.cFunctionBuilder.getType()), targetFunctionName) - val targetFunctionVariable = CVariable(CTypes.pointer(cFunctionBuilder.getType()), targetFunctionName) if (isInvoke) { callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = ${targetPtrParameter!!};") } else { val cCallSymbolName = callee.getAnnotationArgumentValue(cCall, "id")!! - cLines += "extern const $targetFunctionVariable __asm(\"$cCallSymbolName\");" // Exported from cinterop stubs. + this.addC(listOf("extern const $targetFunctionVariable __asm(\"$cCallSymbolName\");")) // Exported from cinterop stubs. } - cLines += "${callBuilder.bridgeBuilder.buildCSignature(cBridgeName)} {" - cLines += callBuilder.cBridgeBodyLines - cLines += "}" - - this.addC(cLines) + callBuilder.emitCBridge() return result } +private fun KotlinToCCallBuilder.emitCBridge() { + val cLines = mutableListOf() + + cLines += "${bridgeBuilder.buildCSignature(cBridgeName)} {" + cLines += cBridgeBodyLines + cLines += "}" + + stubs.addC(cLines) +} + +private fun KotlinToCCallBuilder.buildCall( + targetFunctionName: String, + returnValuePassing: ValueReturning +): IrExpression = with(returnValuePassing) { + returnValue(cCallBuilder.build(targetFunctionName)) +} + + +internal fun KotlinStubs.generateObjCCall( + builder: IrBuilderWithScope, + method: IrSimpleFunction, + isStret: Boolean, + selector: String, + call: IrFunctionAccessExpression, + superQualifier: IrClassSymbol?, + receiver: IrExpression, + arguments: List +): IrExpression { + + val callBuilder = KotlinToCCallBuilder(builder, this) + + val superClass = superQualifier?.let { builder.getObjCClass(symbols, it) } + ?: builder.irNullNativePtr(symbols) + + val messenger = builder.irCall(if (isStret) { + symbols.interopGetMessengerStret + } else { + symbols.interopGetMessenger + }.owner).apply { + putValueArgument(0, superClass) // TODO: check superClass statically. + } + + val targetPtrParameter = callBuilder.passThroughBridge( + messenger, + symbols.interopCPointer.typeWithStarProjections, + CTypes.voidPtr + ).name + val targetFunctionName = "targetPtr" + + val preparedReceiver = if (method.consumesReceiver()) { + builder.irCall(symbols.interopObjCRetain.owner).apply { + putValueArgument(0, receiver) + } + } else { + receiver + } + + val receiverOrSuper = if (superQualifier != null) { + builder.irCall(symbols.interopCreateObjCSuperStruct.owner).apply { + putValueArgument(0, preparedReceiver) + putValueArgument(1, superClass) + } + } else { + preparedReceiver + } + + callBuilder.cCallBuilder.arguments += callBuilder.passThroughBridge( + receiverOrSuper, symbols.nativePtrType, CTypes.voidPtr).name + callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr) + + callBuilder.cCallBuilder.arguments += "@selector($selector)" + callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr) + + (0 until method.valueParameters.size).forEach { + val parameter = method.valueParameters[it] + callBuilder.addArgument( + arguments[it], + type = parameter.type, + variadic = false, + parameter = parameter + ) + } + + val returnValuePassing = + mapReturnType(method.returnType, TypeLocation.FunctionCallResult(call), signature = method) + + val result = callBuilder.buildCall(targetFunctionName, returnValuePassing) + + val targetFunctionVariable = CVariable(CTypes.pointer(callBuilder.cFunctionBuilder.getType()), targetFunctionName) + callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = $targetPtrParameter;") + + callBuilder.emitCBridge() + + return result +} + +private fun IrBuilderWithScope.getObjCClass(symbols: KonanSymbols, symbol: IrClassSymbol): IrExpression { + val classDescriptor = symbol.descriptor + assert(!classDescriptor.isObjCMetaClass()) + return irCall(symbols.interopGetObjCClass, symbols.nativePtrType, listOf(symbol.typeWithStarProjections)) +} + +private fun IrBuilderWithScope.irNullNativePtr(symbols: KonanSymbols) = irCall(symbols.getNativeNullPtr.owner) + private class CCallbackBuilder( val stubs: KotlinStubs, val location: IrElement, @@ -212,36 +327,48 @@ private fun CCallbackBuilder.addParameter(it: IrValueParameter, functionParamete TypeLocation.FunctionPointerParameter(cFunctionBuilder.numberOfParameters, location) } - val valuePassing = mapParameter(it, typeLocation) + val valuePassing = stubs.mapType( + it.type, + retained = it.isConsumed(), + variadic = false, + location = typeLocation + ) val kotlinArgument = with(valuePassing) { receiveValue() } kotlinCallBuilder.arguments += kotlinArgument } private fun CCallbackBuilder.build(function: IrSimpleFunction, signature: IrSimpleFunction): String { - val kotlinCall = kotlinCallBuilder.build(function) - val typeLocation = if (isObjCMethod) { TypeLocation.ObjCMethodReturnValue(function) } else { TypeLocation.FunctionPointerReturnValue(location) } + val valueReturning = stubs.mapReturnType(signature.returnType, typeLocation, signature) + buildValueReturn(function, valueReturning) + return buildCFunction() +} - with(stubs.mapReturnType(signature.returnType, typeLocation, signature)) { +private fun CCallbackBuilder.buildValueReturn(function: IrSimpleFunction, valueReturning: ValueReturning) { + val kotlinCall = kotlinCallBuilder.build(function) + with(valueReturning) { returnValue(kotlinCall) } - val cLines = mutableListOf() - val kotlinBridge = bridgeBuilder.buildKotlinBridge() kotlinBridge.body = bridgeBuilder.kotlinIrBuilder.irBlockBody { kotlinBridgeStatements.forEach { +it } } stubs.addKotlin(kotlinBridge) + stubs.addC(listOf("${buildCBridge()};")) +} + +private fun CCallbackBuilder.buildCFunction(): String { val result = stubs.getUniqueCName("kncfun") - cLines += "${buildCBridge()};" + val cLines = mutableListOf() + cLines += "${cFunctionBuilder.buildSignature(result)} {" cLines += cBodyLines cLines += "}" @@ -427,42 +554,91 @@ private fun KotlinToCCallBuilder.mapParameter( classifier == symbols.string && parameter?.isWCStringParameter() == true -> WCStringArgumentPassing() - else -> stubs.mapType(type, TypeLocation.FunctionArgument(argument)) + else -> stubs.mapType( + type, + retained = parameter?.isConsumed() ?: false, + variadic = variadic, + location = TypeLocation.FunctionArgument(argument) + ) } } -private fun CCallbackBuilder.mapParameter( - parameter: IrValueParameter, - typeLocation: TypeLocation -): ValuePassing = when { - stubs.isObjCReferenceType(parameter.type) -> - ObjCReferenceValuePassing(symbols, parameter.type, retained = parameter.isConsumed()) - - else -> stubs.mapType(parameter.type, typeLocation) -} - private sealed class TypeLocation(val element: IrElement) { class FunctionArgument(val argument: IrExpression) : TypeLocation(argument) - class FunctionCallResult(val call: IrCall) : TypeLocation(call) + class FunctionCallResult(val call: IrFunctionAccessExpression) : TypeLocation(call) class FunctionPointerParameter(val index: Int, element: IrElement) : TypeLocation(element) class FunctionPointerReturnValue(element: IrElement) : TypeLocation(element) class ObjCMethodParameter(val index: Int, element: IrElement) : TypeLocation(element) class ObjCMethodReturnValue(element: IrElement) : TypeLocation(element) + + class BlockParameter(val index: Int, val blockLocation: TypeLocation) : TypeLocation(blockLocation.element) + class BlockReturnValue(val blockLocation: TypeLocation) : TypeLocation(blockLocation.element) } -private fun KotlinStubs.mapReturnType(type: IrType, location: TypeLocation, signature: IrSimpleFunction?): ValueReturning = - when { - type.isUnit() -> VoidReturning - isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, signature?.returnsRetained() ?: false) - else -> mapType(type, location) +private fun KotlinStubs.mapReturnType( + type: IrType, + location: TypeLocation, + signature: IrSimpleFunction? +): ValueReturning = when { + type.isUnit() -> VoidReturning + else -> mapType(type, retained = signature?.returnsRetained() ?: false, variadic = false, location = location) +} + +private fun KotlinStubs.mapBlockType( + type: IrType, + retained: Boolean, + location: TypeLocation +): ObjCBlockPointerValuePassing { + type as IrSimpleType + require(type.classifier == symbols.functions[type.arguments.size - 1]) + val returnTypeArgument = type.arguments.last() + val valueReturning = when (returnTypeArgument) { + is IrTypeProjection -> if (returnTypeArgument.variance == Variance.INVARIANT) { + mapReturnType(returnTypeArgument.type, TypeLocation.BlockReturnValue(location), null) + } else { + reportUnsupportedType("${returnTypeArgument.variance.label}-variance of return type", type, location) } + is IrStarProjection -> reportUnsupportedType("* as return type", type, location) + else -> error(returnTypeArgument) + } + val parameterValuePassings = type.arguments.dropLast(1).mapIndexed { index, argument -> + when (argument) { + is IrTypeProjection -> if (argument.variance == Variance.INVARIANT) { + mapType( + argument.type, + retained = false, + variadic = false, + location = TypeLocation.BlockParameter(index, location) + ) + } else { + reportUnsupportedType("${argument.variance.label}-variance of ${index + 1} parameter type", type, location) + } + is IrStarProjection -> reportUnsupportedType("* as ${index + 1} parameter type", type, location) + else -> error(argument) + } + } + return ObjCBlockPointerValuePassing( + this, + location.element, + type, + valueReturning, + parameterValuePassings, + retained + ) +} -private fun KotlinStubs.mapType(type: IrType, location: TypeLocation): ValuePassing = - mapType(type, { reportUnsupportedType(it, type, location) }) +private fun KotlinStubs.mapType(type: IrType, retained: Boolean, variadic: Boolean, location: TypeLocation): ValuePassing = + mapType(type, retained, variadic, location, { reportUnsupportedType(it, type, location) }) -private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) -> Nothing): ValuePassing = when { +private fun KotlinStubs.mapType( + type: IrType, + retained: Boolean, + variadic: Boolean, + typeLocation: TypeLocation, + reportUnsupportedType: (String) -> Nothing +): ValuePassing = when { type.isBoolean() -> BooleanValuePassing( cBoolType(target) ?: reportUnsupportedType("unavailable on target platform"), irBuiltIns @@ -486,7 +662,11 @@ private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) -> .filterIsInstance() .single { it.name.asString() == "value" } - CEnumValuePassing(enumClass, value, mapType(value.getter!!.returnType, reportUnsupportedType) as SimpleValuePassing) + CEnumValuePassing( + enumClass, + value, + mapType(value.getter!!.returnType, retained, variadic, typeLocation) as SimpleValuePassing + ) } type.classifierOrNull == symbols.interopCValue -> if (type.isNullable()) { @@ -499,8 +679,13 @@ private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) -> ?: reportUnsupportedType("not a structure or too complex")) } - type.isFunction() -> reportUnsupportedType("") - isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, retained = false) + type.isFunction() -> if (variadic){ + reportUnsupportedType("not supported as variadic argument") + } else { + mapBlockType(type, retained = retained, location = typeLocation) + } + + isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, retained = retained) else -> reportUnsupportedType("doesn't correspond to any C type") } @@ -532,6 +717,8 @@ private interface KotlinToCArgumentPassing { } private interface ValueReturning { + val cType: CType + fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression fun CCallbackBuilder.returnValue(expression: IrExpression) } @@ -543,7 +730,8 @@ private interface ValuePassing : KotlinToCArgumentPassing, ValueReturning { private abstract class SimpleValuePassing : ValuePassing { abstract val kotlinBridgeType: IrType abstract val cBridgeType: CType - abstract val cType: CType + override abstract val cType: CType + open val callbackParameterCType get() = cType abstract fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression open fun IrBuilderWithScope.kotlinCallbackResultToBridged(expression: IrExpression): IrExpression = @@ -568,7 +756,7 @@ private abstract class SimpleValuePassing : ValuePassing { } override fun CCallbackBuilder.receiveValue(): IrExpression { - val cParameter = cFunctionBuilder.addParameter(cType) + val cParameter = cFunctionBuilder.addParameter(callbackParameterCType) val cBridgeArgument = cToBridged(cParameter.name) val kotlinParameter = passThroughBridge(cBridgeArgument, cBridgeType, kotlinBridgeType) return with(bridgeBuilder.kotlinIrBuilder) { @@ -639,7 +827,7 @@ private class BooleanValuePassing(override val cType: CType, private val irBuilt override fun cToBridged(expression: String): String = cBridgeType.cast(expression) } -private class StructValuePassing(private val kotlinClass: IrClass, private val cType: CType) : ValuePassing { +private class StructValuePassing(private val kotlinClass: IrClass, override val cType: CType) : ValuePassing { override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression { val cBridgeValue = passThroughBridge( cValuesRefToPointer(expression), @@ -783,30 +971,282 @@ private class ObjCReferenceValuePassing( // TODO: optimize by using specialized Kotlin-to-ObjC converter. } - override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression { - fun wrapObjCPtr(ptr: IrExpression) = irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply { - putValueArgument(0, ptr) - } - - return if (retained) { - irBlock(startOffset, endOffset) { - val ptrVar = irTemporary(expression) - val resultVar = irTemporary(wrapObjCPtr(irGet(ptrVar))) - +irCall(symbols.interopObjCRelease.owner).apply { - putValueArgument(0, irGet(ptrVar)) + override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression = + convertPossiblyRetainedObjCPointer(symbols, retained, expression) { + irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply { + putValueArgument(0, it) } - +irGet(resultVar) } - } else { - wrapObjCPtr(expression) - } - } override fun bridgedToC(expression: String): String = expression override fun cToBridged(expression: String): String = expression } +private fun IrBuilderWithScope.convertPossiblyRetainedObjCPointer( + symbols: KonanSymbols, + retained: Boolean, + pointer: IrExpression, + convert: (IrExpression) -> IrExpression +): IrExpression = if (retained) { + irBlock(startOffset, endOffset) { + val ptrVar = irTemporary(pointer) + val resultVar = irTemporary(convert(irGet(ptrVar))) + +irCall(symbols.interopObjCRelease.owner).apply { + putValueArgument(0, irGet(ptrVar)) + } + +irGet(resultVar) + } +} else { + convert(pointer) +} + +private class ObjCBlockPointerValuePassing( + val stubs: KotlinStubs, + private val location: IrElement, + private val functionType: IrSimpleType, + private val valueReturning: ValueReturning, + private val parameterValuePassings: List, + private val retained: Boolean +) : SimpleValuePassing() { + val symbols get() = stubs.symbols + + override val kotlinBridgeType: IrType + get() = symbols.nativePtrType + override val cBridgeType: CType + get() = CTypes.id + override val cType: CType + get() = CTypes.id + + /** + * Callback can receive stack-allocated block. Using block type for parameter and passing it as `id` to the bridge + * makes Objective-C compiler generate proper copying to heap. + */ + override val callbackParameterCType: CType + get() = CTypes.blockPointer( + CTypes.function( + valueReturning.cType, + parameterValuePassings.map { it.cType }, + variadic = false + )) + + override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression = + irCall(symbols.interopCreateKotlinObjectHolder.owner).apply { + putValueArgument(0, expression) + } + + override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression = + irLetS(expression) { blockPointerVarSymbol -> + val blockPointerVar = blockPointerVarSymbol.owner + irIfThenElse( + functionType.makeNullable(), + condition = irCall(symbols.areEqualByValue.getValue(PrimitiveBinaryType.POINTER).owner).apply { + putValueArgument(0, irGet(blockPointerVar)) + putValueArgument(1, irNullNativePtr(symbols)) + }, + thenPart = irNull(), + elsePart = convertPossiblyRetainedObjCPointer(symbols, retained, irGet(blockPointerVar)) { + createKotlinFunctionObject(it) + } + ) + } + + private object OBJC_BLOCK_FUNCTION_IMPL : IrDeclarationOriginImpl("OBJC_BLOCK_FUNCTION_IMPL") + + private fun IrBuilderWithScope.createKotlinFunctionObject(blockPointer: IrExpression): IrExpression { + val constructor = generateKotlinFunctionClass() + return irCall(constructor).apply { + putValueArgument(0, blockPointer) + } + } + + private fun IrBuilderWithScope.generateKotlinFunctionClass(): IrConstructorImpl { + val symbols = stubs.symbols + + val classDescriptor = WrappedClassDescriptor() + val irClass = IrClassImpl( + startOffset, endOffset, + OBJC_BLOCK_FUNCTION_IMPL, IrClassSymbolImpl(classDescriptor), + Name.identifier(stubs.getUniqueKotlinFunctionReferenceClassName("BlockFunctionImpl")), + ClassKind.CLASS, Visibilities.PRIVATE, Modality.FINAL, + isCompanion = false, isInner = false, isData = false, isExternal = false, isInline = false + ) + classDescriptor.bind(irClass) + irClass.createParameterDeclarations() + + irClass.superTypes += stubs.irBuiltIns.anyType + irClass.superTypes += functionType.makeNotNull() + + val blockHolderField = createField( + startOffset, endOffset, + OBJC_BLOCK_FUNCTION_IMPL, + stubs.irBuiltIns.anyType, + Name.identifier("blockHolder"), + isMutable = false, owner = irClass + ) + irClass.addChild(blockHolderField) + + val constructorDescriptor = WrappedClassConstructorDescriptor() + val constructor = IrConstructorImpl( + startOffset, endOffset, + OBJC_BLOCK_FUNCTION_IMPL, + IrConstructorSymbolImpl(constructorDescriptor), + Name.special(""), + Visibilities.PUBLIC, + irClass.defaultType, + isInline = false, isExternal = false, isPrimary = true + ) + constructorDescriptor.bind(constructor) + irClass.addChild(constructor) + + val constructorParameterDescriptor = WrappedValueParameterDescriptor() + val constructorParameter = IrValueParameterImpl( + startOffset, endOffset, + OBJC_BLOCK_FUNCTION_IMPL, + IrValueParameterSymbolImpl(constructorParameterDescriptor), + Name.identifier("blockPointer"), + 0, + symbols.nativePtrType, + varargElementType = null, isCrossinline = false, isNoinline = false + ) + constructorParameterDescriptor.bind(constructorParameter) + constructor.valueParameters += constructorParameter + constructorParameter.parent = constructor + + constructor.body = irBuilder(stubs.irBuiltIns, constructor.symbol).irBlockBody(startOffset, endOffset) { + +irDelegatingConstructorCall(symbols.any.owner.constructors.single()) + +irSetField(irGet(irClass.thisReceiver!!), blockHolderField, + irCall(symbols.interopCreateObjCObjectHolder.owner).apply { + putValueArgument(0, irGet(constructorParameter)) + }) + } + + val parameterCount = parameterValuePassings.size + assert(functionType.arguments.size == parameterCount + 1) + + val overriddenInvokeMethod = (functionType.classifier.owner as IrClass).simpleFunctions() + .single { it.name == OperatorNameConventions.INVOKE } + + val invokeMethodDescriptor = WrappedSimpleFunctionDescriptor() + val invokeMethod = IrFunctionImpl( + startOffset, endOffset, + OBJC_BLOCK_FUNCTION_IMPL, + IrSimpleFunctionSymbolImpl(invokeMethodDescriptor), + overriddenInvokeMethod.name, + Visibilities.PUBLIC, Modality.FINAL, + returnType = functionType.arguments.last().typeOrNull!!, + isInline = false, isExternal = false, isTailrec = false, isSuspend = false + ) + invokeMethodDescriptor.bind(invokeMethod) + invokeMethod.overriddenSymbols += overriddenInvokeMethod.symbol + irClass.addChild(invokeMethod) + invokeMethod.createDispatchReceiverParameter() + + (0 until parameterCount).mapTo(invokeMethod.valueParameters) { index -> + val parameterDescriptor = WrappedValueParameterDescriptor() + val parameter = IrValueParameterImpl( + startOffset, endOffset, + OBJC_BLOCK_FUNCTION_IMPL, + IrValueParameterSymbolImpl(parameterDescriptor), + Name.identifier("p$index"), + index, + functionType.arguments[index].typeOrNull!!, + varargElementType = null, isCrossinline = false, isNoinline = false + ) + parameterDescriptor.bind(parameter) + parameter.parent = invokeMethod + parameter + } + + invokeMethod.body = irBuilder(stubs.irBuiltIns, invokeMethod.symbol).irBlockBody(startOffset, endOffset) { + val blockPointer = irCall(symbols.interopObjCObjectRawValueGetter.owner).apply { + extensionReceiver = irGetField(irGet(invokeMethod.dispatchReceiverParameter!!), blockHolderField) + } + + val arguments = (0 until parameterCount).map { index -> + irGet(invokeMethod.valueParameters[index]) + } + + +irReturn(callBlock(blockPointer, arguments)) + } + + irClass.addFakeOverrides() + + stubs.addKotlin(irClass) + return constructor + } + + private fun IrBuilderWithScope.callBlock(blockPtr: IrExpression, arguments: List): IrExpression { + val callBuilder = KotlinToCCallBuilder(this, stubs) + + val rawBlockPointerParameter = callBuilder.passThroughBridge(blockPtr, blockPtr.type, CTypes.id) + val blockVariableName = "block" + + arguments.forEachIndexed { index, argument -> + callBuilder.addArgument(argument, parameterValuePassings[index], variadic = false) + } + + val result = callBuilder.buildCall(blockVariableName, valueReturning) + + val blockVariableType = CTypes.blockPointer(callBuilder.cFunctionBuilder.getType()) + val blockVariable = CVariable(blockVariableType, blockVariableName) + callBuilder.cBridgeBodyLines.add(0, "$blockVariable = ${rawBlockPointerParameter.name};") + + callBuilder.emitCBridge() + + return result + } + + override fun bridgedToC(expression: String): String { + val callbackBuilder = CCallbackBuilder(stubs, location, isObjCMethod = false) + val kotlinFunctionHolder = "kotlinFunctionHolder" + + callbackBuilder.cBridgeCallBuilder.arguments += kotlinFunctionHolder + val (kotlinFunctionHolderParameter, _) = + callbackBuilder.bridgeBuilder.addParameter(symbols.nativePtrType, CTypes.id) + + callbackBuilder.kotlinCallBuilder.arguments += with(callbackBuilder.bridgeBuilder.kotlinIrBuilder) { + // TODO: consider casting to [functionType]. + irCall(symbols.interopUnwrapKotlinObjectHolderImpl.owner).apply { + putValueArgument(0, irGet(kotlinFunctionHolderParameter) ) + } + } + + parameterValuePassings.forEach { + callbackBuilder.kotlinCallBuilder.arguments += with(it) { + callbackBuilder.receiveValue() + } + } + + assert(functionType.isFunction()) + val invokeFunction = (functionType.classifier.owner as IrClass) + .simpleFunctions().single { it.name == OperatorNameConventions.INVOKE } + + callbackBuilder.buildValueReturn(invokeFunction, valueReturning) + + val block = buildString { + append('^') + append(callbackBuilder.cFunctionBuilder.buildSignature("")) + append(" { ") + callbackBuilder.cBodyLines.forEach { + append(it) + append(' ') + } + append(" }") + } + val blockAsId = if (retained) { + "(__bridge id)(__bridge_retained void*)$block" // Retain and convert to id. + } else { + "(id)$block" + } + + return "({ id $kotlinFunctionHolder = $expression; $kotlinFunctionHolder ? $blockAsId : (id)0; })" + } + + override fun cToBridged(expression: String) = expression + +} + private class WCStringArgumentPassing : KotlinToCArgumentPassing { override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression { @@ -869,6 +1309,9 @@ private fun KotlinToCCallBuilder.cValuesRefToPointer( } private object VoidReturning : ValueReturning { + override val cType: CType + get() = CTypes.void + override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression { bridgeBuilder.setReturnType(irBuilder.context.irBuiltIns.unitType, CTypes.void) cFunctionBuilder.setReturnType(CTypes.void) @@ -887,15 +1330,20 @@ private object VoidReturning : ValueReturning { internal fun CType.cast(expression: String): String = "((${this.render("")})$expression)" private fun KotlinStubs.reportUnsupportedType(reason: String, type: IrType, location: TypeLocation): Nothing { - val typeLocation: String = when (location) { + // TODO: report errors in frontend instead. + fun TypeLocation.render(): String = when (this) { is TypeLocation.FunctionArgument -> "" is TypeLocation.FunctionCallResult -> " of return value" - is TypeLocation.FunctionPointerParameter -> " of callback parameter ${location.index + 1}" + is TypeLocation.FunctionPointerParameter -> " of callback parameter ${index + 1}" is TypeLocation.FunctionPointerReturnValue -> " of callback return value" is TypeLocation.ObjCMethodParameter -> " of overridden Objective-C method parameter" is TypeLocation.ObjCMethodReturnValue -> " of overridden Objective-C method return value" + is TypeLocation.BlockParameter -> " of ${index + 1} parameter in Objective-C block type${blockLocation.render()}" + is TypeLocation.BlockReturnValue -> " of return value of Objective-C block type${blockLocation.render()}" } + val typeLocation: String = location.render() + reportError(location.element, "type ${type.toKotlinType()}$typeLocation is not supported here" + if (reason.isNotEmpty()) ": $reason" else "") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt index 2450fc7c620..cda8152bcee 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt @@ -127,6 +127,10 @@ private fun createKotlinBridge( isSuspend = false ) bridgeDescriptor.bind(bridge) + if (isExternal) { + bridge.annotations += + irCall(startOffset, endOffset, symbols.filterExceptions.owner.constructors.single(), emptyList()) + } return bridge } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt index 273e208f4cc..6efe146b791 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CSyntaxSupport.kt @@ -14,6 +14,10 @@ internal object CTypes { fun function(returnType: CType, parameterTypes: List, variadic: Boolean): CType = FunctionCType(returnType, parameterTypes, variadic) + fun blockPointer(pointee: CType): CType = object : CType { + override fun render(name: String): String = pointee.render("^$name") + } + val void = simple("void") val voidPtr = pointer(void) val signedChar = simple("signed char") @@ -28,6 +32,8 @@ internal object CTypes { val double = simple("double") val C99Bool = simple("_Bool") val char = simple("char") + + val id = simple("id") } private class SimpleCType(private val type: String) : CType { 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 b9287ad8a3f..ecca2bc2e38 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 @@ -184,6 +184,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val arrayList = symbolTable.referenceClass(getArrayListClassDescriptor(context)) val symbolName = topLevelClass(RuntimeNames.symbolName) + val filterExceptions = topLevelClass(RuntimeNames.filterExceptions) val exportForCppRuntime = topLevelClass(RuntimeNames.exportForCppRuntime) val objCMethodImp = symbolTable.referenceClass(context.interopBuiltIns.objCMethodImp) @@ -218,6 +219,16 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val interopObjcRetainAutoreleaseReturnValue = interopFunction("objc_retainAutoreleaseReturnValue") + val interopCreateObjCObjectHolder = interopFunction("createObjCObjectHolder") + + val interopCreateKotlinObjectHolder = interopFunction("createKotlinObjectHolder") + val interopUnwrapKotlinObjectHolderImpl = interopFunction("unwrapKotlinObjectHolderImpl") + + val interopCreateObjCSuperStruct = interopFunction("createObjCSuperStruct") + + val interopGetMessenger = interopFunction("getMessenger") + val interopGetMessengerStret = interopFunction("getMessengerStret") + val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass) val interopObjCObjectSuperInitCheck = 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 43f705e2b6e..4e0505cd17c 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 @@ -531,6 +531,53 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return LLVMBuildLandingPad(builder, landingpadType, personalityFunction, numClauses, name)!! } + fun filteringExceptionHandler(codeContext: CodeContext): ExceptionHandler { + val lpBlock = basicBlockInFunction("filteringExceptionHandler", position()) + + appendingTo(lpBlock) { + val landingpad = gxxLandingpad(2) + LLVMAddClause(landingpad, kotlinExceptionRtti.llvm) + LLVMAddClause(landingpad, LLVMConstNull(kInt8Ptr)) + + val fatalForeignExceptionBlock = basicBlock("fatalForeignException", position()) + val forwardKotlinExceptionBlock = basicBlock("forwardKotlinException", position()) + + val isKotlinException = icmpEq( + extractValue(landingpad, 1), + call(context.llvm.llvmEhTypeidFor, listOf(kotlinExceptionRtti.llvm)) + ) + + condBr(isKotlinException, forwardKotlinExceptionBlock, fatalForeignExceptionBlock) + + appendingTo(forwardKotlinExceptionBlock) { + // Rethrow Kotlin exception to real handler. + codeContext.genThrow(extractKotlinException(landingpad)) + } + + appendingTo(fatalForeignExceptionBlock) { + val exceptionRecord = extractValue(landingpad, 0) + call(context.llvm.cxaBeginCatchFunction, listOf(exceptionRecord)) + terminate() + } + } + + return object : ExceptionHandler.Local() { + override val unwind: LLVMBasicBlockRef + get() = lpBlock + } + } + + fun terminate() { + call(context.llvm.cxxStdTerminate, emptyList()) + + // Note: unreachable instruction to be generated here, but debug information is improper in this case. + val loopBlock = basicBlock("loop", position()) + br(loopBlock) + appendingTo(loopBlock) { + br(loopBlock) + } + } + fun kotlinExceptionHandler( block: FunctionGenerationContext.(exception: LLVMValueRef) -> Unit ): ExceptionHandler { @@ -554,6 +601,10 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, // FIXME: properly handle C++ exceptions: currently C++ exception can be thrown out from try-finally // bypassing the finally block. + return extractKotlinException(landingpadResult) + } + + private fun extractKotlinException(landingpadResult: LLVMValueRef): LLVMValueRef { val exceptionRecord = extractValue(landingpadResult, 0, "er") // __cxa_begin_catch returns pointer to C++ exception object. @@ -908,14 +959,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, LLVMSetCleanup(landingpad, 1) forwardingForeignExceptionsTerminatedWith?.let { terminator -> - val kotlinExceptionRtti = constPointer(importGlobal( - "_ZTI9ObjHolder", // typeinfo for ObjHolder - int8TypePtr, - origin = context.stdlibModule.llvmSymbolOrigin - )) - // Catch all but Kotlin exceptions. - val clause = ConstArray(int8TypePtr, listOf(kotlinExceptionRtti.bitcast(int8TypePtr))) + val clause = ConstArray(int8TypePtr, listOf(kotlinExceptionRtti)) LLVMAddClause(landingpad, clause.llvm) val bbCleanup = basicBlock("forwardException", null) @@ -951,6 +996,13 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, slotsPhi = null } + private val kotlinExceptionRtti: ConstPointer + get() = constPointer(importGlobal( + "_ZTI9ObjHolder", // typeinfo for ObjHolder + int8TypePtr, + origin = context.stdlibModule.llvmSymbolOrigin + )).bitcast(int8TypePtr) + //-------------------------------------------------------------------------// /** 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 e1cb88d3f4f..8c55ccf5ad5 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 @@ -465,6 +465,12 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { else -> "__gxx_personality_v0" } + val cxxStdTerminate = externalNounwindFunction( + "_ZSt9terminatev", // mangled C++ 'std::terminate' + functionType(voidType, false), + origin = context.standardLlvmSymbolsOrigin + ) + val gxxPersonalityFunction = externalNounwindFunction( personalityFunctionName, functionType(int32Type, true), @@ -490,6 +496,12 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { "cold", "noreturn", "nounwind" ) + val llvmEhTypeidFor = llvmIntrinsic( + "llvm.eh.typeid.for", + functionType(int32Type, false, int8TypePtr), + "nounwind", "readnone" + ) + val usedFunctions = mutableListOf() val usedGlobals = mutableListOf() val compilerUsedGlobals = mutableListOf() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt index 823025138b0..6f5c2369596 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt @@ -53,7 +53,7 @@ internal enum class IntrinsicType { OBJC_GET_MESSENGER, OBJC_GET_MESSENGER_STRET, OBJC_GET_OBJC_CLASS, - OBJC_GET_RECEIVER_OR_SUPER, + OBJC_CREATE_SUPER_STRUCT, OBJC_INIT_BY, OBJC_GET_SELECTOR, // Other @@ -224,7 +224,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv IntrinsicType.OBJC_GET_MESSENGER -> emitObjCGetMessenger(args, isStret = false) IntrinsicType.OBJC_GET_MESSENGER_STRET -> emitObjCGetMessenger(args, isStret = true) IntrinsicType.OBJC_GET_OBJC_CLASS -> emitGetObjCClass(callSite) - IntrinsicType.OBJC_GET_RECEIVER_OR_SUPER -> emitGetReceiverOrSuper(args) + IntrinsicType.OBJC_CREATE_SUPER_STRUCT -> emitObjCCreateSuperStruct(args) IntrinsicType.GET_CLASS_TYPE_INFO -> emitGetClassTypeInfo(callSite) IntrinsicType.INTEROP_READ_BITS -> emitReadBits(args) IntrinsicType.INTEROP_WRITE_BITS -> emitWriteBits(args) @@ -433,20 +433,16 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv } } - private fun FunctionGenerationContext.emitGetReceiverOrSuper(args: List): LLVMValueRef { + private fun FunctionGenerationContext.emitObjCCreateSuperStruct(args: List): LLVMValueRef { assert(args.size == 2) val receiver = args[0] val superClass = args[1] - val superClassIsNull = icmpEq(superClass, kNullInt8Ptr) - - return ifThenElse(superClassIsNull, receiver) { - val structType = structType(kInt8Ptr, kInt8Ptr) - val ptr = alloca(structType) - store(receiver, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmZero), 2, "")!!) - store(superClass, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmOne), 2, "")!!) - bitcast(int8TypePtr, ptr) - } + val structType = structType(kInt8Ptr, kInt8Ptr) + val ptr = alloca(structType) + store(receiver, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmZero), 2, "")!!) + store(superClass, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmOne), 2, "")!!) + return bitcast(int8TypePtr, ptr) } // TODO: Find better place for these guys. 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 46df093ffe8..d52ba8736cc 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 @@ -2147,7 +2147,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map, resultLifetime: Lifetime): LLVMValueRef { - val result = call(llvmFunction, args, resultLifetime) + val exceptionHandler = if (function.hasAnnotation(RuntimeNames.filterExceptions)) { + functionGenerationContext.filteringExceptionHandler(currentCodeContext) + } else { + currentCodeContext.exceptionHandler + } + + val result = call(llvmFunction, args, resultLifetime, exceptionHandler) if (!function.isSuspend && function.returnType.isNothing()) { functionGenerationContext.unreachable() } @@ -2175,8 +2181,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map, - resultLifetime: Lifetime = Lifetime.IRRELEVANT): LLVMValueRef { - return functionGenerationContext.call(function, args, resultLifetime, currentCodeContext.exceptionHandler) + resultLifetime: Lifetime = Lifetime.IRRELEVANT, + exceptionHandler: ExceptionHandler = currentCodeContext.exceptionHandler): LLVMValueRef { + return functionGenerationContext.call(function, args, resultLifetime, exceptionHandler) } //-------------------------------------------------------------------------// diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt index cb48407f7b6..56cc4b6198a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.util.constructors -import org.jetbrains.kotlin.ir.util.simpleFunctions import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe internal class KotlinObjCClassInfoGenerator(override val context: Context) : ContextUtils { @@ -70,8 +69,7 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0)) } - private fun IrClass.generateMethodDescs(): List = - this.generateOverridingMethodDescs() + this.generateImpMethodDescs() + private fun IrClass.generateMethodDescs(): List = this.generateImpMethodDescs() private fun generateInstanceMethodDescs( irClass: IrClass @@ -113,23 +111,6 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con staticData.cStringLiteral(encoding) ) - private fun generateMethodDesc(info: ObjCMethodInfo) = info.imp?.let { imp -> - ObjCMethodDesc( - info.selector, - info.encoding, - context.llvm.externalFunction( - imp, - functionType(voidType), - origin = info.bridge.llvmSymbolOrigin, - independent = true - ) - ) - } - - private fun IrClass.generateOverridingMethodDescs(): List = - this.simpleFunctions().filter { it.isReal } - .mapNotNull { it.getObjCMethodInfo() }.mapNotNull { generateMethodDesc(it) } - private fun IrClass.generateImpMethodDescs(): List = this.declarations .filterIsInstance() .mapNotNull { 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 e15541edd0a..a2a5d6fa797 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 @@ -11,10 +11,7 @@ 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.cgen.KotlinStubs -import org.jetbrains.kotlin.backend.konan.cgen.generateCCall -import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionAndFakeKotlinExternalFunction -import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionPointer +import org.jetbrains.kotlin.backend.konan.cgen.* import org.jetbrains.kotlin.backend.konan.getInlinedClass import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions import org.jetbrains.kotlin.backend.konan.descriptors.isInterface @@ -28,7 +25,6 @@ import org.jetbrains.kotlin.builtins.UnsignedTypes import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl @@ -56,7 +52,48 @@ import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny -internal class InteropLoweringPart1(val context: Context) : IrBuildingTransformer(context), FileLoweringPass { +internal abstract class BaseInteropIrTransformer(private val context: Context) : IrBuildingTransformer(context) { + + protected inline fun generateWithStubs(element: IrElement? = null, block: KotlinStubs.() -> T): T = + createKotlinStubs(element).block() + + protected fun createKotlinStubs(element: IrElement?): KotlinStubs { + val location = if (element != null) { + element.getCompilerMessageLocation(irFile) + } else { + builder.getCompilerMessageLocation() + } + + return object : KotlinStubs { + override val irBuiltIns get() = context.irBuiltIns + override val symbols get() = context.ir.symbols + + override fun addKotlin(declaration: IrDeclaration) { + addTopLevel(declaration) + } + + override fun addC(lines: List) { + context.cStubsManager.addStub(location, lines) + } + + override fun getUniqueCName(prefix: String) = + "_${context.moduleDescriptor.namePrefix}_${context.cStubsManager.getUniqueName(prefix)}" + + override fun getUniqueKotlinFunctionReferenceClassName(prefix: String) = + "$prefix${context.functionReferenceCount++}" + + override val target get() = context.config.target + + override fun reportError(location: IrElement, message: String): Nothing = + context.reportCompilationError(message, irFile, location) + } + } + + protected abstract val irFile: IrFile + protected abstract fun addTopLevel(declaration: IrDeclaration) +} + +internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransformer(context), FileLoweringPass { private val symbols get() = context.ir.symbols private val symbolTable get() = symbols.symbolTable @@ -64,6 +101,15 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme lateinit var currentFile: IrFile private val topLevelInitializers = mutableListOf() + private val newTopLevelDeclarations = mutableListOf() + + override val irFile: IrFile + get() = currentFile + + override fun addTopLevel(declaration: IrDeclaration) { + declaration.parent = currentFile + newTopLevelDeclarations += declaration + } override fun lower(irFile: IrFile) { currentFile = irFile @@ -71,6 +117,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme topLevelInitializers.forEach { irFile.addTopLevelInitializer(it, context, false) } topLevelInitializers.clear() + + irFile.addChildren(newTopLevelDeclarations) + newTopLevelDeclarations.clear() } private fun IrBuilderWithScope.callAlloc(classPtr: IrExpression): IrExpression = @@ -530,7 +579,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass) - val initMethod = expression.descriptor.getObjCInitMethod()!! + val initMethod = expression.symbol.owner.getObjCInitMethod()!! if (!expression.symbol.owner.objCConstructorIsDesignated()) { context.reportCompilationError( @@ -549,7 +598,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme initMethodInfo, superQualifier = symbolTable.referenceClass(expression.descriptor.constructedClass), receiver = builder.getRawPtr(builder.irGet(constructedClass.thisReceiver!!)), - arguments = initMethod.valueParameters.map { expression.getValueArgument(it)!! } + arguments = initMethod.valueParameters.map { expression.getValueArgument(it.index)!! }, + call = expression, + method = initMethod ) val superConstructor = symbolTable.referenceConstructor( @@ -577,25 +628,24 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme return expression } - private fun IrBuilderWithScope.genLoweredObjCMethodCall(info: ObjCMethodInfo, superQualifier: IrClassSymbol?, - receiver: IrExpression, arguments: List): IrExpression { - - val superClass = superQualifier?.let { getObjCClass(it) } ?: - irCall(symbols.getNativeNullPtr, symbols.nativePtrType) - - val bridge = symbols.lazySymbolTable.referenceSimpleFunction(info.bridge) - return irCall(bridge, symbolTable.translateErased(info.bridge.returnType!!)).apply { - putValueArgument(0, superClass) - putValueArgument(1, receiver) - putValueArgument(2, irCall(symbols.interopObjCGetSelector.owner).apply { - putValueArgument(0, irString(info.selector)) - }) - - assert(arguments.size + 3 == info.bridge.valueParameters.size) - arguments.forEachIndexed { index, argument -> - putValueArgument(index + 3, argument) - } - } + private fun IrBuilderWithScope.genLoweredObjCMethodCall( + info: ObjCMethodInfo, + superQualifier: IrClassSymbol?, + receiver: IrExpression, + arguments: List, + call: IrFunctionAccessExpression, + method: IrSimpleFunction + ): IrExpression = generateWithStubs(call) { + this.generateObjCCall( + this@genLoweredObjCMethodCall, + method, + info.isStret, + info.selector, + call, + superQualifier, + receiver, + arguments + ) } override fun visitCall(expression: IrCall): IrExpression { @@ -603,19 +653,20 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme val descriptor = expression.descriptor.original - if (descriptor is ConstructorDescriptor) { - val initMethod = descriptor.getObjCInitMethod() + val callee = expression.symbol.owner + if (callee is IrConstructor) { + val initMethod = callee.getObjCInitMethod() if (initMethod != null) { val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! } assert(expression.extensionReceiver == null) assert(expression.dispatchReceiver == null) - val constructedClass = descriptor.constructedClass + val constructedClass = callee.constructedClass val initMethodInfo = initMethod.getExternalObjCMethodInfo()!! return builder.at(expression).run { - val classPtr = getObjCClass(symbolTable.referenceClass(constructedClass)) - irForceNotNull(callAllocAndInit(classPtr, initMethodInfo, arguments)) + val classPtr = getObjCClass(constructedClass.symbol) + irForceNotNull(callAllocAndInit(classPtr, initMethodInfo, arguments, expression, initMethod)) } } } @@ -626,7 +677,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme return builder.at(expression).run { val classPtr = getRawPtr(expression.extensionReceiver!!) - callAllocAndInit(classPtr, initMethodInfo, arguments) + callAllocAndInit(classPtr, initMethodInfo, arguments, expression, callee as IrSimpleFunction) } } @@ -662,7 +713,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme methodInfo, superQualifier = expression.superQualifierSymbol, receiver = builder.getRawPtr(expression.dispatchReceiver ?: expression.extensionReceiver!!), - arguments = arguments + arguments = arguments, + call = expression, + method = callee as IrSimpleFunction ) } } @@ -722,15 +775,19 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme private fun IrBuilderWithScope.callAllocAndInit( classPtr: IrExpression, initMethodInfo: ObjCMethodInfo, - arguments: List - ): IrExpression = irBlock { + arguments: List, + call: IrFunctionAccessExpression, + initMethod: IrSimpleFunction + ): IrExpression = irBlock(startOffset, endOffset) { val allocated = irTemporaryVar(callAlloc(classPtr)) val initCall = genLoweredObjCMethodCall( initMethodInfo, superQualifier = null, receiver = irGet(allocated), - arguments = arguments + arguments = arguments, + call = call, + method = initMethod ) +IrTryImpl(startOffset, endOffset, initCall.type).apply { @@ -759,43 +816,16 @@ internal class InteropLoweringPart2(val context: Context) : FileLoweringPass { } } -private class InteropTransformer(val context: Context, val irFile: IrFile) : IrBuildingTransformer(context) { +private class InteropTransformer(val context: Context, override val irFile: IrFile) : BaseInteropIrTransformer(context) { val newTopLevelDeclarations = mutableListOf() val interop = context.interopBuiltIns val symbols = context.ir.symbols - private inline fun generateWithStubs(element: IrElement? = null, block: KotlinStubs.() -> T): T = - createKotlinStubs(element).block() - - private fun createKotlinStubs(element: IrElement?): KotlinStubs { - val location = if (element != null) { - element.getCompilerMessageLocation(irFile) - } else { - builder.getCompilerMessageLocation() - } - - return object : KotlinStubs { - override val irBuiltIns get() = context.irBuiltIns - override val symbols get() = context.ir.symbols - - override fun addKotlin(declaration: IrDeclaration) { - newTopLevelDeclarations += declaration - } - - override fun addC(lines: List) { - context.cStubsManager.addStub(location, lines) - } - - override fun getUniqueCName(prefix: String) = - "_${context.moduleDescriptor.namePrefix}_${context.cStubsManager.getUniqueName(prefix)}" - - override val target get() = context.config.target - - override fun reportError(location: IrElement, message: String): Nothing = - context.reportCompilationError(message, irFile, location) - } + override fun addTopLevel(declaration: IrDeclaration) { + declaration.parent = irFile + newTopLevelDeclarations += declaration } override fun visitClass(declaration: IrClass): IrStatement { @@ -804,7 +834,7 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB val imps = declaration.simpleFunctions().filter { it.isReal }.flatMap { function -> function.overriddenSymbols.mapNotNull { val info = it.owner.getExternalObjCMethodInfo() - if (info == null || info.imp != null) { + if (info == null) { null } else { generateWithStubs(it.owner) { diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index dd914aa3cf6..8258feb4945 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -3112,13 +3112,14 @@ task interop_opengl_teapot(type: RunStandaloneKonanTest) { task interop_objc_smoke(type: RunInteropKonanTest) { disabled = !isAppleTarget(project) - goldValue = "84\nFoo\nHello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" + + goldValue = "84\nFoo\nDeallocated\n" + + "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" + "true\ntrue\n" + "Global string\nAnother global string\nnull\nglobal object\n" + "5\n" + "String macro\n" + "CFString macro\n" + - "Deallocated\nDeallocated\nDeallocated\n" + "Deallocated\nDeallocated\n" if (isAppleTarget(project)) { source = "interop/objc/smoke.kt" diff --git a/backend.native/tests/interop/objc/smoke.h b/backend.native/tests/interop/objc/smoke.h index 999fd010c13..a176feb3728 100644 --- a/backend.native/tests/interop/objc/smoke.h +++ b/backend.native/tests/interop/objc/smoke.h @@ -99,6 +99,7 @@ int callPlusOneBlock(id blockConsumer, int argument) { -(id)returnRetained:(id)obj __attribute__((ns_returns_retained)); -(void)consume:(id) __attribute__((ns_consumed)) obj; -(void)consumeSelf __attribute__((ns_consumes_self)); +-(void (^)(void))returnRetainedBlock:(void (^)(void))block __attribute__((ns_returns_retained)); @end; extern BOOL unexpectedDeallocation; @@ -106,11 +107,41 @@ extern BOOL unexpectedDeallocation; @interface MustNotBeDeallocated : NSObject @end; +@interface CustomRetainMethodsImpl : MustNotBeDeallocated +@end; + +static MustNotBeDeallocated* retainedObj; +static void (^retainedBlock)(void); + void useCustomRetainMethods(id p) { MustNotBeDeallocated* obj = [MustNotBeDeallocated new]; - CFBridgingRetain(obj); // Over-retain to detect possible over-release. + retainedObj = obj; // Retain to detect possible over-release. [p returnRetained:obj]; [p consume:p]; [p consumeSelf]; -} \ No newline at end of file + + MustNotBeDeallocated* capturedObj = [MustNotBeDeallocated new]; + retainedBlock = ^{ [capturedObj description]; }; // Retain to detect possible over-release. + [p returnRetainedBlock:retainedBlock](); +} + +NSObject* createNSObject() { + return [NSObject new]; +} + +@protocol ExceptionThrower +-(void)throwException; +@end; + +@interface ExceptionThrowerManager : NSObject ++(void)throwExceptionWith:(id)thrower; +@end; + +@interface Blocks : NSObject ++(BOOL)blockIsNull:(void (^)(void))block; ++(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block; + +@property (class) void (^nullBlock)(void); +@property (class) void (^notNullBlock)(void); +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 867f9342217..eaa62670d1e 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -18,6 +18,9 @@ fun run() { testTypeOps() testConversions() testWeakRefs() + testExceptions() + testBlocks() + testCustomRetain() assertEquals(2, ForwardDeclaredEnum.TWO.value) @@ -88,24 +91,6 @@ fun run() { override fun create() = autoreleasepool { NSObject() } }) - assertEquals(222, callProvidedBlock(object : NSObject(), BlockProviderProtocol { - override fun block(): (Int) -> Int = { it * 2 } - }, 111)) - - assertEquals(322, callPlusOneBlock(object : NSObject(), BlockConsumerProtocol { - override fun callBlock(block: ((Int) -> Int)?, argument: Int) = block!!(argument) - }, 321)) - - autoreleasepool { - useCustomRetainMethods(object : Foo(), CustomRetainMethodsProtocol { - override fun returnRetained(obj: Any?) = obj - override fun consume(obj: Any?) {} - override fun consumeSelf() {} - }) - } - - assertFalse(unexpectedDeallocation) - } fun MutablePairProtocol.swap() { @@ -185,7 +170,7 @@ fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: An } fun testWeakRefs() { - testWeakReference({ NSObject.new()!! }) + testWeakReference({ createNSObject()!! }) createAndAbandonWeakRef(NSObject()) @@ -213,6 +198,64 @@ fun createAndAbandonWeakRef(obj: NSObject) { WeakReference(obj) } +fun testExceptions() { + assertFailsWith { + ExceptionThrowerManager.throwExceptionWith(object : NSObject(), ExceptionThrowerProtocol { + override fun throwException() { + throw MyException() + } + }) + } +} + +fun testBlocks() { + assertTrue(Blocks.blockIsNull(null)) + assertFalse(Blocks.blockIsNull({})) + + assertEquals(null, Blocks.nullBlock) + assertNotEquals(null, Blocks.notNullBlock) + + assertEquals(10, Blocks.same({ a, b, c, d -> a + b + c + d })!!(1, 2, 3, 4)) + + assertEquals(222, callProvidedBlock(object : NSObject(), BlockProviderProtocol { + override fun block(): (Int) -> Int = { it * 2 } + }, 111)) + + assertEquals(322, callPlusOneBlock(object : NSObject(), BlockConsumerProtocol { + override fun callBlock(block: ((Int) -> Int)?, argument: Int) = block!!(argument) + }, 321)) +} + +private lateinit var retainedMustNotBeDeallocated: MustNotBeDeallocated + +fun testCustomRetain() { + fun test() { + useCustomRetainMethods(object : Foo(), CustomRetainMethodsProtocol { + override fun returnRetained(obj: Any?) = obj + override fun consume(obj: Any?) {} + override fun consumeSelf() {} + override fun returnRetainedBlock(block: (() -> Unit)?) = block + }) + + CustomRetainMethodsImpl().let { + it.returnRetained(Any()) + retainedMustNotBeDeallocated = MustNotBeDeallocated() // Retain to detect possible over-release. + it.consume(retainedMustNotBeDeallocated) + it.consumeSelf() + it.returnRetainedBlock({})!!() + } + } + + autoreleasepool { + test() + kotlin.native.internal.GC.collect() + } + + assertFalse(unexpectedDeallocation) +} + +private class MyException : Throwable() + fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply { elements.forEach { this.addObject(it as ObjCObject) diff --git a/backend.native/tests/interop/objc/smoke.m b/backend.native/tests/interop/objc/smoke.m index ce9d5509055..4a0485800d6 100644 --- a/backend.native/tests/interop/objc/smoke.m +++ b/backend.native/tests/interop/objc/smoke.m @@ -76,3 +76,47 @@ BOOL unexpectedDeallocation = NO; unexpectedDeallocation = YES; } @end; + +static CustomRetainMethodsImpl* retainedCustomRetainMethodsImpl; + +@implementation CustomRetainMethodsImpl +-(id)returnRetained:(id)obj __attribute__((ns_returns_retained)) { + return obj; +} + +-(void)consume:(id) __attribute__((ns_consumed)) obj { +} + +-(void)consumeSelf __attribute__((ns_consumes_self)) { + retainedCustomRetainMethodsImpl = self; // Retain to detect possible over-release. +} + +-(void (^)(void))returnRetainedBlock:(void (^)(void))block __attribute__((ns_returns_retained)) { + return block; +} +@end; + +@implementation ExceptionThrowerManager ++(void)throwExceptionWith:(id)thrower { + [thrower throwException]; +} +@end; + +@implementation Blocks ++(BOOL)blockIsNull:(void (^)(void))block { + return block == nil; +} + ++(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block { + return block; +} + ++(void (^)(void)) nullBlock { + return nil; +} + ++(void (^)(void)) notNullBlock { + return ^{}; +} + +@end; \ No newline at end of file diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index debcea57057..b6043d8812f 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -643,6 +643,12 @@ extern "C" ALWAYS_INLINE OBJ_GETTER(Kotlin_Interop_refFromObjC, id obj) { RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, obj); } +extern "C" OBJ_GETTER(Kotlin_Interop_CreateObjCObjectHolder, id obj) { + RuntimeAssert(obj != nullptr, "wrapped object must not be null"); + const TypeInfo* typeInfo = theForeignObjCObjectTypeInfo; + RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, typeInfo, objc_retain(obj)); +} + extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj) { if (obj == nullptr) RETURN_OBJ(nullptr); id convertible = (id)obj; diff --git a/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt b/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt index 476147bd4a0..98dde3dc87c 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt @@ -112,4 +112,14 @@ internal annotation class TypedIntrinsic(val kind: String) */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.BINARY) -annotation class Independent \ No newline at end of file +annotation class Independent + +/** + * Indicates that `@SymbolName external` function can throw foreign exception to be filtered on callsite. + * + * Note: this annotation describes rather behaviour of the (direct) call than that of the function. + * E.g. it doesn't have any effect when calling the function virtually. TODO: rework. + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +@PublishedApi internal annotation class FilterExceptions \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt b/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt index 20fc3589359..f69e5bcd12d 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt @@ -40,7 +40,7 @@ class IntrinsicType { const val OBJC_GET_MESSENGER = "OBJC_GET_MESSENGER" const val OBJC_GET_MESSENGER_STRET = "OBJC_GET_MESSENGER_STRET" const val OBJC_GET_OBJC_CLASS = "OBJC_GET_OBJC_CLASS" - const val OBJC_GET_RECEIVER_OR_SUPER = "OBJC_GET_RECEIVER_OR_SUPER" + const val OBJC_CREATE_SUPER_STRUCT = "OBJC_CREATE_SUPER_STRUCT" const val OBJC_INIT_BY = "OBJC_INIT_BY" const val OBJC_GET_SELECTOR = "OBJC_GET_SELECTOR"