[IR] Extracted different backend checks into a separate pass
This commit is contained in:
+6
@@ -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",
|
||||
|
||||
+2
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-91
@@ -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 {
|
||||
|
||||
+118
@@ -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)
|
||||
|
||||
+14
-50
@@ -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}")
|
||||
|
||||
+1
-4
@@ -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
|
||||
|
||||
+757
@@ -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 "")
|
||||
}
|
||||
Reference in New Issue
Block a user