From 8de328e2693c5e3a107ebfedd903648c92f53d50 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Wed, 29 Jan 2020 16:47:32 +0700 Subject: [PATCH] [Interop] lowering of CEnumVar value accessors Replace calls to `T.value` where `T : CEnumVar` with direct memory operations. It is required for metadata-based interop libraries where these methods are external. Similar approach will be used for struct field accessors. --- .../kotlin/backend/konan/InteropUtils.kt | 9 + .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 10 + .../konan/lower/InteropCallConvertors.kt | 193 ++++++++++++++++++ .../backend/konan/lower/InteropLowering.kt | 2 + 4 files changed, 214 insertions(+) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt 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 dbb8a59caae..62f5e8477f4 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 @@ -41,6 +41,13 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val cValueRead = this.packageScope.getContributedFunctions("readValue") .single { it.valueParameters.size == 1 } + val cEnum = this.packageScope.getContributedClass("CEnum") + val cEnumVar = this.packageScope.getContributedClass("CEnumVar") + val cPrimitiveVar = this.packageScope.getContributedClass("CPrimitiveVar") + val cPrimitiveVarType = cPrimitiveVar.defaultType.memberScope.getContributedClass("Type") + + val nativeMemUtils = this.packageScope.getContributedClass("nativeMemUtils") + val allocType = this.packageScope.getContributedFunctions("alloc") .single { it.extensionReceiverParameter != null && it.valueParameters.singleOrNull()?.name?.toString() == "type" } @@ -85,6 +92,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val interpretObjCPointerOrNull = packageScope.getContributedFunctions("interpretObjCPointerOrNull").single() val interpretObjCPointer = packageScope.getContributedFunctions("interpretObjCPointer").single() + val interpretNullablePointed = packageScope.getContributedFunctions("interpretNullablePointed").single() + val interpretCPointer = packageScope.getContributedFunctions("interpretCPointer").single() val objCObjectSuperInitCheck = packageScope.getContributedFunctions("superInitCheck").single() val objCObjectInitBy = packageScope.getContributedFunctions("initBy").single() 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 b6939168606..b913d13a961 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 @@ -183,11 +183,21 @@ internal class KonanSymbols( val interopInterpretObjCPointerOrNull = symbolTable.referenceSimpleFunction(context.interopBuiltIns.interpretObjCPointerOrNull) + val interopInterpretNullablePointed = + symbolTable.referenceSimpleFunction(context.interopBuiltIns.interpretNullablePointed) + + val interopInterpretCPointer = + symbolTable.referenceSimpleFunction(context.interopBuiltIns.interpretCPointer) + val interopCreateNSStringFromKString = symbolTable.referenceSimpleFunction(context.interopBuiltIns.CreateNSStringFromKString) val interopObjCGetSelector = interopFunction("objCGetSelector") + val interopCEnumVar = interopClass("CEnumVar") + + val nativeMemUtils = symbolTable.referenceClass(context.interopBuiltIns.nativeMemUtils) + 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 new file mode 100644 index 00000000000..b38e5b1cca0 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropCallConvertors.kt @@ -0,0 +1,193 @@ +/* + * Copyright 2010-2019 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.ir.simpleFunctions +import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor +import org.jetbrains.kotlin.backend.konan.RuntimeNames +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.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.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.util.* + +/** + * 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 class InteropCallContext( + val symbols: KonanSymbols, + val builder: IrBuilderWithScope +) + +private inline fun generateInteropCall( + symbols: KonanSymbols, + builder: IrBuilderWithScope, + block: InteropCallContext.() -> T +) = InteropCallContext(symbols, builder).block() + +/** + * Search for memory read/write function in [kotlinx.cinterop.nativeMemUtils] of a given [valueType]. + */ +private fun InteropCallContext.findMemoryAccessFunction(isRead: Boolean, valueType: IrType): IrFunction { + val requiredType = if (isRead) { + IntrinsicType.INTEROP_READ_PRIMITIVE + } else { + IntrinsicType.INTEROP_WRITE_PRIMITIVE + } + val nativeMemUtilsClass = symbols.nativeMemUtils.owner + return nativeMemUtilsClass.functions.filter { + val annotationArgument = it.annotations + .findAnnotation(RuntimeNames.typedIntrinsicAnnotation) + ?.getAnnotationStringValue() + annotationArgument == requiredType.name + }.first { + if (isRead) { + it.returnType.classOrNull == valueType.classOrNull + } else { + it.valueParameters.last().type.classOrNull == valueType.classOrNull + } + } +} + +private fun InteropCallContext.readPrimitiveFromMemory( + nativePtr: IrExpression, + returnType: IrType +): IrExpression { + val memoryValueType = determineInMemoryType(returnType) + val memReadFn = findMemoryAccessFunction(isRead = true, valueType = memoryValueType) + val memRead = builder.irCall(memReadFn).also { memRead -> + memRead.dispatchReceiver = builder.irGetObject(symbols.nativeMemUtils) + memRead.putValueArgument(0, builder.irCall(symbols.interopInterpretNullablePointed).also { + it.putValueArgument(0, nativePtr) + }) + } + return castPrimitiveIfNeeded(memRead, returnType) +} + +private fun InteropCallContext.writePrimitiveToMemory( + nativePtr: IrExpression, + value: IrExpression +): IrExpression { + val memoryValueType = determineInMemoryType(value.type) + val memWriteFn = findMemoryAccessFunction(isRead = false, valueType = memoryValueType) + val valueToWrite = castPrimitiveIfNeeded(value, memoryValueType) + return with(builder) { + irCall(memWriteFn).also { memWrite -> + memWrite.dispatchReceiver = irGetObject(symbols.nativeMemUtils) + memWrite.putValueArgument(0, irCall(symbols.interopInterpretNullablePointed).also { + it.putValueArgument(0, nativePtr) + }) + memWrite.putValueArgument(1, valueToWrite) + } + } +} + +private fun InteropCallContext.determineInMemoryType(type: IrType): IrType { + val classifier = type.classOrNull!! + return if (classifier in symbols.unsignedIntegerClasses) { + symbols.unsignedToSignedOfSameBitWidth.getValue(classifier).owner.defaultType + } else { + type + } +} + +private fun InteropCallContext.castPrimitiveIfNeeded( + value: IrExpression, + targetType: IrType +): IrExpression { + val valueClass = value.type.classOrNull!! + val targetClass = targetType.classOrNull!! + return if (valueClass != targetClass) { + val conversion = symbols.integerConversions.getValue(valueClass to targetClass) + builder.irCall(conversion.owner).apply { + if (conversion.owner.dispatchReceiverParameter != null) { + dispatchReceiver = value + } else { + extensionReceiver = value + } + } + } else { + value + } +} + +private fun InteropCallContext.convertEnumToIntegral(enumValue: IrExpression): IrExpression { + val enumClass = enumValue.type.getClass()!! + val valueProperty = enumClass.properties.single { it.name.asString() == "value" } + return builder.irCall(valueProperty.getter!!).also { + it.dispatchReceiver = enumValue + } +} + +private fun InteropCallContext.convertIntegralToEnum(value: IrExpression, enumType: IrType): IrExpression { + val enumClass = enumType.getClass()!! + val companionClass = enumClass.companionObject()!! as IrClass + val byValue = companionClass.simpleFunctions().single { it.name.asString() == "byValue" } + val byValueArg = castPrimitiveIfNeeded(value, byValue.valueParameters.first().type) + return builder.irCall(byValue).apply { + dispatchReceiver = builder.irGetObject(companionClass.symbol) + putValueArgument(0, byValueArg) + } +} + +/** + * 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 + */ +internal fun tryGenerateEnumVarValueAccess( + callSite: IrCall, + symbols: KonanSymbols, + builder: IrBuilderWithScope +): IrExpression? = if (isEnumVarValueAccessor(callSite.symbol.owner, symbols)) + generateInteropCall(symbols, builder) { generateEnumVarValueAccess(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 -> { + 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) + } + accessor.isSetter -> { + val arg = callSite.getValueArgument(0)!! + val valueToWrite = convertEnumToIntegral(arg) + writePrimitiveToMemory(nativePtr, valueToWrite) + } + else -> error("") + } +} \ 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 b841854fde8..f25004c548f 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 @@ -864,6 +864,8 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi return generateWithStubs { generateCCall(expression, builder, isInvoke = false) } } + tryGenerateEnumVarValueAccess(expression, symbols, builder)?.let { return it } + val intrinsicType = tryGetIntrinsicType(expression) if (intrinsicType != null) {