From 9e716291c1759f8a22d4fff858881680c6c40cf1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 13 Feb 2017 14:50:48 +0700 Subject: [PATCH] Interop, backend: add partial support for callbacks Support callbacks that don't take or return structs by value. --- .../kotlinx/cinterop/NativeCallbacks.kt | 10 ++++++ .../native/interop/gen/jvm/StubGenerator.kt | 22 ++++++++++--- .../kotlin/backend/konan/InteropUtils.kt | 20 +++++++++++- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 11 +++++-- .../konan/lower/CallableReferenceLowering.kt | 5 ++- .../backend/konan/lower/InteropLowering.kt | 31 +++++++++++++++++-- 6 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeCallbacks.kt diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeCallbacks.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeCallbacks.kt new file mode 100644 index 00000000000..96c4e71d772 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeCallbacks.kt @@ -0,0 +1,10 @@ +package kotlinx.cinterop + +/** + * The type of C function which can be constructed from the appropriate Kotlin function without using any adapter. + */ +abstract class CTriviallyAdaptedFunctionType> : CAdaptedFunctionType { + @konan.internal.Intrinsic + override final fun fromStatic(function: F): NativePtr = + throw Error("CTriviallyAdaptedFunctionType.fromStatic is called virtually") +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 2f41203ec48..0b8595a2f62 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -731,13 +731,27 @@ class StubGenerator( private fun getRetValFfiType(type: Type) = getArgFfiType(type) - private fun generateFunctionType(type: FunctionType, name: String) { - val kotlinFunctionType = getKotlinFunctionType(type) + private fun generateFunctionType(type: FunctionType, name: String) = when (platform) { + KotlinPlatform.JVM -> generateJvmFunctionType(type, name) + KotlinPlatform.NATIVE -> generateNativeFunctionType(type, name) + } - if (platform == KotlinPlatform.NATIVE) { + private fun requiresAdapterOnNative(type: FunctionType): Boolean { + assert (platform == KotlinPlatform.NATIVE) + return (type.parameterTypes + type.returnType).any { it.unwrapTypedefs() is RecordType } + } + + private fun generateNativeFunctionType(type: FunctionType, name: String) { + if (requiresAdapterOnNative(type)) { out("object $name : CFunctionType {}") - return + } else { + val kotlinFunctionType = getKotlinFunctionType(type) + out("object $name : CTriviallyAdaptedFunctionType<$kotlinFunctionType>()") } + } + + private fun generateJvmFunctionType(type: FunctionType, name: String) { + val kotlinFunctionType = getKotlinFunctionType(type) val constructorArgs = listOf(getRetValFfiType(type.returnType)) + type.parameterTypes.map { getArgFfiType(it) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index a93fa71890e..cca7e1f1ba2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -6,7 +6,11 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.StarProjectionImpl import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.replace +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf private val cPointerName = "CPointer" private val nativePointedName = "NativePointed" @@ -34,7 +38,7 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { private val nativePointed = packageScope.getContributedClassifier(nativePointedName) as ClassDescriptor - private val cPointer = this.packageScope.getContributedClassifier(cPointerName) as ClassDescriptor + val cPointer = this.packageScope.getContributedClassifier(cPointerName) as ClassDescriptor val cPointerRawValue = cPointer.unsubstitutedMemberScope.getContributedVariables("rawValue").single() @@ -112,6 +116,20 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val bitsToDouble = packageScope.getContributedFunctions("bitsToDouble").single() + val staticCFunction = packageScope.getContributedFunctions("staticCFunction").single() + + private val triviallyAdaptedFunctionTypeClass = + packageScope.getContributedClassifier("CTriviallyAdaptedFunctionType") as ClassDescriptor + + private val trivallyAdaptedFunctionTypeType = + triviallyAdaptedFunctionTypeClass.defaultType.replace( + newArguments = listOf( + StarProjectionImpl(triviallyAdaptedFunctionTypeClass.declaredTypeParameters.single()) + ) + ) + + fun isTriviallyAdaptedFunctionType(type: KotlinType): Boolean = + type.isSubtypeOf(trivallyAdaptedFunctionTypeType) } private fun MemberScope.getContributedVariables(name: String) = diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index cdb5f2f7625..e79dda5da2f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -1540,9 +1540,16 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// private fun evaluateCallableReference(expression: IrCallableReference): LLVMValueRef { - assert (expression.type.isUnboundCallableReference()) + // TODO: consider creating separate IR element for pointer to function. + assert (expression.type.isUnboundCallableReference() || + TypeUtils.getClassDescriptor(expression.type) == context.interopBuiltIns.cPointer) + assert (expression.getArguments().isEmpty()) - val entry = codegen.functionEntryPointAddress(expression.descriptor as FunctionDescriptor) + + val descriptor = expression.descriptor + assert (descriptor.dispatchReceiverParameter == null) + + val entry = codegen.functionEntryPointAddress(descriptor as FunctionDescriptor) return entry } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt index 6de421d7cea..2c3da575ad1 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/CallableReferenceLowering.kt @@ -100,7 +100,10 @@ private class CallableReferencesUnbinder(val lower: CallableReferenceLowering, } override fun visitCallableReference(expression: IrCallableReference): IrExpression { - assert (expression.type.isFunctionOrKFunctionType) + if (!expression.type.isFunctionOrKFunctionType) { + // Not a subject of this lowering. + return expression + } val descriptor = expression.descriptor val boundArgs = expression.getArguments() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 4354a018e83..7f48b8e0c72 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -5,7 +5,9 @@ import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer import org.jetbrains.kotlin.backend.common.lower.at import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.ValueType +import org.jetbrains.kotlin.backend.konan.ir.getArguments import org.jetbrains.kotlin.backend.konan.isRepresentedAs +import org.jetbrains.kotlin.backend.konan.reportCompilationError import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.builders.IrBuilder @@ -14,11 +16,13 @@ import org.jetbrains.kotlin.ir.builders.irCall import org.jetbrains.kotlin.ir.builders.irGet import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrCallableReferenceImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.OverridingUtil +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils @@ -29,12 +33,12 @@ import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf */ internal class InteropLowering(val context: Context) : FileLoweringPass { override fun lower(irFile: IrFile) { - val transformer = InteropTransformer(context) + val transformer = InteropTransformer(context, irFile) irFile.transformChildrenVoid(transformer) } } -private class InteropTransformer(val context: Context) : IrBuildingTransformer(context) { +private class InteropTransformer(val context: Context, val irFile: IrFile) : IrBuildingTransformer(context) { val interop = context.interopBuiltIns @@ -212,6 +216,29 @@ private class InteropTransformer(val context: Context) : IrBuildingTransformer(c } } + interop.staticCFunction -> { + val argument = expression.getValueArgument(0)!! + if (argument !is IrCallableReference || argument.getArguments().isNotEmpty()) { + context.reportCompilationError( + "${interop.staticCFunction.fqNameSafe} must take an unbound, non-capturing function", + irFile, expression + ) + // TODO: should probably be reported during analysis. + } + + val cFunctionType = expression.getTypeArgument(descriptor.typeParameters[1])!! + + if (interop.isTriviallyAdaptedFunctionType(cFunctionType)) { + IrCallableReferenceImpl( + builder.startOffset, builder.endOffset, + expression.type, + argument.descriptor, + typeArguments = null) + } else { + TODO("$cFunctionType requiring non-trivial C adapter") + } + } + else -> expression } }