From fc96eb6d8dcad71746377666d16536c2c1457641 Mon Sep 17 00:00:00 2001 From: Gleb Lukianets Date: Thu, 2 Mar 2023 10:36:53 +0000 Subject: [PATCH] [KN] CInterop: __attribute__((objc_direct)) support Merge-request: KT-MR-8828 Merged-by: Gleb Lukianets --- .../kotlin/native/interop/indexer/Indexer.kt | 20 ++++--- .../native/interop/indexer/NativeIndex.kt | 2 +- .../kotlin/kotlinx/cinterop/ObjectiveCImpl.kt | 4 ++ .../kotlin/native/interop/gen/ObjCStubs.kt | 14 ++++- .../kotlin/native/interop/gen/StubIr.kt | 3 + .../interop/gen/StubIrMetadataEmitter.kt | 3 + .../native/interop/gen/StubIrTextEmitter.kt | 1 + .../interop/gen/ObjCMethodSignaturesTest.kt | 60 +++++++++++++++++++ .../kotlin/backend/konan/ObjCInterop.kt | 38 +++++++++--- .../kotlin/backend/konan/cgen/CBridgeGen.kt | 54 +++++++++++------ .../backend/konan/lower/InteropLowering.kt | 2 + .../backend.native/tests/build.gradle | 21 +++++++ .../tests/filecheck/objc_direct.kt | 26 ++++++++ .../tests/interop/objc/direct/direct.def | 2 + .../tests/interop/objc/direct/direct.h | 32 ++++++++++ .../tests/interop/objc/direct/direct.m | 27 +++++++++ .../tests/interop/objc/direct/main.kt | 46 ++++++++++++++ 17 files changed, 319 insertions(+), 36 deletions(-) create mode 100644 kotlin-native/Interop/StubGenerator/src/test/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCMethodSignaturesTest.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/objc_direct.kt create mode 100644 kotlin-native/backend.native/tests/interop/objc/direct/direct.def create mode 100644 kotlin-native/backend.native/tests/interop/objc/direct/direct.h create mode 100644 kotlin-native/backend.native/tests/interop/objc/direct/direct.m create mode 100644 kotlin-native/backend.native/tests/interop/objc/direct/main.kt diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 0e4bc71da4f..6b88aad3096 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -1150,14 +1150,15 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole } return ObjCMethod( - selector, encoding, parameters, returnType, - isVariadic = clang_Cursor_isVariadic(cursor) != 0, - isClass = isClass, - nsConsumesSelf = clang_Cursor_isObjCConsumingSelfMethod(cursor) != 0, - nsReturnsRetained = clang_Cursor_isObjCReturningRetainedMethod(cursor) != 0, - isOptional = (clang_Cursor_isObjCOptional(cursor) != 0), - isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0), - isExplicitlyDesignatedInitializer = hasAttribute(cursor, OBJC_DESGINATED_INITIALIZER) + selector, encoding, parameters, returnType, + isVariadic = clang_Cursor_isVariadic(cursor) != 0, + isClass = isClass, + nsConsumesSelf = clang_Cursor_isObjCConsumingSelfMethod(cursor) != 0, + nsReturnsRetained = clang_Cursor_isObjCReturningRetainedMethod(cursor) != 0, + isOptional = (clang_Cursor_isObjCOptional(cursor) != 0), + isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0), + isExplicitlyDesignatedInitializer = hasAttribute(cursor, OBJC_DESIGNATED_INITIALIZER), + isDirect = hasAttribute(cursor, OBJC_DIRECT), ) } @@ -1201,7 +1202,8 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole } private val NS_CONSUMED = "ns_consumed" - private val OBJC_DESGINATED_INITIALIZER = "objc_designated_initializer" + private val OBJC_DESIGNATED_INITIALIZER = "objc_designated_initializer" + private val OBJC_DIRECT = "objc_direct" private fun hasAttribute(cursor: CValue, name: String): Boolean { var result = false diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index 097ea8044d4..d8a8b368abd 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -257,7 +257,7 @@ sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclar data class ObjCMethod( val selector: String, val encoding: String, val parameters: List, private val returnType: Type, val isVariadic: Boolean, val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean, - val isOptional: Boolean, val isInit: Boolean, val isExplicitlyDesignatedInitializer: Boolean + val isOptional: Boolean, val isInit: Boolean, val isExplicitlyDesignatedInitializer: Boolean, val isDirect: Boolean ) { fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType diff --git a/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 41d85fe34ce..f32cf4bf175 100644 --- a/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -112,6 +112,10 @@ annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryNa @Retention(AnnotationRetention.BINARY) annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false) +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +annotation class ObjCDirect(val symbol: String) + @Target(AnnotationTarget.CONSTRUCTOR) @Retention(AnnotationRetention.BINARY) annotation class ObjCConstructor(val initSelector: String, val designated: Boolean) diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index 019371eb74b..77a2e791d17 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -132,7 +132,7 @@ private class ObjCMethodStubBuilder( kotlinMethodParameters = method.getKotlinParameters(context, forConstructorOrFactory = false) external = (container !is ObjCProtocol) modality = when (container) { - is ObjCClass -> MemberStubModality.OPEN + is ObjCClass -> if (method.isDirect) MemberStubModality.FINAL else MemberStubModality.OPEN is ObjCProtocol -> if (method.isOptional) MemberStubModality.OPEN else MemberStubModality.ABSTRACT is ObjCCategory -> MemberStubModality.FINAL } @@ -145,7 +145,17 @@ private class ObjCMethodStubBuilder( private fun buildObjCMethodAnnotations(main: AnnotationStub): List = listOfNotNull( main, AnnotationStub.ObjC.ConsumesReceiver.takeIf { method.nsConsumesSelf }, - AnnotationStub.ObjC.ReturnsRetained.takeIf { method.nsReturnsRetained } + AnnotationStub.ObjC.ReturnsRetained.takeIf { method.nsReturnsRetained }, + if (method.isDirect) { + when (container) { + is ObjCClass -> container.name + is ObjCCategory -> container.clazz.name + is ObjCProtocol -> null + }?.let { + val prefix = if (method.isClass) '+' else '-' + AnnotationStub.ObjC.Direct("$prefix[$it ${method.selector}]") + } + } else { null }, ) fun isDefaultConstructor(): Boolean = diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt index 0ad20d69bd0..f10e70c4521 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt @@ -185,6 +185,9 @@ sealed class AnnotationStub(val classifier: Classifier) { class Method(val selector: String, val encoding: String, val isStret: Boolean = false) : ObjC(Classifier.topLevel(cinteropPackage, "ObjCMethod")) + class Direct(val symbol: String) : + ObjC(Classifier.topLevel(cinteropPackage, "ObjCDirect")) + class Factory(val selector: String, val encoding: String, val isStret: Boolean = false) : ObjC(Classifier.topLevel(cinteropPackage, "ObjCFactory")) diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt index a710d02c183..fb92921ec24 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt @@ -394,6 +394,9 @@ private class MappingExtensions( ("encoding" to encoding).asAnnotationArgument(), ("isStret" to KmAnnotationArgument.BooleanValue(isStret)) ) + is AnnotationStub.ObjC.Direct -> mapOfNotNull( + ("symbol" to symbol).asAnnotationArgument(), + ) is AnnotationStub.ObjC.Factory -> mapOfNotNull( ("selector" to selector).asAnnotationArgument(), ("encoding" to encoding).asAnnotationArgument(), diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt index cf669376c96..1871ea0887e 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt @@ -458,6 +458,7 @@ class StubIrTextEmitter( val encoding = annotationStub.encoding.quoteAsKotlinLiteral() "@ObjCMethod($selector, $encoding$stret)" } + is AnnotationStub.ObjC.Direct -> "@ObjCDirect(${annotationStub.symbol.quoteAsKotlinLiteral()})" is AnnotationStub.ObjC.Factory -> { val stret = if (annotationStub.isStret) ", true" else "" val selector = annotationStub.selector.quoteAsKotlinLiteral() diff --git a/kotlin-native/Interop/StubGenerator/src/test/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCMethodSignaturesTest.kt b/kotlin-native/Interop/StubGenerator/src/test/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCMethodSignaturesTest.kt new file mode 100644 index 00000000000..d739eb5add9 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCMethodSignaturesTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.native.interop.indexer.buildNativeIndex +import org.junit.Assume +import org.junit.BeforeClass +import org.junit.Test +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ObjCMethodSignaturesTest : InteropTestsBase() { + companion object { + @BeforeClass + @JvmStatic + fun assumeMacOS() { + Assume.assumeTrue(HostManager.hostIsMac) + } + } + + @Test + fun `ObjC Direct`() { + val files = TempFiles("ObjCDirect") + files.file("header.h", """ + #import + + @interface Foo : NSObject + + (void)direct __attribute__((objc_direct)); + - (void)direct __attribute__((objc_direct)); + @end + + @interface Foo(Ext) + + (void)directExt __attribute__((objc_direct)); + - (void)directExt __attribute__((objc_direct)); + @end + """.trimIndent()) + val defFile = files.file("direct.def", """ + language = Objective-C + headers = header.h + """.trimIndent()) + val library = buildNativeLibraryFrom(defFile, files.directory) + val index = buildNativeIndex(library, false).index + + index.objCClasses.find { it.name == "Foo" }.let { cls -> + assertNotNull(cls, "Class 'Foo' not found in native library $library") + assertNotNull(cls.methods.find { it.selector == "direct" && it.isClass && it.isDirect }) + assertNotNull(cls.methods.find { it.selector == "direct" && !it.isClass && it.isDirect }) + } + + index.objCCategories.find { it.name == "Ext" && it.clazz.name == "Foo" }.let { cat -> + assertNotNull(cat, "Category 'Foo(Ext)' not found in native library $library") + assertNotNull(cat.methods.find { it.selector == "directExt" && it.isClass && it.isDirect }) + assertNotNull(cat.methods.find { it.selector == "directExt" && !it.isClass && it.isDirect }) + } + } +} \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt index caa183eda75..596e78bf19a 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt @@ -37,6 +37,7 @@ private val objCProtocolFqName = interopPackageName.child(Name.identifier("ObjCP private val objCProtocolIdSignature = getTopLevelPublicSignature(objCProtocolFqName) internal val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass")) private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod")) +private val objCDirectFqName = interopPackageName.child(Name.identifier("ObjCDirect")) private val objCConstructorFqName = FqName("kotlinx.cinterop.ObjCConstructor") private val objCFactoryFqName = interopPackageName.child(Name.identifier("ObjCFactory")) private val objcnamesForwardDeclarationsPackageName = Name.identifier("objcnames") @@ -111,30 +112,53 @@ fun IrClass.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExterna data class ObjCMethodInfo(val selector: String, val encoding: String, - val isStret: Boolean) + val isStret: Boolean, + val directSymbol: String?) private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? { assert (this.kind.isReal) - val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null - return objCMethodInfo(methodAnnotation) + + val methodInfo = this.annotations.findAnnotation(objCMethodFqName)?.let { + ObjCMethodInfo( + selector = it.getStringValue("selector"), + encoding = it.getStringValue("encoding"), + isStret = it.getArgumentValueOrNull("isStret") ?: false, + directSymbol = this.annotations.findAnnotation(objCDirectFqName)?.let { + it.getStringValue("symbol") + }, + ) + } + + return methodInfo } private fun IrFunction.decodeObjCMethodAnnotation(): ObjCMethodInfo? { assert (this.isReal) - val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null - return objCMethodInfo(methodAnnotation) + + val methodInfo = this.annotations.findAnnotation(objCMethodFqName)?.let { + ObjCMethodInfo( + selector = it.getAnnotationStringValue("selector"), + encoding = it.getAnnotationStringValue("encoding"), + isStret = it.getAnnotationValueOrNull("isStret") ?: false, + directSymbol = this.annotations.findAnnotation(objCDirectFqName)?.getAnnotationStringValue("symbol"), + ) + } + + return methodInfo } private fun objCMethodInfo(annotation: AnnotationDescriptor) = ObjCMethodInfo( selector = annotation.getStringValue("selector"), encoding = annotation.getStringValue("encoding"), - isStret = annotation.getArgumentValueOrNull("isStret") ?: false + isStret = annotation.getArgumentValueOrNull("isStret") ?: false, + directSymbol = null, ) private fun objCMethodInfo(annotation: IrConstructorCall) = ObjCMethodInfo( selector = annotation.getAnnotationStringValue("selector"), encoding = annotation.getAnnotationStringValue("encoding"), - isStret = annotation.getAnnotationValueOrNull("isStret") ?: false + isStret = annotation.getAnnotationValueOrNull("isStret") ?: false, + directSymbol = null, ) /** diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt index 7e55df8e8f7..24ec8bca6fa 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt @@ -307,12 +307,15 @@ internal fun KotlinStubs.generateObjCCall( method: IrSimpleFunction, isStret: Boolean, selector: String, + directSymbolName: String?, call: IrFunctionAccessExpression, superQualifier: IrClassSymbol?, receiver: ObjCCallReceiver, arguments: List ) = builder.irBlock { val resolved = method.resolveFakeOverride(allowAbstract = true)?: method + val isDirect = directSymbolName != null + val exceptionMode = ForeignExceptionMode.byValue( resolved.konanLibrary?.manifestProperties ?.getProperty(ForeignExceptionMode.manifestKey) @@ -325,20 +328,23 @@ internal fun KotlinStubs.generateObjCCall( isMutable = true ) - val messenger = irCall(if (isStret) { - symbols.interopGetMessengerStret - } else { - symbols.interopGetMessenger - }.owner).apply { - putValueArgument(0, irGet(superClass)) // TODO: check superClass statically. - } + val targetPtrParameter = if (!isDirect) { + val messenger = irCall(if (isStret) { + symbols.interopGetMessengerStret + } else { + symbols.interopGetMessenger + }.owner).apply { + putValueArgument(0, irGet(superClass)) // TODO: check superClass statically. + } - val targetPtrParameter = callBuilder.passThroughBridge( - messenger, - symbols.interopCPointer.starProjectedType, - CTypes.voidPtr - ).name - val targetFunctionName = "targetPtr" + callBuilder.passThroughBridge( + messenger, + symbols.interopCPointer.starProjectedType, + CTypes.voidPtr + ).name + } else { + null + } val preparedReceiver = if (method.objCConsumesReceiver()) { when (receiver) { @@ -381,17 +387,31 @@ internal fun KotlinStubs.generateObjCCall( receiverOrSuper, symbols.nativePtrType, CTypes.voidPtr).name callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr) - callBuilder.cCallBuilder.arguments += "@selector($selector)" - callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr) + if (isDirect) { + callBuilder.cCallBuilder.arguments += "0" + callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr) + } else { + callBuilder.cCallBuilder.arguments += "@selector($selector)" + callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr) + } callBuilder.addArguments(arguments, method) val returnValuePassing = mapReturnType(method.returnType, call, signature = method) + val targetFunctionName = getUniqueCName("knbridge_targetPtr") + val result = callBuilder.buildCall(targetFunctionName, returnValuePassing) - val targetFunctionVariable = CVariable(CTypes.pointer(callBuilder.cFunctionBuilder.getType()), targetFunctionName) - callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = $targetPtrParameter;") + if (isDirect) { + // This declares a function + val targetFunctionVariable = CVariable(callBuilder.cFunctionBuilder.getType(), targetFunctionName) + callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable __asm(\"$directSymbolName\");") + + } else { + val targetFunctionVariable = CVariable(CTypes.pointer(callBuilder.cFunctionBuilder.getType()), targetFunctionName) + callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = $targetPtrParameter;") + } callBuilder.emitCBridge() diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 07b81112883..6bde91ea5af 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -551,6 +551,7 @@ private class InteropLoweringPart1(val generationState: NativeGenerationState) : method, info.isStret, info.selector, + info.directSymbol, call, superQualifier, receiver, @@ -626,6 +627,7 @@ private class InteropLoweringPart1(val generationState: NativeGenerationState) : require(expression.superQualifierSymbol?.owner?.isInterface != true) { renderCompilerError(expression) } builder.at(expression) + return builder.genLoweredObjCMethodCall( methodInfo, superQualifier = expression.superQualifierSymbol, diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 2d81c56bd95..ddd84e1952a 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -4379,6 +4379,13 @@ if (PlatformInfo.isAppleTarget(project)) { it.headers "$projectDir/interop/objc/msg_send/messaging.h" } + createInterop("objcDirect") { + it.defFile 'interop/objc/direct/direct.def' + it.headers "$projectDir/interop/objc/direct/direct.h" + it.extraOpts "-Xcompile-source", "$projectDir/interop/objc/direct/direct.m", "-Xsource-compiler-option", "-DNS_FORMAT_ARGUMENT(A)=" + it.compilerOpts '-DNS_FORMAT_ARGUMENT(A)=' + } + createInterop("foreignException") { it.defFile 'interop/objc/foreignException/objc_wrap.def' it.headers "$projectDir/interop/objc/foreignException/objc_wrap.h" @@ -5081,6 +5088,11 @@ if (PlatformInfo.isAppleTarget(project)) { } } + interopTest("interop_objc_direct") { + source = "interop/objc/direct/main.kt" + interop = "objcDirect" + } + interopTest("interop_objc_foreignException") { disabled = isK2(project) // KT-55909 source = "interop/objc/foreignException/objc_wrap.kt" @@ -6583,6 +6595,15 @@ fileCheckTest("filecheck_constants_merge") { annotatedSource = project.file('filecheck/constants_merge.kt') } + +fileCheckTest("filecheck_objc_direct") { + enabled = enabled && cacheTesting == null + annotatedSource = project.file('filecheck/objc_direct.kt') + interop = "objcDirect" + enabled = target.family.appleFamily +} + + dependencies { nopPluginApi kotlinCompilerModule nopPluginApi project(":native:kotlin-native-utils") diff --git a/kotlin-native/backend.native/tests/filecheck/objc_direct.kt b/kotlin-native/backend.native/tests/filecheck/objc_direct.kt new file mode 100644 index 00000000000..e699035e074 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/objc_direct.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +import direct.* +import kotlinx.cinterop.* + +//CHECK-LABEL: kfun:#callDirect(){}kotlin.ULong +fun callDirect(): ULong { + val cc = CallingConventions() + //CHECK: invoke i64 @_{{[a-zA-Z0-9]+}}_knbridge{{[0-9]+}}(i8* %{{[0-9]+}}, i64 42) + return cc.direct(42) +} + +//CHECK-LABEL: kfun:#callRegular(){}kotlin.ULong +fun callRegular(): ULong { + val cc = CallingConventions() + //CHECK: invoke i64 @_{{[a-zA-Z0-9]+}}_knbridge{{[0-9]+}}(i8* %{{[0-9]+}}, i8* %{{[0-9]+}}, i64 42) + return cc.regular(42) +} + +fun main() { + callDirect() + callRegular() +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/interop/objc/direct/direct.def b/kotlin-native/backend.native/tests/interop/objc/direct/direct.def new file mode 100644 index 00000000000..b4612e2088e --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/objc/direct/direct.def @@ -0,0 +1,2 @@ +language = Objective-C +headerFilter = **/direct.h \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/interop/objc/direct/direct.h b/kotlin-native/backend.native/tests/interop/objc/direct/direct.h new file mode 100644 index 00000000000..666439b387f --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/objc/direct/direct.h @@ -0,0 +1,32 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +__attribute__((objc_runtime_name("CC"))) +__attribute__((swift_name("CC"))) +@interface CallingConventions : NSObject + ++ (NSUInteger)regular:(NSUInteger)arg; +- (NSUInteger)regular:(NSUInteger)arg; + ++ (NSUInteger)direct:(NSUInteger)arg __attribute__((objc_direct)); +- (NSUInteger)direct:(NSUInteger)arg __attribute__((objc_direct)); + +@end + +@interface CallingConventions(Ext) + ++ (NSUInteger)regularExt:(NSUInteger)arg; +- (NSUInteger)regularExt:(NSUInteger)arg; + ++ (NSUInteger)directExt:(NSUInteger)arg __attribute__((objc_direct)); +- (NSUInteger)directExt:(NSUInteger)arg __attribute__((objc_direct)); + +@end + +__attribute__((objc_runtime_name("CCH"))) +__attribute__((swift_name("CCH"))) +@interface CallingConventionsHeir : CallingConventions +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/interop/objc/direct/direct.m b/kotlin-native/backend.native/tests/interop/objc/direct/direct.m new file mode 100644 index 00000000000..5d6a7f7efb6 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/objc/direct/direct.m @@ -0,0 +1,27 @@ +#import "direct.h" + +#define TEST_METHOD_IMPL(NAME) (NSUInteger)NAME:(NSUInteger)arg { return arg; } + +@implementation CallingConventions : NSObject + ++ TEST_METHOD_IMPL(regular); +- TEST_METHOD_IMPL(regular); + ++ TEST_METHOD_IMPL(direct); +- TEST_METHOD_IMPL(direct); + +@end + +@implementation CallingConventions(Ext) + ++ TEST_METHOD_IMPL(regularExt); +- TEST_METHOD_IMPL(regularExt); + ++ TEST_METHOD_IMPL(directExt); +- TEST_METHOD_IMPL(directExt); + +@end + +@implementation CallingConventionsHeir + +@end diff --git a/kotlin-native/backend.native/tests/interop/objc/direct/main.kt b/kotlin-native/backend.native/tests/interop/objc/direct/main.kt new file mode 100644 index 00000000000..0a018613305 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/objc/direct/main.kt @@ -0,0 +1,46 @@ +import direct.* +import kotlinx.cinterop.* +import kotlin.test.* + +class CallingConventionsNativeHeir() : CallingConventions() { + // nothing +} + +typealias CC = CallingConventions +typealias CCH = CallingConventionsHeir +typealias CCN = CallingConventionsNativeHeir + +// KT-54610 +fun main(args: Array) { + autoreleasepool { + val cc = CC() + val cch = CCH() + val ccn = CCN() + + assertEquals(42UL, CC.regular(42)) + assertEquals(42UL, cc.regular(42)) + assertEquals(42UL, CC.regularExt(42)) + assertEquals(42UL, cc.regularExt(42)) + + assertEquals(42UL, CCH.regular(42)) + assertEquals(42UL, cch.regular(42)) + assertEquals(42UL, CCH.regularExt(42)) + assertEquals(42UL, cch.regularExt(42)) + + assertEquals(42UL, ccn.regular(42UL)) + assertEquals(42UL, ccn.regularExt(42UL)) + + assertEquals(42UL, CC.direct(42)) + assertEquals(42UL, cc.direct(42)) + assertEquals(42UL, CC.directExt(42)) + assertEquals(42UL, cc.directExt(42)) + + assertEquals(42UL, CCH.direct(42)) + assertEquals(42UL, cch .direct(42)) + assertEquals(42UL, CCH.directExt(42)) + assertEquals(42UL, cch .directExt(42)) + + assertEquals(42UL, ccn .direct(42UL)) + assertEquals(42UL, ccn .directExt(42UL)) + } +}