[Interop][Lowering] Support metadata-based structs

This commit is contained in:
Sergey Bogolepov
2020-01-31 12:30:14 +07:00
committed by Sergey Bogolepov
parent 7d48c580ca
commit 2e594bb19c
5 changed files with 193 additions and 28 deletions
@@ -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")
@@ -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
@@ -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"),
@@ -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 <T> 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.<field-name>, 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<Long>).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<Long>).value
val size = (bitField.getValueArgument(1) as IrConst<Int>).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}")
}
}
@@ -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)