diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt index 9ee3b134375..d81b491452c 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt @@ -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 { - 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) { diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt index 9a20342f7f8..f2dfc60ab42 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt @@ -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 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, + 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() + .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, + 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 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) } } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt index bd2a5650413..6ce4ca31c27 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt @@ -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> = 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() - .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() - .forEach { initializer -> - initializer.body.deepCopyWithVariables().statements.forEach { +it } - } - } - - private fun IrBlockBodyBuilder.generateOptimizedGoldenMaskCheck(seenVars: List) { - 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!! diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt index b6d23e642e4..f91c9a8cddb 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt @@ -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: for primitives - private fun IrBuilderWithScope.defaultValueAndType(prop: SerializableProperty): Pair { - val kType = prop.descriptor.returnType!! + private fun IrBuilderWithScope.defaultValueAndType(descriptor: PropertyDescriptor): Pair { + 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() + .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 = 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 = 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 = + 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 + ) { + 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 { diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt index 946c85ee490..98fca4b96f3 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt @@ -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 + } } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt index 2382943ebf0..08d13828810 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt @@ -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 + 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.bitMaskSlotCount() = size / 32 + 1 internal fun bitMaskSlotAt(propertyIndex: Int) = propertyIndex / 32