Added external serializers in serialization plugin for IR backend

- added fallback support of external serializers in IR
- implemented calculations of properties default values in IR
- swapped check of shouldEncodeElementDefault and comparing the property with default value in IR. Now default value calculated only of shouldEncodeElementDefault returns false
This commit is contained in:
Sergey Shanshin
2021-02-17 20:16:34 +03:00
committed by GitHub
parent 66f00a2eb5
commit de06a69b12
6 changed files with 399 additions and 190 deletions
@@ -5,14 +5,11 @@
package org.jetbrains.kotlinx.serialization.compiler.backend.common
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.descriptorUtil.secondaryConstructors
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.VersionReader
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
abstract class SerializableCodegen(
@@ -22,9 +19,6 @@ abstract class SerializableCodegen(
protected val properties = bindingContext.serializablePropertiesFor(serializableDescriptor)
protected val staticDescriptor = serializableDescriptor.declaredTypeParameters.isEmpty()
private val fieldMissingOptimizationVersion = ApiVersion.parse("1.1")!!
protected val useFieldMissingOptimization = canUseFieldMissingOptimization()
fun generate() {
generateSyntheticInternalConstructor()
generateSyntheticMethods()
@@ -49,40 +43,6 @@ abstract class SerializableCodegen(
}
}
protected fun getGoldenMask(): Int {
var goldenMask = 0
var requiredBit = 1
for (property in properties.serializableProperties) {
if (!property.optional) {
goldenMask = goldenMask or requiredBit
}
requiredBit = requiredBit shl 1
}
return goldenMask
}
protected fun getGoldenMaskList(): List<Int> {
val maskSlotCount = properties.serializableProperties.bitMaskSlotCount()
val goldenMaskList = MutableList(maskSlotCount) { 0 }
for (i in properties.serializableProperties.indices) {
if (!properties.serializableProperties[i].optional) {
val slotNumber = i / 32
val bitInSlot = i % 32
goldenMaskList[slotNumber] = goldenMaskList[slotNumber] or (1 shl bitInSlot)
}
}
return goldenMaskList
}
private fun canUseFieldMissingOptimization(): Boolean {
val implementationVersion = VersionReader.getVersionsForCurrentModuleFromContext(
serializableDescriptor.module,
bindingContext
)?.implementationVersion
return if (implementationVersion != null) implementationVersion >= fieldMissingOptimizationVersion else false
}
protected abstract fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor)
protected open fun generateWriteSelfMethod(methodDescriptor: FunctionDescriptor) {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2021 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.
*/
@@ -21,10 +21,12 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
@@ -34,6 +36,7 @@ import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlinx.serialization.compiler.backend.common.*
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
@@ -42,10 +45,20 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.*
interface IrBuilderExtension {
val compilerContext: SerializationPluginContext
private val throwMissedFieldExceptionFunc
get() = compilerContext.referenceFunctions(SerialEntityNames.SINGLE_MASK_FIELD_MISSING_FUNC_FQ).singleOrNull()
private val throwMissedFieldExceptionArrayFunc
get() = compilerContext.referenceFunctions(SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_FQ).singleOrNull()
private inline fun <reified T : IrDeclaration> IrClass.searchForDeclaration(descriptor: DeclarationDescriptor): T? {
return declarations.singleOrNull { it.descriptor == descriptor } as? T
}
fun useFieldMissingOptimization(): Boolean {
return throwMissedFieldExceptionFunc != null && throwMissedFieldExceptionArrayFunc != null
}
fun IrClass.contributeFunction(descriptor: FunctionDescriptor, bodyGen: IrBlockBodyBuilder.(IrFunction) -> Unit) {
val f: IrSimpleFunction = searchForDeclaration(descriptor) ?: compilerContext.symbolTable.referenceSimpleFunction(descriptor).owner
// TODO: default parameters
@@ -214,14 +227,62 @@ interface IrBuilderExtension {
// note: this method should be used only for properties from current module. Fields from other modules are private and inaccessible.
val SerializableProperty.irField: IrField get() = compilerContext.symbolTable.referenceField(this.descriptor).owner
fun SerializableProperty.getIrPropertyFrom(thisClass: IrClass): IrProperty {
val desc = this.descriptor
fun IrClass.searchForProperty(descriptor: PropertyDescriptor): IrProperty {
// this API is used to reference both current module descriptors and external ones (because serializable class can be in any of them),
// so we use descriptor api for current module because it is not possible to obtain FQname for e.g. local classes.
return thisClass.searchForDeclaration(desc) ?: if (desc.module == compilerContext.moduleDescriptor) {
compilerContext.symbolTable.referenceProperty(desc).owner
return searchForDeclaration(descriptor) ?: if (descriptor.module == compilerContext.moduleDescriptor) {
compilerContext.symbolTable.referenceProperty(descriptor).owner
} else {
compilerContext.referenceProperties(desc.fqNameSafe).single().owner
compilerContext.referenceProperties(descriptor.fqNameSafe).single().owner
}
}
fun SerializableProperty.getIrPropertyFrom(thisClass: IrClass): IrProperty {
return thisClass.searchForProperty(descriptor)
}
/*
Create a function that creates `get property value expressions` for given corresponded constructor's param
(constructor_params) -> get_property_value_expression
*/
fun IrBuilderWithScope.createPropertyByParamReplacer(
irClass: IrClass,
serialProperties: List<SerializableProperty>,
instance: IrValueParameter,
bindingContext: BindingContext
): (ValueParameterDescriptor) -> IrExpression? {
fun SerializableProperty.irGet(): IrExpression {
val ownerType = instance.symbol.owner.type
return getProperty(
irGet(
type = ownerType,
variable = instance.symbol
), getIrPropertyFrom(irClass)
)
}
val serialPropertiesMap = serialProperties.associateBy { it.descriptor }
val transientPropertiesMap =
irClass.declarations.asSequence()
.filterIsInstance<IrProperty>()
.filter { it.backingField != null }.filter { !serialPropertiesMap.containsKey(it.descriptor) }
.associateBy { it.symbol.descriptor }
return {
val propertyDescriptor = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, it]
if (propertyDescriptor != null) {
val value = serialPropertiesMap[propertyDescriptor]
value?.irGet() ?: transientPropertiesMap[propertyDescriptor]?.let { prop ->
getProperty(
irGet(instance),
prop
)
}
} else {
null
}
}
}
@@ -257,6 +318,78 @@ interface IrBuilderExtension {
}
}
fun IrBlockBodyBuilder.generateGoldenMaskCheck(
seenVars: List<IrValueDeclaration>,
properties: SerializableProperties,
serialDescriptor: IrExpression
) {
val fieldsMissedTest: IrExpression
val throwErrorExpr: IrExpression
val maskSlotCount = seenVars.size
if (maskSlotCount == 1) {
val goldenMask = properties.goldenMask
throwErrorExpr = irInvoke(
null,
throwMissedFieldExceptionFunc!!,
irGet(seenVars[0]),
irInt(goldenMask),
serialDescriptor,
typeHint = compilerContext.irBuiltIns.unitType
)
fieldsMissedTest = irNotEquals(
irInt(goldenMask),
irBinOp(
OperatorNameConventions.AND,
irInt(goldenMask),
irGet(seenVars[0])
)
)
} else {
val goldenMaskList = properties.goldenMaskList
var compositeExpression: IrExpression? = null
for (i in goldenMaskList.indices) {
val singleCheckExpr = irNotEquals(
irInt(goldenMaskList[i]),
irBinOp(
OperatorNameConventions.AND,
irInt(goldenMaskList[i]),
irGet(seenVars[i])
)
)
compositeExpression = if (compositeExpression == null) {
singleCheckExpr
} else {
irBinOp(
OperatorNameConventions.OR,
compositeExpression,
singleCheckExpr
)
}
}
fieldsMissedTest = compositeExpression!!
throwErrorExpr = irBlock {
+irInvoke(
null,
throwMissedFieldExceptionArrayFunc!!,
createPrimitiveArrayOfExpression(compilerContext.irBuiltIns.intType, goldenMaskList.indices.map { irGet(seenVars[it]) }),
createPrimitiveArrayOfExpression(compilerContext.irBuiltIns.intType, goldenMaskList.map { irInt(it) }),
serialDescriptor,
typeHint = compilerContext.irBuiltIns.unitType
)
}
}
+irIfThen(compilerContext.irBuiltIns.unitType, fieldsMissedTest, throwErrorExpr)
}
fun generateSimplePropertyWithBackingField(
propertyDescriptor: PropertyDescriptor,
propertyParent: IrClass,
@@ -485,18 +618,45 @@ interface IrBuilderExtension {
return defaultsMap + extractDefaultValuesFromConstructor(irClass.getSuperClassNotAny())
}
fun buildInitializersRemapping(irClass: IrClass): (IrField) -> IrExpression? {
val defaultsMap = extractDefaultValuesFromConstructor(irClass)
return fun(f: IrField): IrExpression? {
val i = f.initializer?.expression ?: return null
val irExpression =
if (i is IrGetValueImpl && i.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER) {
// this is a primary constructor property, use corresponding default of value parameter
defaultsMap.getValue(i.symbol.descriptor as ParameterDescriptor)
} else {
i
/*
Creates an initializer adapter function that can replace IR expressions of getting constructor parameter value by some other expression.
Also adapter may replace IR expression of getting `this` value by another expression.
*/
fun createInitializerAdapter(
irClass: IrClass,
paramGetReplacer: (ValueParameterDescriptor) -> IrExpression?,
thisGetReplacer: Pair<IrValueSymbol, () -> IrExpression>? = null
): (IrExpressionBody) -> IrExpression {
val initializerTransformer = object : IrElementTransformerVoid() {
// try to replace `get some value` expression
override fun visitGetValue(expression: IrGetValue): IrExpression {
val symbol = expression.symbol
if (thisGetReplacer != null && thisGetReplacer.first == symbol) {
// replace `get this value` expression
return thisGetReplacer.second()
}
return irExpression?.deepCopyWithVariables()
val descriptor = symbol.descriptor
if (descriptor is ValueParameterDescriptor) {
// replace `get parameter value` expression
paramGetReplacer(descriptor)?.let { return it }
}
// otherwise leave expression as it is
return super.visitGetValue(expression)
}
}
val defaultsMap = extractDefaultValuesFromConstructor(irClass)
return fun(initializer: IrExpressionBody): IrExpression {
val rawExpression = initializer.expression
val expression =
if (rawExpression is IrGetValueImpl && rawExpression.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER) {
// this is a primary constructor property, use corresponding default of value parameter
defaultsMap.getValue(rawExpression.symbol.descriptor as ParameterDescriptor)!!
} else {
rawExpression
}
return expression.deepCopyWithVariables().transform(initializerTransformer, null)
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2021 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.
*/
@@ -9,10 +9,12 @@ import org.jetbrains.kotlin.backend.common.deepCopyWithVariables
import org.jetbrains.kotlin.backend.common.lower.irThrow
import org.jetbrains.kotlin.codegen.CompilationException
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addField
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
@@ -23,15 +25,14 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.getOrPutNullable
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen
import org.jetbrains.kotlinx.serialization.compiler.backend.common.serialName
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.serializableAnnotationIsUseless
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_FQ
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MISSING_FIELD_EXC
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SERIAL_DESC_FIELD
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SINGLE_MASK_FIELD_MISSING_FUNC_FQ
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.initializedDescriptorFieldName
class SerializableIrGenerator(
@@ -50,11 +51,6 @@ class SerializableIrGenerator(
private val addElementFun = serialDescImplClass.findFunctionSymbol(CallingConventions.addElement)
val throwMissedFieldExceptionFunc =
if (useFieldMissingOptimization) compilerContext.referenceFunctions(SINGLE_MASK_FIELD_MISSING_FUNC_FQ).single() else null
val throwMissedFieldExceptionArrayFunc =
if (useFieldMissingOptimization) compilerContext.referenceFunctions(ARRAY_MASK_FIELD_MISSING_FUNC_FQ).single() else null
private fun IrClass.hasSerializableAnnotationWithoutArgs(): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName) ?: return false
@@ -69,7 +65,40 @@ class SerializableIrGenerator(
override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) =
irClass.contributeConstructor(constructorDescriptor) { ctor ->
val transformFieldInitializer = buildInitializersRemapping(irClass)
val thiz = irClass.thisReceiver!!
val serializableProperties = properties.serializableProperties
val serialDescs = serializableProperties.map { it.descriptor }.toSet()
val propertyByParamReplacer: (ValueParameterDescriptor) -> IrExpression? =
createPropertyByParamReplacer(irClass, serializableProperties, thiz, bindingContext)
val initializerAdapter: (IrExpressionBody) -> IrExpression = createInitializerAdapter(irClass, propertyByParamReplacer)
var current: PropertyDescriptor? = null
val statementsAfterSerializableProperty: MutableMap<PropertyDescriptor?, MutableList<IrStatement>> = mutableMapOf()
irClass.declarations.asSequence().forEach {
when {
// only properties with backing field
it is IrProperty && it.backingField != null -> {
if (it.descriptor in serialDescs) {
current = it.descriptor
} else if (it.backingField?.initializer != null) {
// skip transient lateinit or deferred properties (with null initializer)
val expression = initializerAdapter(it.backingField!!.initializer!!)
statementsAfterSerializableProperty.getOrPutNullable(current, { mutableListOf() })
.add(irSetField(irGet(thiz), it.backingField!!, expression))
}
}
it is IrAnonymousInitializer -> {
val statements = it.body.deepCopyWithVariables().statements
statementsAfterSerializableProperty.getOrPutNullable(current, { mutableListOf() })
.addAll(statements)
}
}
}
// Missing field exception parts
val exceptionFqn = getSerializationPackageFqn(MISSING_FIELD_EXC)
@@ -77,16 +106,19 @@ class SerializableIrGenerator(
.single { it.owner.valueParameters.singleOrNull()?.type?.isString() == true }
val exceptionType = exceptionCtorRef.owner.returnType
val serializableProperties = properties.serializableProperties
val seenVarsOffset = serializableProperties.bitMaskSlotCount()
val seenVars = (0 until seenVarsOffset).map { ctor.valueParameters[it] }
val thiz = irClass.thisReceiver!!
val superClass = irClass.getSuperClassOrAny()
var startPropOffset: Int = 0
if (useFieldMissingOptimization) {
generateOptimizedGoldenMaskCheck(seenVars)
if (useFieldMissingOptimization() &&
// for abstract classes fields MUST BE checked in child classes
!serializableDescriptor.isAbstractSerializableClass() && !serializableDescriptor.isSealedSerializableClass()
) {
generateGoldenMaskCheck(seenVars, properties, getSerialDescriptorExpr())
}
when {
superClass.symbol == compilerContext.irBuiltIns.anyClass -> generateAnySuperConstructorCall(toBuilder = this@contributeConstructor)
@@ -96,6 +128,7 @@ class SerializableIrGenerator(
else -> generateSuperNonSerializableCall(superClass)
}
statementsAfterSerializableProperty[null]?.forEach { +it }
for (index in startPropOffset until serializableProperties.size) {
val prop = serializableProperties[index]
val paramRef = ctor.valueParameters[index + seenVarsOffset]
@@ -106,13 +139,14 @@ class SerializableIrGenerator(
val ifNotSeenExpr: IrExpression = if (prop.optional) {
val initializerBody =
requireNotNull(transformFieldInitializer(prop.irField)) { "Optional value without an initializer" } // todo: filter abstract here
requireNotNull(initializerAdapter(prop.irField.initializer!!)) { "Optional value without an initializer" } // todo: filter abstract here
irSetField(irGet(thiz), backingFieldToAssign, initializerBody)
} else {
// property required
if (useFieldMissingOptimization) {
if (useFieldMissingOptimization()) {
// field definitely not empty as it's checked before - no need another IF, only assign property from param
+assignParamExpr
statementsAfterSerializableProperty[prop.descriptor]?.forEach { +it }
continue
} else {
irThrow(irInvoke(null, exceptionCtorRef, irString(prop.name), typeHint = exceptionType))
@@ -130,97 +164,11 @@ class SerializableIrGenerator(
)
+irIfThenElse(compilerContext.irBuiltIns.unitType, propNotSeenTest, ifNotSeenExpr, assignParamExpr)
}
// remaining initializers of variables
val serialDescs = serializableProperties.map { it.descriptor }.toSet()
irClass.declarations.asSequence()
.filterIsInstance<IrProperty>()
.filter { it.descriptor !in serialDescs }
.filter { it.backingField != null }
.mapNotNull { prop -> transformFieldInitializer(prop.backingField!!)?.let { prop to it } }
.forEach { (prop, expr) -> +irSetField(irGet(thiz), prop.backingField!!, expr) }
// init blocks
irClass.declarations.asSequence()
.filterIsInstance<IrAnonymousInitializer>()
.forEach { initializer ->
initializer.body.deepCopyWithVariables().statements.forEach { +it }
}
}
private fun IrBlockBodyBuilder.generateOptimizedGoldenMaskCheck(seenVars: List<IrValueParameter>) {
if (serializableDescriptor.isAbstractSerializableClass() || serializableDescriptor.isSealedSerializableClass()) {
// for abstract classes fields MUST BE checked in child classes
return
}
val fieldsMissedTest: IrExpression
val throwErrorExpr: IrExpression
val maskSlotCount = seenVars.size
if (maskSlotCount == 1) {
val goldenMask = getGoldenMask()
throwErrorExpr = irInvoke(
null,
throwMissedFieldExceptionFunc!!,
irGet(seenVars[0]),
irInt(goldenMask),
getSerialDescriptorExpr(),
typeHint = compilerContext.irBuiltIns.unitType
)
fieldsMissedTest = irNotEquals(
irInt(goldenMask),
irBinOp(
OperatorNameConventions.AND,
irInt(goldenMask),
irGet(seenVars[0])
)
)
} else {
val goldenMaskList = getGoldenMaskList()
var compositeExpression: IrExpression? = null
for (i in goldenMaskList.indices) {
val singleCheckExpr = irNotEquals(
irInt(goldenMaskList[i]),
irBinOp(
OperatorNameConventions.AND,
irInt(goldenMaskList[i]),
irGet(seenVars[i])
)
)
compositeExpression = if (compositeExpression == null) {
singleCheckExpr
} else {
irBinOp(
OperatorNameConventions.OR,
compositeExpression,
singleCheckExpr
)
}
}
fieldsMissedTest = compositeExpression!!
throwErrorExpr = irBlock {
+irInvoke(
null,
throwMissedFieldExceptionArrayFunc!!,
createPrimitiveArrayOfExpression(compilerContext.irBuiltIns.intType, goldenMaskList.indices.map { irGet(seenVars[it]) }),
createPrimitiveArrayOfExpression(compilerContext.irBuiltIns.intType, goldenMaskList.map { irInt(it) }),
getSerialDescriptorExpr(),
typeHint = compilerContext.irBuiltIns.unitType
)
statementsAfterSerializableProperty[prop.descriptor]?.forEach { +it }
}
}
+irIfThen(compilerContext.irBuiltIns.unitType, fieldsMissedTest, throwErrorExpr)
}
private fun IrBlockBodyBuilder.getSerialDescriptorExpr(): IrExpression {
return if (serializableDescriptor.shouldHaveGeneratedSerializer && staticDescriptor) {
val serializer = serializableDescriptor.classSerializer!!
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2021 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.
*/
@@ -12,10 +12,8 @@ import org.jetbrains.kotlin.backend.common.lower.irThrow
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.expressions.mapValueParametersIndexed
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl
@@ -23,13 +21,12 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerialTypeInfo
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen
import org.jetbrains.kotlinx.serialization.compiler.backend.common.getSerialTypeInfo
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerForEnumsCodegen
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationDescriptorSerializerPlugin
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
@@ -243,9 +240,6 @@ open class SerializerIrGenerator(
override fun generateSave(function: FunctionDescriptor) = irClass.contributeFunction(function) { saveFunc ->
val fieldInitializer: (SerializableProperty) -> IrExpression? =
buildInitializersRemapping(serializableIrClass).run { { invoke(it.irField) } }
fun irThis(): IrExpression =
IrGetValueImpl(startOffset, endOffset, saveFunc.dispatchReceiverParameter!!.symbol)
@@ -280,16 +274,22 @@ open class SerializerIrGenerator(
// Ignore comparing to default values of properties from superclass,
// because we do not have access to their fields (and initializers), if superclass is in another module.
// In future, IR analogue of JVM's write$Self should be implemented.
val superClass = irClass.getSuperClassOrAny().descriptor
val superClass = serializableIrClass.getSuperClassOrAny().descriptor
val ignoreIndexTo = if (superClass.isInternalSerializable) {
bindingContext.serializablePropertiesFor(superClass).size
} else {
-1
}
val propertyByParamReplacer: (ValueParameterDescriptor) -> IrExpression? =
createPropertyByParamReplacer(serializableIrClass, serializableProperties, objectToSerialize, bindingContext)
val thisSymbol = serializableIrClass.thisReceiver!!.symbol
val initializerAdapter: (IrExpressionBody) -> IrExpression =
createInitializerAdapter(serializableIrClass, propertyByParamReplacer, thisSymbol to { irGet(objectToSerialize) })
// internal serialization via virtual calls?
for ((index, property) in serializableProperties.filter { !it.transient }.withIndex()) {
for ((index, property) in serializableProperties.withIndex()) {
// output.writeXxxElementValue(classDesc, index, value)
val elementCall = formEncodeDecodePropertyCall(irGet(localOutput), saveFunc.dispatchReceiverParameter!!, property, {innerSerial, sti ->
val f =
@@ -314,11 +314,12 @@ open class SerializerIrGenerator(
+elementCall
} else {
// emit check:
// if (obj.prop != DEFAULT_VALUE || output.shouldEncodeElementDefault(this.descriptor, i))
// if ( if (output.shouldEncodeElementDefault(this.descriptor, i)) true else {obj.prop != DEFAULT_VALUE} ) {
// output.encodeIntElement(this.descriptor, i, obj.prop)
// block {obj.prop != DEFAULT_VALUE} may contain several statements
val shouldEncodeFunc = kOutputClass.referenceMethod(CallingConventions.shouldEncodeDefault)
val partA = irNotEquals(property.irGet(), fieldInitializer(property)!!)
val partB = irInvoke(irGet(localOutput), shouldEncodeFunc, irGet(localSerialDesc), irInt(index))
val partA = irInvoke(irGet(localOutput), shouldEncodeFunc, irGet(localSerialDesc), irInt(index))
val partB = irNotEquals(property.irGet(), initializerAdapter(property.irField.initializer!!))
// Ir infrastructure does not have dedicated symbol for ||, so
// `a || b == if (a) true else b`, see org.jetbrains.kotlin.ir.builders.PrimitivesKt.oror
val condition = irIfThenElse(compilerContext.irBuiltIns.booleanType, partA, irTrue(), partB)
@@ -354,8 +355,8 @@ open class SerializerIrGenerator(
}
// returns null: Any? for boxed types and 0: <number type> for primitives
private fun IrBuilderWithScope.defaultValueAndType(prop: SerializableProperty): Pair<IrExpression, IrType> {
val kType = prop.descriptor.returnType!!
private fun IrBuilderWithScope.defaultValueAndType(descriptor: PropertyDescriptor): Pair<IrExpression, IrType> {
val kType = descriptor.returnType!!
val T = kType.toIrType()
val defaultPrimitive: IrExpression? = when {
T.isInt() -> IrConstImpl.int(startOffset, endOffset, T, 0)
@@ -397,12 +398,26 @@ open class SerializerIrGenerator(
// calculating bit mask vars
val blocksCnt = serializableProperties.bitMaskSlotCount()
val serialPropertiesIndexes = serializableProperties
.mapIndexed { i, property -> property to i }
.associate { (p, i) -> p.descriptor to i }
val transients = serializableIrClass.declarations.asSequence()
.filterIsInstance<IrProperty>()
.filter { !serialPropertiesIndexes.containsKey(it.descriptor) }
.filter { it.backingField != null }
// var bitMask0 = 0, bitMask1 = 0...
val bitMasks = (0 until blocksCnt).map { irTemporary(irInt(0), "bitMask$it", isMutable = true) }
// var local0 = null, local1 = null ...
val localProps = serializableProperties.mapIndexed { i, prop ->
val (expr, type) = defaultValueAndType(prop)
irTemporary(expr, "local$i", type, isMutable = true)
val serialPropertiesMap = serializableProperties.mapIndexed { i, prop -> i to prop.descriptor }.associate { (i, descriptor) ->
val (expr, type) = defaultValueAndType(descriptor)
descriptor to irTemporary(expr, "local$i", type, isMutable = true)
}
// var transient0 = null, transient0 = null ...
val transientsPropertiesMap = transients.mapIndexed { i, prop -> i to prop.descriptor }.associate { (i, descriptor) ->
val (expr, type) = defaultValueAndType(descriptor)
descriptor to irTemporary(expr, "transient$i", type, isMutable = true)
}
//input = input.beginStructure(...)
@@ -423,7 +438,7 @@ open class SerializerIrGenerator(
inputClass.referenceMethod(
"${CallingConventions.decode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}", {it.valueParameters.size == 4}
) to listOf(
localSerialDesc.get(), irInt(index), innerSerial, localProps[index].get()
localSerialDesc.get(), irInt(index), innerSerial, serialPropertiesMap.getValue(property.descriptor).get()
)
}, {
inputClass.referenceMethod(
@@ -434,7 +449,7 @@ open class SerializerIrGenerator(
}, returnTypeHint = property.type.toIrType())
// local$i = localInput.decode...(...)
+irSet(
localProps[index].symbol,
serialPropertiesMap.getValue(property.descriptor).symbol,
decodeFuncToCall
)
// bitMask[i] |= 1 << x
@@ -491,17 +506,103 @@ open class SerializerIrGenerator(
irGet(localSerialDesc)
)
// todo: set properties in external deserialization
var args: List<IrExpression> = localProps.map { it.get() }
val typeArgs = (loadFunc.returnType as IrSimpleType).arguments.map { (it as IrTypeProjection).type }
val ctor: IrConstructorSymbol = if (serializableDescriptor.isInternalSerializable) {
if (serializableDescriptor.isInternalSerializable) {
var args: List<IrExpression> = serializableProperties.map { serialPropertiesMap.getValue(it.descriptor).get() }
args = bitMasks.map { irGet(it) } + args + irNull()
serializableSyntheticConstructor(serializableIrClass)
val ctor: IrConstructorSymbol = serializableSyntheticConstructor(serializableIrClass)
+irReturn(irInvoke(null, ctor, typeArgs, args))
} else {
compilerContext.referenceConstructors(serializableDescriptor.fqNameSafe).single { it.owner.isPrimary }
}
// check properties presence
val serialDescIrProperty = irClass.searchForProperty(generatedSerialDescPropertyDescriptor!!)
val serialDescriptorExpr = irGetField(null, serialDescIrProperty.backingField!!)
generateGoldenMaskCheck(bitMasks, properties, serialDescriptorExpr)
+irReturn(irInvoke(null, ctor, typeArgs, args))
val ctor: IrConstructorSymbol =
compilerContext.referenceConstructors(serializableDescriptor.fqNameSafe).single { it.owner.isPrimary }
val params = ctor.owner.valueParameters
val variableByParamReplacer: (ValueParameterDescriptor) -> IrExpression? = {
val propertyDescriptor = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, it]
if (propertyDescriptor != null) {
val serializable = serialPropertiesMap[propertyDescriptor]
(serializable ?: transientsPropertiesMap[propertyDescriptor])?.get()
} else {
null
}
}
val initializerAdapter: (IrExpressionBody) -> IrExpression =
createInitializerAdapter(serializableIrClass, variableByParamReplacer)
// constructor args:
val ctorArgs = params.map { parameter ->
val parameterDescriptor = parameter.descriptor as ValueParameterDescriptor
val propertyDescriptor = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameterDescriptor]!!
val serialProperty = serialPropertiesMap[propertyDescriptor]
// null if transient
if (serialProperty != null) {
val index = serialPropertiesIndexes.getValue(propertyDescriptor)
if (parameterDescriptor.hasDefaultValue()) {
val propNotSeenTest =
irEquals(
irInt(0),
irBinOp(
OperatorNameConventions.AND,
bitMasks[index / 32].get(),
irInt(1 shl (index % 32))
)
)
// if(mask$j && propertyMask == 0) local$i = <initializer>
val defaultValueExp = parameter.defaultValue!!
val expr = initializerAdapter(defaultValueExp)
+irIfThen(propNotSeenTest, irSet(serialProperty.symbol, expr))
}
serialProperty.get()
} else {
val transientVar = transientsPropertiesMap.getValue(propertyDescriptor)
if (parameterDescriptor.hasDefaultValue()) {
val defaultValueExp = parameter.defaultValue!!
val expr = initializerAdapter(defaultValueExp)
+irSet(transientVar.symbol, expr)
}
transientVar.get()
}
}
val serializerVar = irTemporary(irInvoke(null, ctor, typeArgs, ctorArgs), "serializable")
generateSetStandaloneProperties(serializerVar, serialPropertiesMap::getValue, serialPropertiesIndexes::getValue, bitMasks)
+irReturn(irGet(serializerVar))
}
}
private fun IrBlockBodyBuilder.generateSetStandaloneProperties(
serializableVar: IrVariable,
propVars: (PropertyDescriptor) -> IrVariable,
propIndexes: (PropertyDescriptor) -> Int,
bitMasks: List<IrVariable>
) {
for (property in properties.serializableStandaloneProperties) {
val localPropIndex = propIndexes(property.descriptor)
// generate setter call
val setter = property.getIrPropertyFrom(serializableIrClass).setter!!
val propSeenTest =
irNotEquals(
irInt(0),
irBinOp(
OperatorNameConventions.AND,
irGet(bitMasks[localPropIndex / 32]),
irInt(1 shl (localPropIndex % 32))
)
)
val setterInvokeExpr = irSet(setter.returnType, irGet(serializableVar), setter.symbol, irGet(propVars(property.descriptor)))
+irIfThen(propSeenTest, setterInvokeExpr)
}
}
companion object {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
* Copyright 2010-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.jetbrains.kotlinx.serialization.compiler.backend.jvm
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin
import org.jetbrains.kotlinx.serialization.compiler.backend.common.*
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.VersionReader
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.serializableAnnotationIsUseless
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_NAME
@@ -45,6 +47,8 @@ class SerializableCodegenImpl(
) : SerializableCodegen(classCodegen.descriptor, classCodegen.bindingContext) {
private val thisAsmType = classCodegen.typeMapper.mapClass(serializableDescriptor)
private val fieldMissingOptimizationVersion = ApiVersion.parse("1.1")!!
private val useFieldMissingOptimization = canUseFieldMissingOptimization()
companion object {
fun generateSerializableExtensions(codegen: ImplementationBodyCodegen) {
@@ -289,7 +293,7 @@ class SerializableCodegenImpl(
val allPresentsLabel = Label()
val maskSlotCount = properties.serializableProperties.bitMaskSlotCount()
if (maskSlotCount == 1) {
val goldenMask = getGoldenMask()
val goldenMask = properties.goldenMask
iconst(goldenMask)
dup()
@@ -310,7 +314,7 @@ class SerializableCodegenImpl(
} else {
val fieldsMissingLabel = Label()
val goldenMaskList = getGoldenMaskList()
val goldenMaskList = properties.goldenMaskList
goldenMaskList.forEachIndexed { i, goldenMask ->
val maskIndex = maskVar + i
// if( (goldenMask & seen) != goldenMask )
@@ -404,4 +408,12 @@ class SerializableCodegenImpl(
this.gen(param.defaultValue, mapType)
this.v.putfield(thisAsmType.internalName, prop.name.asString(), mapType.descriptor)
}
private fun canUseFieldMissingOptimization(): Boolean {
val implementationVersion = VersionReader.getVersionsForCurrentModuleFromContext(
currentDeclaration.module,
bindingContext
)?.implementationVersion
return if (implementationVersion != null) implementationVersion >= fieldMissingOptimizationVersion else false
}
}
@@ -88,6 +88,34 @@ class SerializableProperties(private val serializableClass: ClassDescriptor, val
?.original?.valueParameters?.any { it.declaresDefaultValue() } ?: false
}
internal val SerializableProperties.goldenMask: Int
get() {
var goldenMask = 0
var requiredBit = 1
for (property in serializableProperties) {
if (!property.optional) {
goldenMask = goldenMask or requiredBit
}
requiredBit = requiredBit shl 1
}
return goldenMask
}
internal val SerializableProperties.goldenMaskList: List<Int>
get() {
val maskSlotCount = serializableProperties.bitMaskSlotCount()
val goldenMaskList = MutableList(maskSlotCount) { 0 }
for (i in serializableProperties.indices) {
if (!serializableProperties[i].optional) {
val slotNumber = i / 32
val bitInSlot = i % 32
goldenMaskList[slotNumber] = goldenMaskList[slotNumber] or (1 shl bitInSlot)
}
}
return goldenMaskList
}
internal fun List<SerializableProperty>.bitMaskSlotCount() = size / 32 + 1
internal fun bitMaskSlotAt(propertyIndex: Int) = propertyIndex / 32