diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt index 6ddc583d3a0..bd632fcbf5d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt @@ -66,6 +66,12 @@ internal fun makeKonanModuleOpPhase( actions = modulePhaseActions ) +internal val specialBackendChecksPhase = konanUnitPhase( + op = { irModule!!.files.forEach { SpecialBackendChecksTraversal(this).lower(it) } }, + name = "SpecialBackendChecks", + description = "Special backend checks" +) + internal val removeExpectDeclarationsPhase = makeKonanModuleLoweringPhase( ::ExpectDeclarationsRemoving, name = "RemoveExpectDeclarations", diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index 6d815936c7b..5592285d6c3 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -424,6 +424,7 @@ val toplevelPhase: CompilerPhase<*, Unit, Unit> = namedUnitPhase( destroySymbolTablePhase then copyDefaultValuesToActualPhase then serializerPhase then + specialBackendChecksPhase then namedUnitPhase( name = "Backend", description = "All backend", @@ -451,10 +452,9 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) { // Don't serialize anything to a final executable. disableUnless(serializerPhase, config.produce == CompilerOutputKind.LIBRARY) - disableIf(dependenciesLowerPhase, config.produce == CompilerOutputKind.LIBRARY) disableUnless(entryPointPhase, config.produce == CompilerOutputKind.PROGRAM) disableUnless(exportInternalAbiPhase, config.produce.isCache) - disableIf(bitcodePhase, config.produce == CompilerOutputKind.LIBRARY) + disableIf(backendCodegen, config.produce == CompilerOutputKind.LIBRARY) disableUnless(bitcodeOptimizationPhase, config.produce.involvesLinkStage) disableUnless(linkBitcodeDependenciesPhase, config.produce.involvesLinkStage) disableUnless(objectFilesPhase, config.produce.involvesLinkStage) @@ -471,6 +471,5 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) { disableIf(psiToIrPhase, isDescriptorsOnlyLibrary) disableIf(destroySymbolTablePhase, isDescriptorsOnlyLibrary) disableIf(copyDefaultValuesToActualPhase, isDescriptorsOnlyLibrary) - disableIf(backendCodegen, isDescriptorsOnlyLibrary) } } 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 61ba7dcf128..1e06e798d85 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 @@ -6,17 +6,19 @@ import org.jetbrains.kotlin.backend.common.ir.createParameterDeclarations import org.jetbrains.kotlin.backend.common.ir.simpleFunctions import org.jetbrains.kotlin.backend.common.lower.at import org.jetbrains.kotlin.backend.common.lower.irNot -import org.jetbrains.kotlin.backend.konan.KonanFqNames import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType import org.jetbrains.kotlin.backend.konan.RuntimeNames import org.jetbrains.kotlin.backend.konan.descriptors.konanLibrary -import org.jetbrains.kotlin.backend.konan.ir.* +import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo +import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols +import org.jetbrains.kotlin.backend.konan.ir.buildSimpleAnnotation +import org.jetbrains.kotlin.backend.konan.ir.getAnnotationArgumentValue +import org.jetbrains.kotlin.backend.konan.ir.typeWithStarProjections import org.jetbrains.kotlin.backend.konan.isObjCMetaClass -import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.builtins.UnsignedType +import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET @@ -26,6 +28,7 @@ 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.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl @@ -36,17 +39,12 @@ 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.ForeignExceptionMode 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 -import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo -import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering -import org.jetbrains.kotlin.builtins.StandardNames -import org.jetbrains.kotlin.ir.descriptors.* -import org.jetbrains.kotlin.konan.ForeignExceptionMode internal interface KotlinStubs { val irBuiltIns: IrBuiltIns @@ -167,7 +165,7 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit if (isInvoke) { callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = ${targetPtrParameter!!};") } else { - val cCallSymbolName = callee.getAnnotationArgumentValue(cCall, "id")!! + val cCallSymbolName = callee.getAnnotationArgumentValue(RuntimeNames.cCall, "id")!! this.addC(listOf("extern const $targetFunctionVariable __asm(\"$cCallSymbolName\");")) // Exported from cinterop stubs. } @@ -339,7 +337,7 @@ internal fun KotlinStubs.generateObjCCall( ).name val targetFunctionName = "targetPtr" - val preparedReceiver = if (method.consumesReceiver()) { + val preparedReceiver = if (method.objCConsumesReceiver()) { when (receiver) { is ObjCCallReceiver.Regular -> irCall(symbols.interopObjCRetain.owner).apply { putValueArgument(0, receiver.rawPtr) @@ -455,7 +453,7 @@ private fun CCallbackBuilder.addParameter(it: IrValueParameter, functionParamete val valuePassing = stubs.mapFunctionParameterType( it.type, - retained = it.isConsumed(), + retained = it.isObjCConsumed(), variadic = false, location = typeLocation ) @@ -514,8 +512,8 @@ private fun KotlinStubs.generateCFunction( if (isObjCMethod) { val receiver = signature.dispatchReceiverParameter!! - assert(isObjCReferenceType(receiver.type)) - val valuePassing = ObjCReferenceValuePassing(symbols, receiver.type, retained = signature.consumesReceiver()) + require(receiver.type.isObjCReferenceType(target, irBuiltIns)) + val valuePassing = ObjCReferenceValuePassing(symbols, receiver.type, retained = signature.objCConsumesReceiver()) val kotlinArgument = with(valuePassing) { callbackBuilder.receiveValue() } callbackBuilder.kotlinCallBuilder.arguments += kotlinArgument @@ -606,48 +604,8 @@ private fun KotlinStubs.createFakeKotlinExternalFunction( return bridge } -private val cCall = RuntimeNames.cCall - -private fun IrType.isUnsigned(unsignedType: UnsignedType) = this is IrSimpleType && !this.hasQuestionMark && - (this.classifier.owner as? IrClass)?.classId == unsignedType.classId - -private fun IrType.isUByte() = this.isUnsigned(UnsignedType.UBYTE) -private fun IrType.isUShort() = this.isUnsigned(UnsignedType.USHORT) -private fun IrType.isUInt() = this.isUnsigned(UnsignedType.UINT) -private fun IrType.isULong() = this.isUnsigned(UnsignedType.ULONG) - -internal fun IrType.isCEnumType(): Boolean { - val simpleType = this as? IrSimpleType ?: return false - if (simpleType.hasQuestionMark) return false - val enumClass = simpleType.classifier.owner as? IrClass ?: return false - if (!enumClass.isEnumClass) return false - - return enumClass.superTypes - .any { (it.classifierOrNull?.owner as? IrClass)?.fqNameForIrSerialization == FqName("kotlinx.cinterop.CEnum") } -} - -// Make sure external stubs always get proper annotaions. -private fun IrDeclaration.hasCCallAnnotation(name: String): Boolean = - this.annotations.hasAnnotation(cCall.child(Name.identifier(name))) - // LazyIr doesn't pass annotations from descriptor to IrValueParameter. - || this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier(name))) - - -private fun IrValueParameter.isWCStringParameter() = hasCCallAnnotation("WCString") - -private fun IrValueParameter.isCStringParameter() = hasCCallAnnotation("CString") - -private fun IrValueParameter.isConsumed() = hasCCallAnnotation("Consumed") - -private fun IrSimpleFunction.consumesReceiver() = hasCCallAnnotation("ConsumesReceiver") - -private fun IrSimpleFunction.returnsRetained() = hasCCallAnnotation("ReturnsRetained") - -private fun getStructSpelling(kotlinClass: IrClass): String? = - kotlinClass.getAnnotationArgumentValue(FqName("kotlinx.cinterop.internal.CStruct"), "spelling") - private fun getCStructType(kotlinClass: IrClass): CType? = - getStructSpelling(kotlinClass)?.let { CTypes.simple(it) } + kotlinClass.getCStructSpelling()?.let { CTypes.simple(it) } private fun KotlinStubs.getNamedCStructType(kotlinClass: IrClass): CType? { val cStructType = getCStructType(kotlinClass) ?: return null @@ -658,7 +616,7 @@ private fun KotlinStubs.getNamedCStructType(kotlinClass: IrClass): CType? { // TODO: rework Boolean support. // TODO: What should be used on watchOS? -private fun cBoolType(target: KonanTarget): CType? = when (target.family) { +internal fun cBoolType(target: KonanTarget): CType? = when (target.family) { Family.IOS, Family.TVOS, Family.WATCHOS -> CTypes.C99Bool else -> CTypes.signedChar } @@ -688,7 +646,7 @@ private fun KotlinToCCallBuilder.mapCalleeFunctionParameter( else -> stubs.mapFunctionParameterType( type, - retained = parameter?.isConsumed() ?: false, + retained = parameter?.isObjCConsumed() ?: false, variadic = variadic, location = TypeLocation.FunctionArgument(argument) ) @@ -725,7 +683,7 @@ private fun KotlinStubs.mapReturnType( signature: IrSimpleFunction? ): ValueReturning = when { type.isUnit() -> VoidReturning - else -> mapType(type, retained = signature?.returnsRetained() ?: false, variadic = false, location = location) + else -> mapType(type, retained = signature?.objCReturnsRetained() ?: false, variadic = false, location = location) } private fun KotlinStubs.mapBlockType( @@ -774,16 +732,6 @@ private fun KotlinStubs.mapBlockType( private fun KotlinStubs.mapType(type: IrType, retained: Boolean, variadic: Boolean, location: TypeLocation): ValuePassing = mapType(type, retained, variadic, location, { reportUnsupportedType(it, type, location) }) -private fun IrType.isTypeOfNullLiteral(): Boolean = this is IrSimpleType && hasQuestionMark - && classifier.isClassWithFqName(StandardNames.FqNames.nothing) - -internal fun IrType.isVector(): Boolean { - if (this is IrSimpleType && !this.hasQuestionMark) { - return classifier.isClassWithFqName(KonanFqNames.Vector128.toUnsafe()) - } - return false -} - private fun KotlinStubs.mapType( type: IrType, retained: Boolean, @@ -844,31 +792,11 @@ private fun KotlinStubs.mapType( mapBlockType(type, retained = retained, location = typeLocation) } - isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, retained = retained) + type.isObjCReferenceType(target, irBuiltIns) -> ObjCReferenceValuePassing(symbols, type, retained = retained) else -> reportUnsupportedType("doesn't correspond to any C type") } -private fun KotlinStubs.isObjCReferenceType(type: IrType): Boolean { - if (!target.family.isAppleFamily) return false - - // Handle the same types as produced by [objCPointerMirror] in Interop/StubGenerator/.../Mappings.kt. - - if (type.isObjCObjectType()) return true - - val descriptor = type.classifierOrNull?.descriptor ?: return false - val builtIns = irBuiltIns.builtIns - - return when (descriptor) { - builtIns.any, - builtIns.string, - builtIns.list, builtIns.mutableList, - builtIns.set, - builtIns.map -> true - else -> false - } -} - private class CExpression(val expression: String, val type: CType) private interface KotlinToCArgumentPassing { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/InteropIrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/InteropIrUtils.kt new file mode 100644 index 00000000000..f25cc253fa6 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/InteropIrUtils.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.kotlin.backend.konan.cgen + +import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor +import org.jetbrains.kotlin.backend.konan.KonanFqNames +import org.jetbrains.kotlin.backend.konan.RuntimeNames +import org.jetbrains.kotlin.backend.konan.ir.* +import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols +import org.jetbrains.kotlin.backend.konan.ir.isObjCObjectType +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +internal fun IrType.isCEnumType(): Boolean { + val simpleType = this as? IrSimpleType ?: return false + if (simpleType.hasQuestionMark) return false + val enumClass = simpleType.classifier.owner as? IrClass ?: return false + if (!enumClass.isEnumClass) return false + + return enumClass.superTypes + .any { (it.classifierOrNull?.owner as? IrClass)?.fqNameForIrSerialization == FqName("kotlinx.cinterop.CEnum") } +} + +private val cCall = RuntimeNames.cCall + +// Make sure external stubs always get proper annotaions. +private fun IrDeclaration.hasCCallAnnotation(name: String): Boolean = + this.annotations.hasAnnotation(cCall.child(Name.identifier(name))) + // LazyIr doesn't pass annotations from descriptor to IrValueParameter. + || this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier(name))) + +internal fun IrValueParameter.isWCStringParameter() = hasCCallAnnotation("WCString") + +internal fun IrValueParameter.isCStringParameter() = hasCCallAnnotation("CString") + +internal fun IrValueParameter.isObjCConsumed() = hasCCallAnnotation("Consumed") + +internal fun IrSimpleFunction.objCConsumesReceiver() = hasCCallAnnotation("ConsumesReceiver") + +internal fun IrSimpleFunction.objCReturnsRetained() = hasCCallAnnotation("ReturnsRetained") + +internal fun IrClass.getCStructSpelling(): String? = + getAnnotationArgumentValue(FqName("kotlinx.cinterop.internal.CStruct"), "spelling") + +internal fun IrType.isTypeOfNullLiteral(): Boolean = this is IrSimpleType && hasQuestionMark + && classifier.isClassWithFqName(StandardNames.FqNames.nothing) + +internal fun IrType.isVector(): Boolean { + if (this is IrSimpleType && !this.hasQuestionMark) { + return classifier.isClassWithFqName(KonanFqNames.Vector128.toUnsafe()) + } + return false +} + +internal fun IrType.isObjCReferenceType(target: KonanTarget, irBuiltIns: IrBuiltIns): Boolean { + if (!target.family.isAppleFamily) return false + + // Handle the same types as produced by [objCPointerMirror] in Interop/StubGenerator/.../Mappings.kt. + + if (isObjCObjectType()) return true + + val descriptor = classifierOrNull?.descriptor ?: return false + val builtIns = irBuiltIns.builtIns + + return when (descriptor) { + builtIns.any, + builtIns.string, + builtIns.list, builtIns.mutableList, + builtIns.set, + builtIns.map -> true + else -> false + } +} + +internal fun IrType.isCPointer(symbols: KonanSymbols): Boolean = this.classOrNull == symbols.interopCPointer +internal fun IrType.isCValue(symbols: KonanSymbols): Boolean = this.classOrNull == symbols.interopCValue + +internal fun IrType.isNativePointed(symbols: KonanSymbols): Boolean = isSubtypeOfClass(symbols.nativePointed) + +internal fun IrType.isCStructFieldTypeStoredInMemoryDirectly(): Boolean = isPrimitiveType() || isUnsigned() || isVector() + +internal fun IrType.isCStructFieldSupportedReferenceType(symbols: KonanSymbols): Boolean = + isObjCObjectType() + || getClass()?.isAny() == true + || isStringClassType() + || classOrNull == symbols.list + || classOrNull == symbols.mutableList + || classOrNull == symbols.set + || classOrNull == symbols.map + +/** + * Check given function is a getter or setter + * for `value` property of CEnumVar subclass. + */ +internal fun IrFunction.isCEnumVarValueAccessor(symbols: KonanSymbols): Boolean { + val parent = parent as? IrClass ?: return false + return if (symbols.interopCEnumVar in parent.superClasses && isPropertyAccessor) { + (propertyIfAccessor as IrProperty).name.asString() == "value" + } else { + false + } +} + +internal fun IrFunction.isCStructMemberAtAccessor() = hasAnnotation(RuntimeNames.cStructMemberAt) + +internal fun IrFunction.isCStructArrayMemberAtAccessor() = hasAnnotation(RuntimeNames.cStructArrayMemberAt) + +internal fun IrFunction.isCStructBitFieldAccessor() = hasAnnotation(RuntimeNames.cStructBitField) + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt index 33ab73eacce..7e979e33ea5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt @@ -5,72 +5,36 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.ir.simpleFunctions -import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType import org.jetbrains.kotlin.backend.konan.RuntimeNames -import org.jetbrains.kotlin.backend.konan.cgen.isCEnumType -import org.jetbrains.kotlin.backend.konan.cgen.isVector +import org.jetbrains.kotlin.backend.konan.cgen.* import org.jetbrains.kotlin.backend.konan.descriptors.getAnnotationStringValue import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols -import org.jetbrains.kotlin.backend.konan.ir.isAny -import org.jetbrains.kotlin.backend.konan.ir.isObjCObjectType -import org.jetbrains.kotlin.backend.konan.ir.superClasses import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType import org.jetbrains.kotlin.ir.builders.* -import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrProperty -import org.jetbrains.kotlin.ir.declarations.isPropertyAccessor import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.types.defaultType +import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.utils.addToStdlib.cast -/** - * Check given function is a getter or setter - * for `value` property of CEnumVar subclass. - */ -private fun isEnumVarValueAccessor(function: IrFunction, symbols: KonanSymbols): Boolean { - val parent = function.parent as? IrClass ?: return false - return if (symbols.interopCEnumVar in parent.superClasses && function.isPropertyAccessor) { - (function.propertyIfAccessor as IrProperty).name.asString() == "value" - } else { - false - } -} - -private fun isMemberAtAccessor(function: IrFunction): Boolean = - function.hasAnnotation(RuntimeNames.cStructMemberAt) - -private fun isArrayMemberAtAccessor(function: IrFunction): Boolean = - function.hasAnnotation(RuntimeNames.cStructArrayMemberAt) - -private fun isBitFieldAccessor(function: IrFunction): Boolean = - function.hasAnnotation(RuntimeNames.cStructBitField) - private class InteropCallContext( val symbols: KonanSymbols, val builder: IrBuilderWithScope, val failCompilation: (String) -> Nothing ) { - fun IrType.isCPointer(): Boolean = this.classOrNull == symbols.interopCPointer + fun IrType.isCPointer() = this.isCPointer(symbols) - fun IrType.isNativePointed(): Boolean = isSubtypeOfClass(symbols.nativePointed) + fun IrType.isNativePointed() = this.isNativePointed(symbols) - fun IrType.isStoredInMemoryDirectly(): Boolean = - isPrimitiveType() || isUnsigned() || isVector() - - fun IrType.isSupportedReference(): Boolean = isObjCObjectType() - || getClass()?.isAny() == true - || isStringClassType() - || classOrNull == symbols.list - || classOrNull == symbols.mutableList - || classOrNull == symbols.set - || classOrNull == symbols.map + fun IrType.isSupportedReference() = this.isCStructFieldSupportedReferenceType(symbols) val irBuiltIns: IrBuiltIns = builder.context.irBuiltIns } @@ -337,13 +301,13 @@ internal fun tryGenerateInteropMemberAccess( builder: IrBuilderWithScope, failCompilation: (String) -> Nothing ): IrExpression? = when { - isEnumVarValueAccessor(callSite.symbol.owner, symbols) -> + callSite.symbol.owner.isCEnumVarValueAccessor(symbols) -> generateInteropCall(symbols, builder, failCompilation) { generateEnumVarValueAccess(callSite) } - isMemberAtAccessor(callSite.symbol.owner) -> + callSite.symbol.owner.isCStructMemberAtAccessor() -> generateInteropCall(symbols, builder, failCompilation) { generateMemberAtAccess(callSite) } - isBitFieldAccessor(callSite.symbol.owner) -> + callSite.symbol.owner.isCStructBitFieldAccessor() -> generateInteropCall(symbols, builder, failCompilation) { generateBitFieldAccess(callSite) } - isArrayMemberAtAccessor(callSite.symbol.owner) -> + callSite.symbol.owner.isCStructArrayMemberAtAccessor() -> generateInteropCall(symbols, builder, failCompilation) { generateArrayMemberAtAccess(callSite) } else -> null } @@ -373,7 +337,7 @@ private fun InteropCallContext.generateMemberAtAccess(callSite: IrCall): IrExpre val type = accessor.returnType when { type.isCEnumType() -> readEnumValueFromMemory(fieldPointer, type) - type.isStoredInMemoryDirectly() -> readValueFromMemory(fieldPointer, type) + type.isCStructFieldTypeStoredInMemoryDirectly() -> readValueFromMemory(fieldPointer, type) type.isCPointer() -> readPointerFromMemory(fieldPointer) type.isNativePointed() -> readPointed(fieldPointer) type.isSupportedReference() -> readObjectiveCReferenceFromMemory(fieldPointer, type) @@ -385,7 +349,7 @@ private fun InteropCallContext.generateMemberAtAccess(callSite: IrCall): IrExpre val type = accessor.valueParameters[0].type when { type.isCEnumType() -> writeEnumValueToMemory(fieldPointer, value, type) - type.isStoredInMemoryDirectly() -> writeValueToMemory(fieldPointer, value, type) + type.isCStructFieldTypeStoredInMemoryDirectly() -> writeValueToMemory(fieldPointer, value, type) type.isCPointer() -> writePointerToMemory(fieldPointer, value, type) type.isSupportedReference() -> writeObjCReferenceToMemory(fieldPointer, value) else -> failCompilation("Unsupported struct field type: ${type.getClass()?.name}") 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 a5e6f2194da..7ef31cfd89f 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 @@ -5,15 +5,12 @@ package org.jetbrains.kotlin.backend.konan.lower -import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.* import org.jetbrains.kotlin.backend.common.ir.allParameters import org.jetbrains.kotlin.backend.common.ir.copyTo import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter import org.jetbrains.kotlin.backend.common.ir.simpleFunctions import org.jetbrains.kotlin.backend.common.lower.* -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.* import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt new file mode 100644 index 00000000000..18cb42775ba --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt @@ -0,0 +1,757 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.* +import org.jetbrains.kotlin.backend.common.ir.allParameters +import org.jetbrains.kotlin.backend.common.lower.Closure +import org.jetbrains.kotlin.backend.common.lower.ClosureAnnotator +import org.jetbrains.kotlin.backend.konan.* +import org.jetbrains.kotlin.backend.konan.cgen.* +import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions +import org.jetbrains.kotlin.backend.konan.ir.* +import org.jetbrains.kotlin.backend.konan.ir.companionObject +import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType +import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.isFinalClass +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.Variance + +internal class SpecialBackendChecksTraversal(val context: Context) : FileLoweringPass { + override fun lower(irFile: IrFile) = irFile.acceptChildrenVoid(BackendChecker(context, irFile)) +} + +private class BackendChecker(val context: Context, val irFile: IrFile) : IrElementVisitorVoid { + val interop = context.interopBuiltIns + val symbols = context.ir.symbols + val irBuiltIns = context.irBuiltIns + val target = context.config.target + + fun reportError(location: IrElement, message: String): Nothing = + context.reportCompilationError(message, irFile, location) + + private val outerDeclarations = mutableListOf() + + private val outerClass: IrClass? get() = outerDeclarations.last { it is IrClass } as? IrClass + private val outerFunction: IrFunction? get() = outerDeclarations.last { it is IrFunction } as? IrFunction + + private val outerAnnotators = mutableListOf>() + private val functionAnnotators = mutableMapOf>() + private val functionClosures = mutableMapOf() + + private fun capturesAnything(function: IrFunction): Boolean { + if (function.visibility != DescriptorVisibilities.LOCAL) return false + val closure = functionClosures.getOrPut(function) { + functionAnnotators[function]!!.value.getFunctionClosure(function) + } + return closure.capturedValues.isNotEmpty() + } + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitDeclaration(declaration: IrDeclarationBase) { + if (declaration is IrClass && declaration.isKotlinObjCClass()) { + checkKotlinObjCClass(declaration) + } + + outerDeclarations.push(declaration) + try { + super.visitDeclaration(declaration) + } finally { + outerDeclarations.pop() + } + } + + override fun visitBody(body: IrBody) { + val declaration = outerDeclarations.peek()!! + if ((declaration as? IrFunction)?.visibility == DescriptorVisibilities.LOCAL) { + functionAnnotators[declaration] = outerAnnotators.peek()!! + super.visitBody(body) + return + } + + outerAnnotators.push(lazy { ClosureAnnotator(body, declaration) }) + try { + super.visitBody(body) + } finally { + outerAnnotators.pop() + } + } + + private fun IrConstructor.isOverrideInit() = + this.annotations.hasAnnotation(context.interopBuiltIns.objCOverrideInit.fqNameSafe) + + private fun checkCanGenerateOverrideInit(irClass: IrClass, constructor: IrConstructor) { + val superClass = irClass.getSuperClassNotAny()!! + val superConstructors = superClass.constructors.filter { + constructor.overridesConstructor(it) + }.toList() + + val superConstructor = superConstructors.singleOrNull() ?: run { + val annotation = context.interopBuiltIns.objCOverrideInit.name + if (superConstructors.isEmpty()) + reportError(constructor, + """ + constructor with @$annotation doesn't override any super class constructor. + It must completely match by parameter names and types.""".trimIndent() + ) + else + reportError(constructor, + "constructor with @$annotation matches more than one of super constructors" + ) + } + + val initMethod = superConstructor.getObjCInitMethod()!! + + // Remove fake overrides of this init method, also check for explicit overriding: + irClass.declarations.forEach { + if (it is IrSimpleFunction && initMethod.symbol in it.overriddenSymbols && it.isReal) { + val annotation = context.interopBuiltIns.objCOverrideInit.name + reportError(constructor, + "constructor with @$annotation overrides initializer that is already overridden explicitly" + ) + } + } + } + + private fun IrConstructor.overridesConstructor(other: IrConstructor) = + this.descriptor.valueParameters.size == other.descriptor.valueParameters.size && + this.descriptor.valueParameters.all { + val otherParameter = other.descriptor.valueParameters[it.index] + it.name == otherParameter.name && it.type == otherParameter.type + } + + private fun checkCanGenerateActionImp(function: IrSimpleFunction) { + val action = "@${context.interopBuiltIns.objCAction.name}" + + function.extensionReceiverParameter?.let { + reportError(it, "$action method must not have extension receiver") + } + + function.valueParameters.forEach { + val kotlinType = it.descriptor.type + if (!kotlinType.isObjCObjectType()) + reportError(it, "Unexpected $action method parameter type: $kotlinType\n" + + "Only Objective-C object types are supported here") + } + + val returnType = function.returnType + if (!returnType.isUnit()) + reportError(function, "Unexpected $action method return type: ${returnType.toKotlinType()}\n" + + "Only 'Unit' is supported here") + + checkCanGenerateFunctionImp(function) + } + + private fun checkCanGenerateOutletSetterImp(property: IrProperty) { + val descriptor = property.descriptor + + val outlet = "@${context.interopBuiltIns.objCOutlet.name}" + + if (!descriptor.isVar) + reportError(property, "$outlet property must be var") + + property.getter?.extensionReceiverParameter?.let { + reportError(it, "$outlet must not have extension receiver") + } + + val type = descriptor.type + if (!type.isObjCObjectType()) + reportError(property, "Unexpected $outlet type: $type\n" + + "Only Objective-C object types are supported here") + + checkCanGenerateFunctionImp(property.setter!!) + } + + private fun checkCanGenerateFunctionImp(function: IrFunction) { + if (function.valueParameters.size > 2) + reportError(function, "Only 0, 1 or 2 parameters are supported here") + } + + private fun IrClass.hasFields() = + this.declarations.any { + when (it) { + is IrField -> it.isReal + is IrProperty -> it.isReal && it.backingField != null + else -> false + } + } + + private fun checkKotlinObjCClass(irClass: IrClass) { + for (declaration in irClass.declarations) { + if (declaration is IrSimpleFunction && declaration.annotations.hasAnnotation(interop.objCAction.fqNameSafe)) + checkCanGenerateActionImp(declaration) + if (declaration is IrProperty && declaration.annotations.hasAnnotation(interop.objCOutlet.fqNameSafe)) + checkCanGenerateOutletSetterImp(declaration) + if (declaration is IrConstructor && declaration.isOverrideInit()) + checkCanGenerateOverrideInit(irClass, declaration) + if (declaration is IrSimpleFunction && declaration.isReal) { + for (overriddenSymbol in declaration.overriddenSymbols) + overriddenSymbol.owner.getExternalObjCMethodInfo()?.selector?.let { + checkCanGenerateCFunction( + function = declaration, + signature = overriddenSymbol.owner, + isObjCMethod = true, + location = declaration + ) + } + } + } + + val kind = irClass.descriptor.kind + if (kind != ClassKind.CLASS && kind != ClassKind.OBJECT) + reportError(irClass, "Only classes are supported as subtypes of Objective-C types") + + if (!irClass.descriptor.isFinalClass) + reportError(irClass, "Non-final Kotlin subclasses of Objective-C classes are not yet supported") + + irClass.companionObject()?.let { + if (it.hasFields() || it.getSuperClassNotAny()?.hasFields() == true) + reportError(irClass, "Fields are not supported for Companion of subclass of ObjC type") + } + + var hasObjCClassSupertype = false + irClass.descriptor.defaultType.constructor.supertypes.forEach { + val descriptor = it.constructor.declarationDescriptor as ClassDescriptor + if (!descriptor.isObjCClass()) + reportError(irClass, "Mixing Kotlin and Objective-C supertypes is not supported") + + if (descriptor.kind == ClassKind.CLASS) + hasObjCClassSupertype = true + } + + if (!hasObjCClassSupertype) + reportError(irClass, "Kotlin implementation of Objective-C protocol must have Objective-C superclass (e.g. NSObject)") + + val methodsOfAny = context.ir.symbols.any.owner.declarations.filterIsInstance().toSet() + + irClass.declarations.filterIsInstance().filter { it.isReal }.forEach { method -> + val overriddenMethodOfAny = method.allOverriddenFunctions.firstOrNull { + it in methodsOfAny + } + + if (overriddenMethodOfAny != null) { + val correspondingObjCMethod = when (method.name.asString()) { + "toString" -> "'description'" + "hashCode" -> "'hash'" + "equals" -> "'isEqual:'" + else -> "corresponding Objective-C method" + } + + context.report( + method, + "can't override '${method.name}', override $correspondingObjCMethod instead", + isError = true + ) + } + } + } + + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall) { + expression.acceptChildrenVoid(this) + + val constructedClass = outerClass!! + + if (!constructedClass.isObjCClass()) + return + + constructedClass.parent.let { parent -> + if (parent is IrClass && parent.isObjCClass() && constructedClass.isCompanion) { + // Note: it is actually not used; getting values of such objects is handled by code generator + // in [FunctionGenerationContext.getObjectValue]. + + return + } + } + + val delegatingCallConstructingClass = expression.symbol.owner.constructedClass + if (!constructedClass.isExternalObjCClass() && + delegatingCallConstructingClass.isExternalObjCClass()) { + // Calling super constructor from Kotlin Objective-C class. + + val initMethod = expression.symbol.owner.getObjCInitMethod()!! + + if (!expression.symbol.owner.objCConstructorIsDesignated()) + reportError(expression, "Unable to call non-designated initializer as super constructor") + + checkCanGenerateObjCCall( + method = initMethod, + call = expression, + arguments = initMethod.valueParameters.map { expression.getValueArgument(it.index) } + ) + } + } + + override fun visitConstructorCall(expression: IrConstructorCall) { + expression.acceptChildrenVoid(this) + + val callee = expression.symbol.owner + val initMethod = callee.getObjCInitMethod() + if (initMethod != null) { + val arguments = callee.valueParameters.map { expression.getValueArgument(it.index) } + + checkCanGenerateObjCCall( + method = initMethod, + call = expression, + arguments = arguments + ) + } + + if (callee.returnType.getInlinedClassNative()?.descriptor == interop.nativePointed) + reportError(expression, "Native interop types constructors must not be called directly") + } + + override fun visitCall(expression: IrCall) { + expression.acceptChildrenVoid(this) + + val callee = expression.symbol.owner + + callee.getObjCFactoryInitMethodInfo()?.let { _ -> + val arguments = (0 until expression.valueArgumentsCount).map(expression::getValueArgument) + + checkCanGenerateObjCCall( + method = callee, + call = expression, + arguments = arguments + ) + } + + callee.getExternalObjCMethodInfo()?.let { _ -> + val isInteropStubsFile = irFile.annotations.hasAnnotation(FqName("kotlinx.cinterop.InteropStubs")) + + // Special case: bridge from Objective-C method implementation template to Kotlin method; + // handled in CodeGeneratorVisitor.callVirtual. + val useKotlinDispatch = isInteropStubsFile && + outerFunction!!.annotations.hasAnnotation(FqName("kotlin.native.internal.ExportForCppRuntime")) + + if (!useKotlinDispatch) { + val arguments = callee.valueParameters.map { expression.getValueArgument(it.index) } + + if (expression.superQualifierSymbol?.owner?.isObjCMetaClass() == true) + reportError(expression, "Super calls to Objective-C meta classes are not supported yet") + + if (expression.superQualifierSymbol?.owner?.isInterface == true) + reportError(expression, "Super calls to Objective-C protocols are not allowed") + + checkCanGenerateObjCCall( + method = callee, + call = expression, + arguments = arguments + ) + } + } + + if (callee.annotations.hasAnnotation(RuntimeNames.cCall)) + checkCanGenerateCCall(expression, isInvoke = false) + + when (val intrinsicType = tryGetIntrinsicType(expression)) { + IntrinsicType.INTEROP_STATIC_C_FUNCTION -> { + val target = getUnboundReferencedFunction(expression.getValueArgument(0)!!) + + if (target == null || target.symbol !is IrSimpleFunctionSymbol) + reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda") + + val signatureTypes = target.allParameters.map { it.type } + target.returnType + + callee.typeParameters.indices.forEach { index -> + val typeArgument = expression.getTypeArgument(index)!!.toKotlinType() + val signatureType = signatureTypes[index].toKotlinType() + if (typeArgument.constructor != signatureType.constructor || + typeArgument.isMarkedNullable != signatureType.isMarkedNullable + ) { + reportError(expression, "C function signature element mismatch: expected '$signatureType', got '$typeArgument'") + } + } + + checkCanGenerateCFunctionPointer(target as IrSimpleFunction, expression) + } + IntrinsicType.INTEROP_FUNPTR_INVOKE -> { + checkCanGenerateCCall(expression, isInvoke = true) + } + IntrinsicType.INTEROP_SIGN_EXTEND, IntrinsicType.INTEROP_NARROW -> { + + val integerTypePredicates = arrayOf( + IrType::isByte, IrType::isShort, IrType::isInt, IrType::isLong + ) + + val receiver = expression.extensionReceiver!! + val typeOperand = expression.getSingleTypeArgument() + val kotlinTypeOperand = typeOperand.toKotlinType() + + val receiverTypeIndex = integerTypePredicates.indexOfFirst { it(receiver.type) } + val typeOperandIndex = integerTypePredicates.indexOfFirst { it(typeOperand) } + + val receiverKotlinType = receiver.type.toKotlinType() + + if (receiverTypeIndex == -1) + reportError(receiver, "Receiver's type $receiverKotlinType is not an integer type") + + if (typeOperandIndex == -1) + reportError(expression, "Type argument $kotlinTypeOperand is not an integer type") + + when (intrinsicType) { + IntrinsicType.INTEROP_SIGN_EXTEND -> if (receiverTypeIndex > typeOperandIndex) + reportError(expression, "unable to sign extend $receiverKotlinType to $kotlinTypeOperand") + + IntrinsicType.INTEROP_NARROW -> if (receiverTypeIndex < typeOperandIndex) + reportError(expression, "unable to narrow $receiverKotlinType to $kotlinTypeOperand") + + else -> error(intrinsicType) + } + } + IntrinsicType.INTEROP_CONVERT -> { + val integerClasses = symbols.allIntegerClasses + val typeOperand = expression.getTypeArgument(0)!! + val receiverType = expression.symbol.owner.extensionReceiverParameter!!.type + + if (typeOperand !is IrSimpleType || typeOperand.classifier !in integerClasses || typeOperand.hasQuestionMark) + reportError(expression, "unable to convert ${receiverType.toKotlinType()} to ${typeOperand.toKotlinType()}") + } + IntrinsicType.WORKER_EXECUTE -> { + getUnboundReferencedFunction(expression.getValueArgument(2)!!) + ?: reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda") + } + else -> when (callee.symbol) { + symbols.createCleaner -> + getUnboundReferencedFunction(expression.getValueArgument(1)!!) + ?: reportError(expression, "${callee.fqNameForIrSerialization} must take an unbound, non-capturing function or lambda") + symbols.immutableBlobOf -> { + val args = expression.getValueArgument(0) + ?: reportError(expression, "expected at least one element") + val elements = (args as IrVararg).elements + if (elements.any { it is IrSpreadElement }) + reportError(args, "no spread elements allowed here") + elements.forEach { + if (it !is IrConst<*>) + reportError(args, "all elements of binary blob must be constants") + val value = it.value as Short + if (value < 0 || value > 0xff) + reportError(it, "incorrect value for binary data: $value") + } + } + } + } + } + + override fun visitFunctionExpression(expression: IrFunctionExpression) { + expression.acceptChildrenVoid(this) + + checkCanReferenceFunction(expression.function, expression) + } + + override fun visitFunctionReference(expression: IrFunctionReference) { + expression.acceptChildrenVoid(this) + + checkCanReferenceFunction(expression.symbol.owner, expression) + } + + private fun checkCanReferenceFunction(callee: IrFunction, expression: IrExpression) { + // Corresponds to the check in [KotlinToCCallBuilder.handleArgumentForVarargParameter]. + if (callee.valueParameters.any { it.isVararg }) { + if (callee.annotations.hasAnnotation(RuntimeNames.cCall)) + reportError(expression, "callable references to variadic C functions are not supported") + if (callee is IrConstructor && callee.getObjCInitMethod() != null + || callee.getObjCFactoryInitMethodInfo() != null + || callee.getExternalObjCMethodInfo() != null + ) { + reportError(expression, "callable references to variadic Objective-C methods are not supported") + } + } + } + + private fun getUnboundReferencedFunction(expression: IrExpression) = when (expression) { + is IrFunctionReference -> expression.symbol.owner.takeIf { expression.getArguments().isEmpty() && !capturesAnything(it) } + is IrFunctionExpression -> expression.function.takeIf { !capturesAnything(it) } + else -> null + } + + private fun IrCall.getSingleTypeArgument(): IrType { + val typeParameter = symbol.owner.typeParameters.single() + return getTypeArgument(typeParameter.index)!! + } +} + +private fun BackendChecker.checkCanGenerateCCall(expression: IrCall, isInvoke: Boolean) { + val callee = expression.symbol.owner + + if (isInvoke) { + (0 until expression.valueArgumentsCount).forEach { + checkCanMapCalleeFunctionParameter( + type = expression.getTypeArgument(it)!!, + isObjCMethod = false, + variadic = false, + parameter = null, + argument = expression.getValueArgument(it)!!) + } + + val returnType = expression.getTypeArgument(expression.typeArgumentsCount - 1)!! + checkCanMapReturnType(returnType, TypeLocation.FunctionCallResult(expression)) + } else { + val arguments = (0 until expression.valueArgumentsCount).map(expression::getValueArgument) + checkCanAddArguments(arguments, callee, isObjCMethod = false) + + checkCanMapReturnType(callee.returnType, TypeLocation.FunctionCallResult(expression)) + } +} + +private fun BackendChecker.checkCanAddArguments(arguments: List, callee: IrFunction, isObjCMethod: Boolean) { + arguments.forEachIndexed { index, argument -> + val parameter = callee.valueParameters[index] + if (parameter.isVararg) + checkCanHandleArgumentForVarargParameter(argument, isObjCMethod) + else + checkCanMapCalleeFunctionParameter(parameter.type, isObjCMethod, variadic = false, parameter = parameter, argument = argument!!) + } +} + +private fun BackendChecker.checkCanUnwrapVariadicArguments(elements: List, isObjCMethod: Boolean) { + for (element in elements) when (element) { + is IrExpression -> + checkCanMapCalleeFunctionParameter(element.type, isObjCMethod, variadic = true, parameter = null, argument = element) + is IrSpreadElement -> { + val expression = element.expression + if (expression is IrCall && expression.symbol == symbols.arrayOf) + checkCanHandleArgumentForVarargParameter(expression.getValueArgument(0), isObjCMethod) + else + reportError(element, "When calling variadic " + + (if (isObjCMethod) "Objective-C methods " else "C functions ") + + "spread operator is supported only for *arrayOf(...)") + } + } +} + +private fun BackendChecker.checkCanHandleArgumentForVarargParameter(argument: IrExpression?, isObjCMethod: Boolean) { + when (argument) { + is IrVararg -> checkCanUnwrapVariadicArguments(argument.elements, isObjCMethod) + + is IrGetValue -> { + /* This is possible when using named arguments with reordering, i.e. + * + * foo(second = *arrayOf(...), first = ...) + * + * psi2ir generates as + * + * val secondTmp = *arrayOf(...) + * val firstTmp = ... + * foo(firstTmp, secondTmp) + * + * + **/ + + val variable = argument.symbol.owner + if (variable is IrVariable && variable.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE && !variable.isVar) + checkCanUnwrapVariadicArguments((variable.initializer as IrVararg).elements, isObjCMethod) + } + } +} + +private fun BackendChecker.checkCanGenerateObjCCall( + method: IrSimpleFunction, + call: IrFunctionAccessExpression, + arguments: List +) { + checkCanAddArguments(arguments, method, isObjCMethod = true) + + checkCanMapReturnType(method.returnType, TypeLocation.FunctionCallResult(call)) +} + +private fun BackendChecker.checkParameter(it: IrValueParameter, functionParameter: IrValueParameter, + isObjCMethod: Boolean, location: IrElement) { + val typeLocation = if (isObjCMethod) + TypeLocation.ObjCMethodParameter(it.index, functionParameter) + else + TypeLocation.FunctionPointerParameter(it.index, location) + + if (functionParameter.isVararg) + reportError(typeLocation.element, if (isObjCMethod) { + "overriding variadic Objective-C methods is not supported" + } else { + "variadic function pointers are not supported" + }) + + checkCanMapFunctionParameterType(it.type, variadic = false, location = typeLocation) +} + +private fun BackendChecker.checkCanGenerateCFunction( + function: IrSimpleFunction, + signature: IrSimpleFunction, + isObjCMethod: Boolean, + location: IrElement +) { + signature.extensionReceiverParameter?.let { + checkParameter(it, function.extensionReceiverParameter!!, isObjCMethod, location) + } + + signature.valueParameters.forEach { + checkParameter(it, function.valueParameters[it.index], isObjCMethod, location) + } +} + +private fun BackendChecker.checkCanGenerateCFunctionPointer(function: IrSimpleFunction, expression: IrExpression) = + checkCanGenerateCFunction( + function = function, + signature = function, + isObjCMethod = false, + location = expression + ) + +private fun BackendChecker.checkCanMapCalleeFunctionParameter( + type: IrType, + isObjCMethod: Boolean, + variadic: Boolean, + parameter: IrValueParameter?, + argument: IrExpression +) { + val classifier = type.classifierOrNull + when { + classifier == symbols.interopCValues || // Note: this should not be accepted, but is required for compatibility + classifier == symbols.interopCValuesRef -> return + + classifier == symbols.string && (variadic || parameter?.isCStringParameter() == true) -> { + if (variadic && isObjCMethod) { + reportError(argument, "Passing String as variadic Objective-C argument is ambiguous; " + + "cast it to NSString or pass with '.cstr' as C string") + // TODO: consider reporting a warning for C functions. + } + } + + classifier == symbols.string && parameter?.isWCStringParameter() == true -> return + + else -> checkCanMapFunctionParameterType(type, variadic = variadic, location = TypeLocation.FunctionArgument(argument)) + } +} + +private sealed class TypeLocation(val element: IrElement) { + class FunctionArgument(val argument: IrExpression) : TypeLocation(argument) + 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 BackendChecker.checkCanMapFunctionParameterType(type: IrType, variadic: Boolean, location: TypeLocation) { + if (!type.isUnit() || variadic) + checkCanMapType(type, variadic = variadic, location = location) +} + +private fun BackendChecker.checkCanMapReturnType(type: IrType, location: TypeLocation) { + if (!type.isUnit()) + checkCanMapType(type, variadic = false, location = location) +} + +private fun BackendChecker.checkCanMapBlockType(type: IrType, location: TypeLocation) { + when (val returnTypeArgument = (type as IrSimpleType).arguments.last()) { + is IrTypeProjection -> if (returnTypeArgument.variance == Variance.INVARIANT) + checkCanMapReturnType(returnTypeArgument.type, TypeLocation.BlockReturnValue(location)) + else + reportUnsupportedType("${returnTypeArgument.variance.label}-variance of return type", type, location) + + is IrStarProjection -> reportUnsupportedType("* as return type", type, location) + } + type.arguments.dropLast(1).forEachIndexed { index, argument -> + when (argument) { + is IrTypeProjection -> if (argument.variance == Variance.INVARIANT) + checkCanMapType(argument.type, 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) + } + } +} + +private fun BackendChecker.checkCanMapType(type: IrType, variadic: Boolean, location: TypeLocation) = + checkCanMapType(type, variadic, location) { reportUnsupportedType(it, type, location) } + +private fun BackendChecker.checkCanMapType( + type: IrType, + variadic: Boolean, + typeLocation: TypeLocation, + reportUnsupportedType: (String) -> Nothing +) { + when { + type.isBoolean() -> { + if (cBoolType(target) == null) + reportUnsupportedType("unavailable on target platform") + } + + type.isByte() -> return + type.isShort() -> return + type.isInt() -> return + type.isLong() -> return + type.isFloat() -> return + type.isDouble() -> return + type.isCPointer(symbols) -> return + type.isTypeOfNullLiteral() && variadic -> return + type.isUByte() -> return + type.isUShort() -> return + type.isUInt() -> return + type.isULong() -> return + type.isVector() -> return + + type.isCEnumType() -> return + + type.isCValue(symbols) -> if (type.isNullable()) + reportUnsupportedType("must not be nullable") + else { + val kotlinClass = (type as IrSimpleType).arguments.singleOrNull()?.typeOrNull?.getClass() + ?: reportUnsupportedType("must be parameterized with concrete class") + + kotlinClass.getCStructSpelling() + ?: reportUnsupportedType("not a structure or too complex") + } + + type.isNativePointed(symbols) -> return + + type.isFunction() -> if (variadic) + reportUnsupportedType("not supported as variadic argument") + else + checkCanMapBlockType(type, location = typeLocation) + + type.isObjCReferenceType(target, irBuiltIns) -> return + + else -> reportUnsupportedType("doesn't correspond to any C type") + } +} + +private fun BackendChecker.reportUnsupportedType(reason: String, type: IrType, location: TypeLocation): Nothing { + // 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 ${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.render()} $typeLocation is not supported here" + + if (reason.isNotEmpty()) ": $reason" else "") +}