[IR] Extracted different backend checks into a separate pass

This commit is contained in:
Igor Chevdar
2020-09-24 14:12:30 +05:00
parent e7441037f6
commit 5b457eadb1
7 changed files with 917 additions and 148 deletions
@@ -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",
@@ -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)
}
}
@@ -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<String>(cCall, "id")!!
val cCallSymbolName = callee.getAnnotationArgumentValue<String>(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 {
@@ -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)
@@ -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}")
@@ -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
@@ -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<IrDeclaration>()
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<Lazy<ClosureAnnotator>>()
private val functionAnnotators = mutableMapOf<IrFunction, Lazy<ClosureAnnotator>>()
private val functionClosures = mutableMapOf<IrFunction, Closure>()
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<IrSimpleFunction>().toSet()
irClass.declarations.filterIsInstance<IrSimpleFunction>().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<IrExpression?>, 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<IrVarargElement>, 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<IrExpression?>
) {
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 "")
}