[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.
This commit is contained in:
Sergey Bogolepov
2020-01-29 16:47:32 +07:00
committed by Sergey Bogolepov
parent 9224ee3abf
commit 8de328e269
4 changed files with 214 additions and 0 deletions
@@ -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()
@@ -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"),
@@ -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 <T> 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("")
}
}
@@ -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) {