From 2e594bb19c68f8b81405c262401ef551cd8e19e1 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Fri, 31 Jan 2020 12:30:14 +0700 Subject: [PATCH] [Interop][Lowering] Support metadata-based structs --- .../kotlin/backend/konan/RuntimeNames.kt | 2 + .../kotlin/backend/konan/cgen/CBridgeGen.kt | 2 +- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 3 + .../konan/lower/InteropCallConvertors.kt | 212 +++++++++++++++--- .../backend/konan/lower/InteropLowering.kt | 2 +- 5 files changed, 193 insertions(+), 28 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt index 6817e2965da..41b97946cfe 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt @@ -10,6 +10,8 @@ object RuntimeNames { val exportForCompilerAnnotation = FqName("kotlin.native.internal.ExportForCompiler") val exportTypeInfoAnnotation = FqName("kotlin.native.internal.ExportTypeInfo") val cCall = FqName("kotlinx.cinterop.internal.CCall") + val cStructMemberAt = FqName("kotlinx.cinterop.internal.CStruct.MemberAt") + val cStructBitField = FqName("kotlinx.cinterop.internal.CStruct.BitField") val objCMethodAnnotation = FqName("kotlinx.cinterop.ObjCMethod") val objCMethodImp = FqName("kotlinx.cinterop.ObjCMethodImp") val independent = FqName("kotlin.native.internal.Independent") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt index 1d7d1909268..e43967c9fc0 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt @@ -602,7 +602,7 @@ private fun IrType.isUShort() = this.isUnsigned(UnsignedType.USHORT) private fun IrType.isUInt() = this.isUnsigned(UnsignedType.UINT) private fun IrType.isULong() = this.isUnsigned(UnsignedType.ULONG) -private fun IrType.isCEnumType(): Boolean { +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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index b913d13a961..8228e420776 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -198,6 +198,9 @@ internal class KonanSymbols( val nativeMemUtils = symbolTable.referenceClass(context.interopBuiltIns.nativeMemUtils) + val readBits = interopFunction("readBits") + val writeBits = interopFunction("writeBits") + val objCExportTrapOnUndeclaredException = symbolTable.referenceSimpleFunction(context.builtIns.kotlinNativeInternal.getContributedFunctions( Name.identifier("trapOnUndeclaredException"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt index b38e5b1cca0..05973df75ca 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt @@ -7,22 +7,20 @@ 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.RuntimeNames +import org.jetbrains.kotlin.backend.konan.cgen.isCEnumType import org.jetbrains.kotlin.backend.konan.descriptors.getAnnotationStringValue import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols import org.jetbrains.kotlin.backend.konan.ir.superClasses import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType -import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope -import org.jetbrains.kotlin.ir.builders.irCall -import org.jetbrains.kotlin.ir.builders.irGetObject +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.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.types.classOrNull -import org.jetbrains.kotlin.ir.types.getClass +import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* /** @@ -38,10 +36,20 @@ private fun isEnumVarValueAccessor(function: IrFunction, symbols: KonanSymbols): } } +private fun isMemberAtAccessor(function: IrFunction): Boolean = + function.hasAnnotation(RuntimeNames.cStructMemberAt) + +private fun isBitFieldAccessor(function: IrFunction): Boolean = + function.hasAnnotation(RuntimeNames.cStructBitField) + private class InteropCallContext( val symbols: KonanSymbols, val builder: IrBuilderWithScope -) +) { + fun IrType.isCPointer(): Boolean = this.classOrNull == symbols.interopCPointer + + fun IrType.isNativePointed(): Boolean = isSubtypeOfClass(symbols.nativePointed) +} private inline fun generateInteropCall( symbols: KonanSymbols, @@ -154,40 +162,192 @@ private fun InteropCallContext.convertIntegralToEnum(value: IrExpression, enumTy } } -/** - * Returns null if the given call-site is not an accessor for T.value, T : CEnumVar. - * Otherwise, generates: - * - * get() => byValue(this.reinterpret().value) - * - * set(value) => this.reinterpret().value = value.value +private fun IrType.getCEnumPrimitiveType(): IrType { + assert(this.isCEnumType()) + val enumClass = this.getClass()!! + return enumClass.properties.single { it.name.asString() == "value" } + .getter!!.returnType +} + +private fun InteropCallContext.readEnumValueFromMemory(nativePtr: IrExpression, enumType: IrType): IrExpression { + val enumPrimitiveType = enumType.getCEnumPrimitiveType() + val readMemory = readPrimitiveFromMemory(nativePtr, enumPrimitiveType) + return convertIntegralToEnum(readMemory, enumType) +} + +private fun InteropCallContext.writeEnumValueToMemory(nativePtr: IrExpression, value: IrExpression): IrExpression { + val valueToWrite = convertEnumToIntegral(value) + return writePrimitiveToMemory(nativePtr, valueToWrite) +} + +private fun InteropCallContext.convertCPointerToNativePtr(cPointer: IrExpression): IrExpression { + return builder.irCall(symbols.interopCPointerGetRawValue).also { + it.extensionReceiver = cPointer + } +} + + +private fun InteropCallContext.writePointerToMemory(nativePtr: IrExpression, value: IrExpression): IrExpression { + val valueToWrite = when { + value.type.isCPointer() -> convertCPointerToNativePtr(value) + else -> error("Unsupported pointer type") + } + return writePrimitiveToMemory(nativePtr, valueToWrite) +} + +private fun InteropCallContext.calculateFieldPointer(receiver: IrExpression, offset: Long): IrExpression { + val base = builder.irCall(symbols.interopNativePointedRawPtrGetter).also { + it.dispatchReceiver = receiver + } + val nativePtrPlusLong = symbols.nativePtrType.getClass()!! + .functions.single { it.name.identifier == "plus" } + return with (builder) { + irCall(nativePtrPlusLong).also { + it.dispatchReceiver = base + it.putValueArgument(0, irLong(offset)) + } + } +} + +private fun InteropCallContext.readPointerFromMemory(nativePtr: IrExpression): IrExpression { + val readMemory = readPrimitiveFromMemory(nativePtr, symbols.nativePtrType) + return builder.irCall(symbols.interopInterpretCPointer).also { + it.putValueArgument(0, readMemory) + } +} + +private fun InteropCallContext.readPointed(nativePtr: IrExpression): IrExpression { + return builder.irCall(symbols.interopInterpretNullablePointed).also { + it.putValueArgument(0, nativePtr) + } +} + +/** Returns non-null result if [callSite] is accessor to: + * 1. T.value, T : CEnumVar + * 2. T., T : CStructVar and accessor is annotated with + * [kotlinx.cinterop.internal.CStruct.MemberAt] or [kotlinx.cinterop.internal.CStruct.BitField] */ -internal fun tryGenerateEnumVarValueAccess( +internal fun tryGenerateInteropMemberAccess( callSite: IrCall, symbols: KonanSymbols, builder: IrBuilderWithScope -): IrExpression? = if (isEnumVarValueAccessor(callSite.symbol.owner, symbols)) - generateInteropCall(symbols, builder) { generateEnumVarValueAccess(callSite) } -else null +): IrExpression? = when { + isEnumVarValueAccessor(callSite.symbol.owner, symbols) -> + generateInteropCall(symbols, builder) { generateEnumVarValueAccess(callSite) } + isMemberAtAccessor(callSite.symbol.owner) -> + generateInteropCall(symbols, builder) { generateMemberAtAccess(callSite) } + isBitFieldAccessor(callSite.symbol.owner) -> + generateInteropCall(symbols, builder) { generateBitFieldAccess(callSite) } + else -> null +} private fun InteropCallContext.generateEnumVarValueAccess(callSite: IrCall): IrExpression { val accessor = callSite.symbol.owner val nativePtr = builder.irCall(symbols.interopNativePointedRawPtrGetter).also { it.dispatchReceiver = callSite.dispatchReceiver!! } + return when { + accessor.isGetter -> readEnumValueFromMemory(nativePtr, accessor.returnType) + accessor.isSetter -> writeEnumValueToMemory(nativePtr, callSite.getValueArgument(0)!!) + else -> error("") + } +} + +private fun InteropCallContext.generateMemberAtAccess(callSite: IrCall): IrExpression { + val accessor = callSite.symbol.owner + val memberAt = accessor.getAnnotation(RuntimeNames.cStructMemberAt)!! + val offset = (memberAt.getValueArgument(0) as IrConst).value + val fieldPointer = calculateFieldPointer(callSite.dispatchReceiver!!, offset) return when { accessor.isGetter -> { - val enumClass = accessor.returnType.getClass()!! - val enumPrimitiveType = enumClass.properties.single { it.name.asString() == "value" } - .getter!!.returnType - val readMemory = readPrimitiveFromMemory(nativePtr, enumPrimitiveType) - return convertIntegralToEnum(readMemory, accessor.returnType) + val type = accessor.returnType + when { + type.isCEnumType() -> readEnumValueFromMemory(fieldPointer, type) + type.isPrimitiveType() -> readPrimitiveFromMemory(fieldPointer, type) + type.isCPointer() -> readPointerFromMemory(fieldPointer) + type.isNativePointed() -> readPointed(fieldPointer) + else -> error("Cannot get field type: ${type.getClass()?.name}") + } } accessor.isSetter -> { - val arg = callSite.getValueArgument(0)!! - val valueToWrite = convertEnumToIntegral(arg) - writePrimitiveToMemory(nativePtr, valueToWrite) + val value = callSite.getValueArgument(0)!! + val type = accessor.valueParameters[0].type + when { + type.isCEnumType() -> writeEnumValueToMemory(fieldPointer, value) + type.isPrimitiveType() -> writePrimitiveToMemory(fieldPointer, value) + type.isCPointer() -> writePointerToMemory(fieldPointer, value) + else -> error("Cannot set field of type ${type.getClass()?.name}") + } } else -> error("") } +} + +private fun InteropCallContext.writeBits( + base: IrExpression, + offset: Long, + size: Int, + value: IrExpression +): IrExpression { + val integralValue = when { + value.type.isCEnumType() -> convertEnumToIntegral(value) + else -> value + } + val targetType = symbols.writeBits.owner.valueParameters.last().type + val valueToWrite = castPrimitiveIfNeeded(integralValue, targetType) + return with(builder) { + irCall(symbols.writeBits).also { + it.putValueArgument(0, base) + it.putValueArgument(1, irLong(offset)) + it.putValueArgument(2, irInt(size)) + it.putValueArgument(3, valueToWrite) + } + } +} + +private fun InteropCallContext.readBits( + base: IrExpression, + offset: Long, + size: Int, + type: IrType +): IrExpression { + val isSigned = when { + type.isCEnumType() -> + !type.getCEnumPrimitiveType().isUnsigned() + else -> + !type.isUnsigned() + } + val integralValue = with (builder) { + irCall(symbols.readBits).also { + it.putValueArgument(0, base) + it.putValueArgument(1, irLong(offset)) + it.putValueArgument(2, irInt(size)) + it.putValueArgument(3, irBoolean(isSigned)) + } + } + return when { + type.isCEnumType() -> convertIntegralToEnum(integralValue, type) + else -> castPrimitiveIfNeeded(integralValue, type) + } +} + +private fun InteropCallContext.generateBitFieldAccess(callSite: IrCall): IrExpression { + val accessor = callSite.symbol.owner + val bitField = accessor.getAnnotation(RuntimeNames.cStructBitField)!! + val offset = (bitField.getValueArgument(0) as IrConst).value + val size = (bitField.getValueArgument(1) as IrConst).value + val base = builder.irCall(symbols.interopNativePointedRawPtrGetter).also { + it.dispatchReceiver = callSite.dispatchReceiver!! + } + return when { + accessor.isSetter -> { + val argument = callSite.getValueArgument(0)!! + writeBits(base, offset, size, argument) + } + accessor.isGetter -> { + val type = accessor.returnType + readBits(base, offset, size, type) + } + else -> error("Unexpected function: ${accessor.name}") + } } \ No newline at end of file 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 37ae52387c0..832d1ce9186 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 @@ -901,7 +901,7 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi return generateWithStubs { generateCCall(expression, builder, isInvoke = false) } } - tryGenerateEnumVarValueAccess(expression, symbols, builder)?.let { return it } + tryGenerateInteropMemberAccess(expression, symbols, builder)?.let { return it } val intrinsicType = tryGetIntrinsicType(expression)