From 2aa6da5e94c1e0b143aad17745b329980a405fc8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 13 May 2019 16:59:04 +0300 Subject: [PATCH 1/2] Map returned Unit to void in more cases when producing framework Including function-typed parameters and return types --- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 7 + .../kotlin/backend/konan/llvm/ContextUtils.kt | 1 + .../backend/konan/llvm/RTTIGenerator.kt | 29 +-- .../llvm/objcexport/BlockPointerSupport.kt | 177 +++++++++++++----- .../objcexport/ObjCExportCodeGenerator.kt | 89 +++++---- .../konan/objcexport/CustomTypeMapper.kt | 14 +- .../backend/konan/objcexport/MethodBridge.kt | 6 + .../objcexport/ObjCExportHeaderGenerator.kt | 55 +++++- .../konan/objcexport/ObjCExportMapper.kt | 25 ++- .../backend/konan/objcexport/objcTypes.kt | 2 +- .../tests/framework/values/values.kt | 44 ++++- .../tests/framework/values/values.swift | 36 ++++ runtime/src/main/cpp/ObjCExport.mm | 25 ++- 13 files changed, 381 insertions(+), 129 deletions(-) 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 401cfced070..794681fad41 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 @@ -134,6 +134,8 @@ internal class KonanSymbols(context: Context, private val symbolTable: SymbolTab val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject) + val interopForeignObjCObject = interopClass("ForeignObjCObject") + // These are possible supertypes of forward declarations - we need to reference them explicitly to force their deserialization. // TODO: Do it lazily. val interopCOpaque = symbolTable.referenceClass(context.interopBuiltIns.cOpaque) @@ -430,6 +432,11 @@ internal class KonanSymbols(context: Context, private val symbolTable: SymbolTab .single() ) + private fun interopClass(name: String) = symbolTable.referenceClass( + context.interopBuiltIns.packageScope + .getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BACKEND) as ClassDescriptor + ) + val functions = (0 .. KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS) .map { symbolTable.referenceClass(builtIns.getFunction(it)) } 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 a876a2e6406..ba7e1cb9862 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 @@ -460,6 +460,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val Kotlin_ObjCExport_AbstractMethodCalled by lazyRtFunction val Kotlin_ObjCExport_RethrowExceptionAsNSError by lazyRtFunction val Kotlin_ObjCExport_RethrowNSErrorAsException by lazyRtFunction + val Kotlin_ObjCExport_AllocInstanceWithAssociatedObject by lazyRtFunction val tlsMode by lazy { when (target) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 2488ec2c75a..7154ad783bd 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -188,16 +188,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { val interfacesPtr = staticData.placeGlobalConstArray("kintf:$className", pointerType(runtime.typeInfoType), interfaces) - val objOffsets = getStructElements(bodyType).mapIndexedNotNull { index, type -> - if (isObjectType(type)) { - LLVMOffsetOfElement(llvmTargetData, bodyType, index) - } else { - null - } - } + val objOffsets = getObjOffsets(bodyType) - val objOffsetsPtr = staticData.placeGlobalConstArray("krefs:$className", int32Type, - objOffsets.map { Int32(it.toInt()) }) + val objOffsetsPtr = staticData.placeGlobalConstArray("krefs:$className", int32Type, objOffsets) val objOffsetsCount = if (irClass.descriptor == context.builtIns.array) { 1 // To mark it as non-leaf. @@ -242,6 +235,15 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { exportTypeInfoIfRequired(irClass, irClass.llvmTypeInfoPtr) } + private fun getObjOffsets(bodyType: LLVMTypeRef): List = + getStructElements(bodyType).mapIndexedNotNull { index, type -> + if (isObjectType(type)) { + LLVMOffsetOfElement(llvmTargetData, bodyType, index) + } else { + null + } + }.map { Int32(it.toInt()) } + fun vtable(irClass: IrClass): ConstArray { // TODO: compile-time resolution limits binary compatibility. val vtableEntries = context.getVtableBuilder(irClass).vtableEntries.map { @@ -314,11 +316,12 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { fun generateSyntheticInterfaceImpl( irClass: IrClass, methodImpls: Map, + bodyType: LLVMTypeRef, immutable: Boolean = false ): ConstPointer { assert(irClass.isInterface) - val size = LLVMStoreSizeOfType(llvmTargetData, kObjHeader).toInt() + val size = LLVMStoreSizeOfType(llvmTargetData, bodyType).toInt() val superClass = context.ir.symbols.any.owner @@ -328,8 +331,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { pointerType(runtime.typeInfoType), interfaces) assert(superClass.declarations.all { it !is IrProperty && it !is IrField }) - val objOffsetsPtr = NullPointer(int32Type) - val objOffsetsCount = 0 + + val objOffsets = getObjOffsets(bodyType) + val objOffsetsPtr = staticData.placeGlobalConstArray("", int32Type, objOffsets) + val objOffsetsCount = objOffsets.size val methods = (methodTableRecords(superClass) + methodImpls.map { (method, impl) -> assert(method.parent == irClass) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt index 1cb484a70d9..8c49f55cff8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt @@ -5,47 +5,133 @@ package org.jetbrains.kotlin.backend.konan.llvm.objcexport -import llvm.LLVMLinkage -import llvm.LLVMSetLinkage -import llvm.LLVMStoreSizeOfType -import llvm.LLVMValueRef +import llvm.* import org.jetbrains.kotlin.backend.konan.llvm.* +import org.jetbrains.kotlin.backend.konan.objcexport.BlockPointerBridge import org.jetbrains.kotlin.descriptors.konan.CurrentKonanModuleOrigin import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.util.OperatorNameConventions -internal fun ObjCExportCodeGenerator.generateKotlinFunctionImpl(invokeMethod: IrSimpleFunction): ConstPointer { - // TODO: consider also overriding methods of `Any`. - - val numberOfParameters = invokeMethod.valueParameters.size +internal fun ObjCExportCodeGenerator.generateBlockToKotlinFunctionConverter( + bridge: BlockPointerBridge +): LLVMValueRef { + val irInterface = symbols.functions[bridge.numberOfParameters].owner + val invokeMethod = irInterface.declarations.filterIsInstance() + .single { it.name == OperatorNameConventions.INVOKE } + + // Note: we can store Objective-C block pointer as associated object of Kotlin function object itself, + // but only if it is equivalent to its dynamic translation result. If block returns void, then it's not like that: + val useSeparateHolder = bridge.returnsVoid + + val bodyType = if (useSeparateHolder) { + structType(codegen.kObjHeader, codegen.kObjHeaderPtr) + } else { + structType(codegen.kObjHeader) + } - val function = generateFunction( + val invokeImpl = generateFunction( codegen, codegen.getLlvmFunctionType(invokeMethod), - "invokeFunction$numberOfParameters" + "invokeFunction${bridge.nameSuffix}" ) { - val args = (0 until numberOfParameters).map { index -> kotlinReferenceToObjC(param(index + 1)) } + val args = (0 until bridge.numberOfParameters).map { index -> + kotlinReferenceToObjC(param(index + 1)) + } - val rawBlockPtr = callFromBridge(context.llvm.Kotlin_ObjCExport_GetAssociatedObject, listOf(param(0))) + val thisRef = param(0) + val associatedObjectHolder = if (useSeparateHolder) { + val bodyPtr = bitcast(pointerType(bodyType), thisRef) + loadSlot(structGep(bodyPtr, 1), isVar = false) + } else { + thisRef + } + val blockPtr = callFromBridge( + context.llvm.Kotlin_ObjCExport_GetAssociatedObject, + listOf(associatedObjectHolder) + ) - val blockLiteralType = codegen.runtime.getStructType("Block_literal_1") - val blockPtr = bitcast(pointerType(blockLiteralType), rawBlockPtr) - val invokePtr = structGep(blockPtr, 3) + val invoke = loadBlockInvoke(blockPtr, bridge) + val result = callFromBridge(invoke, listOf(blockPtr) + args) - val blockInvokeType = functionType(int8TypePtr, false, (0 .. numberOfParameters).map { int8TypePtr }) + val kotlinResult = if (bridge.returnsVoid) { + theUnitInstanceRef.llvm + } else { + objCReferenceToKotlin(result, Lifetime.RETURN_VALUE) + } + ret(kotlinResult) + }.also { + LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage) + } - val invoke = bitcast(pointerType(blockInvokeType), load(invokePtr)) - val result = callFromBridge(invoke, listOf(rawBlockPtr) + args) + val typeInfo = rttiGenerator.generateSyntheticInterfaceImpl( + irInterface, + mapOf(invokeMethod to constPointer(invokeImpl)), + bodyType, + immutable = true + ) + + return generateFunction( + codegen, + functionType(codegen.kObjHeaderPtr, false, int8TypePtr, codegen.kObjHeaderPtrPtr), + "" + ) { + val blockPtr = param(0) + ifThen(icmpEq(blockPtr, kNullInt8Ptr)) { + ret(kNullObjHeaderPtr) + } + + val retainedBlockPtr = callFromBridge(retainBlock, listOf(blockPtr)) - // TODO: support void-as-Unit. - ret(objCReferenceToKotlin(result, Lifetime.RETURN_VALUE)) + val result = if (useSeparateHolder) { + val result = allocInstance(typeInfo.llvm, Lifetime.RETURN_VALUE) + val bodyPtr = bitcast(pointerType(bodyType), result) + val holder = allocInstanceWithAssociatedObject( + symbols.interopForeignObjCObject.owner.typeInfoPtr, + retainedBlockPtr, + Lifetime.ARGUMENT + ) + storeHeapRef(holder, structGep(bodyPtr, 1)) + result + } else { + allocInstanceWithAssociatedObject(typeInfo, retainedBlockPtr, Lifetime.RETURN_VALUE) + } + + ret(result) }.also { LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage) } +} - return constPointer(function) +private fun FunctionGenerationContext.loadBlockInvoke( + blockPtr: LLVMValueRef, + bridge: BlockPointerBridge +): LLVMValueRef { + val blockLiteralType = codegen.runtime.getStructType("Block_literal_1") + val invokePtr = structGep(bitcast(pointerType(blockLiteralType), blockPtr), 3) + + return bitcast(pointerType(bridge.blockInvokeLlvmType), load(invokePtr)) } +private fun FunctionGenerationContext.allocInstanceWithAssociatedObject( + typeInfo: ConstPointer, + associatedObject: LLVMValueRef, + resultLifetime: Lifetime +): LLVMValueRef = call( + context.llvm.Kotlin_ObjCExport_AllocInstanceWithAssociatedObject, + listOf(typeInfo.llvm, associatedObject), + resultLifetime +) + +private val BlockPointerBridge.blockInvokeLlvmType: LLVMTypeRef + get() = functionType( + if (returnsVoid) voidType else int8TypePtr, + false, + (0..numberOfParameters).map { int8TypePtr } + ) + +private val BlockPointerBridge.nameSuffix: String + get() = numberOfParameters.toString() + if (returnsVoid) "V" else "" + internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjCExportCodeGenerator) { private val codegen get() = objCExportCodeGenerator.codegen @@ -94,9 +180,11 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC fun org.jetbrains.kotlin.backend.konan.Context.LongInt(value: Long) = if (is64Bit()) Int64(value) else Int32(value.toInt()) - private fun generateDescriptorForBlockAdapterToFunction(numberOfParameters: Int): ConstValue { + private fun generateDescriptorForBlockAdapterToFunction(bridge: BlockPointerBridge): ConstValue { + val numberOfParameters = bridge.numberOfParameters + val signature = buildString { - append('@') + append(if (bridge.returnsVoid) 'v' else '@') val pointerSize = codegen.runtime.pointerSize append(pointerSize * (numberOfParameters + 1)) @@ -132,14 +220,10 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC ) } - private fun ObjCExportCodeGenerator.generateInvoke(numberOfParameters: Int): ConstPointer { - val functionType = functionType( - int8TypePtr, - false, - (0 .. numberOfParameters).map { int8TypePtr } - ) + private fun ObjCExportCodeGenerator.generateInvoke(bridge: BlockPointerBridge): ConstPointer { + val numberOfParameters = bridge.numberOfParameters - val result = generateFunction(codegen, functionType, "invokeBlock$numberOfParameters") { + val result = generateFunction(codegen, bridge.blockInvokeLlvmType, "invokeBlock${bridge.nameSuffix}") { val blockPtr = bitcast(pointerType(blockLiteralType), param(0)) val kotlinFunction = loadSlot(structGep(blockPtr, 1), isVar = false) @@ -154,7 +238,11 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC val result = callFromBridge(callee, listOf(kotlinFunction) + args, Lifetime.ARGUMENT) - ret(kotlinReferenceToObjC(result)) + if (bridge.returnsVoid) { + ret(null) + } else { + ret(kotlinReferenceToObjC(result)) + } }.also { LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage) } @@ -162,17 +250,22 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC return constPointer(result) } - fun ObjCExportCodeGenerator.generateConvertFunctionToBlock(numberOfParameters: Int): LLVMValueRef { + fun ObjCExportCodeGenerator.generateConvertFunctionToBlock(bridge: BlockPointerBridge): LLVMValueRef { val blockDescriptor = codegen.staticData.placeGlobal( "", - generateDescriptorForBlockAdapterToFunction(numberOfParameters) + generateDescriptorForBlockAdapterToFunction(bridge) ) return generateFunction( codegen, functionType(int8TypePtr, false, codegen.kObjHeaderPtr), - "convertFunction$numberOfParameters" + "convertFunction${bridge.nameSuffix}" ) { + val kotlinRef = param(0) + ifThen(icmpEq(kotlinRef, kNullObjHeaderPtr)) { + ret(kNullInt8Ptr) + } + val isa = codegen.importGlobal( "_NSConcreteStackBlock", int8TypePtr, @@ -183,7 +276,7 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC val reserved = Int32(0).llvm val invokeType = pointerType(functionType(voidType, true, int8TypePtr)) - val invoke = generateInvoke(numberOfParameters).bitcast(invokeType).llvm + val invoke = generateInvoke(bridge).bitcast(invokeType).llvm val descriptor = blockDescriptor.llvmGlobal val blockOnStack = alloca(blockLiteralType) @@ -197,13 +290,7 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC } // Note: it is the slot in the block located on stack, so no need to manage it properly: - storeRefUnsafe(param(0), slot) - - val retainBlock = context.llvm.externalFunction( - "objc_retainBlock", - functionType(int8TypePtr, false, int8TypePtr), - CurrentKonanModuleOrigin - ) + storeRefUnsafe(kotlinRef, slot) val copiedBlock = callFromBridge(retainBlock, listOf(bitcast(int8TypePtr, blockOnStack))) @@ -219,3 +306,9 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC } } } + +private val ObjCExportCodeGenerator.retainBlock get() = context.llvm.externalFunction( + "objc_retainBlock", + functionType(int8TypePtr, false, int8TypePtr), + CurrentKonanModuleOrigin +) \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 4c288f995f9..064e8dcbf84 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -25,10 +25,9 @@ import org.jetbrains.kotlin.ir.util.constructedClass import org.jetbrains.kotlin.ir.util.isInterface import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.util.OperatorNameConventions internal fun TypeBridge.makeNothing() = when (this) { - is ReferenceBridge -> kNullInt8Ptr + is ReferenceBridge, is BlockPointerBridge -> kNullInt8Ptr is ValueTypeBridge -> LLVMConstNull(this.objCValueType.llvmType)!! } @@ -121,23 +120,59 @@ internal class ObjCExportCodeGenerator( fun FunctionGenerationContext.objCReferenceToKotlin(value: LLVMValueRef, resultLifetime: Lifetime) = callFromBridge(context.llvm.Kotlin_ObjCExport_refFromObjC, listOf(value), resultLifetime) + private fun FunctionGenerationContext.objCBlockPointerToKotlin( + value: LLVMValueRef, + typeBridge: BlockPointerBridge, + resultLifetime: Lifetime + ) = callFromBridge( + blockToKotlinFunctionConverter(typeBridge), + listOf(value), + resultLifetime + ) + + private val blockToKotlinFunctionConverterCache = mutableMapOf() + + internal fun blockToKotlinFunctionConverter(bridge: BlockPointerBridge): LLVMValueRef = + blockToKotlinFunctionConverterCache.getOrPut(bridge) { + generateBlockToKotlinFunctionConverter(bridge) + } + + private fun FunctionGenerationContext.kotlinFunctionToObjCBlockPointer( + typeBridge: BlockPointerBridge, + value: LLVMValueRef + ) = callFromBridge(kotlinFunctionToBlockConverter(typeBridge), listOf(value)) + + private val blockAdapterToFunctionGenerator = BlockAdapterToFunctionGenerator(this) + + private val functionToBlockConverterCache = mutableMapOf() + + internal fun kotlinFunctionToBlockConverter(bridge: BlockPointerBridge): LLVMValueRef = + functionToBlockConverterCache.getOrPut(bridge) { + blockAdapterToFunctionGenerator.run { + generateConvertFunctionToBlock(bridge) + } + } + fun FunctionGenerationContext.kotlinToObjC( value: LLVMValueRef, typeBridge: TypeBridge - ): LLVMValueRef = when { - LLVMTypeOf(value) == voidType -> typeBridge.makeNothing() - typeBridge is ReferenceBridge -> kotlinReferenceToObjC(value) - typeBridge is ValueTypeBridge -> kotlinToObjC(value, typeBridge.objCValueType) - else -> TODO() + ): LLVMValueRef = if (LLVMTypeOf(value) == voidType) { + typeBridge.makeNothing() + } else { + when (typeBridge) { + is ReferenceBridge -> kotlinReferenceToObjC(value) + is BlockPointerBridge -> kotlinFunctionToObjCBlockPointer(typeBridge, value) + is ValueTypeBridge -> kotlinToObjC(value, typeBridge.objCValueType) } + } fun FunctionGenerationContext.objCToKotlin( value: LLVMValueRef, typeBridge: TypeBridge, resultLifetime: Lifetime ): LLVMValueRef = when (typeBridge) { - // TODO: if we add value type check here, we could bridge on Unit better. is ReferenceBridge -> objCReferenceToKotlin(value, resultLifetime) + is BlockPointerBridge -> objCBlockPointerToKotlin(value, typeBridge, resultLifetime) is ValueTypeBridge -> objCToKotlin(value, typeBridge.objCValueType) } @@ -426,39 +461,25 @@ private fun ObjCExportCodeGenerator.emitBoxConverter( } private fun ObjCExportCodeGenerator.emitFunctionConverters() { - val generator = BlockAdapterToFunctionGenerator(this) - (0 .. ObjCExportMapper.maxFunctionTypeParameterCount).forEach { numberOfParameters -> - val converter = generator.run { generateConvertFunctionToBlock(numberOfParameters) } + val converter = kotlinFunctionToBlockConverter(BlockPointerBridge(numberOfParameters, returnsVoid = false)) setObjCExportTypeInfo(symbols.functions[numberOfParameters].owner, constPointer(converter)) } } -private fun ObjCExportCodeGenerator.generateKotlinFunctionAdapterToBlock(numberOfParameters: Int): ConstPointer { - val irInterface = symbols.functions[numberOfParameters].owner - val invokeMethod = irInterface.declarations.filterIsInstance() - .single { it.name == OperatorNameConventions.INVOKE } - - val invokeImpl = generateKotlinFunctionImpl(invokeMethod) - - return rttiGenerator.generateSyntheticInterfaceImpl( - irInterface, - mapOf(invokeMethod to invokeImpl), - immutable = true - ) -} - -private fun ObjCExportCodeGenerator.emitKotlinFunctionAdaptersToBlock() { +private fun ObjCExportCodeGenerator.emitBlockToKotlinFunctionConverters() { + val converters = (0 .. ObjCExportMapper.maxFunctionTypeParameterCount).map { + val bridge = BlockPointerBridge(numberOfParameters = it, returnsVoid = false) + constPointer(blockToKotlinFunctionConverter(bridge)) + } val ptr = staticData.placeGlobalArray( "", - pointerType(runtime.typeInfoType), - (0 .. ObjCExportMapper.maxFunctionTypeParameterCount).map { - generateKotlinFunctionAdapterToBlock(it) - } + converters.first().llvmType, + converters ).pointer.getElementPtr(0) // Note: this global replaces the weak global defined in runtime. - staticData.placeGlobal("Kotlin_ObjCExport_functionAdaptersToBlock", ptr, isExported = true) + staticData.placeGlobal("Kotlin_ObjCExport_blockToFunctionConverters", ptr, isExported = true) } private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() { @@ -501,7 +522,7 @@ private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() { emitFunctionConverters() - emitKotlinFunctionAdaptersToBlock() + emitBlockToKotlinFunctionConverters() } private inline fun ObjCExportCodeGenerator.generateObjCImpBy( @@ -1199,7 +1220,7 @@ private fun MethodBridge.ReturnValue.objCType(context: Context): LLVMTypeRef { } private val TypeBridge.objCType: LLVMTypeRef get() = when (this) { - is ReferenceBridge -> int8TypePtr + is ReferenceBridge, is BlockPointerBridge -> int8TypePtr is ValueTypeBridge -> this.objCValueType.llvmType } @@ -1240,7 +1261,7 @@ private val MethodBridgeParameter.objCEncoding: String get() = when (this) { } private val TypeBridge.objCEncoding: String get() = when (this) { - ReferenceBridge -> "@" + ReferenceBridge, is BlockPointerBridge -> "@" is ValueTypeBridge -> this.objCValueType.encoding } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt index 33f4c70aade..34dd5802da4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt @@ -6,9 +6,6 @@ package org.jetbrains.kotlin.backend.konan.objcexport import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType -import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType -import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.types.KotlinType @@ -116,16 +113,7 @@ internal object CustomTypeMappers { override val mappedClassId: ClassId = KotlinBuiltIns.getFunctionClassId(parameterCount) override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl, objCExportScope: ObjCExportScope): ObjCNonNullReferenceType { - val functionType = mappedSuperType - - val returnType = functionType.getReturnTypeFromFunctionType() - val parameterTypes = listOfNotNull(functionType.getReceiverTypeFromFunctionType()) + - functionType.getValueParameterTypesFromFunctionType().map { it.type } - - return ObjCBlockPointerType( - translator.mapReferenceType(returnType, objCExportScope), - parameterTypes.map { translator.mapReferenceType(it, objCExportScope) } - ) + return translator.mapFunctionTypeIgnoringNullability(mappedSuperType, objCExportScope, returnsVoid = false) } } } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/MethodBridge.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/MethodBridge.kt index f9fdcaf0356..cd1364e2ec1 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/MethodBridge.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/MethodBridge.kt @@ -14,6 +14,12 @@ import org.jetbrains.kotlin.ir.declarations.IrValueParameter internal sealed class TypeBridge internal object ReferenceBridge : TypeBridge() + +internal data class BlockPointerBridge( + val numberOfParameters: Int, + val returnsVoid: Boolean +) : TypeBridge() + internal data class ValueTypeBridge(val objCValueType: ObjCValueType) : TypeBridge() internal sealed class MethodBridgeParameter diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index ab009e486ca..0f9adb5020f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -7,7 +7,7 @@ package org.jetbrains.kotlin.backend.konan.objcexport import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.* -import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.ClassId @@ -596,12 +596,13 @@ internal class ObjCExportTranslatorImpl( } internal fun mapReferenceType(kotlinType: KotlinType, objCExportScope: ObjCExportScope): ObjCReferenceType = - mapReferenceTypeIgnoringNullability(kotlinType, objCExportScope).let { - if (kotlinType.binaryRepresentationIsNullable()) { - ObjCNullableReferenceType(it) - } else { - it - } + mapReferenceTypeIgnoringNullability(kotlinType, objCExportScope).withNullabilityOf(kotlinType) + + private fun ObjCNonNullReferenceType.withNullabilityOf(kotlinType: KotlinType): ObjCReferenceType = + if (kotlinType.binaryRepresentationIsNullable()) { + ObjCNullableReferenceType(this) + } else { + this } internal fun mapReferenceTypeIgnoringNullability(kotlinType: KotlinType, objCExportScope: ObjCExportScope): ObjCNonNullReferenceType { @@ -695,8 +696,46 @@ internal class ObjCExportTranslatorImpl( return ObjCIdType } + internal fun mapFunctionTypeIgnoringNullability( + functionType: KotlinType, + objCExportScope: ObjCExportScope, + returnsVoid: Boolean + ): ObjCBlockPointerType { + val parameterTypes = listOfNotNull(functionType.getReceiverTypeFromFunctionType()) + + functionType.getValueParameterTypesFromFunctionType().map { it.type } + + return ObjCBlockPointerType( + if (returnsVoid) { + ObjCVoidType + } else { + mapReferenceType(functionType.getReturnTypeFromFunctionType(), objCExportScope) + }, + parameterTypes.map { mapReferenceType(it, objCExportScope) } + ) + } + + private fun mapFunctionType( + kotlinType: KotlinType, + objCExportScope: ObjCExportScope, + typeBridge: BlockPointerBridge + ): ObjCReferenceType { + val expectedDescriptor = builtIns.getFunction(typeBridge.numberOfParameters) + + // Somewhat similar to mapType: + val functionType = if (TypeUtils.getClassDescriptor(kotlinType) == expectedDescriptor) { + kotlinType + } else { + kotlinType.supertypes().singleOrNull { TypeUtils.getClassDescriptor(it) == expectedDescriptor } + ?: expectedDescriptor.defaultType // Should not happen though. + } + + return mapFunctionTypeIgnoringNullability(functionType, objCExportScope, typeBridge.returnsVoid) + .withNullabilityOf(kotlinType) + } + private fun mapType(kotlinType: KotlinType, typeBridge: TypeBridge, objCExportScope: ObjCExportScope): ObjCType = when (typeBridge) { ReferenceBridge -> mapReferenceType(kotlinType, objCExportScope) + is BlockPointerBridge -> mapFunctionType(kotlinType, objCExportScope, typeBridge) is ValueTypeBridge -> { when (typeBridge.objCValueType) { ObjCValueType.BOOL -> ObjCPrimitiveType("BOOL") @@ -770,7 +809,7 @@ abstract class ObjCExportHeaderGenerator internal constructor( override fun reportWarning(method: FunctionDescriptor, text: String) = this@ObjCExportHeaderGenerator.reportWarning(method, text) - }, + }, objcGenerics) private val generatedClasses = mutableSetOf() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index c5e018be146..5d2248b7491 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -11,9 +11,7 @@ import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface -import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.builtins.PrimitiveType -import org.jetbrains.kotlin.builtins.UnsignedType +import org.jetbrains.kotlin.builtins.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.resolve.descriptorUtil.* @@ -114,7 +112,9 @@ internal fun ObjCExportMapper.doesThrow(method: FunctionDescriptor): Boolean = m it.overriddenDescriptors.isEmpty() && it.annotations.hasAnnotation(KonanFqNames.throws) } -private fun ObjCExportMapper.bridgeType(kotlinType: KotlinType): TypeBridge = kotlinType.unwrapToPrimitiveOrReference( +private fun ObjCExportMapper.bridgeType( + kotlinType: KotlinType +): TypeBridge = kotlinType.unwrapToPrimitiveOrReference( eachInlinedClass = { inlinedClass, _ -> when (inlinedClass.classId) { UnsignedType.UBYTE.classId -> return ValueTypeBridge(ObjCValueType.UNSIGNED_CHAR) @@ -138,10 +138,25 @@ private fun ObjCExportMapper.bridgeType(kotlinType: KotlinType): TypeBridge = ko ValueTypeBridge(objCValueType) }, ifReference = { - ReferenceBridge + if (kotlinType.isFunctionType) { + bridgeFunctionType(kotlinType) + } else { + ReferenceBridge + } } ) +private fun ObjCExportMapper.bridgeFunctionType(kotlinType: KotlinType): TypeBridge { + val numberOfParameters = kotlinType.arguments.size - 1 + + val returnType = kotlinType.getReturnTypeFromFunctionType() + val returnsVoid = returnType.isUnit() || returnType.isNothing() + // Note: this is correct because overriding method can't turn this into false + // neither for a parameter nor for a return type. + + return BlockPointerBridge(numberOfParameters, returnsVoid) +} + private fun ObjCExportMapper.bridgeParameter(parameter: ParameterDescriptor): MethodBridgeValueParameter = MethodBridgeValueParameter.Mapped(bridgeType(parameter.type)) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt index 60e1b567184..5218a2e4d6f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt @@ -75,7 +75,7 @@ object ObjCInstanceType : ObjCNonNullReferenceType() { } class ObjCBlockPointerType( - val returnType: ObjCReferenceType, + val returnType: ObjCType, val parameterTypes: List ) : ObjCNonNullReferenceType() { diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index c248d2d504f..da44d302596 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -378,4 +378,46 @@ class GH2959 { private class PrivateImpl(override val id: Int) : I fun getI(id: Int): List = listOf(PrivateImpl(id)) -} \ No newline at end of file +} + +fun runUnitBlock(block: () -> Unit): Boolean { + val blockAny: () -> Any? = block + return blockAny() === Unit +} + +fun asUnitBlock(block: () -> Any?): () -> Unit = { block() } + +fun runNothingBlock(block: () -> Nothing) = try { + block() + false +} catch (e: Throwable) { + true +} + +fun asNothingBlock(block: () -> Any?): () -> Nothing = { + block() + TODO() +} + +fun getNullBlock(): (() -> Unit)? = null +fun isBlockNull(block: (() -> Unit)?): Boolean = block == null + +interface IntBlocks { + fun getPlusOneBlock(): T + fun callBlock(argument: Int, block: T): Int +} + +object IntBlocksImpl : IntBlocks<(Int) -> Int> { + override fun getPlusOneBlock(): (Int) -> Int = { it: Int -> it + 1 } + override fun callBlock(argument: Int, block: (Int) -> Int): Int = block(argument) +} + +interface UnitBlockCoercion { + fun coerce(block: () -> Unit): T + fun uncoerce(block: T): () -> Unit +} + +object UnitBlockCoercionImpl : UnitBlockCoercion<() -> Unit> { + override fun coerce(block: () -> Unit): () -> Unit = block + override fun uncoerce(block: () -> Unit): () -> Unit = block +} diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index 4a127a90bea..8c9469ee4cd 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -350,6 +350,42 @@ func testCharExtensions() throws { func testLambda() throws { try assertEquals(actual: ValuesKt.sumLambda(3, 4), expected: 7) + + var blockRuns = 0 + + try assertTrue(ValuesKt.runUnitBlock { blockRuns += 1 }) + try assertEquals(actual: blockRuns, expected: 1) + + let unitBlock: () -> Void = ValuesKt.asUnitBlock { + blockRuns += 1 + return 42 + } + try assertTrue(unitBlock() == Void()) + try assertEquals(actual: blockRuns, expected: 2) + + let nothingBlock: () -> Void = ValuesKt.asNothingBlock { blockRuns += 1 } + try assertTrue(ValuesKt.runNothingBlock(block: nothingBlock)) + try assertEquals(actual: blockRuns, expected: 3) + + try assertTrue(ValuesKt.getNullBlock() == nil) + try assertTrue(ValuesKt.isBlockNull(block: nil)) + + // Test dynamic conversion: + let intBlocks = IntBlocksImpl() + try assertEquals(actual: intBlocks.getPlusOneBlock()(1), expected: 2) + try assertEquals(actual: intBlocks.callBlock(argument: 2) { KotlinInt(value: $0.int32Value + 2) }, expected: 4) + + // Test round trip with dynamic conversion: + let coercedUnitBlock: () -> KotlinUnit = UnitBlockCoercionImpl().coerce { blockRuns += 1 } + try assertTrue(coercedUnitBlock() === KotlinUnit()) + try assertEquals(actual: blockRuns, expected: 4) + + let uncoercedUnitBlock: () -> Void = UnitBlockCoercionImpl().uncoerce { + blockRuns += 1 + return KotlinUnit() + } + try assertTrue(uncoercedUnitBlock() == Void()) + try assertEquals(actual: blockRuns, expected: 5) } // -------- Tests for classes and interfaces ------- diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 9ee2a083493..491aaf4f1a8 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -76,6 +76,7 @@ }; typedef id (*convertReferenceToObjC)(ObjHeader* obj); +typedef OBJ_GETTER((*convertReferenceFromObjC), id obj); struct TypeInfoObjCExportAddition { /*convertReferenceToObjC*/ void* convert; @@ -108,6 +109,14 @@ inline static OBJ_GETTER(AllocInstanceWithAssociatedObject, const TypeInfo* type return result; } +extern "C" OBJ_GETTER(Kotlin_ObjCExport_AllocInstanceWithAssociatedObject, + const TypeInfo* typeInfo, id associatedObject) RUNTIME_NOTHROW; + +extern "C" OBJ_GETTER(Kotlin_ObjCExport_AllocInstanceWithAssociatedObject, + const TypeInfo* typeInfo, id associatedObject) { + RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, typeInfo, associatedObject); +} + static Class getOrCreateClass(const TypeInfo* typeInfo); static void initializeClass(Class clazz); extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject); @@ -249,8 +258,6 @@ - (instancetype)copyWithZone:(NSZone *)zone { return instance; } -extern "C" id objc_retainBlock(id self); - extern "C" id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str) { KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0); auto numBytes = str->array()->count_ * sizeof(KChar); @@ -575,9 +582,9 @@ -(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { } // Note: replaced by compiler in appropriate compilation modes. -__attribute__((weak)) const TypeInfo * const * Kotlin_ObjCExport_functionAdaptersToBlock = nullptr; +__attribute__((weak)) convertReferenceFromObjC* Kotlin_ObjCExport_blockToFunctionConverters = nullptr; -static const TypeInfo* getFunctionTypeInfoForBlock(id block) { +static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) { const char* encoding = getBlockEncoding(block); // TODO: optimize: @@ -602,15 +609,7 @@ -(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { format:@"Blocks with non-reference-typed return value aren't supported (%s)", returnTypeEncoding]; } - // TODO: support Unit-as-void. - - return Kotlin_ObjCExport_functionAdaptersToBlock[parameterCount]; -} - -static OBJ_GETTER(blockToKotlinImp, id self, SEL cmd) { - const TypeInfo* typeInfo = getFunctionTypeInfoForBlock(self); - RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, typeInfo, objc_retainBlock(self)); - // TODO: call (Any) constructor? + RETURN_RESULT_OF(Kotlin_ObjCExport_blockToFunctionConverters[parameterCount], block); } static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj); From a0abfa79c5e6a822ddf2ed74b9e8e6374e18e12f Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 21 May 2019 14:54:11 +0300 Subject: [PATCH 2/2] fixup! Map returned Unit to void in more cases when producing framework --- .../kotlin/backend/konan/objcexport/ObjCExportMapper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index 5d2248b7491..40906fb3714 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -147,6 +147,7 @@ private fun ObjCExportMapper.bridgeType( ) private fun ObjCExportMapper.bridgeFunctionType(kotlinType: KotlinType): TypeBridge { + // kotlinType.arguments include return type: val numberOfParameters = kotlinType.arguments.size - 1 val returnType = kotlinType.getReturnTypeFromFunctionType()