Kx-serialization: support SerialInfo for JVM IR

No tests added because this is going to be checked by the upcoming build
of kotlinx-serialization runtime with JVM IR.

 #KT-42976 In Progress
This commit is contained in:
Alexander Udalov
2020-10-27 21:00:01 +01:00
parent e4ba787034
commit 51ded98c4b
6 changed files with 124 additions and 20 deletions
@@ -218,7 +218,9 @@ class ExpressionCodegen(
mv.visitLineNumber(1, startLabel)
}
val info = BlockInfo()
val body = irFunction.body!!
val body = irFunction.body
?: error("Function has no body: ${irFunction.render()}")
generateNonNullAssertions()
generateFakeContinuationConstructorIfNeeded()
val result = body.accept(this, info)
@@ -220,10 +220,10 @@ interface IrBuilderExtension {
}
fun generateSimplePropertyWithBackingField(
ownerSymbol: IrValueSymbol,
propertyDescriptor: PropertyDescriptor,
propertyParent: IrClass,
declare: Boolean
declare: Boolean,
fieldName: Name = propertyDescriptor.name,
): IrProperty {
val irPropertySymbol = compilerContext.symbolTable.referenceProperty(propertyDescriptor)
assert(irPropertySymbol.isBound || declare)
@@ -241,7 +241,7 @@ interface IrBuilderExtension {
}
val irProperty = irPropertySymbol.owner
irProperty.backingField = generatePropertyBackingField(propertyDescriptor, irProperty).apply {
irProperty.backingField = generatePropertyBackingField(propertyDescriptor, irProperty, fieldName).apply {
parent = propertyParent
correspondingPropertySymbol = irPropertySymbol
}
@@ -255,7 +255,8 @@ interface IrBuilderExtension {
private fun generatePropertyBackingField(
descriptor: PropertyDescriptor,
originProperty: IrProperty
originProperty: IrProperty,
name: Name,
): IrField {
val fieldSymbol = compilerContext.symbolTable.referenceField(descriptor)
if (fieldSymbol.isBound) return fieldSymbol.owner
@@ -0,0 +1,94 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlinx.serialization.compiler.backend.ir
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
import org.jetbrains.kotlinx.serialization.compiler.resolve.KSerializerDescriptorResolver
// This doesn't support annotation arguments of type KClass and Array<KClass> because the codegen doesn't compute JVM signatures for
// such cases correctly (because inheriting from annotation classes is prohibited in Kotlin).
// Currently it results in an "accidental override" error where a method with return type KClass conflicts with the one with Class.
// TODO: support annotation properties of types KClass<...> and Array<KClass<...>>.
class SerialInfoImplJvmIrGenerator(
private val irClass: IrClass,
private val context: SerializationPluginContext,
) : IrBuilderExtension {
override val compilerContext: SerializationPluginContext
get() = context
private val jvmNameClass = context.referenceClass(DescriptorUtils.JVM_NAME)!!.owner
private fun generate() {
val properties = irClass.declarations.filterIsInstance<IrProperty>()
if (properties.isEmpty()) return
val startOffset = UNDEFINED_OFFSET
val endOffset = UNDEFINED_OFFSET
val ctor = irClass.addConstructor {
visibility = DescriptorVisibilities.PUBLIC
}
val ctorBody = context.irFactory.createBlockBody(
startOffset, endOffset, listOf(
IrDelegatingConstructorCallImpl(
startOffset, endOffset, context.irBuiltIns.unitType, context.irBuiltIns.anyClass.constructors.single(),
typeArgumentsCount = 0, valueArgumentsCount = 0
)
)
)
ctor.body = ctorBody
for (property in properties) {
generateSimplePropertyWithBackingField(property.descriptor, irClass, false, Name.identifier("_" + property.name.asString()))
val getter = property.getter!!
getter.origin = SERIALIZABLE_SYNTHETIC_ORIGIN
// Add JvmName annotation to property getters to force the resulting JVM method name for 'x' be 'x', instead of 'getX',
// and to avoid having useless bridges for it generated in BridgeLowering.
// Unfortunately, this results in an extra `@JvmName` annotation in the bytecode, but it shouldn't matter very much.
getter.annotations += jvmName(property.name.asString())
val field = property.backingField!!
field.visibility = DescriptorVisibilities.PRIVATE
field.origin = SERIALIZABLE_SYNTHETIC_ORIGIN
val parameter = ctor.addValueParameter(property.name.asString(), getter.returnType)
ctorBody.statements += IrSetFieldImpl(
startOffset, endOffset, field.symbol,
IrGetValueImpl(startOffset, endOffset, irClass.thisReceiver!!.symbol),
IrGetValueImpl(startOffset, endOffset, parameter.symbol),
context.irBuiltIns.unitType,
)
}
}
private fun jvmName(name: String): IrConstructorCall =
IrConstructorCallImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, jvmNameClass.defaultType, jvmNameClass.constructors.single().symbol,
typeArgumentsCount = 0, constructorTypeArgumentsCount = 0, valueArgumentsCount = 1,
).apply {
putValueArgument(0, IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.stringType, name))
}
companion object {
fun generate(irClass: IrClass, context: SerializationPluginContext) {
if (KSerializerDescriptorResolver.isSerialInfoImpl(irClass.descriptor))
SerialInfoImplJvmIrGenerator(irClass, context).generate()
}
}
}
@@ -28,7 +28,6 @@ import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.ir.util.withReferenceScope
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen
import org.jetbrains.kotlinx.serialization.compiler.backend.common.getSerialTypeInfo
@@ -42,9 +41,11 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ST
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.STRUCTURE_ENCODER_CLASS
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.UNKNOWN_FIELD_EXC
// Is creating synthetic origin is a good idea or not?
object SERIALIZABLE_PLUGIN_ORIGIN : IrDeclarationOriginImpl("SERIALIZER")
// TODO: use in places where elements need to have ACC_SYNTHETIC on JVM
object SERIALIZABLE_SYNTHETIC_ORIGIN : IrDeclarationOriginImpl("SERIALIZER")
open class SerializerIrGenerator(
val irClass: IrClass,
final override val compilerContext: SerializationPluginContext,
@@ -69,12 +70,11 @@ open class SerializerIrGenerator(
// how to (auto)create backing field and getter/setter?
compilerContext.symbolTable.withReferenceScope(irClass.descriptor) {
prop = generateSimplePropertyWithBackingField(thisAsReceiverParameter.symbol, desc, irClass, false)
prop = generateSimplePropertyWithBackingField(desc, irClass, false)
// TODO: Do not use descriptors here
localSerializersFieldsDescriptors.forEach {
generateSimplePropertyWithBackingField(thisAsReceiverParameter.symbol, it, irClass, true)
generateSimplePropertyWithBackingField(it, irClass, true)
}
}
@@ -6,8 +6,8 @@
package org.jetbrains.kotlinx.serialization.compiler.extensions
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
@@ -17,6 +17,8 @@ import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerialInfoImplJvmIrGenerator
import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializableCompanionIrGenerator
import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializableIrGenerator
import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializerIrGenerator
@@ -48,10 +50,16 @@ private class SerializerClassLowering(
SerializableIrGenerator.generate(irClass, context, context.bindingContext)
SerializerIrGenerator.generate(irClass, context, context.bindingContext, metadataPlugin)
SerializableCompanionIrGenerator.generate(irClass, context, context.bindingContext)
if (context.platform.isJvm()) {
SerialInfoImplJvmIrGenerator.generate(irClass, context)
}
}
}
open class SerializationLoweringExtension @JvmOverloads constructor(val metadataPlugin: SerializationDescriptorSerializerPlugin? = null) : IrGenerationExtension {
open class SerializationLoweringExtension @JvmOverloads constructor(
val metadataPlugin: SerializationDescriptorSerializerPlugin? = null
) : IrGenerationExtension {
override fun generate(
moduleFragment: IrModuleFragment,
pluginContext: IrPluginContext
@@ -60,4 +68,4 @@ open class SerializationLoweringExtension @JvmOverloads constructor(val metadata
for (file in moduleFragment.files)
serializerClassLowering.runOnFileInOrder(file)
}
}
}
@@ -588,13 +588,12 @@ object KSerializerDescriptorResolver {
) {
if (isSerialInfoImpl(thisDescriptor)) {
result.add(
fromSupertypes[0].copy(
thisDescriptor,
Modality.FINAL,
DescriptorVisibilities.PUBLIC,
CallableMemberDescriptor.Kind.SYNTHESIZED,
true
) as PropertyDescriptor
fromSupertypes.first().newCopyBuilder().apply {
setOwner(thisDescriptor)
setModality(Modality.FINAL)
setKind(CallableMemberDescriptor.Kind.SYNTHESIZED)
setDispatchReceiverParameter(thisDescriptor.thisAsReceiverParameter)
}.build()!!
)
}
}