Refactor most of the new code, split huge GeneratorHelpers.kt appropriately
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/AbstractSerialGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/AbstractSerialGenerator.kt
index b9fab87..ea5ee96 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/AbstractSerialGenerator.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/AbstractSerialGenerator.kt
@@ -5,19 +5,9 @@
 
 package org.jetbrains.kotlinx.serialization.compiler.backend.common
 
-import org.jetbrains.kotlin.backend.jvm.ir.fileParent
 import org.jetbrains.kotlin.descriptors.ClassDescriptor
 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
 import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.ir.declarations.IrClass
-import org.jetbrains.kotlin.ir.expressions.IrClassReference
-import org.jetbrains.kotlin.ir.expressions.IrVararg
-import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
-import org.jetbrains.kotlin.ir.types.IrSimpleType
-import org.jetbrains.kotlin.ir.types.classOrNull
-import org.jetbrains.kotlin.ir.types.isNullable
-import org.jetbrains.kotlin.ir.types.typeOrNull
-import org.jetbrains.kotlin.ir.util.findAnnotation
 import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
 import org.jetbrains.kotlin.name.FqName
 import org.jetbrains.kotlin.name.Name
@@ -31,46 +21,15 @@
 import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
 import org.jetbrains.kotlinx.serialization.compiler.resolve.isKSerializer
 import org.jetbrains.kotlinx.serialization.compiler.resolve.toClassDescriptor
-abstract class AbstractIrGenerator(private val currentClass: IrClass) {
-    private fun getClassListFromFileAnnotation(annotationFqName: FqName): List<IrClassSymbol> {
-        val annotation = currentClass.fileParent.annotations.findAnnotation(annotationFqName) ?: return emptyList()
-        val vararg = annotation.getValueArgument(0) as? IrVararg ?: return emptyList()
-        return vararg.elements
-            .mapNotNull { (it as? IrClassReference)?.symbol as? IrClassSymbol}
-    }
-
-    val contextualKClassListInCurrentFile: Set<IrClassSymbol> by lazy {
-        getClassListFromFileAnnotation(
-            SerializationAnnotations.contextualFqName,
-        ).plus(
-            getClassListFromFileAnnotation(
-                SerializationAnnotations.contextualOnFileFqName,
-            )
-        ).toSet()
-    }
-
-    val additionalSerializersInScopeOfCurrentFile: Map<Pair<IrClassSymbol, Boolean>, IrClassSymbol> by lazy {
-        getClassListFromFileAnnotation(SerializationAnnotations.additionalSerializersFqName,)
-            .associateBy(
-                { serializerSymbol ->
-                    val kotlinType = (serializerSymbol.owner.superTypes.find(::isKSerializer) as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull
-                    val classSymbol = kotlinType?.classOrNull
-                        ?: throw AssertionError("Argument for ${SerializationAnnotations.additionalSerializersFqName} does not implement KSerializer or does not provide serializer for concrete type")
-                    classSymbol to kotlinType.isNullable()
-                },
-                { it }
-            )
-    }
-}
 
 abstract class AbstractSerialGenerator(val bindingContext: BindingContext?, val currentDeclaration: ClassDescriptor) {
-
     private fun getKClassListFromFileAnnotation(annotationFqName: FqName, declarationInFile: DeclarationDescriptor): List<KotlinType> {
-        if (bindingContext == null) return emptyList() // TODO: support @UseSerializers in FIR
+        if (bindingContext == null) return emptyList()
         val annotation = AnnotationsUtils
             .getContainingFileAnnotations(bindingContext, declarationInFile)
             .find { it.fqName == annotationFqName }
             ?: return emptyList()
+
         @Suppress("UNCHECKED_CAST")
         val typeList: List<KClassValue> = annotation.firstArgument()?.value as? List<KClassValue> ?: return emptyList()
         return typeList.map { it.getArgumentType(declarationInFile.module) }
@@ -102,5 +61,6 @@
     }
 
     protected fun ClassDescriptor.getFuncDesc(funcName: String): Sequence<FunctionDescriptor> =
-        unsubstitutedMemberScope.getDescriptorsFiltered { it == Name.identifier(funcName) }.asSequence().filterIsInstance<FunctionDescriptor>()
+        unsubstitutedMemberScope.getDescriptorsFiltered { it == Name.identifier(funcName) }.asSequence()
+            .filterIsInstance<FunctionDescriptor>()
 }
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt
index f0cf7c7..14fa565 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt
@@ -8,7 +8,6 @@
 import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
 import org.jetbrains.kotlin.descriptors.ClassDescriptor
 import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
 import org.jetbrains.kotlin.incremental.components.NoLookupLocation
 import org.jetbrains.kotlin.resolve.BindingContext
 import org.jetbrains.kotlinx.serialization.compiler.resolve.*
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt
index 9de53f7..e10fb3e 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt
@@ -10,11 +10,6 @@
 import org.jetbrains.kotlin.codegen.CompilationException
 import org.jetbrains.kotlin.descriptors.*
 import org.jetbrains.kotlin.descriptors.annotations.Annotations
-import org.jetbrains.kotlin.ir.declarations.IrProperty
-import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
-import org.jetbrains.kotlin.ir.types.*
-import org.jetbrains.kotlin.ir.util.hasAnnotation
-import org.jetbrains.kotlin.ir.util.isTypeParameter
 import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
 import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
 import org.jetbrains.kotlin.name.ClassId
@@ -26,32 +21,17 @@
 import org.jetbrains.kotlin.resolve.BindingContext
 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
 import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.model.KotlinTypeMarker
 import org.jetbrains.kotlin.types.typeUtil.*
 import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.enumSerializerId
 import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId
-import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
 import org.jetbrains.kotlinx.serialization.compiler.resolve.*
 import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages.internalPackageFqName
 
-interface ISerialTypeInfo<C, D, T : KotlinTypeMarker, S : ISerializableProperty<D, T>> {
-    val property: S
-    val elementMethodPrefix: String
-    val serializer: C?
-}
-
 open class SerialTypeInfo(
-    override val property: SerializableProperty,
-    override val elementMethodPrefix: String,
-    override val serializer: ClassDescriptor? = null
-) : ISerialTypeInfo<ClassDescriptor, PropertyDescriptor, KotlinType, SerializableProperty>
-
-class IrSerialTypeInfo(
-    override val property: IrSerializableProperty,
-    override val elementMethodPrefix: String,
-    override val serializer: IrClassSymbol? = null
-) : ISerialTypeInfo<IrClassSymbol, IrProperty, IrSimpleType, IrSerializableProperty>
-
+    val property: SerializableProperty,
+    val elementMethodPrefix: String,
+    val serializer: ClassDescriptor? = null
+)
 
 fun AbstractSerialGenerator.findAddOnSerializer(propertyType: KotlinType, module: ModuleDescriptor): ClassDescriptor? {
     additionalSerializersInScopeOfCurrentFile[propertyType.toClassDescriptor to propertyType.isMarkedNullable]?.let { return it }
@@ -63,48 +43,9 @@
     return null
 }
 
-fun AbstractIrGenerator.findAddOnSerializer(propertyType: IrType, ctx: SerializationPluginContext): IrClassSymbol? {
-    val classSymbol = propertyType.classOrNull ?: return null
-    additionalSerializersInScopeOfCurrentFile[classSymbol to propertyType.isNullable()]?.let { return it }
-    if (classSymbol in contextualKClassListInCurrentFile)
-        return ctx.getClassFromRuntime(SpecialBuiltins.contextSerializer)
-    if (classSymbol.owner.annotations.hasAnnotation(SerializationAnnotations.polymorphicFqName))
-        return ctx.getClassFromRuntime(SpecialBuiltins.polymorphicSerializer)
-    if (propertyType.isNullable()) return findAddOnSerializer(propertyType.makeNotNull(), ctx)
-    return null
-}
-
-
 fun KotlinType.isGeneratedSerializableObject() =
     toClassDescriptor?.run { kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotationWithoutArgs } == true
 
-fun AbstractIrGenerator.getIrSerialTypeInfo(property: IrSerializableProperty, ctx: SerializationPluginContext): IrSerialTypeInfo {
-    fun SerializableInfo(serializer: IrClassSymbol?) =
-        IrSerialTypeInfo(property, if (property.type.isNullable()) "Nullable" else "", serializer)
-
-    val T = property.type
-    property.serializableWith(ctx)?.let { return SerializableInfo(it) }
-    findAddOnSerializer(T, ctx)?.let { return SerializableInfo(it) }
-    T.overridenSerializer?.let { return SerializableInfo(it) }
-    return when {
-        T.isTypeParameter() -> IrSerialTypeInfo(property, if (property.type.isMarkedNullable()) "Nullable" else "", null)
-        T.isPrimitiveType() -> IrSerialTypeInfo(
-            property,
-            T.classFqName!!.asString().removePrefix("kotlin.")
-        )
-        T.isString() -> IrSerialTypeInfo(property, "String")
-        T.isArray() -> {
-            val serializer = property.serializableWith(ctx) ?: ctx.getClassFromInternalSerializationPackage(SpecialBuiltins.referenceArraySerializer)
-            SerializableInfo(serializer)
-        }
-        else -> {
-            val serializer =
-                findTypeSerializerOrContext(ctx, property.type)
-            SerializableInfo(serializer)
-        }
-    }
-}
-
 @Suppress("FunctionName", "LocalVariableName")
 fun AbstractSerialGenerator.getSerialTypeInfo(property: SerializableProperty): SerialTypeInfo {
     fun SerializableInfo(serializer: ClassDescriptor?) =
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtilIr.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtilIr.kt
deleted file mode 100644
index a10c7c0..0000000
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtilIr.kt
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright 2010-2022 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.common
-
-import org.jetbrains.kotlin.backend.jvm.ir.getStringConstArgument
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
-import org.jetbrains.kotlin.descriptors.ClassKind
-import org.jetbrains.kotlin.descriptors.Modality
-import org.jetbrains.kotlin.ir.declarations.*
-import org.jetbrains.kotlin.ir.expressions.IrClassReference
-import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
-import org.jetbrains.kotlin.ir.expressions.IrExpression
-import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
-import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
-import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
-import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
-import org.jetbrains.kotlin.ir.types.*
-import org.jetbrains.kotlin.ir.util.*
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
-import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
-import org.jetbrains.kotlinx.serialization.compiler.fir.SerializationPluginKey
-import org.jetbrains.kotlinx.serialization.compiler.resolve.*
-
-fun AbstractIrGenerator.findTypeSerializerOrContextUnchecked(
-    context: SerializationPluginContext, kType: IrType
-): IrClassSymbol? {
-    val annotations = kType.annotations
-    if (kType.isTypeParameter()) return null
-    annotations.serializableWith()?.let { return it }
-    additionalSerializersInScopeOfCurrentFile[kType.classOrNull!! to kType.isNullable()]?.let {
-        return it
-    }
-    if (kType.isMarkedNullable()) return findTypeSerializerOrContextUnchecked(context, kType.makeNotNull())
-    if (kType.classOrNull in contextualKClassListInCurrentFile) return context.referenceClass(contextSerializerId)
-    return analyzeSpecialSerializers(context, annotations) ?: findTypeSerializer(context, kType)
-}
-
-fun analyzeSpecialSerializers(
-    context: SerializationPluginContext,
-    annotations: List<IrConstructorCall>
-): IrClassSymbol? = when {
-    annotations.hasAnnotation(SerializationAnnotations.contextualFqName) || annotations.hasAnnotation(SerializationAnnotations.contextualOnPropertyFqName) ->
-        context.referenceClass(contextSerializerId)
-    // can be annotation on type usage, e.g. List<@Polymorphic Any>
-    annotations.hasAnnotation(SerializationAnnotations.polymorphicFqName) ->
-        context.referenceClass(polymorphicSerializerId)
-    else -> null
-}
-
-fun AbstractIrGenerator.findTypeSerializerOrContext(
-    context: SerializationPluginContext, kType: IrType
-): IrClassSymbol? {
-    if (kType.isTypeParameter()) return null
-    return findTypeSerializerOrContextUnchecked(context, kType) ?: error("Serializer for element of type ${kType.render()} has not been found")
-}
-
-fun findTypeSerializer(context: SerializationPluginContext, type: IrType): IrClassSymbol? {
-    type.overridenSerializer?.let { return it }
-    if (type.isTypeParameter()) return null
-    if (type.isArray()) return context.referenceClass(referenceArraySerializerId)
-    if (type.isGeneratedSerializableObject()) return context.referenceClass(objectSerializerId)
-    val stdSer = findStandardKotlinTypeSerializer(context, type) // see if there is a standard serializer
-        ?: findEnumTypeSerializer(context, type)
-    if (stdSer != null) return stdSer
-    if (type.isInterface() && type.classOrNull?.owner?.isSealedSerializableInterface == false) return context.referenceClass(
-        polymorphicSerializerId
-    )
-    return type.classOrNull?.owner.classSerializer(context) // check for serializer defined on the type
-}
-
-internal fun IrClass?.classSerializer(context: SerializationPluginContext): IrClassSymbol? = this?.let {
-    // serializer annotation on class?
-    serializableWith?.let { return it }
-    // companion object serializer?
-    if (hasCompanionObjectAsSerializer) return companionObject()?.symbol
-    // can infer @Poly?
-    polymorphicSerializerIfApplicableAutomatically(context)?.let { return it }
-    // default serializable?
-    if (shouldHaveGeneratedSerializer) {
-        // $serializer nested class
-        return this.declarations
-            .filterIsInstance<IrClass>()
-            .singleOrNull { it.name == SerialEntityNames.SERIALIZER_CLASS_NAME }?.symbol
-    }
-    return null
-}
-
-private val IrClass.isSerialInfoAnnotation: Boolean
-    get() = annotations.hasAnnotation(SerializationAnnotations.serialInfoFqName)
-            || annotations.hasAnnotation(SerializationAnnotations.inheritableSerialInfoFqName)
-            || annotations.hasAnnotation(SerializationAnnotations.metaSerializableAnnotationFqName)
-
-internal val IrClass.shouldHaveGeneratedSerializer: Boolean
-    get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN))
-            || isEnumWithLegacyGeneratedSerializer()
-
-internal fun IrClass.polymorphicSerializerIfApplicableAutomatically(context: SerializationPluginContext): IrClassSymbol? {
-    val serializer = when {
-        kind == ClassKind.INTERFACE && modality == Modality.SEALED -> SpecialBuiltins.sealedSerializer
-        kind == ClassKind.INTERFACE -> SpecialBuiltins.polymorphicSerializer
-        isInternalSerializable && modality == Modality.ABSTRACT -> SpecialBuiltins.polymorphicSerializer
-        isInternalSerializable && modality == Modality.SEALED -> SpecialBuiltins.sealedSerializer
-        else -> null
-    }
-    return serializer?.let {
-        context.getClassFromRuntimeOrNull(
-            it,
-            SerializationPackages.packageFqName,
-            SerializationPackages.internalPackageFqName
-        )
-    }
-}
-
-internal val IrClass.isInternalSerializable: Boolean
-    get() {
-        if (kind != ClassKind.CLASS) return false
-        return hasSerializableOrMetaAnnotationWithoutArgs()
-    }
-
-internal val IrClass.isAbstractOrSealedSerializableClass: Boolean get() = isInternalSerializable && (modality == Modality.ABSTRACT || modality == Modality.SEALED)
-
-internal val IrClass.isStaticSerializable: Boolean get() = this.typeParameters.isEmpty()
-
-
-internal val IrClass.hasCompanionObjectAsSerializer: Boolean
-    get() = isInternallySerializableObject || companionObject()?.serializerForClass == this.symbol
-
-internal val IrClass.isInternallySerializableObject: Boolean
-    get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotationWithoutArgs()
-
-internal val IrClass.serializerForClass: IrClassSymbol?
-    get() = (annotations.findAnnotation(SerializationAnnotations.serializerAnnotationFqName)
-        ?.getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol
-
-fun findEnumTypeSerializer(context: SerializationPluginContext, type: IrType): IrClassSymbol? {
-    val classSymbol = type.classOrNull?.owner ?: return null
-    return if (classSymbol.kind == ClassKind.ENUM_CLASS && !classSymbol.isEnumWithLegacyGeneratedSerializer())
-        context.referenceClass(enumSerializerId)
-    else null
-}
-
-internal fun IrClass.isEnumWithLegacyGeneratedSerializer(): Boolean = isInternallySerializableEnum() && useGeneratedEnumSerializer
-
-internal val IrClass.useGeneratedEnumSerializer: Boolean
-    get() = true // FIXME This would break if we try to use this new IR compiler with pre-1.4.1 serialization versions. I think we just need to raise min runtime version.
-
-internal val IrClass.isSealedSerializableInterface: Boolean
-    get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation()
-
-internal fun IrClass.isInternallySerializableEnum(): Boolean =
-    kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotationWithoutArgs()
-
-fun findStandardKotlinTypeSerializer(context: SerializationPluginContext, type: IrType): IrClassSymbol? {
-    val typeName = type.classFqName?.toString()
-    val name = when (typeName) {
-        "Z" -> if (type.isBoolean()) "BooleanSerializer" else null
-        "B" -> if (type.isByte()) "ByteSerializer" else null
-        "S" -> if (type.isShort()) "ShortSerializer" else null
-        "I" -> if (type.isInt()) "IntSerializer" else null
-        "J" -> if (type.isLong()) "LongSerializer" else null
-        "F" -> if (type.isFloat()) "FloatSerializer" else null
-        "D" -> if (type.isDouble()) "DoubleSerializer" else null
-        "C" -> if (type.isChar()) "CharSerializer" else null
-        null -> null
-        else -> findStandardKotlinTypeSerializer(typeName)
-    } ?: return null
-    return context.getClassFromRuntimeOrNull(name, SerializationPackages.internalPackageFqName, SerializationPackages.packageFqName)
-}
-
-fun IrType.isGeneratedSerializableObject(): Boolean {
-    return classOrNull?.run { owner.kind == ClassKind.OBJECT && owner.hasSerializableOrMetaAnnotationWithoutArgs() } == true
-}
-
-internal val IrClass.isSerializableObject: Boolean
-    get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotation()
-
-// todo: optimize & unify with hasSerializableOrMeta
-internal fun IrClass.hasSerializableOrMetaAnnotationWithoutArgs(): Boolean {
-    val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName)
-    if (annot != null) {
-        for (i in 0 until annot.valueArgumentsCount) {
-            if (annot.getValueArgument(i) != null) return false
-        }
-        return true
-    }
-    val metaAnnotation = annotations
-        .flatMap { it.symbol.owner.constructedClass.annotations }
-        .find { it.isAnnotation(SerializationAnnotations.metaSerializableAnnotationFqName) }
-    return metaAnnotation != null
-}
-
-fun SerializationPluginContext.getClassFromRuntimeOrNull(className: String, vararg packages: FqName): IrClassSymbol? {
-    val listToSearch = if (packages.isEmpty()) SerializationPackages.allPublicPackages else packages.toList()
-    for (pkg in listToSearch) {
-        referenceClass(ClassId(pkg, Name.identifier(className)))?.let { return it }
-    }
-    return null
-}
-
-fun SerializationPluginContext.getClassFromRuntime(className: String, vararg packages: FqName): IrClassSymbol {
-    return getClassFromRuntimeOrNull(className, *packages) ?: error(
-        "Class $className wasn't found in ${packages.toList().ifEmpty { SerializationPackages.allPublicPackages }}. " +
-                "Check that you have correct version of serialization runtime in classpath."
-    )
-}
-
-fun SerializationPluginContext.getClassFromInternalSerializationPackage(className: String): IrClassSymbol =
-    getClassFromRuntimeOrNull(className, SerializationPackages.internalPackageFqName)
-        ?: error("Class $className wasn't found in ${SerializationPackages.internalPackageFqName}. Check that you have correct version of serialization runtime in classpath.")
-
-
-internal val IrType.overridenSerializer: IrClassSymbol?
-    get() {
-        val desc = this.classOrNull ?: return null
-        desc.owner.serializableWith?.let { return it }
-        return null
-    }
-
-internal val IrClass.serializableWith: IrClassSymbol?
-    get() = annotations.serializableWith()
-
-
-// @Serializable(X::class) -> X
-internal fun List<IrConstructorCall>.serializableWith(): IrClassSymbol? {
-    val annotation = findAnnotation(SerializationAnnotations.serializableAnnotationFqName) ?: return null
-    val arg = annotation.getValueArgument(0) as? IrClassReference ?: return null
-    return arg.symbol as? IrClassSymbol
-}
-
-internal fun getSerializableClassByCompanion(companionClass: IrClass): IrClass? {
-    if (companionClass.isSerializableObject) return companionClass
-    if (!companionClass.isCompanion) return null
-    val classDescriptor = (companionClass.parent as? IrClass) ?: return null
-    if (!classDescriptor.shouldHaveGeneratedMethodsInCompanion) return null
-    return classDescriptor
-}
-
-internal val IrClass.shouldHaveGeneratedMethodsInCompanion: Boolean
-    get() = this.isSerializableObject || this.isSerializableEnum() || (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation()) || this.isSealedSerializableInterface
-
-internal fun IrClass.isSerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotation()
-
-fun IrClass.hasSerializableOrMetaAnnotation() = descriptor.hasSerializableOrMetaAnnotation // TODO
-
-internal val IrType.genericIndex: Int?
-    get() = (this.classifierOrNull as? IrTypeParameterSymbol)?.owner?.index
-
-fun IrType.serialName(): String = this.classOrNull!!.owner.serialName()
-fun IrClass.serialName(): String {
-    return annotations.serialNameValue ?: fqNameWhenAvailable?.asString() ?: error("${this.render()} does not have fqName")
-}
-
-fun AbstractIrGenerator.allSealedSerializableSubclassesFor(
-    irClass: IrClass,
-    context: SerializationPluginContext
-): Pair<List<IrSimpleType>, List<IrClassSymbol>> {
-    assert(irClass.modality == Modality.SEALED)
-    fun recursiveSealed(klass: IrClass): Collection<IrClass> {
-        return klass.sealedSubclasses.map { it.owner }.flatMap { if (it.modality == Modality.SEALED) recursiveSealed(it) else setOf(it) }
-    }
-
-    val serializableSubtypes = recursiveSealed(irClass).map { it.defaultType }
-    return serializableSubtypes.mapNotNull { subtype ->
-        findTypeSerializerOrContextUnchecked(context, subtype)?.let { Pair(subtype, it) }
-    }.unzip()
-}
-
-fun IrClass.findEnumValuesMethod() = this.functions.singleOrNull { f ->
-    f.name == Name.identifier("values") && f.valueParameters.isEmpty() && f.extensionReceiverParameter == null
-} ?: throw AssertionError("Enum class does not have single .values() function")
-
-internal fun IrClass.enumEntries(): List<IrEnumEntry> {
-    check(this.kind == ClassKind.ENUM_CLASS)
-    return declarations.filterIsInstance<IrEnumEntry>().toList()
-}
-
-internal fun IrClass.isEnumWithSerialInfoAnnotation(): Boolean {
-    if (kind != ClassKind.ENUM_CLASS) return false
-    if (annotations.hasAnySerialAnnotation) return true
-    return enumEntries().any { (it.annotations.hasAnySerialAnnotation) }
-}
-
-internal val List<IrConstructorCall>.hasAnySerialAnnotation: Boolean
-    get() = serialNameValue != null || any { it.annotationClass.isSerialInfoAnnotation == true }
-
-internal val List<IrConstructorCall>.serialNameValue: String?
-    get() = findAnnotation(SerializationAnnotations.serialNameAnnotationFqName)?.getStringConstArgument(0) // @SerialName("foo")
-
-internal fun getSerializableClassDescriptorBySerializer(serializer: IrClass): IrClass? {
-    val serializerForClass = serializer.serializerForClass
-    if (serializerForClass != null) return serializerForClass.owner
-    if (serializer.name !in setOf(
-            SerialEntityNames.SERIALIZER_CLASS_NAME,
-            SerialEntityNames.GENERATED_SERIALIZER_CLASS
-        )
-    ) return null
-    val classDescriptor = (serializer.parent as? IrClass) ?: return null
-    if (!classDescriptor.shouldHaveGeneratedSerializer) return null
-    return classDescriptor
-}
-
-internal fun isKSerializer(type: IrType): Boolean {
-    val simpleType = type as? IrSimpleType ?: return false
-    val classifier = simpleType.classifier as? IrClassSymbol ?: return false
-    val fqName = classifier.owner.fqNameWhenAvailable
-    return fqName == SerialEntityNames.KSERIALIZER_NAME_FQ || fqName == SerialEntityNames.GENERATED_SERIALIZER_FQ
-}
-
-internal fun IrClass.findPluginGeneratedMethod(name: String): IrSimpleFunction? {
-    return this.functions.find {
-        it.name.asString() == name && it.isFromPlugin()
-    }
-}
-
-internal fun IrDeclaration.isFromPlugin(): Boolean =
-    this.origin == IrDeclarationOrigin.GeneratedByPlugin(SerializationPluginKey) || (this.descriptor as? CallableMemberDescriptor)?.kind == CallableMemberDescriptor.Kind.SYNTHESIZED // old FE doesn't specify origin
-
-internal fun IrConstructor.isSerializationCtor(): Boolean {
-    /*kind == CallableMemberDescriptor.Kind.SYNTHESIZED does not work because DeserializedClassConstructorDescriptor loses its kind*/
-    return valueParameters.lastOrNull()?.run {
-        name == SerialEntityNames.dummyParamName && type.classFqName == SerializationPackages.internalPackageFqName.child(
-            SerialEntityNames.SERIAL_CTOR_MARKER_NAME
-        )
-    } == true
-}
-
-internal fun IrExpression.isInitializePropertyFromParameter(): Boolean =
-    this is IrGetValueImpl && this.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER
-
-internal val IrConstructorCall.annotationClass
-    get() = this.symbol.owner.constructedClass
\ No newline at end of file
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/BaseIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/BaseIrGenerator.kt
new file mode 100644
index 0000000..0a6c9f1
--- /dev/null
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/BaseIrGenerator.kt
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2010-2022 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.backend.common.lower.irIfThen
+import org.jetbrains.kotlin.backend.jvm.functionByName
+import org.jetbrains.kotlin.backend.jvm.ir.fileParent
+import org.jetbrains.kotlin.backend.jvm.ir.representativeUpperBound
+import org.jetbrains.kotlin.descriptors.ClassKind
+import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.descriptors.isSealed
+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.IrValueDeclaration
+import org.jetbrains.kotlin.ir.declarations.IrValueParameter
+import org.jetbrains.kotlin.ir.deepCopyWithVariables
+import org.jetbrains.kotlin.ir.expressions.IrClassReference
+import org.jetbrains.kotlin.ir.expressions.IrExpression
+import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
+import org.jetbrains.kotlin.ir.expressions.IrVararg
+import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
+import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
+import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
+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.name.CallableId
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.types.Variance
+import org.jetbrains.kotlin.util.OperatorNameConventions
+import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
+import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
+import org.jetbrains.kotlinx.serialization.compiler.resolve.*
+
+abstract class BaseIrGenerator(private val currentClass: IrClass, final override val compilerContext: SerializationPluginContext): IrBuilderWithPluginContext {
+
+    private val throwMissedFieldExceptionFunc
+        = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.SINGLE_MASK_FIELD_MISSING_FUNC_NAME)).singleOrNull()
+
+    private val throwMissedFieldExceptionArrayFunc
+        = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_NAME)).singleOrNull()
+
+    private val enumSerializerFactoryFunc
+        = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME)).singleOrNull()
+
+    private val markedEnumSerializerFactoryFunc
+        = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME)).singleOrNull()
+
+    fun useFieldMissingOptimization(): Boolean {
+        return throwMissedFieldExceptionFunc != null && throwMissedFieldExceptionArrayFunc != null
+    }
+
+    private fun getClassListFromFileAnnotation(annotationFqName: FqName): List<IrClassSymbol> {
+        val annotation = currentClass.fileParent.annotations.findAnnotation(annotationFqName) ?: return emptyList()
+        val vararg = annotation.getValueArgument(0) as? IrVararg ?: return emptyList()
+        return vararg.elements
+            .mapNotNull { (it as? IrClassReference)?.symbol as? IrClassSymbol }
+    }
+
+    val contextualKClassListInCurrentFile: Set<IrClassSymbol> by lazy {
+        getClassListFromFileAnnotation(
+            SerializationAnnotations.contextualFqName,
+        ).plus(
+            getClassListFromFileAnnotation(
+                SerializationAnnotations.contextualOnFileFqName,
+            )
+        ).toSet()
+    }
+
+    val additionalSerializersInScopeOfCurrentFile: Map<Pair<IrClassSymbol, Boolean>, IrClassSymbol> by lazy {
+        getClassListFromFileAnnotation(SerializationAnnotations.additionalSerializersFqName,)
+            .associateBy(
+                { serializerSymbol ->
+                    val kotlinType = (serializerSymbol.owner.superTypes.find(IrType::isKSerializer) as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull
+                    val classSymbol = kotlinType?.classOrNull
+                        ?: throw AssertionError("Argument for ${SerializationAnnotations.additionalSerializersFqName} does not implement KSerializer or does not provide serializer for concrete type")
+                    classSymbol to kotlinType.isNullable()
+                },
+                { it }
+            )
+    }
+
+    fun IrBlockBodyBuilder.generateGoldenMaskCheck(
+        seenVars: List<IrValueDeclaration>,
+        properties: IrSerializableProperties,
+        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 IrBlockBodyBuilder.serializeAllProperties(
+        serializableProperties: List<IrSerializableProperty>,
+        objectToSerialize: IrValueDeclaration,
+        localOutput: IrValueDeclaration,
+        localSerialDesc: IrValueDeclaration,
+        kOutputClass: IrClassSymbol,
+        ignoreIndexTo: Int,
+        initializerAdapter: (IrExpressionBody) -> IrExpression,
+        genericGetter: ((Int, IrType) -> IrExpression)?
+    ) {
+
+        fun IrSerializableProperty.irGet(): IrExpression {
+            val ownerType = objectToSerialize.symbol.owner.type
+            return getProperty(
+                irGet(
+                    type = ownerType,
+                    variable = objectToSerialize.symbol
+                ), ir
+            )
+        }
+
+        for ((index, property) in serializableProperties.withIndex()) {
+            if (index < ignoreIndexTo) continue
+            // output.writeXxxElementValue(classDesc, index, value)
+            val elementCall = formEncodeDecodePropertyCall(
+                irGet(localOutput),
+                property, { innerSerial, sti ->
+                    val f =
+                        kOutputClass.functionByName("${CallingConventions.encode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}")
+                    f to listOf(
+                        irGet(localSerialDesc),
+                        irInt(index),
+                        innerSerial,
+                        property.irGet()
+                    )
+                }, {
+                    val f =
+                        kOutputClass.functionByName("${CallingConventions.encode}${it.elementMethodPrefix}${CallingConventions.elementPostfix}")
+                    val args: MutableList<IrExpression> = mutableListOf(irGet(localSerialDesc), irInt(index))
+                    if (it.elementMethodPrefix != "Unit") args.add(property.irGet())
+                    f to args
+                },
+                genericGetter
+            )
+
+            // check for call to .shouldEncodeElementDefault
+            val encodeDefaults = property.ir.getEncodeDefaultAnnotationValue()
+            val field =
+                property.ir.backingField // Nullable when property from another module; can't compare it with default value on JS or Native
+            if (!property.optional || encodeDefaults == true || field == null) {
+                // emit call right away
+                +elementCall
+            } else {
+                val partB = irNotEquals(property.irGet(), initializerAdapter(field.initializer!!))
+
+                val condition = if (encodeDefaults == false) {
+                    // drop default without call to .shouldEncodeElementDefault
+                    partB
+                } else {
+                    // emit check:
+                    // 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.functionByName(CallingConventions.shouldEncodeDefault)
+                    val partA = irInvoke(irGet(localOutput), shouldEncodeFunc, irGet(localSerialDesc), irInt(index))
+                    // Ir infrastructure does not have dedicated symbol for ||, so
+                    //  `a || b == if (a) true else b`, see org.jetbrains.kotlin.ir.builders.PrimitivesKt.oror
+                    irIfThenElse(compilerContext.irBuiltIns.booleanType, partA, irTrue(), partB)
+                }
+                +irIfThen(condition, elementCall)
+            }
+        }
+    }
+
+
+    fun IrBlockBodyBuilder.formEncodeDecodePropertyCall(
+        encoder: IrExpression,
+        property: IrSerializableProperty,
+        whenHaveSerializer: (serializer: IrExpression, sti: IrSerialTypeInfo) -> FunctionWithArgs,
+        whenDoNot: (sti: IrSerialTypeInfo) -> FunctionWithArgs,
+        genericGetter: ((Int, IrType) -> IrExpression)? = null,
+        returnTypeHint: IrType? = null
+    ): IrExpression {
+        val sti = getIrSerialTypeInfo(property, compilerContext)
+        val innerSerial = serializerInstance(
+            sti.serializer,
+            compilerContext,
+            property.type,
+            property.genericIndex,
+            genericGetter
+        )
+        val (functionToCall, args: List<IrExpression>) = if (innerSerial != null) whenHaveSerializer(innerSerial, sti) else whenDoNot(sti)
+        val typeArgs = if (functionToCall.descriptor.typeParameters.isNotEmpty()) listOf(property.type) else listOf()
+        return irInvoke(encoder, functionToCall, typeArguments = typeArgs, valueArguments = args, returnTypeHint = returnTypeHint)
+    }
+
+    fun IrBuilderWithScope.callSerializerFromCompanion(
+        thisIrType: IrType,
+        typeArgs: List<IrType>,
+        args: List<IrExpression>
+    ): IrExpression? {
+        val baseClass = thisIrType.getClass() ?: return null
+        val companionClass = baseClass.companionObject() ?: return null
+        val serializerProviderFunction = companionClass.declarations.singleOrNull {
+            it is IrFunction && it.name == SerialEntityNames.SERIALIZER_PROVIDER_NAME && it.valueParameters.size == baseClass.typeParameters.size
+        } ?: return null
+
+        val adjustedArgs: List<IrExpression> =
+            // if typeArgs.size == args.size then the serializer is custom - we need to use the actual serializers from the arguments
+            if ((typeArgs.size != args.size) && (baseClass.descriptor.isSealed() || baseClass.descriptor.modality == Modality.ABSTRACT)) {
+                val serializer = findStandardKotlinTypeSerializer(compilerContext, context.irBuiltIns.unitType)!!
+                // workaround for sealed and abstract classes - the `serializer` function expects non-null serializers, but does not use them, so serializers of any type can be passed
+                List(baseClass.typeParameters.size) { irGetObject(serializer) }
+            } else {
+                args
+            }
+
+        with(serializerProviderFunction as IrFunction) {
+            // Note that [typeArgs] may be unused if we short-cut to e.g. SealedClassSerializer
+            return irInvoke(
+                irGetObject(companionClass),
+                symbol,
+                typeArgs.takeIf { it.size == typeParameters.size }.orEmpty(),
+                adjustedArgs.takeIf { it.size == valueParameters.size }.orEmpty()
+            )
+        }
+    }
+
+    // Does not use sti and therefore does not perform encoder calls optimization
+    fun IrBuilderWithScope.serializerTower(
+        generator: SerializerIrGenerator,
+        dispatchReceiverParameter: IrValueParameter,
+        property: IrSerializableProperty
+    ): IrExpression? {
+        val nullableSerClass = compilerContext.referenceProperties(SerialEntityNames.wrapIntoNullableCallableId).single()
+        val serializer =
+            property.serializableWith(compilerContext)
+                ?: if (!property.type.isTypeParameter()) generator.findTypeSerializerOrContext(
+                    compilerContext,
+                    property.type
+                ) else null
+        return serializerInstance(
+            serializer,
+            compilerContext,
+            property.type,
+            genericIndex = property.genericIndex
+        ) { it, _ ->
+            val (_, ir) = generator.localSerializersFieldsDescriptors[it]
+            irGetField(irGet(dispatchReceiverParameter), ir.backingField!!)
+        }?.let { expr -> wrapWithNullableSerializerIfNeeded(property.type, expr, nullableSerClass) }
+    }
+
+    private fun IrBuilderWithScope.wrapWithNullableSerializerIfNeeded(
+        type: IrType,
+        expression: IrExpression,
+        nullableProp: IrPropertySymbol
+    ): IrExpression = if (type.isMarkedNullable()) {
+        val resultType = type.makeNotNull()
+        val typeArguments = listOf(resultType)
+        val callee = nullableProp.owner.getter!!
+
+        val returnType = callee.returnType.substitute(callee.typeParameters, typeArguments)
+
+        irInvoke(
+            callee = callee.symbol,
+            typeArguments = typeArguments,
+            valueArguments = emptyList(),
+            returnTypeHint = returnType
+        ).apply { extensionReceiver = expression }
+    } else {
+        expression
+    }
+
+    fun wrapIrTypeIntoKSerializerIrType(
+        type: IrType,
+        variance: Variance = Variance.INVARIANT
+    ): IrType {
+        val kSerClass = compilerContext.referenceClass(ClassId(SerializationPackages.packageFqName, SerialEntityNames.KSERIALIZER_NAME))
+            ?: error("Couldn't find class ${SerialEntityNames.KSERIALIZER_NAME}")
+        return IrSimpleTypeImpl(
+            kSerClass, hasQuestionMark = false, arguments = listOf(
+                makeTypeProjection(type, variance)
+            ), annotations = emptyList()
+        )
+    }
+
+    fun IrBuilderWithScope.serializerInstance(
+        serializerClassOriginal: IrClassSymbol?,
+        pluginContext: SerializationPluginContext,
+        kType: IrType,
+        genericIndex: Int? = null,
+        genericGetter: ((Int, IrType) -> IrExpression)? = null
+    ): IrExpression? {
+        val nullableSerClass = compilerContext.referenceProperties(SerialEntityNames.wrapIntoNullableCallableId).single()
+        if (serializerClassOriginal == null) {
+            if (genericIndex == null) return null
+            return genericGetter?.invoke(genericIndex, kType)
+        }
+        if (serializerClassOriginal.owner.kind == ClassKind.OBJECT) {
+            return irGetObject(serializerClassOriginal)
+        }
+        fun instantiate(serializer: IrClassSymbol?, type: IrType): IrExpression? {
+            val expr = serializerInstance(
+                serializer,
+                pluginContext,
+                type,
+                type.genericIndex,
+                genericGetter
+            ) ?: return null
+            return wrapWithNullableSerializerIfNeeded(type, expr, nullableSerClass)
+        }
+
+        var serializerClass = serializerClassOriginal
+        var args: List<IrExpression>
+        var typeArgs: List<IrType>
+        val thisIrType = (kType as? IrSimpleType) ?: error("Don't know how to work with type ${kType::class}")
+        var needToCopyAnnotations = false
+
+        when (serializerClassOriginal.owner.classId) {
+            polymorphicSerializerId -> {
+                needToCopyAnnotations = true
+                args = listOf(classReference(kType))
+                typeArgs = listOf(thisIrType)
+            }
+            contextSerializerId -> {
+                args = listOf(classReference(kType))
+                typeArgs = listOf(thisIrType)
+
+                val hasNewCtxSerCtor = compilerContext.referenceConstructors(contextSerializerId).any { it.owner.valueParameters.size == 3 }
+
+                if (hasNewCtxSerCtor) {
+                    // new signature of context serializer
+                    args = args + mutableListOf<IrExpression>().apply {
+                        val fallbackDefaultSerializer = findTypeSerializer(pluginContext, kType)
+                        add(instantiate(fallbackDefaultSerializer, kType) ?: irNull())
+                        add(
+                            createArrayOfExpression(
+                                wrapIrTypeIntoKSerializerIrType(
+                                    thisIrType,
+                                    variance = Variance.OUT_VARIANCE
+                                ),
+                                thisIrType.arguments.map {
+                                    val argSer = findTypeSerializerOrContext(
+                                        compilerContext,
+                                        it.typeOrNull!! //todo: handle star projections here?
+                                    )
+                                    instantiate(argSer, it.typeOrNull!!)!!
+                                })
+                        )
+                    }
+                }
+            }
+            objectSerializerId -> {
+                needToCopyAnnotations = true
+                args = listOf(irString(kType.serialName()), irGetObject(kType.classOrNull!!))
+                typeArgs = listOf(thisIrType)
+            }
+            sealedSerializerId -> {
+                needToCopyAnnotations = true
+                args = mutableListOf<IrExpression>().apply {
+                    add(irString(kType.serialName()))
+                    add(classReference(kType))
+                    val (subclasses, subSerializers) = allSealedSerializableSubclassesFor(
+                        kType.classOrNull!!.owner,
+                        pluginContext
+                    )
+                    val projectedOutCurrentKClass =
+                        compilerContext.irBuiltIns.kClassClass.typeWithArguments(
+                            listOf(makeTypeProjection(thisIrType, Variance.OUT_VARIANCE))
+                        )
+                    add(
+                        createArrayOfExpression(
+                            projectedOutCurrentKClass,
+                            subclasses.map { classReference(it) }
+                        )
+                    )
+                    add(
+                        createArrayOfExpression(
+                            wrapIrTypeIntoKSerializerIrType(thisIrType, variance = Variance.OUT_VARIANCE),
+                            subSerializers.mapIndexed { i, serializer ->
+                                val type = subclasses[i]
+                                val expr = serializerInstance(
+                                    serializer,
+                                    pluginContext,
+                                    type,
+                                    type.genericIndex
+                                ) { _, genericType ->
+                                    serializerInstance(
+                                        pluginContext.referenceClass(polymorphicSerializerId),
+                                        pluginContext,
+                                        (genericType.classifierOrNull as IrTypeParameterSymbol).owner.representativeUpperBound
+                                    )!!
+                                }!!
+                                wrapWithNullableSerializerIfNeeded(type, expr, nullableSerClass)
+                            }
+                        )
+                    )
+                }
+                typeArgs = listOf(thisIrType)
+            }
+            enumSerializerId -> {
+                serializerClass = pluginContext.referenceClass(enumSerializerId)
+                val enumDescriptor = kType.classOrNull!!
+                typeArgs = listOf(thisIrType)
+                // instantiate serializer only inside enum Companion
+                if (this@BaseIrGenerator !is SerializableCompanionIrGenerator) {
+                    // otherwise call Companion.serializer()
+                    callSerializerFromCompanion(thisIrType, typeArgs, emptyList())?.let { return it }
+                }
+
+                val enumArgs = mutableListOf(
+                    irString(thisIrType.serialName()),
+                    irCall(enumDescriptor.owner.findEnumValuesMethod()),
+                )
+
+                val enumSerializerFactoryFunc = enumSerializerFactoryFunc
+                val markedEnumSerializerFactoryFunc = markedEnumSerializerFactoryFunc
+                if (enumSerializerFactoryFunc != null && markedEnumSerializerFactoryFunc != null) {
+                    // runtime contains enum serializer factory functions
+                    val factoryFunc: IrSimpleFunctionSymbol = if (enumDescriptor.owner.isEnumWithSerialInfoAnnotation()) {
+                        // need to store SerialInfo annotation in descriptor
+                        val enumEntries = enumDescriptor.owner.enumEntries()
+                        val entriesNames = enumEntries.map { it.annotations.serialNameValue?.let { n -> irString(n) } ?: irNull() }
+                        val entriesAnnotations = enumEntries.map {
+                            val annotationConstructors = it.annotations.map { a ->
+                                a.deepCopyWithVariables()
+                            }
+                            val annotationsConstructors = copyAnnotationsFrom(annotationConstructors)
+                            if (annotationsConstructors.isEmpty()) {
+                                irNull()
+                            } else {
+                                createArrayOfExpression(compilerContext.irBuiltIns.annotationType, annotationsConstructors)
+                            }
+                        }
+                        val annotationArrayType =
+                            compilerContext.irBuiltIns.arrayClass.typeWith(compilerContext.irBuiltIns.annotationType.makeNullable())
+
+                        enumArgs += createArrayOfExpression(compilerContext.irBuiltIns.stringType.makeNullable(), entriesNames)
+                        enumArgs += createArrayOfExpression(annotationArrayType, entriesAnnotations)
+
+                        markedEnumSerializerFactoryFunc
+                    } else {
+                        enumSerializerFactoryFunc
+                    }
+
+                    val factoryReturnType = factoryFunc.owner.returnType.substitute(factoryFunc.owner.typeParameters, typeArgs)
+                    return irInvoke(null, factoryFunc, typeArgs, enumArgs, factoryReturnType)
+                } else {
+                    // support legacy serializer instantiation by constructor for old runtimes
+                    args = enumArgs
+                }
+            }
+            else -> {
+                args = kType.arguments.map {
+                    val argSer = findTypeSerializerOrContext(
+                        pluginContext,
+                        it.typeOrNull!! // todo: stars?
+                    )
+                    instantiate(argSer, it.typeOrNull!!) ?: return null
+                }
+                typeArgs = kType.arguments.map { it.typeOrNull!! }
+            }
+
+        }
+        if (serializerClassOriginal.owner.classId == referenceArraySerializerId) {
+            args = listOf(wrapperClassReference(kType.arguments.single().typeOrNull!!)) + args
+            typeArgs = listOf(typeArgs[0].makeNotNull()) + typeArgs
+        }
+
+        // If KType is interface, .classSerializer always yields PolymorphicSerializer, which may be unavailable for interfaces from other modules
+        if (!kType.isInterface() && serializerClassOriginal == kType.classOrNull!!.owner.classSerializer(pluginContext) && this@BaseIrGenerator !is SerializableCompanionIrGenerator) {
+            // This is default type serializer, we can shortcut through Companion.serializer()
+            // BUT not during generation of this method itself
+            callSerializerFromCompanion(thisIrType, typeArgs, args)?.let { return it }
+        }
+
+
+        val serializable = serializerClass?.owner?.let { getSerializableClassDescriptorBySerializer(it) }
+        requireNotNull(serializerClass)
+        val ctor = if (serializable?.typeParameters?.isNotEmpty() == true) {
+            requireNotNull(
+                findSerializerConstructorForTypeArgumentsSerializers(serializerClass.owner)
+            ) { "Generated serializer does not have constructor with required number of arguments" }
+        } else {
+            val constructors = serializerClass.constructors
+            // search for new signature of polymorphic/sealed/contextual serializer
+            if (!needToCopyAnnotations) {
+                constructors.single { it.owner.isPrimary }
+            } else {
+                constructors.find { it.owner.lastArgumentIsAnnotationArray() } ?: run {
+                    // not found - we are using old serialization runtime without this feature
+                    // todo: optimize allocating an empty array when no annotations defined, maybe use old constructor?
+                    needToCopyAnnotations = false
+                    constructors.single { it.owner.isPrimary }
+                }
+            }
+        }
+        // Return type should be correctly substituted
+        assert(ctor.isBound)
+        val ctorDecl = ctor.owner
+        if (needToCopyAnnotations) {
+            val classAnnotations = copyAnnotationsFrom(thisIrType.getClass()?.let { collectSerialInfoAnnotations(it) }.orEmpty())
+            args = args + createArrayOfExpression(compilerContext.irBuiltIns.annotationType, classAnnotations)
+        }
+
+        val typeParameters = ctorDecl.parentAsClass.typeParameters
+        val substitutedReturnType = ctorDecl.returnType.substitute(typeParameters, typeArgs)
+        return irInvoke(
+            null,
+            ctor,
+            // User may declare serializer with fixed type arguments, e.g. class SomeSerializer : KSerializer<ClosedRange<Float>>
+            typeArguments = typeArgs.takeIf { it.size == ctorDecl.typeParameters.size }.orEmpty(),
+            valueArguments = args.takeIf { it.size == ctorDecl.valueParameters.size }.orEmpty(),
+            returnTypeHint = substitutedReturnType
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/DefaultValuesUtils.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/DefaultValuesUtils.kt
new file mode 100644
index 0000000..43c07c6
--- /dev/null
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/DefaultValuesUtils.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2010-2022 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.ValueParameterDescriptor
+import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
+import org.jetbrains.kotlin.ir.builders.irGet
+import org.jetbrains.kotlin.ir.builders.irGetField
+import org.jetbrains.kotlin.ir.declarations.IrClass
+import org.jetbrains.kotlin.ir.declarations.IrProperty
+import org.jetbrains.kotlin.ir.declarations.IrValueParameter
+import org.jetbrains.kotlin.ir.deepCopyWithVariables
+import org.jetbrains.kotlin.ir.expressions.IrExpression
+import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
+import org.jetbrains.kotlin.ir.expressions.IrGetValue
+import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
+import org.jetbrains.kotlin.ir.util.constructors
+import org.jetbrains.kotlin.ir.util.properties
+import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
+
+fun IrBuilderWithScope.getProperty(receiver: IrExpression, property: IrProperty): IrExpression {
+    return if (property.getter != null)
+        irGet(property.getter!!.returnType, receiver, property.getter!!.symbol)
+    else
+        irGetField(receiver, property.backingField!!)
+}
+
+/*
+  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<IrSerializableProperty>,
+    instance: IrValueParameter
+): (ValueParameterDescriptor) -> IrExpression? {
+    fun IrSerializableProperty.irGet(): IrExpression {
+        val ownerType = instance.symbol.owner.type
+        return getProperty(
+            irGet(
+                type = ownerType,
+                variable = instance.symbol
+            ), ir
+        )
+    }
+
+    val serialPropertiesMap = serialProperties.associateBy { it.ir }
+
+    val transientPropertiesSet =
+        irClass.declarations.asSequence()
+            .filterIsInstance<IrProperty>()
+            .filter { it.backingField != null }
+            .filter { !serialPropertiesMap.containsKey(it) }
+            .toSet()
+
+    return { vpd ->
+        val propertyDescriptor = irClass.properties.find { it.name == vpd.name }
+        if (propertyDescriptor != null) {
+            val value = serialPropertiesMap[propertyDescriptor]
+            value?.irGet() ?: run {
+                if (propertyDescriptor in transientPropertiesSet)
+                    getProperty(
+                        irGet(instance),
+                        propertyDescriptor
+                    )
+                else null
+            }
+        } else {
+            null
+        }
+    }
+}
+
+/*
+    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()
+            }
+
+            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.isInitializePropertyFromParameter()) {
+                // this is a primary constructor property, use corresponding default of value parameter
+                defaultsMap.getValue((rawExpression as IrGetValue).symbol)!!
+            } else {
+                rawExpression
+            }
+        return expression.deepCopyWithVariables().transform(initializerTransformer, null)
+    }
+}
+
+private fun extractDefaultValuesFromConstructor(irClass: IrClass?): Map<IrValueSymbol, IrExpression?> {
+    if (irClass == null) return emptyMap()
+    val original = irClass.constructors.singleOrNull { it.isPrimary }
+    // default arguments of original constructor
+    val defaultsMap: Map<IrValueSymbol, IrExpression?> =
+        original?.valueParameters?.associate { it.symbol to it.defaultValue?.expression } ?: emptyMap()
+    return defaultsMap + extractDefaultValuesFromConstructor(irClass.getSuperClassNotAny())
+}
\ No newline at end of file
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
deleted file mode 100644
index 9f6861a..0000000
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt
+++ /dev/null
@@ -1,1367 +0,0 @@
-/*
- * 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.
- */
-
-package org.jetbrains.kotlinx.serialization.compiler.backend.ir
-
-import org.jetbrains.kotlin.backend.common.extensions.FirIncompatiblePluginAPI
-import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
-import org.jetbrains.kotlin.backend.common.lower.irIfThen
-import org.jetbrains.kotlin.backend.jvm.functionByName
-import org.jetbrains.kotlin.backend.jvm.ir.representativeUpperBound
-import org.jetbrains.kotlin.builtins.KotlinBuiltIns
-import org.jetbrains.kotlin.builtins.StandardNames
-import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.ir.builders.*
-import org.jetbrains.kotlin.ir.builders.declarations.buildFun
-import org.jetbrains.kotlin.ir.declarations.*
-import org.jetbrains.kotlin.ir.deepCopyWithVariables
-import org.jetbrains.kotlin.ir.expressions.*
-import org.jetbrains.kotlin.ir.expressions.impl.*
-import org.jetbrains.kotlin.ir.symbols.*
-import org.jetbrains.kotlin.ir.symbols.impl.*
-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.name.CallableId
-import org.jetbrains.kotlin.name.ClassId
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.platform.jvm.isJvm
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
-import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
-import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.TypeProjectionImpl
-import org.jetbrains.kotlin.types.Variance
-import org.jetbrains.kotlin.types.replace
-import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
-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
-import org.jetbrains.kotlinx.serialization.compiler.resolve.*
-import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationDependencies.LAZY_FQ
-import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationDependencies.LAZY_MODE_FQ
-import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationDependencies.LAZY_PUBLICATION_MODE_NAME
-
-interface IrBuilderExtension {
-    val compilerContext: SerializationPluginContext
-
-    private val throwMissedFieldExceptionFunc
-        get() = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.SINGLE_MASK_FIELD_MISSING_FUNC_NAME)).singleOrNull()
-
-    private val throwMissedFieldExceptionArrayFunc
-        get() = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_NAME)).singleOrNull()
-
-    private val enumSerializerFactoryFunc
-        get() = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME)).singleOrNull()
-
-    private val markedEnumSerializerFactoryFunc
-        get() = compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME)).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 <F: IrFunction> addFunctionBody(function: F, bodyGen: IrBlockBodyBuilder.(F) -> Unit) {
-        val parentClass = function.parent
-        val startOffset = function.startOffset.takeIf { it >= 0 } ?: parentClass.startOffset
-        val endOffset = function.endOffset.takeIf { it >= 0 } ?: parentClass.endOffset
-        function.body = DeclarationIrBuilder(compilerContext, function.symbol, startOffset, endOffset).irBlockBody(
-            startOffset,
-            endOffset
-        ) { bodyGen(function) }
-    }
-
-    fun IrClass.contributeFunction(descriptor: FunctionDescriptor, ignoreWhenMissing: Boolean = false, bodyGen: IrBlockBodyBuilder.(IrFunction) -> Unit) {
-        val f: IrSimpleFunction = searchForDeclaration(descriptor)
-            ?: (if (ignoreWhenMissing) return else compilerContext.symbolTable.referenceSimpleFunction(descriptor).owner)
-        f.body = DeclarationIrBuilder(compilerContext, f.symbol, this.startOffset, this.endOffset).irBlockBody(
-            this.startOffset,
-            this.endOffset
-        ) { bodyGen(f) }
-    }
-
-    fun IrClass.contributeConstructor(
-        descriptor: ClassConstructorDescriptor,
-        declareNew: Boolean = true,
-        overwriteValueParameters: Boolean = false,
-        bodyGen: IrBlockBodyBuilder.(IrConstructor) -> Unit
-    ) {
-        val c: IrConstructor = searchForDeclaration(descriptor) ?: compilerContext.symbolTable.referenceConstructor(descriptor).owner
-        c.body = DeclarationIrBuilder(compilerContext, c.symbol, this.startOffset, this.endOffset).irBlockBody(
-            this.startOffset,
-            this.endOffset
-        ) { bodyGen(c) }
-    }
-
-    fun IrClass.createLambdaExpression(
-        type: IrType,
-        bodyGen: IrBlockBodyBuilder.() -> Unit
-    ): IrFunctionExpression {
-        val function = compilerContext.irFactory.buildFun {
-            this.startOffset = this@createLambdaExpression.startOffset
-            this.endOffset = this@createLambdaExpression.endOffset
-            this.returnType = type
-            name = Name.identifier("<anonymous>")
-            visibility = DescriptorVisibilities.LOCAL
-            origin = SERIALIZABLE_PLUGIN_ORIGIN
-        }
-        function.body =
-            DeclarationIrBuilder(compilerContext, function.symbol, startOffset, endOffset).irBlockBody(startOffset, endOffset, bodyGen)
-        function.parent = this
-
-        val f0Type = compilerContext.irBuiltIns.functionN(0)
-        val f0ParamSymbol = f0Type.typeParameters[0].symbol
-        val f0IrType = f0Type.defaultType.substitute(mapOf(f0ParamSymbol to type))
-
-        return IrFunctionExpressionImpl(
-            startOffset,
-            endOffset,
-            f0IrType,
-            function,
-            IrStatementOrigin.LAMBDA
-        )
-    }
-
-    fun createLazyProperty(
-        containingClass: IrClass,
-        targetIrType: IrType,
-        name: Name,
-        initializerBuilder: IrBlockBodyBuilder.() -> Unit
-    ): IrProperty {
-        val lazySafeModeClassDescriptor = compilerContext.referenceClass(ClassId.topLevel(LAZY_MODE_FQ))!!.owner
-        val lazyFunctionSymbol = compilerContext.referenceFunctions(CallableId(StandardNames.BUILT_INS_PACKAGE_FQ_NAME, Name.identifier("lazy"))).single {
-            it.owner.valueParameters.size == 2 && it.owner.valueParameters[0].type == lazySafeModeClassDescriptor.defaultType
-        }
-        val publicationEntryDescriptor = lazySafeModeClassDescriptor.enumEntries().single { it.name == LAZY_PUBLICATION_MODE_NAME }
-
-        val lazyIrClass = compilerContext.referenceClass(ClassId.topLevel(LAZY_FQ))!!.owner
-        val lazyIrType = lazyIrClass.defaultType.substitute(mapOf(lazyIrClass.typeParameters[0].symbol to targetIrType))
-
-        val propertyDescriptor =
-            KSerializerDescriptorResolver.createValPropertyDescriptor(
-                Name.identifier(name.asString() + "\$delegate"),
-                containingClass.descriptor,
-                lazyIrType.toKotlinType(),
-                createGetter = true
-            )
-
-        return generateSimplePropertyWithBackingField(propertyDescriptor, containingClass).apply {
-            val builder = DeclarationIrBuilder(compilerContext, containingClass.symbol, startOffset, endOffset)
-            val initializerBody = builder.run {
-                val enumElement = IrGetEnumValueImpl(
-                    startOffset,
-                    endOffset,
-                    lazySafeModeClassDescriptor.defaultType,
-                    publicationEntryDescriptor.symbol
-                )
-
-                val lambdaExpression = containingClass.createLambdaExpression(targetIrType, initializerBuilder)
-
-                irExprBody(
-                    irInvoke(null, lazyFunctionSymbol, listOf(targetIrType), listOf(enumElement, lambdaExpression), lazyIrType)
-                )
-            }
-            backingField!!.initializer = initializerBody
-        }
-    }
-
-    fun createCompanionValProperty(
-        companionClass: IrClass,
-        type: IrType,
-        name: Name,
-        initializerBuilder: IrBlockBodyBuilder.() -> Unit
-    ): IrProperty {
-        val targetKotlinType = type.toKotlinType()
-        val propertyDescriptor =
-            KSerializerDescriptorResolver.createValPropertyDescriptor(name, companionClass.descriptor, targetKotlinType)
-
-        return generateSimplePropertyWithBackingField(propertyDescriptor, companionClass, name).apply {
-            companionClass.contributeAnonymousInitializer {
-                val irBlockBody = irBlockBody(startOffset, endOffset, initializerBuilder)
-                irBlockBody.statements.dropLast(1).forEach { +it }
-                val expression = irBlockBody.statements.last() as? IrExpression
-                    ?: throw AssertionError("Last statement in property initializer builder is not an a expression")
-                +irSetField(irGetObject(companionClass), backingField!!, expression)
-            }
-        }
-    }
-
-    fun IrClass.contributeAnonymousInitializer(bodyGen: IrBlockBodyBuilder.() -> Unit) {
-        val symbol = IrAnonymousInitializerSymbolImpl(descriptor)
-        factory.createAnonymousInitializer(startOffset, endOffset, SERIALIZABLE_PLUGIN_ORIGIN, symbol).also {
-            it.parent = this
-            declarations.add(it)
-            it.body = DeclarationIrBuilder(compilerContext, symbol, startOffset, endOffset).irBlockBody(startOffset, endOffset, bodyGen)
-        }
-    }
-
-    fun IrBlockBodyBuilder.getLazyValueExpression(thisParam: IrValueParameter, property: IrProperty, type: IrType): IrExpression {
-        val lazyIrClass = compilerContext.referenceClass(ClassId.topLevel(LAZY_FQ))!!.owner
-        val valueGetter = lazyIrClass.getPropertyGetter("value")!!
-
-        val propertyGetter = property.getter!!
-
-        return irInvoke(
-            irGet(propertyGetter.returnType, irGet(thisParam), propertyGetter.symbol),
-            valueGetter,
-            typeHint = type
-        )
-    }
-
-    fun IrBuilderWithScope.irInvoke(
-        dispatchReceiver: IrExpression? = null,
-        callee: IrFunctionSymbol,
-        vararg args: IrExpression,
-        typeHint: IrType? = null
-    ): IrMemberAccessExpression<*> {
-        assert(callee.isBound) { "Symbol $callee expected to be bound" }
-        val returnType = typeHint ?: callee.owner.returnType
-        val call = irCall(callee, type = returnType)
-        call.dispatchReceiver = dispatchReceiver
-        args.forEachIndexed(call::putValueArgument)
-        return call
-    }
-
-    fun IrBuilderWithScope.irInvoke(
-        dispatchReceiver: IrExpression? = null,
-        callee: IrFunctionSymbol,
-        typeArguments: List<IrType?>,
-        valueArguments: List<IrExpression>,
-        returnTypeHint: IrType? = null
-    ): IrMemberAccessExpression<*> =
-        irInvoke(
-            dispatchReceiver,
-            callee,
-            *valueArguments.toTypedArray(),
-            typeHint = returnTypeHint
-        ).also { call -> typeArguments.forEachIndexed(call::putTypeArgument) }
-
-    fun IrBuilderWithScope.createArrayOfExpression(
-        arrayElementType: IrType,
-        arrayElements: List<IrExpression>
-    ): IrExpression {
-
-        val arrayType = compilerContext.irBuiltIns.arrayClass.typeWith(arrayElementType)
-        val arg0 = IrVarargImpl(startOffset, endOffset, arrayType, arrayElementType, arrayElements)
-        val typeArguments = listOf(arrayElementType)
-
-        return irCall(compilerContext.irBuiltIns.arrayOf, arrayType, typeArguments = typeArguments).apply {
-            putValueArgument(0, arg0)
-        }
-    }
-
-    fun IrBuilderWithScope.createPrimitiveArrayOfExpression(
-        elementPrimitiveType: IrType,
-        arrayElements: List<IrExpression>
-    ): IrExpression {
-        val arrayType = compilerContext.irBuiltIns.primitiveArrayForType.getValue(elementPrimitiveType).defaultType
-        val arg0 = IrVarargImpl(startOffset, endOffset, arrayType, elementPrimitiveType, arrayElements)
-        val typeArguments = listOf(elementPrimitiveType)
-
-        return irCall(compilerContext.irBuiltIns.arrayOf, arrayType, typeArguments = typeArguments).apply {
-            putValueArgument(0, arg0)
-        }
-    }
-
-    fun IrBuilderWithScope.irBinOp(name: Name, lhs: IrExpression, rhs: IrExpression): IrExpression {
-        val classFqName = (lhs.type as IrSimpleType).classOrNull!!.owner.fqNameWhenAvailable!!
-        val symbol = compilerContext.referenceFunctions(CallableId(ClassId.topLevel(classFqName), name)).single()
-        return irInvoke(lhs, symbol, rhs)
-    }
-
-    fun IrBuilderWithScope.irGetObject(irObject: IrClass) =
-        IrGetObjectValueImpl(
-            startOffset,
-            endOffset,
-            irObject.defaultType,
-            irObject.symbol
-        )
-
-    fun <T : IrDeclaration> T.buildWithScope(builder: (T) -> Unit): T =
-        also { irDeclaration ->
-            compilerContext.symbolTable.withReferenceScope(irDeclaration) {
-                builder(irDeclaration)
-            }
-        }
-
-    class BranchBuilder(
-        val irWhen: IrWhen,
-        context: IrGeneratorContext,
-        scope: Scope,
-        startOffset: Int,
-        endOffset: Int
-    ) : IrBuilderWithScope(context, scope, startOffset, endOffset) {
-        operator fun IrBranch.unaryPlus() {
-            irWhen.branches.add(this)
-        }
-    }
-
-    fun IrBuilderWithScope.irWhen(typeHint: IrType? = null, block: BranchBuilder.() -> Unit): IrWhen {
-        val whenExpr = IrWhenImpl(startOffset, endOffset, typeHint ?: compilerContext.irBuiltIns.unitType)
-        val builder = BranchBuilder(whenExpr, context, scope, startOffset, endOffset)
-        builder.block()
-        return whenExpr
-    }
-
-    fun BranchBuilder.elseBranch(result: IrExpression): IrElseBranch =
-        IrElseBranchImpl(
-            IrConstImpl.boolean(result.startOffset, result.endOffset, compilerContext.irBuiltIns.booleanType, true),
-            result
-        )
-
-    @FirIncompatiblePluginAPI
-    fun KotlinType.toIrType() = compilerContext.typeTranslator.translateType(this)
-
-    // note: this method should be used only for properties from current module. Fields from other modules are private and inaccessible.
-    val IrSerializableProperty.irField: IrField?
-        get() = this.descriptor.backingField
-
-
-
-
-    /*
-      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<IrSerializableProperty>,
-        instance: IrValueParameter
-    ): (ValueParameterDescriptor) -> IrExpression? {
-        fun IrSerializableProperty.irGet(): IrExpression {
-            val ownerType = instance.symbol.owner.type
-            return getProperty(
-                irGet(
-                    type = ownerType,
-                    variable = instance.symbol
-                ), descriptor
-            )
-        }
-
-        val serialPropertiesMap = serialProperties.associateBy { it.descriptor }
-
-        val transientPropertiesSet =
-            irClass.declarations.asSequence()
-                .filterIsInstance<IrProperty>()
-                .filter { it.backingField != null }
-                .filter { !serialPropertiesMap.containsKey(it) }
-                .toSet()
-
-        return { vpd ->
-            val propertyDescriptor = irClass.properties.find { it.name == vpd.name }
-            if (propertyDescriptor != null) {
-                val value = serialPropertiesMap[propertyDescriptor]
-                value?.irGet() ?: run {
-                    if (propertyDescriptor in transientPropertiesSet)
-                        getProperty(
-                            irGet(instance),
-                            propertyDescriptor
-                        )
-                    else null
-                }
-            } else {
-                null
-            }
-        }
-    }
-
-    fun IrBuilderWithScope.getProperty(receiver: IrExpression, property: IrProperty): IrExpression {
-        return if (property.getter != null)
-            irGet(property.getter!!.returnType, receiver, property.getter!!.symbol)
-        else
-            irGetField(receiver, property.backingField!!)
-    }
-
-    fun IrBuilderWithScope.setProperty(receiver: IrExpression, property: IrProperty, value: IrExpression): IrExpression {
-        return if (property.setter != null)
-            irSet(property.setter!!.returnType, receiver, property.setter!!.symbol, value)
-        else
-            irSetField(receiver, property.backingField!!, value)
-    }
-
-    /*
-     The rest of the file is mainly copied from FunctionGenerator.
-     However, I can't use it's directly because all generateSomething methods require KtProperty (psi element)
-     Also, FunctionGenerator itself has DeclarationGenerator as ctor param, which is a part of psi2ir
-     (it can be instantiated here, but I don't know how good is that idea)
-     */
-
-    fun IrBuilderWithScope.generateAnySuperConstructorCall(toBuilder: IrBlockBodyBuilder) {
-        val anyConstructor = compilerContext.irBuiltIns.anyClass.owner.declarations.single { it is IrConstructor } as IrConstructor
-        with(toBuilder) {
-            +IrDelegatingConstructorCallImpl.fromSymbolDescriptor(
-                startOffset, endOffset,
-                compilerContext.irBuiltIns.unitType,
-                anyConstructor.symbol
-            )
-        }
-    }
-
-    fun IrBlockBodyBuilder.generateGoldenMaskCheck(
-        seenVars: List<IrValueDeclaration>,
-        properties: IrSerializableProperties,
-        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,
-        fieldName: Name = propertyDescriptor.name,
-    ): IrProperty {
-        val irProperty = propertyParent.searchForDeclaration(propertyDescriptor) ?: run {
-            with(propertyDescriptor) {
-                propertyParent.factory.createProperty(
-                    propertyParent.startOffset, propertyParent.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrPropertySymbolImpl(propertyDescriptor),
-                    name, visibility, modality, isVar, isConst, isLateInit, isDelegated, isExternal
-                ).also {
-                    it.parent = propertyParent
-                    propertyParent.addMember(it)
-                }
-            }
-        }
-
-        propertyParent.generatePropertyBackingFieldIfNeeded(propertyDescriptor, irProperty, fieldName)
-        val fieldSymbol = irProperty.backingField!!.symbol
-        irProperty.getter = propertyDescriptor.getter?.let {
-            propertyParent.generatePropertyAccessor(propertyDescriptor, irProperty, it, fieldSymbol, isGetter = true)
-        }?.apply { parent = propertyParent }
-        irProperty.setter = propertyDescriptor.setter?.let {
-            propertyParent.generatePropertyAccessor(propertyDescriptor, irProperty, it, fieldSymbol, isGetter = false)
-        }?.apply { parent = propertyParent }
-        return irProperty
-    }
-
-    fun IrType.kClassToJClassIfNeeded(): IrType = this
-
-    fun kClassExprToJClassIfNeeded(startOffset: Int, endOffset: Int, irExpression: IrExpression): IrExpression = irExpression
-
-    private fun IrClass.generatePropertyBackingFieldIfNeeded(
-        propertyDescriptor: PropertyDescriptor,
-        originProperty: IrProperty,
-        name: Name,
-    ) {
-        if (originProperty.backingField != null) return
-
-        val field = with(propertyDescriptor) {
-            // TODO: type parameters
-            @OptIn(FirIncompatiblePluginAPI::class)// should be called only with old FE
-            originProperty.factory.createField(
-                originProperty.startOffset, originProperty.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrFieldSymbolImpl(propertyDescriptor), name, type.toIrType(),
-                visibility, !isVar, isEffectivelyExternal(), dispatchReceiverParameter == null
-            )
-        }
-        field.apply {
-            parent = this@generatePropertyBackingFieldIfNeeded
-            correspondingPropertySymbol = originProperty.symbol
-        }
-
-        originProperty.backingField = field
-    }
-
-    private fun IrClass.generatePropertyAccessor(
-        propertyDescriptor: PropertyDescriptor,
-        property: IrProperty,
-        descriptor: PropertyAccessorDescriptor,
-        fieldSymbol: IrFieldSymbol,
-        isGetter: Boolean,
-    ): IrSimpleFunction {
-        val irAccessor: IrSimpleFunction = when (isGetter) {
-            true -> searchForDeclaration<IrProperty>(propertyDescriptor)?.getter
-            false -> searchForDeclaration<IrProperty>(propertyDescriptor)?.setter
-        } ?: run {
-            with(descriptor) {
-                @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
-                property.factory.createFunction(
-                    fieldSymbol.owner.startOffset, fieldSymbol.owner.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrSimpleFunctionSymbolImpl(descriptor),
-                    name, visibility, modality, returnType!!.toIrType(),
-                    isInline, isEffectivelyExternal(), isTailrec, isSuspend, isOperator, isInfix, isExpect
-                )
-            }.also { f ->
-                generateOverriddenFunctionSymbols(f, compilerContext.symbolTable)
-                f.createParameterDeclarations(descriptor)
-                @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
-                f.returnType = descriptor.returnType!!.toIrType()
-                f.correspondingPropertySymbol = fieldSymbol.owner.correspondingPropertySymbol
-            }
-        }
-
-        irAccessor.body = when (isGetter) {
-            true -> generateDefaultGetterBody(descriptor as PropertyGetterDescriptor, irAccessor)
-            false -> generateDefaultSetterBody(descriptor as PropertySetterDescriptor, irAccessor)
-        }
-
-        return irAccessor
-    }
-
-    private fun generateDefaultGetterBody(
-        getter: PropertyGetterDescriptor,
-        irAccessor: IrSimpleFunction
-    ): IrBlockBody {
-        val property = getter.correspondingProperty
-        val irProperty = irAccessor.correspondingPropertySymbol?.owner ?: error("Expected property for $getter")
-
-        val startOffset = irAccessor.startOffset
-        val endOffset = irAccessor.endOffset
-        val irBody = irAccessor.factory.createBlockBody(startOffset, endOffset)
-
-        val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol, property)
-
-        val propertyIrType = irAccessor.returnType
-        irBody.statements.add(
-            IrReturnImpl(
-                startOffset, endOffset, compilerContext.irBuiltIns.nothingType,
-                irAccessor.symbol,
-                IrGetFieldImpl(
-                    startOffset, endOffset,
-                    irProperty.backingField?.symbol ?: error("Property expected to have backing field"),
-                    propertyIrType,
-                    receiver
-                ).let {
-                    if (propertyIrType.isKClass()) {
-                        irAccessor.returnType = irAccessor.returnType.kClassToJClassIfNeeded()
-                        kClassExprToJClassIfNeeded(startOffset, endOffset, it)
-                    } else it
-                }
-            )
-        )
-        return irBody
-    }
-
-    private fun generateDefaultSetterBody(
-        setter: PropertySetterDescriptor,
-        irAccessor: IrSimpleFunction
-    ): IrBlockBody {
-        val property = setter.correspondingProperty
-        val irProperty = irAccessor.correspondingPropertySymbol?.owner ?: error("Expected corresponding property for accessor $setter")
-        val startOffset = irAccessor.startOffset
-        val endOffset = irAccessor.endOffset
-        val irBody = irAccessor.factory.createBlockBody(startOffset, endOffset)
-
-        val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol, property)
-
-        val irValueParameter = irAccessor.valueParameters.single()
-        irBody.statements.add(
-            IrSetFieldImpl(
-                startOffset, endOffset,
-                irProperty.backingField?.symbol ?: error("Property $property expected to have backing field"),
-                receiver,
-                IrGetValueImpl(startOffset, endOffset, irValueParameter.type, irValueParameter.symbol),
-                compilerContext.irBuiltIns.unitType
-            )
-        )
-        return irBody
-    }
-
-    fun generateReceiverExpressionForFieldAccess( // todo: remove this
-        ownerSymbol: IrValueSymbol,
-        property: PropertyDescriptor
-    ): IrExpression {
-        val containingDeclaration = property.containingDeclaration
-        return when (containingDeclaration) {
-            is ClassDescriptor ->
-                IrGetValueImpl(
-                    ownerSymbol.owner.startOffset, ownerSymbol.owner.endOffset,
-                    ownerSymbol
-                )
-            else -> throw AssertionError("Property must be in class")
-        }
-    }
-
-    fun IrFunction.createParameterDeclarations(
-        descriptor: FunctionDescriptor,
-        overwriteValueParameters: Boolean = false,
-        copyTypeParameters: Boolean = true
-    ) {
-        val function = this
-        fun irValueParameter(descriptor: ParameterDescriptor): IrValueParameter = with(descriptor) {
-            @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
-            factory.createValueParameter(
-                function.startOffset, function.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrValueParameterSymbolImpl(this),
-                name, indexOrMinusOne, type.toIrType(), varargElementType?.toIrType(), isCrossinline, isNoinline,
-                isHidden = false, isAssignable = false
-            ).also {
-                it.parent = function
-            }
-        }
-
-        if (copyTypeParameters) {
-            assert(typeParameters.isEmpty())
-            copyTypeParamsFromDescriptor(descriptor)
-        }
-
-        dispatchReceiverParameter = descriptor.dispatchReceiverParameter?.let { irValueParameter(it) }
-        extensionReceiverParameter = descriptor.extensionReceiverParameter?.let { irValueParameter(it) }
-
-        if (!overwriteValueParameters)
-            assert(valueParameters.isEmpty())
-
-        valueParameters = descriptor.valueParameters.map { irValueParameter(it) }
-    }
-
-    fun IrFunction.copyTypeParamsFromDescriptor(descriptor: FunctionDescriptor) {
-        val newTypeParameters = descriptor.typeParameters.map {
-            factory.createTypeParameter(
-                startOffset, endOffset,
-                SERIALIZABLE_PLUGIN_ORIGIN,
-                IrTypeParameterSymbolImpl(it),
-                it.name, it.index, it.isReified, it.variance
-            ).also { typeParameter ->
-                typeParameter.parent = this
-            }
-        }
-        @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
-        newTypeParameters.forEach { typeParameter ->
-            typeParameter.superTypes = typeParameter.descriptor.upperBounds.map { it.toIrType() }
-        }
-
-        typeParameters = newTypeParameters
-    }
-
-    fun createClassReference(classType: IrType, startOffset: Int, endOffset: Int): IrClassReference {
-        return IrClassReferenceImpl(
-            startOffset,
-            endOffset,
-            compilerContext.irBuiltIns.kClassClass.starProjectedType,
-            classType.classifierOrFail,
-            classType
-        )
-    }
-
-    // It is impossible to use star projections right away, because Array<Int>::class differ from Array<String>::class :
-    // detailed information about type arguments required for class references on jvm
-    private val KotlinType.approximateJvmErasure: KotlinType
-        get() = when (val classifier = constructor.declarationDescriptor) {
-            is TypeParameterDescriptor -> {
-                // Note that if the upper bound is Array, the argument type will be replaced with star here, which is probably incorrect.
-                classifier.classBound.defaultType.replaceArgumentsWithStarProjections()
-            }
-            is ClassDescriptor -> if (KotlinBuiltIns.isArray(this)) {
-                replace(arguments.map { TypeProjectionImpl(Variance.INVARIANT, it.type.approximateJvmErasure) })
-            } else {
-                replaceArgumentsWithStarProjections()
-            }
-            else -> error("Unsupported classifier: $this")
-        }
-
-    private val TypeParameterDescriptor.classBound: ClassDescriptor
-        get() = when (val bound = representativeUpperBound.constructor.declarationDescriptor) {
-            is ClassDescriptor -> bound
-            is TypeParameterDescriptor -> bound.classBound
-            else -> error("Unsupported classifier: $this")
-        }
-
-
-    fun IrBuilderWithScope.classReference(classSymbol: IrType): IrClassReference =
-        createClassReference(classSymbol, startOffset, endOffset)
-
-    private fun extractDefaultValuesFromConstructor(irClass: IrClass?): Map<IrValueSymbol, IrExpression?> {
-        if (irClass == null) return emptyMap()
-        val original = irClass.constructors.singleOrNull { it.isPrimary }
-        // default arguments of original constructor
-        val defaultsMap: Map<IrValueSymbol, IrExpression?> =
-            original?.valueParameters?.associate { it.symbol to it.defaultValue?.expression } ?: emptyMap()
-        return defaultsMap + extractDefaultValuesFromConstructor(irClass.getSuperClassNotAny())
-    }
-
-    /*
-    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()
-                }
-
-                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.isInitializePropertyFromParameter()) {
-                    // this is a primary constructor property, use corresponding default of value parameter
-                     defaultsMap.getValue((rawExpression as IrGetValue).symbol)!!
-                } else {
-                    rawExpression
-                }
-            return expression.deepCopyWithVariables().transform(initializerTransformer, null)
-        }
-    }
-
-    private fun getEnumMembersNames(enumClass: ClassDescriptor): Sequence<String> {
-        assert(enumClass.kind == ClassKind.ENUM_CLASS)
-        return enumClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
-            .filterIsInstance<ClassDescriptor>()
-            .filter { it.kind == ClassKind.ENUM_ENTRY }
-            .map { it.name.toString() }
-    }
-
-    fun IrBuilderWithScope.copyAnnotationsFrom(annotations: List<IrConstructorCall>): List<IrExpression> =
-        annotations.mapNotNull { annotationCall ->
-            val annotationClass = annotationCall.symbol.owner.parentAsClass
-            if (!annotationClass.descriptor.isSerialInfoAnnotation) return@mapNotNull null
-
-            if (compilerContext.platform.isJvm()) {
-                val implClass = compilerContext.serialInfoImplJvmIrGenerator.getImplClass(annotationClass)
-                val ctor = implClass.constructors.singleOrNull { it.valueParameters.size == annotationCall.valueArgumentsCount }
-                    ?: error("No constructor args found for SerialInfo annotation Impl class: ${implClass.render()}")
-                irCall(ctor).apply {
-                    for (i in 0 until annotationCall.valueArgumentsCount) {
-                        val argument = annotationCall.getValueArgument(i)
-                            ?: annotationClass.primaryConstructor!!.valueParameters[i].defaultValue?.expression
-                        putValueArgument(i, argument!!.deepCopyWithVariables())
-                    }
-                }
-            } else {
-                annotationCall.deepCopyWithVariables()
-            }
-        }
-
-    // Does not use sti and therefore does not perform encoder calls optimization
-    fun IrBuilderWithScope.serializerTower(
-        generator: SerializerIrGenerator,
-        dispatchReceiverParameter: IrValueParameter,
-        property: IrSerializableProperty
-    ): IrExpression? {
-        val nullableSerClass = compilerContext.referenceProperties(SerialEntityNames.wrapIntoNullableCallableId).single()
-        val serializer =
-            property.serializableWith(compilerContext)
-                ?: if (!property.type.isTypeParameter()) generator.findTypeSerializerOrContext(
-                    compilerContext,
-                    property.type
-                ) else null
-        return serializerInstance(
-            generator,
-            dispatchReceiverParameter,
-            serializer,
-            compilerContext,
-            property.type,
-            genericIndex = property.genericIndex
-        )
-            ?.let { expr -> wrapWithNullableSerializerIfNeeded(property.type, expr, nullableSerClass) }
-    }
-
-    private fun IrBuilderWithScope.wrapWithNullableSerializerIfNeeded(
-        type: IrType,
-        expression: IrExpression,
-        nullableProp: IrPropertySymbol
-    ): IrExpression = if (type.isMarkedNullable()) {
-        val resultType = type.makeNotNull()
-        val typeArguments = listOf(resultType)
-        val callee = nullableProp.owner.getter!!
-
-        val returnType = callee.returnType.substitute(callee.typeParameters, typeArguments)
-
-        irInvoke(
-            callee = callee.symbol,
-            typeArguments = typeArguments,
-            valueArguments = emptyList(),
-            returnTypeHint = returnType
-        ).apply { extensionReceiver = expression }
-    } else {
-        expression
-    }
-
-    fun wrapIrTypeIntoKSerializerIrType(
-        type: IrType,
-        variance: Variance = Variance.INVARIANT
-    ): IrType {
-        val kSerClass = compilerContext.referenceClass(ClassId(SerializationPackages.packageFqName, SerialEntityNames.KSERIALIZER_NAME))
-            ?: error("Couldn't find class ${SerialEntityNames.KSERIALIZER_NAME}")
-        return IrSimpleTypeImpl(
-            kSerClass, hasQuestionMark = false, arguments = listOf(
-                makeTypeProjection(type, variance)
-            ), annotations = emptyList()
-        )
-    }
-
-    fun IrBuilderWithScope.serializerInstance(
-        enclosingGenerator: SerializerIrGenerator,
-        dispatchReceiverParameter: IrValueParameter,
-        serializerClassOriginal: IrClassSymbol?,
-        pluginContext: SerializationPluginContext,
-        kType: IrType,
-        genericIndex: Int? = null
-    ): IrExpression? = serializerInstance(
-        enclosingGenerator,
-        serializerClassOriginal,
-        pluginContext,
-        kType,
-        genericIndex
-    ) { it, _ ->
-        val (_, ir) = enclosingGenerator.localSerializersFieldsDescriptors[it]
-        irGetField(irGet(dispatchReceiverParameter), ir.backingField!!)
-    }
-
-    fun IrBuilderWithScope.serializerInstance(
-        enclosingGenerator: AbstractIrGenerator,
-        serializerClassOriginal: ClassDescriptor?,
-        module: ModuleDescriptor,
-        kType: IrType,
-        genericIndex: Int? = null,
-        genericGetter: ((Int) -> IrExpression)? = null
-    ): IrExpression? {
-        return serializerInstance(
-            enclosingGenerator,
-            serializerClassOriginal?.let { compilerContext.symbolTable.referenceClass(it) },
-            compilerContext,
-            kType,
-            genericIndex,
-            if (genericGetter != null) { i, _ -> genericGetter(i) } else null
-        )
-    }
-
-    fun IrBuilderWithScope.serializerInstance(
-        enclosingGenerator: AbstractIrGenerator,
-        serializerClassOriginal: IrClassSymbol?,
-        pluginContext: SerializationPluginContext,
-        kType: IrType,
-        genericIndex: Int? = null,
-        genericGetter: ((Int, IrType) -> IrExpression)? = null
-    ): IrExpression? {
-        val nullableSerClass = compilerContext.referenceProperties(SerialEntityNames.wrapIntoNullableCallableId).single()
-        if (serializerClassOriginal == null) {
-            if (genericIndex == null) return null
-            return genericGetter?.invoke(genericIndex, kType)
-        }
-        if (serializerClassOriginal.owner.kind == ClassKind.OBJECT) {
-            return irGetObject(serializerClassOriginal)
-        }
-        fun instantiate(serializer: IrClassSymbol?, type: IrType): IrExpression? {
-            val expr = serializerInstance(
-                enclosingGenerator,
-                serializer,
-                pluginContext,
-                type,
-                type.genericIndex,
-                genericGetter
-            ) ?: return null
-            return wrapWithNullableSerializerIfNeeded(type, expr, nullableSerClass)
-        }
-
-        var serializerClass = serializerClassOriginal
-        var args: List<IrExpression>
-        var typeArgs: List<IrType>
-        val thisIrType = (kType as? IrSimpleType) ?: error("Don't know how to work with type ${kType::class}")
-        var needToCopyAnnotations = false
-
-        when (serializerClassOriginal.owner.classId) {
-            polymorphicSerializerId -> {
-                needToCopyAnnotations = true
-                args = listOf(classReference(kType))
-                typeArgs = listOf(thisIrType)
-            }
-            contextSerializerId -> {
-                args = listOf(classReference(kType))
-                typeArgs = listOf(thisIrType)
-
-                val hasNewCtxSerCtor = compilerContext.referenceConstructors(contextSerializerId).any { it.owner.valueParameters.size == 3 }
-
-                if (hasNewCtxSerCtor) {
-                    // new signature of context serializer
-                    args = args + mutableListOf<IrExpression>().apply {
-                        val fallbackDefaultSerializer = findTypeSerializer(pluginContext, kType)
-                        add(instantiate(fallbackDefaultSerializer, kType) ?: irNull())
-                        add(
-                            createArrayOfExpression(
-                                wrapIrTypeIntoKSerializerIrType(
-                                    thisIrType,
-                                    variance = Variance.OUT_VARIANCE
-                                ),
-                                thisIrType.arguments.map {
-                                    val argSer = enclosingGenerator.findTypeSerializerOrContext(
-                                        compilerContext,
-                                        it.typeOrNull!! //todo: handle star projections here?
-                                    )
-                                    instantiate(argSer, it.typeOrNull!!)!!
-                                })
-                        )
-                    }
-                }
-            }
-            objectSerializerId -> {
-                needToCopyAnnotations = true
-                args = listOf(irString(kType.serialName()), irGetObject(kType.classOrNull!!))
-                typeArgs = listOf(thisIrType)
-            }
-            sealedSerializerId -> {
-                needToCopyAnnotations = true
-                args = mutableListOf<IrExpression>().apply {
-                    add(irString(kType.serialName()))
-                    add(classReference(kType))
-                    val (subclasses, subSerializers) = enclosingGenerator.allSealedSerializableSubclassesFor(
-                        kType.classOrNull!!.owner,
-                        pluginContext
-                    )
-                    val projectedOutCurrentKClass =
-                        compilerContext.irBuiltIns.kClassClass.typeWithArguments(
-                            listOf(makeTypeProjection(thisIrType, Variance.OUT_VARIANCE))
-                        )
-                    add(
-                        createArrayOfExpression(
-                            projectedOutCurrentKClass,
-                            subclasses.map { classReference(it) }
-                        )
-                    )
-                    add(
-                        createArrayOfExpression(
-                            wrapIrTypeIntoKSerializerIrType(thisIrType, variance = Variance.OUT_VARIANCE),
-                            subSerializers.mapIndexed { i, serializer ->
-                                val type = subclasses[i]
-                                val expr = serializerInstance(
-                                    enclosingGenerator,
-                                    serializer,
-                                    pluginContext,
-                                    type,
-                                    type.genericIndex
-                                ) { _, genericType ->
-                                    serializerInstance(
-                                        enclosingGenerator,
-                                        pluginContext.referenceClass(polymorphicSerializerId),
-                                        pluginContext,
-                                        (genericType.classifierOrNull as IrTypeParameterSymbol).owner.representativeUpperBound
-                                    )!!
-                                }!!
-                                wrapWithNullableSerializerIfNeeded(type, expr, nullableSerClass)
-                            }
-                        )
-                    )
-                }
-                typeArgs = listOf(thisIrType)
-            }
-            enumSerializerId -> {
-                serializerClass = pluginContext.referenceClass(enumSerializerId)
-                val enumDescriptor = kType.classOrNull!!
-                typeArgs = listOf(thisIrType)
-                // instantiate serializer only inside enum Companion
-                if (enclosingGenerator !is SerializableCompanionIrGenerator) {
-                    // otherwise call Companion.serializer()
-                    callSerializerFromCompanion(thisIrType, typeArgs, emptyList())?.let { return it }
-                }
-
-                val enumArgs = mutableListOf(
-                    irString(thisIrType.serialName()),
-                    irCall(enumDescriptor.owner.findEnumValuesMethod()),
-                )
-
-                val enumSerializerFactoryFunc = enumSerializerFactoryFunc
-                val markedEnumSerializerFactoryFunc = markedEnumSerializerFactoryFunc
-                if (enumSerializerFactoryFunc != null && markedEnumSerializerFactoryFunc != null) {
-                    // runtime contains enum serializer factory functions
-                    val factoryFunc: IrSimpleFunctionSymbol = if (enumDescriptor.owner.isEnumWithSerialInfoAnnotation()) {
-                        // need to store SerialInfo annotation in descriptor
-                        val enumEntries = enumDescriptor.owner.enumEntries()
-                        val entriesNames = enumEntries.map { it.annotations.serialNameValue?.let { n -> irString(n) } ?: irNull() }
-                        val entriesAnnotations = enumEntries.map {
-                            val annotationConstructors = it.annotations.map { a ->
-//                                compilerContext.typeTranslator.constantValueGenerator.generateAnnotationConstructorCall(a)
-                                a.deepCopyWithVariables()
-                            }
-                            val annotationsConstructors = copyAnnotationsFrom(annotationConstructors)
-                            if (annotationsConstructors.isEmpty()) {
-                                irNull()
-                            } else {
-                                createArrayOfExpression(compilerContext.irBuiltIns.annotationType, annotationsConstructors)
-                            }
-                        }
-                        val annotationArrayType =
-                            compilerContext.irBuiltIns.arrayClass.typeWith(compilerContext.irBuiltIns.annotationType.makeNullable())
-
-                        enumArgs += createArrayOfExpression(compilerContext.irBuiltIns.stringType.makeNullable(), entriesNames)
-                        enumArgs += createArrayOfExpression(annotationArrayType, entriesAnnotations)
-
-                        markedEnumSerializerFactoryFunc
-                    } else {
-                        enumSerializerFactoryFunc
-                    }
-
-                    val factoryReturnType = factoryFunc.owner.returnType.substitute(factoryFunc.owner.typeParameters, typeArgs)
-                    return irInvoke(null, factoryFunc, typeArgs, enumArgs, factoryReturnType)
-                } else {
-                    // support legacy serializer instantiation by constructor for old runtimes
-                    args = enumArgs
-                }
-            }
-            else -> {
-                args = kType.arguments.map {
-                    val argSer = enclosingGenerator.findTypeSerializerOrContext(
-                        pluginContext,
-                        it.typeOrNull!! // todo: stars?
-                    )
-                    instantiate(argSer, it.typeOrNull!!) ?: return null
-                }
-                typeArgs = kType.arguments.map { it.typeOrNull!! }
-            }
-
-        }
-        if (serializerClassOriginal.owner.classId == referenceArraySerializerId) {
-            args = listOf(wrapperClassReference(kType.arguments.single().typeOrNull!!)) + args
-            typeArgs = listOf(typeArgs[0].makeNotNull()) + typeArgs
-        }
-
-        // If KType is interface, .classSerializer always yields PolymorphicSerializer, which may be unavailable for interfaces from other modules
-        if (!kType.isInterface() && serializerClassOriginal == kType.classOrNull!!.owner.classSerializer(pluginContext) && enclosingGenerator !is SerializableCompanionIrGenerator) {
-            // This is default type serializer, we can shortcut through Companion.serializer()
-            // BUT not during generation of this method itself
-            callSerializerFromCompanion(thisIrType, typeArgs, args)?.let { return it }
-        }
-
-
-        val serializable = serializerClass?.owner?.let { getSerializableClassDescriptorBySerializer(it) }
-        requireNotNull(serializerClass)
-        val ctor = if (serializable?.typeParameters?.isNotEmpty() == true) {
-            requireNotNull(
-                findSerializerConstructorForTypeArgumentsSerializers(serializerClass.owner)
-            ) { "Generated serializer does not have constructor with required number of arguments" }
-        } else {
-            val constructors = serializerClass.constructors
-            // search for new signature of polymorphic/sealed/contextual serializer
-            if (!needToCopyAnnotations) {
-                constructors.single { it.owner.isPrimary }
-            } else {
-                constructors.find { it.owner.lastArgumentIsAnnotationArray() } ?: run {
-                    // not found - we are using old serialization runtime without this feature
-                    // todo: optimize allocating an empty array when no annotations defined, maybe use old constructor?
-                    needToCopyAnnotations = false
-                    constructors.single { it.owner.isPrimary }
-                }
-            }
-        }
-        // Return type should be correctly substituted
-        assert(ctor.isBound)
-        val ctorDecl = ctor.owner
-        if (needToCopyAnnotations) {
-            val classAnnotations = copyAnnotationsFrom(thisIrType.getClass()?.let { collectSerialInfoAnnotations(it) }.orEmpty())
-            args = args + createArrayOfExpression(compilerContext.irBuiltIns.annotationType, classAnnotations)
-        }
-
-        val typeParameters = ctorDecl.parentAsClass.typeParameters
-        val substitutedReturnType = ctorDecl.returnType.substitute(typeParameters, typeArgs)
-        return irInvoke(
-            null,
-            ctor,
-            // User may declare serializer with fixed type arguments, e.g. class SomeSerializer : KSerializer<ClosedRange<Float>>
-            typeArguments = typeArgs.takeIf { it.size == ctorDecl.typeParameters.size }.orEmpty(),
-            valueArguments = args.takeIf { it.size == ctorDecl.valueParameters.size }.orEmpty(),
-            returnTypeHint = substitutedReturnType
-        )
-    }
-
-    fun collectSerialInfoAnnotations(irClass: IrClass): List<IrConstructorCall> {
-        if (!(irClass.isInterface || irClass.descriptor.hasSerializableOrMetaAnnotation)) return emptyList()
-        val annotationByFq: MutableMap<FqName, IrConstructorCall> =
-            irClass.annotations.associateBy { it.symbol.owner.parentAsClass.descriptor.fqNameSafe }.toMutableMap()
-        for (clazz in irClass.getAllSuperclasses()) {
-            val annotations = clazz.annotations
-                .mapNotNull {
-                    val descriptor = it.symbol.owner.parentAsClass.descriptor
-                    if (descriptor.isInheritableSerialInfoAnnotation) descriptor.fqNameSafe to it else null
-                }
-            annotations.forEach { (fqname, call) ->
-                if (fqname !in annotationByFq) {
-                    annotationByFq[fqname] = call
-                } else {
-                    // SerializationPluginDeclarationChecker already reported inconsistency
-                }
-            }
-        }
-        return annotationByFq.values.toList()
-    }
-
-    fun IrBuilderWithScope.callSerializerFromCompanion(
-        thisIrType: IrType,
-        typeArgs: List<IrType>,
-        args: List<IrExpression>
-    ): IrExpression? {
-        val baseClass = thisIrType.getClass() ?: return null
-        val companionClass = baseClass.companionObject() ?: return null
-        val serializerProviderFunction = companionClass.declarations.singleOrNull {
-            it is IrFunction && it.name == SerialEntityNames.SERIALIZER_PROVIDER_NAME && it.valueParameters.size == baseClass.typeParameters.size
-        } ?: return null
-
-        val adjustedArgs: List<IrExpression> =
-            // if typeArgs.size == args.size then the serializer is custom - we need to use the actual serializers from the arguments
-            if ((typeArgs.size != args.size) && (baseClass.descriptor.isSealed() || baseClass.descriptor.modality == Modality.ABSTRACT)) {
-                val serializer = findStandardKotlinTypeSerializer(compilerContext, context.irBuiltIns.unitType)!!
-                // workaround for sealed and abstract classes - the `serializer` function expects non-null serializers, but does not use them, so serializers of any type can be passed
-                List(baseClass.typeParameters.size) { irGetObject(serializer) }
-            } else {
-                args
-            }
-
-        with(serializerProviderFunction as IrFunction) {
-            // Note that [typeArgs] may be unused if we short-cut to e.g. SealedClassSerializer
-            return irInvoke(
-                irGetObject(companionClass),
-                symbol,
-                typeArgs.takeIf { it.size == typeParameters.size }.orEmpty(),
-                adjustedArgs.takeIf { it.size == valueParameters.size }.orEmpty()
-            )
-        }
-    }
-
-    private fun IrConstructor.lastArgumentIsAnnotationArray(): Boolean {
-        val lastArgType = valueParameters.lastOrNull()?.type
-        if (lastArgType == null || !lastArgType.isArray()) return false
-        return ((lastArgType as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull?.classFqName?.toString() == "kotlin.Annotation")
-    }
-
-    private fun IrBuilderWithScope.wrapperClassReference(classType: IrType): IrClassReference {
-        if (compilerContext.platform.isJvm()) {
-            // "Byte::class" -> "java.lang.Byte::class"
-//          TODO: get rid of descriptor
-            val wrapperFqName = KotlinBuiltIns.getPrimitiveType(classType.classOrNull!!.descriptor)?.let(JvmPrimitiveType::get)?.wrapperFqName
-            if (wrapperFqName != null) {
-                val wrapperClass = compilerContext.referenceClass(ClassId.topLevel(wrapperFqName))
-                    ?: error("Primitive wrapper class for $classType not found: $wrapperFqName")
-                return classReference(wrapperClass.defaultType)
-            }
-        }
-        return classReference(classType)
-    }
-
-    fun findSerializerConstructorForTypeArgumentsSerializers(serializer: IrClass): IrConstructorSymbol? {
-
-        val typeParamsCount = ((serializer.superTypes.find { isKSerializer(it) } as IrSimpleType).arguments.first().typeOrNull!! as IrSimpleType).arguments.size
-        if (typeParamsCount == 0) return null //don't need it
-
-        return serializer.constructors.singleOrNull {
-            it.valueParameters.let { vps -> vps.size == typeParamsCount && vps.all { vp -> isKSerializer(vp.type) } }
-        }?.symbol
-    }
-
-    private fun IrConstructor.isSerializationCtor(): Boolean {
-        val serialMarker =
-            compilerContext.referenceClass(ClassId.topLevel(SerializationPackages.internalPackageFqName.child(SerialEntityNames.SERIAL_CTOR_MARKER_NAME)))
-
-        return valueParameters.lastOrNull()?.run {
-            name == SerialEntityNames.dummyParamName && type.classifierOrNull == serialMarker
-        } == true
-    }
-
-    fun serializableSyntheticConstructor(forClass: IrClass): IrConstructorSymbol? { // todo: remove this W/A when FIR plugin will add proper ctor
-        return forClass.declarations.filterIsInstance<IrConstructor>().singleOrNull { it.isSerializationCtor() }?.symbol
-    }
-
-    fun IrClass.getSuperClassOrAny(): IrClass = getSuperClassNotAny() ?: compilerContext.irBuiltIns.anyClass.owner
-
-    fun IrClass.getSuperClassNotAny(): IrClass? {
-        val superClasses = superTypes.mapNotNull { it.classOrNull }.map { it.owner }
-
-        return superClasses.singleOrNull { it.kind == ClassKind.CLASS }
-    }
-
-    fun IrClass.findWriteSelfMethod(): IrSimpleFunction? =
-        declarations.singleOrNull { it is IrSimpleFunction && it.name == SerialEntityNames.WRITE_SELF_NAME && !it.isFakeOverride } as IrSimpleFunction?
-
-    fun IrBlockBodyBuilder.serializeAllProperties(
-        generator: AbstractIrGenerator,
-        serializableIrClass: IrClass,
-        serializableProperties: List<IrSerializableProperty>,
-        objectToSerialize: IrValueDeclaration,
-        localOutput: IrValueDeclaration,
-        localSerialDesc: IrValueDeclaration,
-        kOutputClass: IrClassSymbol,
-        ignoreIndexTo: Int,
-        initializerAdapter: (IrExpressionBody) -> IrExpression,
-        genericGetter: ((Int, IrType) -> IrExpression)?
-    ) {
-
-        fun IrSerializableProperty.irGet(): IrExpression {
-            val ownerType = objectToSerialize.symbol.owner.type
-            return getProperty(
-                irGet(
-                    type = ownerType,
-                    variable = objectToSerialize.symbol
-                ), descriptor
-            )
-        }
-
-        for ((index, property) in serializableProperties.withIndex()) {
-            if (index < ignoreIndexTo) continue
-            // output.writeXxxElementValue(classDesc, index, value)
-            val elementCall = formEncodeDecodePropertyCall(
-                generator,
-                irGet(localOutput),
-                property, { innerSerial, sti ->
-                    val f =
-                        kOutputClass.functionByName("${CallingConventions.encode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}")
-                    f to listOf(
-                        irGet(localSerialDesc),
-                        irInt(index),
-                        innerSerial,
-                        property.irGet()
-                    )
-                }, {
-                    val f =
-                        kOutputClass.functionByName("${CallingConventions.encode}${it.elementMethodPrefix}${CallingConventions.elementPostfix}")
-                    val args: MutableList<IrExpression> = mutableListOf(irGet(localSerialDesc), irInt(index))
-                    if (it.elementMethodPrefix != "Unit") args.add(property.irGet())
-                    f to args
-                },
-                genericGetter
-            )
-
-            // check for call to .shouldEncodeElementDefault
-            val encodeDefaults = property.descriptor.getEncodeDefaultAnnotationValue()
-            val field = property.irField // Nullable when property from another module; can't compare it with default value on JS or Native
-            if (!property.optional || encodeDefaults == true || field == null) {
-                // emit call right away
-                +elementCall
-            } else {
-                val partB = irNotEquals(property.irGet(), initializerAdapter(field.initializer!!))
-
-                val condition = if (encodeDefaults == false) {
-                    // drop default without call to .shouldEncodeElementDefault
-                    partB
-                } else {
-                    // emit check:
-                    // 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.functionByName(CallingConventions.shouldEncodeDefault)
-                    val partA = irInvoke(irGet(localOutput), shouldEncodeFunc, irGet(localSerialDesc), irInt(index))
-                    // Ir infrastructure does not have dedicated symbol for ||, so
-                    //  `a || b == if (a) true else b`, see org.jetbrains.kotlin.ir.builders.PrimitivesKt.oror
-                    irIfThenElse(compilerContext.irBuiltIns.booleanType, partA, irTrue(), partB)
-                }
-                +irIfThen(condition, elementCall)
-            }
-        }
-    }
-
-    /**
-     * True — ALWAYS
-     * False — NEVER
-     * null — not specified
-     */
-    fun IrProperty.getEncodeDefaultAnnotationValue(): Boolean? {
-        val call = annotations.findAnnotation(SerializationAnnotations.encodeDefaultFqName) ?: return null
-        val arg = call.getValueArgument(0) ?: return true // ALWAYS by default
-        val argValue = (arg as? IrGetEnumValue
-            ?: error("Argument of enum constructor expected to implement IrGetEnumValue, got $arg")).symbol.owner.name.toString()
-        return when (argValue) {
-            "ALWAYS" -> true
-            "NEVER" -> false
-            else -> error("Unknown EncodeDefaultMode enum value: $argValue")
-        }
-    }
-
-
-    fun IrBlockBodyBuilder.formEncodeDecodePropertyCall(
-        enclosingGenerator: AbstractIrGenerator,
-        encoder: IrExpression,
-        property: IrSerializableProperty,
-        whenHaveSerializer: (serializer: IrExpression, sti: IrSerialTypeInfo) -> FunctionWithArgs,
-        whenDoNot: (sti: IrSerialTypeInfo) -> FunctionWithArgs,
-        genericGetter: ((Int, IrType) -> IrExpression)? = null,
-        returnTypeHint: IrType? = null
-    ): IrExpression {
-        val sti = enclosingGenerator.getIrSerialTypeInfo(property, compilerContext)
-        val innerSerial = serializerInstance(
-            enclosingGenerator,
-            sti.serializer,
-            compilerContext,
-            property.type,
-            property.genericIndex,
-            genericGetter
-        )
-        val (functionToCall, args: List<IrExpression>) = if (innerSerial != null) whenHaveSerializer(innerSerial, sti) else whenDoNot(sti)
-        val typeArgs = if (functionToCall.descriptor.typeParameters.isNotEmpty()) listOf(property.type) else listOf()
-        return irInvoke(encoder, functionToCall, typeArguments = typeArgs, valueArguments = args, returnTypeHint = returnTypeHint)
-    }
-
-}
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt
new file mode 100644
index 0000000..e39488b
--- /dev/null
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt
@@ -0,0 +1,582 @@
+/*
+ * 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.
+ */
+
+package org.jetbrains.kotlinx.serialization.compiler.backend.ir
+
+import org.jetbrains.kotlin.backend.common.extensions.FirIncompatiblePluginAPI
+import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.builtins.StandardNames
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.ir.builders.*
+import org.jetbrains.kotlin.ir.builders.declarations.buildFun
+import org.jetbrains.kotlin.ir.declarations.*
+import org.jetbrains.kotlin.ir.deepCopyWithVariables
+import org.jetbrains.kotlin.ir.expressions.*
+import org.jetbrains.kotlin.ir.expressions.impl.*
+import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
+import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
+import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
+import org.jetbrains.kotlin.ir.symbols.impl.*
+import org.jetbrains.kotlin.ir.types.*
+import org.jetbrains.kotlin.ir.util.*
+import org.jetbrains.kotlin.name.CallableId
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.platform.jvm.isJvm
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
+import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
+import org.jetbrains.kotlinx.serialization.compiler.resolve.KSerializerDescriptorResolver
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationDependencies.LAZY_FQ
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationDependencies.LAZY_MODE_FQ
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationDependencies.LAZY_PUBLICATION_MODE_NAME
+import org.jetbrains.kotlinx.serialization.compiler.resolve.hasSerializableOrMetaAnnotation
+import org.jetbrains.kotlinx.serialization.compiler.resolve.isInheritableSerialInfoAnnotation
+import org.jetbrains.kotlinx.serialization.compiler.resolve.isSerialInfoAnnotation
+
+
+interface IrBuilderWithPluginContext {
+    val compilerContext: SerializationPluginContext
+
+    fun <F: IrFunction> addFunctionBody(function: F, bodyGen: IrBlockBodyBuilder.(F) -> Unit) {
+        val parentClass = function.parent
+        val startOffset = function.startOffset.takeIf { it >= 0 } ?: parentClass.startOffset
+        val endOffset = function.endOffset.takeIf { it >= 0 } ?: parentClass.endOffset
+        function.body = DeclarationIrBuilder(compilerContext, function.symbol, startOffset, endOffset).irBlockBody(
+            startOffset,
+            endOffset
+        ) { bodyGen(function) }
+    }
+
+    fun IrClass.createLambdaExpression(
+        type: IrType,
+        bodyGen: IrBlockBodyBuilder.() -> Unit
+    ): IrFunctionExpression {
+        val function = compilerContext.irFactory.buildFun {
+            this.startOffset = this@createLambdaExpression.startOffset
+            this.endOffset = this@createLambdaExpression.endOffset
+            this.returnType = type
+            name = Name.identifier("<anonymous>")
+            visibility = DescriptorVisibilities.LOCAL
+            origin = SERIALIZABLE_PLUGIN_ORIGIN
+        }
+        function.body =
+            DeclarationIrBuilder(compilerContext, function.symbol, startOffset, endOffset).irBlockBody(startOffset, endOffset, bodyGen)
+        function.parent = this
+
+        val f0Type = compilerContext.irBuiltIns.functionN(0)
+        val f0ParamSymbol = f0Type.typeParameters[0].symbol
+        val f0IrType = f0Type.defaultType.substitute(mapOf(f0ParamSymbol to type))
+
+        return IrFunctionExpressionImpl(
+            startOffset,
+            endOffset,
+            f0IrType,
+            function,
+            IrStatementOrigin.LAMBDA
+        )
+    }
+
+    fun createLazyProperty(
+        containingClass: IrClass,
+        targetIrType: IrType,
+        name: Name,
+        initializerBuilder: IrBlockBodyBuilder.() -> Unit
+    ): IrProperty {
+        val lazySafeModeClassDescriptor = compilerContext.referenceClass(ClassId.topLevel(LAZY_MODE_FQ))!!.owner
+        val lazyFunctionSymbol = compilerContext.referenceFunctions(CallableId(StandardNames.BUILT_INS_PACKAGE_FQ_NAME, Name.identifier("lazy"))).single {
+            it.owner.valueParameters.size == 2 && it.owner.valueParameters[0].type == lazySafeModeClassDescriptor.defaultType
+        }
+        val publicationEntryDescriptor = lazySafeModeClassDescriptor.enumEntries().single { it.name == LAZY_PUBLICATION_MODE_NAME }
+
+        val lazyIrClass = compilerContext.referenceClass(ClassId.topLevel(LAZY_FQ))!!.owner
+        val lazyIrType = lazyIrClass.defaultType.substitute(mapOf(lazyIrClass.typeParameters[0].symbol to targetIrType))
+
+        val propertyDescriptor =
+            KSerializerDescriptorResolver.createValPropertyDescriptor(
+                Name.identifier(name.asString() + "\$delegate"),
+                containingClass.descriptor,
+                lazyIrType.toKotlinType(),
+                createGetter = true
+            )
+
+        return generateSimplePropertyWithBackingField(propertyDescriptor, containingClass).apply {
+            val builder = DeclarationIrBuilder(compilerContext, containingClass.symbol, startOffset, endOffset)
+            val initializerBody = builder.run {
+                val enumElement = IrGetEnumValueImpl(
+                    startOffset,
+                    endOffset,
+                    lazySafeModeClassDescriptor.defaultType,
+                    publicationEntryDescriptor.symbol
+                )
+
+                val lambdaExpression = containingClass.createLambdaExpression(targetIrType, initializerBuilder)
+
+                irExprBody(
+                    irInvoke(null, lazyFunctionSymbol, listOf(targetIrType), listOf(enumElement, lambdaExpression), lazyIrType)
+                )
+            }
+            backingField!!.initializer = initializerBody
+        }
+    }
+
+    fun createCompanionValProperty(
+        companionClass: IrClass,
+        type: IrType,
+        name: Name,
+        initializerBuilder: IrBlockBodyBuilder.() -> Unit
+    ): IrProperty {
+        val targetKotlinType = type.toKotlinType()
+        val propertyDescriptor =
+            KSerializerDescriptorResolver.createValPropertyDescriptor(name, companionClass.descriptor, targetKotlinType)
+
+        return generateSimplePropertyWithBackingField(propertyDescriptor, companionClass, name).apply {
+            companionClass.contributeAnonymousInitializer {
+                val irBlockBody = irBlockBody(startOffset, endOffset, initializerBuilder)
+                irBlockBody.statements.dropLast(1).forEach { +it }
+                val expression = irBlockBody.statements.last() as? IrExpression
+                    ?: throw AssertionError("Last statement in property initializer builder is not an a expression")
+                +irSetField(irGetObject(companionClass), backingField!!, expression)
+            }
+        }
+    }
+
+    fun IrClass.contributeAnonymousInitializer(bodyGen: IrBlockBodyBuilder.() -> Unit) {
+        val symbol = IrAnonymousInitializerSymbolImpl(descriptor)
+        factory.createAnonymousInitializer(startOffset, endOffset, SERIALIZABLE_PLUGIN_ORIGIN, symbol).also {
+            it.parent = this
+            declarations.add(it)
+            it.body = DeclarationIrBuilder(compilerContext, symbol, startOffset, endOffset).irBlockBody(startOffset, endOffset, bodyGen)
+        }
+    }
+
+    fun IrBlockBodyBuilder.getLazyValueExpression(thisParam: IrValueParameter, property: IrProperty, type: IrType): IrExpression {
+        val lazyIrClass = compilerContext.referenceClass(ClassId.topLevel(LAZY_FQ))!!.owner
+        val valueGetter = lazyIrClass.getPropertyGetter("value")!!
+
+        val propertyGetter = property.getter!!
+
+        return irInvoke(
+            irGet(propertyGetter.returnType, irGet(thisParam), propertyGetter.symbol),
+            valueGetter,
+            typeHint = type
+        )
+    }
+
+    fun IrBuilderWithScope.irInvoke(
+        dispatchReceiver: IrExpression? = null,
+        callee: IrFunctionSymbol,
+        vararg args: IrExpression,
+        typeHint: IrType? = null
+    ): IrMemberAccessExpression<*> {
+        assert(callee.isBound) { "Symbol $callee expected to be bound" }
+        val returnType = typeHint ?: callee.owner.returnType
+        val call = irCall(callee, type = returnType)
+        call.dispatchReceiver = dispatchReceiver
+        args.forEachIndexed(call::putValueArgument)
+        return call
+    }
+
+    fun IrBuilderWithScope.irInvoke(
+        dispatchReceiver: IrExpression? = null,
+        callee: IrFunctionSymbol,
+        typeArguments: List<IrType?>,
+        valueArguments: List<IrExpression>,
+        returnTypeHint: IrType? = null
+    ): IrMemberAccessExpression<*> =
+        irInvoke(
+            dispatchReceiver,
+            callee,
+            *valueArguments.toTypedArray(),
+            typeHint = returnTypeHint
+        ).also { call -> typeArguments.forEachIndexed(call::putTypeArgument) }
+
+    fun IrBuilderWithScope.createArrayOfExpression(
+        arrayElementType: IrType,
+        arrayElements: List<IrExpression>
+    ): IrExpression {
+
+        val arrayType = compilerContext.irBuiltIns.arrayClass.typeWith(arrayElementType)
+        val arg0 = IrVarargImpl(startOffset, endOffset, arrayType, arrayElementType, arrayElements)
+        val typeArguments = listOf(arrayElementType)
+
+        return irCall(compilerContext.irBuiltIns.arrayOf, arrayType, typeArguments = typeArguments).apply {
+            putValueArgument(0, arg0)
+        }
+    }
+
+    fun IrBuilderWithScope.createPrimitiveArrayOfExpression(
+        elementPrimitiveType: IrType,
+        arrayElements: List<IrExpression>
+    ): IrExpression {
+        val arrayType = compilerContext.irBuiltIns.primitiveArrayForType.getValue(elementPrimitiveType).defaultType
+        val arg0 = IrVarargImpl(startOffset, endOffset, arrayType, elementPrimitiveType, arrayElements)
+        val typeArguments = listOf(elementPrimitiveType)
+
+        return irCall(compilerContext.irBuiltIns.arrayOf, arrayType, typeArguments = typeArguments).apply {
+            putValueArgument(0, arg0)
+        }
+    }
+
+    fun IrBuilderWithScope.irBinOp(name: Name, lhs: IrExpression, rhs: IrExpression): IrExpression {
+        val classFqName = (lhs.type as IrSimpleType).classOrNull!!.owner.fqNameWhenAvailable!!
+        val symbol = compilerContext.referenceFunctions(CallableId(ClassId.topLevel(classFqName), name)).single()
+        return irInvoke(lhs, symbol, rhs)
+    }
+
+    fun IrBuilderWithScope.irGetObject(irObject: IrClass) =
+        IrGetObjectValueImpl(
+            startOffset,
+            endOffset,
+            irObject.defaultType,
+            irObject.symbol
+        )
+
+    fun <T : IrDeclaration> T.buildWithScope(builder: (T) -> Unit): T =
+        also { irDeclaration ->
+            compilerContext.symbolTable.withReferenceScope(irDeclaration) {
+                builder(irDeclaration)
+            }
+        }
+
+    class BranchBuilder(
+        val irWhen: IrWhen,
+        context: IrGeneratorContext,
+        scope: Scope,
+        startOffset: Int,
+        endOffset: Int
+    ) : IrBuilderWithScope(context, scope, startOffset, endOffset) {
+        operator fun IrBranch.unaryPlus() {
+            irWhen.branches.add(this)
+        }
+    }
+
+    fun IrBuilderWithScope.irWhen(typeHint: IrType? = null, block: BranchBuilder.() -> Unit): IrWhen {
+        val whenExpr = IrWhenImpl(startOffset, endOffset, typeHint ?: compilerContext.irBuiltIns.unitType)
+        val builder = BranchBuilder(whenExpr, context, scope, startOffset, endOffset)
+        builder.block()
+        return whenExpr
+    }
+
+    fun BranchBuilder.elseBranch(result: IrExpression): IrElseBranch =
+        IrElseBranchImpl(
+            IrConstImpl.boolean(result.startOffset, result.endOffset, compilerContext.irBuiltIns.booleanType, true),
+            result
+        )
+
+    @FirIncompatiblePluginAPI
+    fun KotlinType.toIrType() = compilerContext.typeTranslator.translateType(this)
+
+    fun IrBuilderWithScope.setProperty(receiver: IrExpression, property: IrProperty, value: IrExpression): IrExpression {
+        return if (property.setter != null)
+            irSet(property.setter!!.returnType, receiver, property.setter!!.symbol, value)
+        else
+            irSetField(receiver, property.backingField!!, value)
+    }
+
+    fun IrBuilderWithScope.generateAnySuperConstructorCall(toBuilder: IrBlockBodyBuilder) {
+        val anyConstructor = compilerContext.irBuiltIns.anyClass.owner.declarations.single { it is IrConstructor } as IrConstructor
+        with(toBuilder) {
+            +IrDelegatingConstructorCallImpl.fromSymbolDescriptor(
+                startOffset, endOffset,
+                compilerContext.irBuiltIns.unitType,
+                anyConstructor.symbol
+            )
+        }
+    }
+
+    private inline fun <reified T : IrDeclaration> IrClass.searchForDeclaration(descriptor: DeclarationDescriptor): T? {
+        return declarations.singleOrNull { it.descriptor == descriptor } as? T
+    }
+
+    fun generateSimplePropertyWithBackingField(
+        propertyDescriptor: PropertyDescriptor,
+        propertyParent: IrClass,
+        fieldName: Name = propertyDescriptor.name,
+    ): IrProperty {
+        val irProperty = propertyParent.searchForDeclaration(propertyDescriptor) ?: run {
+            with(propertyDescriptor) {
+                propertyParent.factory.createProperty(
+                    propertyParent.startOffset, propertyParent.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrPropertySymbolImpl(propertyDescriptor),
+                    name, visibility, modality, isVar, isConst, isLateInit, isDelegated, isExternal
+                ).also {
+                    it.parent = propertyParent
+                    propertyParent.addMember(it)
+                }
+            }
+        }
+
+        propertyParent.generatePropertyBackingFieldIfNeeded(propertyDescriptor, irProperty, fieldName)
+        val fieldSymbol = irProperty.backingField!!.symbol
+        irProperty.getter = propertyDescriptor.getter?.let {
+            propertyParent.generatePropertyAccessor(propertyDescriptor, irProperty, it, fieldSymbol, isGetter = true)
+        }?.apply { parent = propertyParent }
+        irProperty.setter = propertyDescriptor.setter?.let {
+            propertyParent.generatePropertyAccessor(propertyDescriptor, irProperty, it, fieldSymbol, isGetter = false)
+        }?.apply { parent = propertyParent }
+        return irProperty
+    }
+
+    fun IrType.kClassToJClassIfNeeded(): IrType = this
+
+    fun kClassExprToJClassIfNeeded(startOffset: Int, endOffset: Int, irExpression: IrExpression): IrExpression = irExpression
+
+    private fun IrClass.generatePropertyBackingFieldIfNeeded(
+        propertyDescriptor: PropertyDescriptor,
+        originProperty: IrProperty,
+        name: Name,
+    ) {
+        if (originProperty.backingField != null) return
+
+        val field = with(propertyDescriptor) {
+            // TODO: type parameters
+            @OptIn(FirIncompatiblePluginAPI::class)// should be called only with old FE
+            originProperty.factory.createField(
+                originProperty.startOffset, originProperty.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrFieldSymbolImpl(propertyDescriptor), name, type.toIrType(),
+                visibility, !isVar, isEffectivelyExternal(), dispatchReceiverParameter == null
+            )
+        }
+        field.apply {
+            parent = this@generatePropertyBackingFieldIfNeeded
+            correspondingPropertySymbol = originProperty.symbol
+        }
+
+        originProperty.backingField = field
+    }
+
+    private fun IrClass.generatePropertyAccessor(
+        propertyDescriptor: PropertyDescriptor,
+        property: IrProperty,
+        descriptor: PropertyAccessorDescriptor,
+        fieldSymbol: IrFieldSymbol,
+        isGetter: Boolean,
+    ): IrSimpleFunction {
+        val irAccessor: IrSimpleFunction = when (isGetter) {
+            true -> searchForDeclaration<IrProperty>(propertyDescriptor)?.getter
+            false -> searchForDeclaration<IrProperty>(propertyDescriptor)?.setter
+        } ?: run {
+            with(descriptor) {
+                @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
+                property.factory.createFunction(
+                    fieldSymbol.owner.startOffset, fieldSymbol.owner.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrSimpleFunctionSymbolImpl(descriptor),
+                    name, visibility, modality, returnType!!.toIrType(),
+                    isInline, isEffectivelyExternal(), isTailrec, isSuspend, isOperator, isInfix, isExpect
+                )
+            }.also { f ->
+                generateOverriddenFunctionSymbols(f, compilerContext.symbolTable)
+                f.createParameterDeclarations(descriptor)
+                @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
+                f.returnType = descriptor.returnType!!.toIrType()
+                f.correspondingPropertySymbol = fieldSymbol.owner.correspondingPropertySymbol
+            }
+        }
+
+        irAccessor.body = when (isGetter) {
+            true -> generateDefaultGetterBody(descriptor as PropertyGetterDescriptor, irAccessor)
+            false -> generateDefaultSetterBody(descriptor as PropertySetterDescriptor, irAccessor)
+        }
+
+        return irAccessor
+    }
+
+    private fun generateDefaultGetterBody(
+        getter: PropertyGetterDescriptor,
+        irAccessor: IrSimpleFunction
+    ): IrBlockBody {
+        val property = getter.correspondingProperty
+        val irProperty = irAccessor.correspondingPropertySymbol?.owner ?: error("Expected property for $getter")
+
+        val startOffset = irAccessor.startOffset
+        val endOffset = irAccessor.endOffset
+        val irBody = irAccessor.factory.createBlockBody(startOffset, endOffset)
+
+        val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol, property)
+
+        val propertyIrType = irAccessor.returnType
+        irBody.statements.add(
+            IrReturnImpl(
+                startOffset, endOffset, compilerContext.irBuiltIns.nothingType,
+                irAccessor.symbol,
+                IrGetFieldImpl(
+                    startOffset, endOffset,
+                    irProperty.backingField?.symbol ?: error("Property expected to have backing field"),
+                    propertyIrType,
+                    receiver
+                ).let {
+                    if (propertyIrType.isKClass()) {
+                        irAccessor.returnType = irAccessor.returnType.kClassToJClassIfNeeded()
+                        kClassExprToJClassIfNeeded(startOffset, endOffset, it)
+                    } else it
+                }
+            )
+        )
+        return irBody
+    }
+
+    private fun generateDefaultSetterBody(
+        setter: PropertySetterDescriptor,
+        irAccessor: IrSimpleFunction
+    ): IrBlockBody {
+        val property = setter.correspondingProperty
+        val irProperty = irAccessor.correspondingPropertySymbol?.owner ?: error("Expected corresponding property for accessor $setter")
+        val startOffset = irAccessor.startOffset
+        val endOffset = irAccessor.endOffset
+        val irBody = irAccessor.factory.createBlockBody(startOffset, endOffset)
+
+        val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol, property)
+
+        val irValueParameter = irAccessor.valueParameters.single()
+        irBody.statements.add(
+            IrSetFieldImpl(
+                startOffset, endOffset,
+                irProperty.backingField?.symbol ?: error("Property $property expected to have backing field"),
+                receiver,
+                IrGetValueImpl(startOffset, endOffset, irValueParameter.type, irValueParameter.symbol),
+                compilerContext.irBuiltIns.unitType
+            )
+        )
+        return irBody
+    }
+
+    fun generateReceiverExpressionForFieldAccess( // todo: remove this
+        ownerSymbol: IrValueSymbol,
+        property: PropertyDescriptor
+    ): IrExpression {
+        val containingDeclaration = property.containingDeclaration
+        return when (containingDeclaration) {
+            is ClassDescriptor ->
+                IrGetValueImpl(
+                    ownerSymbol.owner.startOffset, ownerSymbol.owner.endOffset,
+                    ownerSymbol
+                )
+            else -> throw AssertionError("Property must be in class")
+        }
+    }
+
+    fun IrFunction.createParameterDeclarations(
+        descriptor: FunctionDescriptor,
+        overwriteValueParameters: Boolean = false,
+        copyTypeParameters: Boolean = true
+    ) {
+        val function = this
+        fun irValueParameter(descriptor: ParameterDescriptor): IrValueParameter = with(descriptor) {
+            @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
+            factory.createValueParameter(
+                function.startOffset, function.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrValueParameterSymbolImpl(this),
+                name, indexOrMinusOne, type.toIrType(), varargElementType?.toIrType(), isCrossinline, isNoinline,
+                isHidden = false, isAssignable = false
+            ).also {
+                it.parent = function
+            }
+        }
+
+        if (copyTypeParameters) {
+            assert(typeParameters.isEmpty())
+            copyTypeParamsFromDescriptor(descriptor)
+        }
+
+        dispatchReceiverParameter = descriptor.dispatchReceiverParameter?.let { irValueParameter(it) }
+        extensionReceiverParameter = descriptor.extensionReceiverParameter?.let { irValueParameter(it) }
+
+        if (!overwriteValueParameters)
+            assert(valueParameters.isEmpty())
+
+        valueParameters = descriptor.valueParameters.map { irValueParameter(it) }
+    }
+
+    fun IrFunction.copyTypeParamsFromDescriptor(descriptor: FunctionDescriptor) {
+        val newTypeParameters = descriptor.typeParameters.map {
+            factory.createTypeParameter(
+                startOffset, endOffset,
+                SERIALIZABLE_PLUGIN_ORIGIN,
+                IrTypeParameterSymbolImpl(it),
+                it.name, it.index, it.isReified, it.variance
+            ).also { typeParameter ->
+                typeParameter.parent = this
+            }
+        }
+        @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
+        newTypeParameters.forEach { typeParameter ->
+            typeParameter.superTypes = typeParameter.descriptor.upperBounds.map { it.toIrType() }
+        }
+
+        typeParameters = newTypeParameters
+    }
+
+    fun createClassReference(classType: IrType, startOffset: Int, endOffset: Int): IrClassReference {
+        return IrClassReferenceImpl(
+            startOffset,
+            endOffset,
+            compilerContext.irBuiltIns.kClassClass.starProjectedType,
+            classType.classifierOrFail,
+            classType
+        )
+    }
+
+    fun IrBuilderWithScope.classReference(classSymbol: IrType): IrClassReference =
+        createClassReference(classSymbol, startOffset, endOffset)
+
+    fun collectSerialInfoAnnotations(irClass: IrClass): List<IrConstructorCall> {
+        if (!(irClass.isInterface || irClass.descriptor.hasSerializableOrMetaAnnotation)) return emptyList()
+        val annotationByFq: MutableMap<FqName, IrConstructorCall> =
+            irClass.annotations.associateBy { it.symbol.owner.parentAsClass.descriptor.fqNameSafe }.toMutableMap()
+        for (clazz in irClass.getAllSuperclasses()) {
+            val annotations = clazz.annotations
+                .mapNotNull {
+                    val descriptor = it.symbol.owner.parentAsClass.descriptor
+                    if (descriptor.isInheritableSerialInfoAnnotation) descriptor.fqNameSafe to it else null
+                }
+            annotations.forEach { (fqname, call) ->
+                if (fqname !in annotationByFq) {
+                    annotationByFq[fqname] = call
+                } else {
+                    // SerializationPluginDeclarationChecker already reported inconsistency
+                }
+            }
+        }
+        return annotationByFq.values.toList()
+    }
+
+    fun IrBuilderWithScope.copyAnnotationsFrom(annotations: List<IrConstructorCall>): List<IrExpression> =
+        annotations.mapNotNull { annotationCall ->
+            val annotationClass = annotationCall.symbol.owner.parentAsClass
+            if (!annotationClass.descriptor.isSerialInfoAnnotation) return@mapNotNull null
+
+            if (compilerContext.platform.isJvm()) {
+                val implClass = compilerContext.serialInfoImplJvmIrGenerator.getImplClass(annotationClass)
+                val ctor = implClass.constructors.singleOrNull { it.valueParameters.size == annotationCall.valueArgumentsCount }
+                    ?: error("No constructor args found for SerialInfo annotation Impl class: ${implClass.render()}")
+                irCall(ctor).apply {
+                    for (i in 0 until annotationCall.valueArgumentsCount) {
+                        val argument = annotationCall.getValueArgument(i)
+                            ?: annotationClass.primaryConstructor!!.valueParameters[i].defaultValue?.expression
+                        putValueArgument(i, argument!!.deepCopyWithVariables())
+                    }
+                }
+            } else {
+                annotationCall.deepCopyWithVariables()
+            }
+        }
+
+    fun IrBuilderWithScope.wrapperClassReference(classType: IrType): IrClassReference {
+        if (compilerContext.platform.isJvm()) {
+            // "Byte::class" -> "java.lang.Byte::class"
+//          TODO: get rid of descriptor
+            val wrapperFqName = KotlinBuiltIns.getPrimitiveType(classType.classOrNull!!.descriptor)?.let(JvmPrimitiveType::get)?.wrapperFqName
+            if (wrapperFqName != null) {
+                val wrapperClass = compilerContext.referenceClass(ClassId.topLevel(wrapperFqName))
+                    ?: error("Primitive wrapper class for $classType not found: $wrapperFqName")
+                return classReference(wrapperClass.defaultType)
+            }
+        }
+        return classReference(classType)
+    }
+
+    fun IrClass.getSuperClassOrAny(): IrClass = getSuperClassNotAny() ?: compilerContext.irBuiltIns.anyClass.owner
+}
\ No newline at end of file
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt
new file mode 100644
index 0000000..de87f71
--- /dev/null
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2010-2022 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.backend.jvm.ir.getStringConstArgument
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.ClassKind
+import org.jetbrains.kotlin.descriptors.Modality
+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.IrGetEnumValue
+import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
+import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
+import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
+import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
+import org.jetbrains.kotlin.ir.types.*
+import org.jetbrains.kotlin.ir.util.*
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlinx.serialization.compiler.fir.SerializationPluginKey
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
+import org.jetbrains.kotlinx.serialization.compiler.resolve.hasSerializableOrMetaAnnotation
+
+internal fun IrType.isKSerializer(): Boolean {
+    val simpleType = this as? IrSimpleType ?: return false
+    val classifier = simpleType.classifier as? IrClassSymbol ?: return false
+    val fqName = classifier.owner.fqNameWhenAvailable
+    return fqName == SerialEntityNames.KSERIALIZER_NAME_FQ || fqName == SerialEntityNames.GENERATED_SERIALIZER_FQ
+}
+
+internal val IrClass.isInternalSerializable: Boolean
+    get() {
+        if (kind != ClassKind.CLASS) return false
+        return hasSerializableOrMetaAnnotationWithoutArgs()
+    }
+
+internal val IrClass.isAbstractOrSealedSerializableClass: Boolean get() = isInternalSerializable && (modality == Modality.ABSTRACT || modality == Modality.SEALED)
+
+internal val IrClass.isStaticSerializable: Boolean get() = this.typeParameters.isEmpty()
+
+
+internal val IrClass.hasCompanionObjectAsSerializer: Boolean
+    get() = isInternallySerializableObject || companionObject()?.serializerForClass == this.symbol
+
+internal val IrClass.isInternallySerializableObject: Boolean
+    get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotationWithoutArgs()
+
+
+internal fun IrClass.findPluginGeneratedMethod(name: String): IrSimpleFunction? {
+    return this.functions.find {
+        it.name.asString() == name && it.isFromPlugin()
+    }
+}
+
+internal fun IrClass.isEnumWithLegacyGeneratedSerializer(): Boolean = isInternallySerializableEnum() && useGeneratedEnumSerializer
+
+internal val IrClass.useGeneratedEnumSerializer: Boolean
+    get() = true // FIXME This would break if we try to use this new IR compiler with pre-1.4.1 serialization versions. I think we just need to raise min runtime version.
+
+internal val IrClass.isSealedSerializableInterface: Boolean
+    get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation()
+
+internal fun IrClass.isInternallySerializableEnum(): Boolean =
+    kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotationWithoutArgs()
+
+fun IrType.isGeneratedSerializableObject(): Boolean {
+    return classOrNull?.run { owner.kind == ClassKind.OBJECT && owner.hasSerializableOrMetaAnnotationWithoutArgs() } == true
+}
+
+internal val IrClass.isSerializableObject: Boolean
+    get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotation()
+
+// todo: optimize & unify with hasSerializableOrMeta
+internal fun IrClass.hasSerializableOrMetaAnnotationWithoutArgs(): Boolean {
+    val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName)
+    if (annot != null) {
+        for (i in 0 until annot.valueArgumentsCount) {
+            if (annot.getValueArgument(i) != null) return false
+        }
+        return true
+    }
+    val metaAnnotation = annotations
+        .flatMap { it.symbol.owner.constructedClass.annotations }
+        .find { it.isAnnotation(SerializationAnnotations.metaSerializableAnnotationFqName) }
+    return metaAnnotation != null
+}
+
+internal val IrClass.isSerialInfoAnnotation: Boolean
+    get() = annotations.hasAnnotation(SerializationAnnotations.serialInfoFqName)
+            || annotations.hasAnnotation(SerializationAnnotations.inheritableSerialInfoFqName)
+            || annotations.hasAnnotation(SerializationAnnotations.metaSerializableAnnotationFqName)
+
+internal val IrClass.shouldHaveGeneratedSerializer: Boolean
+    get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN))
+            || isEnumWithLegacyGeneratedSerializer()
+
+internal val IrClass.shouldHaveGeneratedMethodsInCompanion: Boolean
+    get() = this.isSerializableObject || this.isSerializableEnum() || (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation()) || this.isSealedSerializableInterface
+
+internal fun IrClass.isSerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotation()
+
+fun IrClass.hasSerializableOrMetaAnnotation() = descriptor.hasSerializableOrMetaAnnotation // TODO
+
+internal val IrType.genericIndex: Int?
+    get() = (this.classifierOrNull as? IrTypeParameterSymbol)?.owner?.index
+
+fun IrType.serialName(): String = this.classOrNull!!.owner.serialName()
+
+fun IrClass.serialName(): String {
+    return annotations.serialNameValue ?: fqNameWhenAvailable?.asString() ?: error("${this.render()} does not have fqName")
+}
+
+fun IrClass.findEnumValuesMethod() = this.functions.singleOrNull { f ->
+    f.name == Name.identifier("values") && f.valueParameters.isEmpty() && f.extensionReceiverParameter == null
+} ?: throw AssertionError("Enum class does not have single .values() function")
+
+internal fun IrClass.enumEntries(): List<IrEnumEntry> {
+    check(this.kind == ClassKind.ENUM_CLASS)
+    return declarations.filterIsInstance<IrEnumEntry>().toList()
+}
+
+internal fun IrClass.isEnumWithSerialInfoAnnotation(): Boolean {
+    if (kind != ClassKind.ENUM_CLASS) return false
+    if (annotations.hasAnySerialAnnotation) return true
+    return enumEntries().any { (it.annotations.hasAnySerialAnnotation) }
+}
+
+fun IrClass.findWriteSelfMethod(): IrSimpleFunction? =
+    functions.singleOrNull { it.name == SerialEntityNames.WRITE_SELF_NAME && !it.isFakeOverride }
+
+fun IrClass.getSuperClassNotAny(): IrClass? {
+    val parentClass =
+        superTypes
+            .mapNotNull { it.classOrNull?.owner }
+            .singleOrNull { it.kind == ClassKind.CLASS || it.kind == ClassKind.ENUM_CLASS } ?: return null
+    return if (parentClass.defaultType.isAny()) null else parentClass
+}
+
+internal fun IrDeclaration.isFromPlugin(): Boolean =
+    this.origin == IrDeclarationOrigin.GeneratedByPlugin(SerializationPluginKey) || (this.descriptor as? CallableMemberDescriptor)?.kind == CallableMemberDescriptor.Kind.SYNTHESIZED // old FE doesn't specify origin
+
+internal fun IrConstructor.isSerializationCtor(): Boolean {
+    /*kind == CallableMemberDescriptor.Kind.SYNTHESIZED does not work because DeserializedClassConstructorDescriptor loses its kind*/
+    return valueParameters.lastOrNull()?.run {
+        name == SerialEntityNames.dummyParamName && type.classFqName == SerializationPackages.internalPackageFqName.child(
+            SerialEntityNames.SERIAL_CTOR_MARKER_NAME
+        )
+    } == true
+}
+
+
+internal fun IrConstructor.lastArgumentIsAnnotationArray(): Boolean {
+    val lastArgType = valueParameters.lastOrNull()?.type
+    if (lastArgType == null || !lastArgType.isArray()) return false
+    return ((lastArgType as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull?.classFqName?.toString() == "kotlin.Annotation")
+}
+
+fun IrClass.serializableSyntheticConstructor(): IrConstructorSymbol? { // todo: remove nullability W/A when FIR plugin will add proper ctor
+    return declarations.filterIsInstance<IrConstructor>().singleOrNull { it.isSerializationCtor() }?.symbol
+}
+
+internal fun IrExpression.isInitializePropertyFromParameter(): Boolean =
+    this is IrGetValueImpl && this.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER
+
+internal val IrConstructorCall.annotationClass
+    get() = this.symbol.owner.constructedClass
+
+internal val List<IrConstructorCall>.hasAnySerialAnnotation: Boolean
+    get() = serialNameValue != null || any { it.annotationClass.isSerialInfoAnnotation == true }
+
+internal val List<IrConstructorCall>.serialNameValue: String?
+    get() = findAnnotation(SerializationAnnotations.serialNameAnnotationFqName)?.getStringConstArgument(0) // @SerialName("foo")
+
+/**
+ * True — ALWAYS
+ * False — NEVER
+ * null — not specified
+ */
+fun IrProperty.getEncodeDefaultAnnotationValue(): Boolean? {
+    val call = annotations.findAnnotation(SerializationAnnotations.encodeDefaultFqName) ?: return null
+    val arg = call.getValueArgument(0) ?: return true // ALWAYS by default
+    val argValue = (arg as? IrGetEnumValue
+        ?: error("Argument of enum constructor expected to implement IrGetEnumValue, got $arg")).symbol.owner.name.toString()
+    return when (argValue) {
+        "ALWAYS" -> true
+        "NEVER" -> false
+        else -> error("Unknown EncodeDefaultMode enum value: $argValue")
+    }
+}
+
+fun findSerializerConstructorForTypeArgumentsSerializers(serializer: IrClass): IrConstructorSymbol? {
+    val typeParamsCount = ((serializer.superTypes.find { it.isKSerializer() } as IrSimpleType).arguments.first().typeOrNull!! as IrSimpleType).arguments.size
+    if (typeParamsCount == 0) return null //don't need it
+
+    return serializer.constructors.singleOrNull {
+        it.valueParameters.let { vps -> vps.size == typeParamsCount && vps.all { vp -> vp.type.isKSerializer() } }
+    }?.symbol
+}
\ No newline at end of file
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrSerializableProperties.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrSerializableProperties.kt
new file mode 100644
index 0000000..1aff0d2
--- /dev/null
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrSerializableProperties.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010-2022 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.declarations.IrClass
+import org.jetbrains.kotlin.ir.declarations.IrProperty
+import org.jetbrains.kotlin.ir.types.IrSimpleType
+import org.jetbrains.kotlin.ir.util.hasAnnotation
+import org.jetbrains.kotlin.ir.util.hasDefaultValue
+import org.jetbrains.kotlin.ir.util.primaryConstructor
+import org.jetbrains.kotlin.ir.util.properties
+import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
+import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationDescriptorSerializerPlugin
+import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
+import org.jetbrains.kotlinx.serialization.compiler.resolve.ISerializableProperties
+import org.jetbrains.kotlinx.serialization.compiler.resolve.ISerializableProperty
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
+import org.jetbrains.kotlinx.serialization.compiler.resolve.declaresDefaultValue
+
+class IrSerializableProperty(
+    val ir: IrProperty,
+    override val isConstructorParameterWithDefault: Boolean,
+    hasBackingField: Boolean,
+    declaresDefaultValue: Boolean
+) : ISerializableProperty<IrSimpleType> {
+    override val name = ir.annotations.serialNameValue ?: ir.name.asString()
+    override val type = ir.getter!!.returnType as IrSimpleType
+    override val genericIndex = type.genericIndex
+    fun serializableWith(ctx: SerializationPluginContext) = ir.annotations.serializableWith() ?: analyzeSpecialSerializers(ctx, ir.annotations)
+    override val optional = !ir.annotations.hasAnnotation(SerializationAnnotations.requiredAnnotationFqName) && declaresDefaultValue
+    override val transient = ir.annotations.hasAnnotation(SerializationAnnotations.serialTransientFqName) || !hasBackingField
+}
+
+class IrSerializableProperties(
+    override val serializableProperties: List<IrSerializableProperty>,
+    override val isExternallySerializable: Boolean,
+    override val serializableConstructorProperties: List<IrSerializableProperty>,
+    override val serializableStandaloneProperties: List<IrSerializableProperty>
+) : ISerializableProperties<IrSimpleType, IrSerializableProperty>
+
+internal fun serializablePropertiesForIrBackend(
+    classDescriptor: IrClass,
+    serializationDescriptorSerializer: SerializationDescriptorSerializerPlugin? = null
+): IrSerializableProperties {
+    val properties = classDescriptor.properties.toList()
+    val primaryConstructorParams = classDescriptor.primaryConstructor?.valueParameters.orEmpty()
+    val primaryParamsAsProps = properties.associateBy { it.name }.let { namesMap ->
+        primaryConstructorParams.mapNotNull {
+            if (it.name !in namesMap) null else namesMap.getValue(it.name) to it.hasDefaultValue()
+        }.toMap()
+    }
+
+    fun isPropSerializable(it: IrProperty) =
+        if (classDescriptor.isInternalSerializable) !it.annotations.hasAnnotation(SerializationAnnotations.serialTransientFqName)
+        else !DescriptorVisibilities.isPrivate(it.visibility) && ((it.isVar && !it.annotations.hasAnnotation(SerializationAnnotations.serialTransientFqName)) || primaryParamsAsProps.contains(
+            it
+        ))
+
+    val (primaryCtorSerializableProps, bodySerializableProps) = properties
+        .asSequence()
+        .filter { !it.isFakeOverride && !it.isDelegated }
+        .filter(::isPropSerializable)
+        .map {
+            val isConstructorParameterWithDefault = primaryParamsAsProps[it] ?: false
+            // FIXME: workaround because IrLazyProperty doesn't deserialize information about backing fields. Fallback to descriptor won't work with FIR.
+            val isPropertyFromAnotherModuleDeclaresDefaultValue = it.descriptor is DeserializedPropertyDescriptor &&  it.descriptor.declaresDefaultValue()
+            val isPropertyWithBackingFieldFromAnotherModule = it.descriptor is DeserializedPropertyDescriptor && (it.descriptor.backingField != null || isPropertyFromAnotherModuleDeclaresDefaultValue)
+            IrSerializableProperty(
+                it,
+                isConstructorParameterWithDefault,
+                it.backingField != null || isPropertyWithBackingFieldFromAnotherModule,
+                it.backingField?.initializer.let { init -> init != null && !init.expression.isInitializePropertyFromParameter() } || isConstructorParameterWithDefault
+                        || isPropertyFromAnotherModuleDeclaresDefaultValue
+            )
+        }
+        .filterNot { it.transient }
+        .partition { primaryParamsAsProps.contains(it.ir) }
+
+    val serializableProps = run {
+        val supers = classDescriptor.getSuperClassNotAny()
+        if (supers == null || !supers.isInternalSerializable)
+            primaryCtorSerializableProps + bodySerializableProps
+        else
+            serializablePropertiesForIrBackend(
+                supers,
+                serializationDescriptorSerializer
+            ).serializableProperties + primaryCtorSerializableProps + bodySerializableProps
+    } // todo: implement unsorting
+
+    val isExternallySerializable =
+        classDescriptor.isInternallySerializableEnum() || primaryConstructorParams.size == primaryParamsAsProps.size
+
+    return IrSerializableProperties(serializableProps, isExternallySerializable, primaryCtorSerializableProps, bodySerializableProps)
+}
\ No newline at end of file
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt
index b5b61c9..ac81e44 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt
@@ -42,7 +42,7 @@
 class SerialInfoImplJvmIrGenerator(
     private val context: SerializationPluginContext,
     private val moduleFragment: IrModuleFragment,
-) : IrBuilderExtension {
+) : IrBuilderWithPluginContext {
     override val compilerContext: SerializationPluginContext
         get() = context
 
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt
index 5b57523..1614cec 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt
@@ -6,7 +6,6 @@
 package org.jetbrains.kotlinx.serialization.compiler.backend.ir
 
 import org.jetbrains.kotlin.descriptors.ClassKind
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
 import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
 import org.jetbrains.kotlin.ir.builders.irGet
 import org.jetbrains.kotlin.ir.builders.irInt
@@ -21,41 +20,37 @@
 import org.jetbrains.kotlin.name.ClassId
 import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.AbstractIrGenerator
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializer
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.getSerializableClassByCompanion
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.isKSerializer
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
-import org.jetbrains.kotlinx.serialization.compiler.resolve.*
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
+import org.jetbrains.kotlinx.serialization.compiler.resolve.needSerializerFactory
 
 class SerializableCompanionIrGenerator(
     val irClass: IrClass,
     val serializableIrClass: IrClass,
-    override val compilerContext: SerializationPluginContext,
-) : AbstractIrGenerator(irClass), IrBuilderExtension {
+    compilerContext: SerializationPluginContext,
+) : BaseIrGenerator(irClass, compilerContext) {
 
-    private val serializableDescriptor = serializableIrClass.descriptor
-
-    private fun getSerializerGetterDescriptor(): FunctionDescriptor {
+    private fun getSerializerGetterFunction(): IrSimpleFunction {
         return irClass.findDeclaration<IrSimpleFunction> {
-            (it.valueParameters.size == serializableDescriptor.declaredTypeParameters.size
-                    && it.valueParameters.all { p -> isKSerializer(p.type) }) && isKSerializer(it.returnType)
-        }?.descriptor ?: throw IllegalStateException(
+            (it.valueParameters.size == serializableIrClass.typeParameters.size
+                    && it.valueParameters.all { p -> p.type.isKSerializer() }) && it.returnType.isKSerializer()
+        } ?: throw IllegalStateException(
             "Can't find synthesized 'Companion.serializer()' function to generate, " +
                     "probably clash with user-defined function has occurred"
         )
     }
 
     fun generate() {
-        val serializerGetterDescriptor = getSerializerGetterDescriptor()
+        val serializerGetterFunction = getSerializerGetterFunction()
 
-        if (serializableDescriptor.isSerializableObject
-            || serializableDescriptor.isAbstractOrSealedSerializableClass()
-            || serializableDescriptor.isSerializableEnum()
+        if (serializableIrClass.isSerializableObject
+            || serializableIrClass.isAbstractOrSealedSerializableClass
+            || serializableIrClass.isSerializableEnum()
         ) {
-            generateLazySerializerGetter(serializerGetterDescriptor)
+            generateLazySerializerGetter(serializerGetterFunction)
         } else {
-            generateSerializerGetter(serializerGetterDescriptor)
+            generateSerializerGetter(serializerGetterFunction)
         }
     }
 
@@ -64,8 +59,8 @@
             irClass: IrClass,
             context: SerializationPluginContext,
         ) {
-            val companionDescriptor = irClass.descriptor
-            val serializableClass = getSerializableClassDescriptorByCompanion(companionDescriptor) ?: return
+            val companionDescriptor = irClass
+            val serializableClass = getSerializableClassByCompanion(companionDescriptor) ?: return
             if (serializableClass.shouldHaveGeneratedMethodsInCompanion) {
                 SerializableCompanionIrGenerator(irClass, getSerializableClassByCompanion(irClass)!!, context).generate()
                 val declaration = irClass.constructors.primary // todo: move to appropriate place
@@ -110,7 +105,7 @@
         irSerializableClass.annotations += annotationCtorCall
     }
 
-    fun generateLazySerializerGetter(methodDescriptor: FunctionDescriptor) {
+    fun generateLazySerializerGetter(methodDescriptor: IrSimpleFunction) {
         val serializer = requireNotNull(
             findTypeSerializer(
                 compilerContext,
@@ -124,21 +119,20 @@
 
         val property = createLazyProperty(irClass, targetIrType, SerialEntityNames.CACHED_SERIALIZER_PROPERTY_NAME) {
             val expr = serializerInstance(
-                this@SerializableCompanionIrGenerator,
                 serializer, compilerContext, serializableIrClass.defaultType
             )
             patchSerializableClassWithMarkerAnnotation(kSerializerIrClass)
             +irReturn(requireNotNull(expr))
         }
 
-        irClass.contributeFunction(methodDescriptor) {
+        addFunctionBody(methodDescriptor) {
             +irReturn(getLazyValueExpression(it.dispatchReceiverParameter!!, property, targetIrType))
         }
         generateSerializerFactoryIfNeeded(methodDescriptor)
     }
 
-    fun generateSerializerGetter(methodDescriptor: FunctionDescriptor) {
-        irClass.contributeFunction(methodDescriptor) { getter ->
+    fun generateSerializerGetter(methodDescriptor: IrSimpleFunction) {
+        addFunctionBody(methodDescriptor) { getter ->
             val serializer = requireNotNull(
                 findTypeSerializer(
                     compilerContext,
@@ -147,7 +141,6 @@
             )
             val args: List<IrExpression> = getter.valueParameters.map { irGet(it) }
             val expr = serializerInstance(
-                this@SerializableCompanionIrGenerator,
                 serializer, compilerContext,
                 serializableIrClass.defaultType
             ) { it, _ -> args[it] }
@@ -157,25 +150,25 @@
         generateSerializerFactoryIfNeeded(methodDescriptor)
     }
 
-    private fun generateSerializerFactoryIfNeeded(getterDescriptor: FunctionDescriptor) {
+    private fun generateSerializerFactoryIfNeeded(getterDescriptor: IrSimpleFunction) {
         if (!irClass.descriptor.needSerializerFactory()) return
         val serialFactoryDescriptor = irClass.findDeclaration<IrSimpleFunction> {
             it.valueParameters.size == 1
                     && it.valueParameters.first().isVararg
-//                    && it.kind == CallableMemberDescriptor.Kind.SYNTHESIZED
-                    && isKSerializer(it.returnType)
+                    && it.returnType.isKSerializer()
+                    && it.isFromPlugin()
         } ?: return
         addFunctionBody(serialFactoryDescriptor) { factory ->
             val kSerializerStarType = factory.returnType
             val array = factory.valueParameters.first()
-            val argsSize = serializableDescriptor.declaredTypeParameters.size
+            val argsSize = serializableIrClass.typeParameters.size
             val arrayGet = compilerContext.irBuiltIns.arrayClass.owner.declarations.filterIsInstance<IrSimpleFunction>()
                 .single { it.name.asString() == "get" }
 
             val serializers: List<IrExpression> = (0 until argsSize).map {
                 irInvoke(irGet(array), arrayGet.symbol, irInt(it), typeHint = kSerializerStarType)
             }
-            val serializerCall = compilerContext.symbolTable.referenceSimpleFunction(getterDescriptor)
+            val serializerCall = getterDescriptor.symbol
             val call = irInvoke(
                 IrGetValueImpl(startOffset, endOffset, factory.dispatchReceiverParameter!!.symbol),
                 serializerCall,
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 b974b20..959e212 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
@@ -24,7 +24,6 @@
 import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.util.OperatorNameConventions
 import org.jetbrains.kotlin.utils.getOrPutNullable
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.*
 import org.jetbrains.kotlinx.serialization.compiler.diagnostic.serializableAnnotationIsUseless
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
 import org.jetbrains.kotlinx.serialization.compiler.resolve.*
@@ -36,8 +35,8 @@
 
 class SerializableIrGenerator(
     val irClass: IrClass,
-    override val compilerContext: SerializationPluginContext
-) : AbstractIrGenerator(irClass), IrBuilderExtension {
+    compilerContext: SerializationPluginContext
+) : BaseIrGenerator(irClass, compilerContext) {
 
     protected val properties = serializablePropertiesForIrBackend(irClass)
 
@@ -65,7 +64,7 @@
             val thiz = irClass.thisReceiver!!
             val serializableProperties = properties.serializableProperties
 
-            val serialDescs = serializableProperties.map { it.descriptor }.toSet()
+            val serialDescs = serializableProperties.map { it.ir }.toSet()
 
             val propertyByParamReplacer: (ValueParameterDescriptor) -> IrExpression? =
                 createPropertyByParamReplacer(irClass, serializableProperties, thiz)
@@ -137,19 +136,19 @@
                 val paramRef = ctor.valueParameters[index + seenVarsOffset]
                 // Assign this.a = a in else branch
                 // Set field directly w/o setter to match behavior of old backend plugin
-                val backingFieldToAssign = prop.descriptor.backingField!!
+                val backingFieldToAssign = prop.ir.backingField!!
                 val assignParamExpr = irSetField(irGet(thiz), backingFieldToAssign, irGet(paramRef))
 
                 val ifNotSeenExpr: IrExpression = if (prop.optional) {
                     val initializerBody =
-                        requireNotNull(initializerAdapter(prop.irField?.initializer!!)) { "Optional value without an initializer" } // todo: filter abstract here
+                        requireNotNull(initializerAdapter(prop.ir.backingField?.initializer!!)) { "Optional value without an initializer" } // todo: filter abstract here
                     irSetField(irGet(thiz), backingFieldToAssign, initializerBody)
                 } else {
                     // property required
                     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 }
+                        statementsAfterSerializableProperty[prop.ir]?.forEach { +it }
                         continue
                     } else {
                         irThrow(irInvoke(null, exceptionCtorRef, irString(prop.name), typeHint = exceptionType))
@@ -168,7 +167,7 @@
 
                 +irIfThenElse(compilerContext.irBuiltIns.unitType, propNotSeenTest, ifNotSeenExpr, assignParamExpr)
 
-                statementsAfterSerializableProperty[prop.descriptor]?.forEach { +it }
+                statementsAfterSerializableProperty[prop.ir]?.forEach { +it }
             }
 
             // Handle function-intialized interface delegates
@@ -271,7 +270,7 @@
         propertiesStart: Int
     ): Int {
         check(superClass.isInternalSerializable)
-        val superCtorRef = serializableSyntheticConstructor(superClass)!!
+        val superCtorRef = superClass.serializableSyntheticConstructor()!!
         val superProperties = serializablePropertiesForIrBackend(superClass).serializableProperties
         val superSlots = superProperties.bitMaskSlotCount()
         val arguments = allValueParameters.subList(0, superSlots) +
@@ -332,7 +331,6 @@
                         val genericIdx = irClass.defaultType.arguments.indexOf(arg).let { if (it == -1) null else it }
                         val serial = findTypeSerializerOrContext(compilerContext, arg.typeOrNull!!)
                         serializerInstance(
-                            this@SerializableIrGenerator,
                             serial,
                             compilerContext,
                             arg.typeOrNull!!,
@@ -346,9 +344,9 @@
             }
 
             serializeAllProperties(
-                this@SerializableIrGenerator, irClass, serializableProperties,
-                objectToSerialize, localOutput, localSerialDesc,
-                kOutputClass, ignoreIndexTo, initializerAdapter
+                serializableProperties, objectToSerialize,
+                localOutput, localSerialDesc, kOutputClass,
+                ignoreIndexTo, initializerAdapter
             ) { it, _ ->
                 irGet(writeSelfFunction.valueParameters[3 + it])
             }
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt
index 5110370..687bfd4 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt
@@ -7,8 +7,6 @@
 
 import org.jetbrains.kotlin.backend.jvm.functionByName
 import org.jetbrains.kotlin.builtins.StandardNames
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
 import org.jetbrains.kotlin.ir.builders.*
 import org.jetbrains.kotlin.ir.declarations.IrClass
 import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
@@ -23,14 +21,9 @@
 import org.jetbrains.kotlin.ir.util.functions
 import org.jetbrains.kotlin.ir.util.properties
 import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.enumEntries
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.getClassFromInternalSerializationPackage
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.getClassFromRuntime
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.serialNameValue
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
-import org.jetbrains.kotlinx.serialization.compiler.resolve.*
+import org.jetbrains.kotlinx.serialization.compiler.resolve.CallingConventions
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
 
 class SerializerForEnumsGenerator(
     irClass: IrClass,
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForInlineClassGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForInlineClassGenerator.kt
index a94c333..9327fc1 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForInlineClassGenerator.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForInlineClassGenerator.kt
@@ -6,8 +6,6 @@
 package org.jetbrains.kotlinx.serialization.compiler.backend.ir
 
 import org.jetbrains.kotlin.backend.jvm.functionByName
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
 import org.jetbrains.kotlin.ir.builders.*
 import org.jetbrains.kotlin.ir.declarations.IrClass
 import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
@@ -18,12 +16,9 @@
 import org.jetbrains.kotlin.ir.types.IrType
 import org.jetbrains.kotlin.ir.types.typeOrNull
 import org.jetbrains.kotlin.ir.util.constructors
-import org.jetbrains.kotlin.resolve.BindingContext
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.getClassFromInternalSerializationPackage
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.getClassFromRuntime
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
-import org.jetbrains.kotlinx.serialization.compiler.resolve.*
+import org.jetbrains.kotlinx.serialization.compiler.resolve.CallingConventions
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
 
 class SerializerForInlineClassGenerator(
     irClass: IrClass,
@@ -44,7 +39,7 @@
         val inlineEncoder = irTemporary(encodeInlineCall, nameHint = "inlineEncoder")
 
         val property = serializableProperties.first()
-        val value = getFromBox(irGet(saveFunc.valueParameters[1]), property)
+        val value = getProperty(irGet(saveFunc.valueParameters[1]), property.ir)
 
         // inlineEncoder.encodeInt/String/SerializableValue
         val elementCall = formEncodeDecodePropertyCall(irGet(inlineEncoder), saveFunc.dispatchReceiverParameter!!, property, {innerSerial, sti ->
@@ -109,6 +104,4 @@
             listOf(expression)
         )
 
-    private fun IrBlockBodyBuilder.getFromBox(expression: IrExpression, serializableProperty: IrSerializableProperty): IrExpression =
-        getProperty(expression, serializableProperty.descriptor)
 }
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 75c4587..706be67 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
@@ -37,7 +37,6 @@
 import org.jetbrains.kotlin.resolve.isInlineClass
 import org.jetbrains.kotlin.util.OperatorNameConventions
 import org.jetbrains.kotlin.utils.addToStdlib.cast
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.*
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationDescriptorSerializerPlugin
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
 import org.jetbrains.kotlinx.serialization.compiler.resolve.*
@@ -55,10 +54,10 @@
 
 open class SerializerIrGenerator(
     val irClass: IrClass,
-    final override val compilerContext: SerializationPluginContext,
+    compilerContext: SerializationPluginContext,
     metadataPlugin: SerializationDescriptorSerializerPlugin?,
     private val serialInfoJvmGenerator: SerialInfoImplJvmIrGenerator,
-) : AbstractIrGenerator(irClass), IrBuilderExtension {
+) : BaseIrGenerator(irClass, compilerContext) {
     protected val serializableIrClass = getSerializableClassDescriptorBySerializer(irClass)!!
 
     protected val serialName: String = serializableIrClass.serialName()
@@ -91,7 +90,7 @@
         if (count == 0) return emptyList()
         val propNames = (0 until count).map { "${SerialEntityNames.typeArgPrefix}$it" }
         return propNames.mapNotNull { name ->
-            getProperty(name) { isKSerializer(it.getter!!.returnType) }
+            getProperty(name) { it.getter!!.returnType.isKSerializer() }
         }
     }
 
@@ -184,7 +183,7 @@
             if (classProp.transient) continue
             +addFieldCall(classProp)
             // add property annotations
-            val property = classProp.descriptor//.getIrPropertyFrom(serializableIrClass)
+            val property = classProp.ir//.getIrPropertyFrom(serializableIrClass)
             copySerialInfoAnnotationsToDescriptor(
                 property.annotations,
                 localDescriptor,
@@ -305,8 +304,8 @@
                 createInitializerAdapter(serializableIrClass, propertyByParamReplacer, thisSymbol to { irGet(objectToSerialize) })
 
             serializeAllProperties(
-                this@SerializerIrGenerator, serializableIrClass, serializableProperties, objectToSerialize,
-                localOutput, localSerialDesc, kOutputClass, ignoreIndexTo = -1, initializerAdapter
+                serializableProperties, objectToSerialize, localOutput,
+                localSerialDesc, kOutputClass, ignoreIndexTo = -1, initializerAdapter
             ) { it, _ ->
                 val (_, ir) = localSerializersFieldsDescriptors[it]
                 irGetField(irGet(saveFunc.dispatchReceiverParameter!!), ir.backingField!!)
@@ -326,7 +325,6 @@
         whenDoNot: (sti: IrSerialTypeInfo) -> FunctionWithArgs,
         returnTypeHint: IrType? = null
     ): IrExpression = formEncodeDecodePropertyCall(
-        this@SerializerIrGenerator,
         encoder,
         property,
         whenHaveSerializer,
@@ -385,7 +383,7 @@
 
         val serialPropertiesIndexes = serializableProperties
             .mapIndexed { i, property -> property to i }
-            .associate { (p, i) -> p.descriptor to i }
+            .associate { (p, i) -> p.ir to i }
 
         val transients = serializableIrClass.declarations.asSequence()
             .filterIsInstance<IrProperty>()
@@ -395,7 +393,7 @@
         // var bitMask0 = 0, bitMask1 = 0...
         val bitMasks = (0 until blocksCnt).map { irTemporary(irInt(0), "bitMask$it", isMutable = true) }
         // var local0 = null, local1 = null ...
-        val serialPropertiesMap = serializableProperties.mapIndexed { i, prop -> i to prop.descriptor }.associate { (i, descriptor) ->
+        val serialPropertiesMap = serializableProperties.mapIndexed { i, prop -> i to prop.ir }.associate { (i, descriptor) ->
             val (expr, type) = defaultValueAndType(descriptor)
             descriptor to irTemporary(expr, "local$i", type, isMutable = true)
         }
@@ -425,7 +423,7 @@
                                 it.owner.name.asString() == "${CallingConventions.decode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}" &&
                                         it.owner.valueParameters.size == 4
                             } to listOf(
-                                localSerialDesc.get(), irInt(index), innerSerial, serialPropertiesMap.getValue(property.descriptor).get()
+                                localSerialDesc.get(), irInt(index), innerSerial, serialPropertiesMap.getValue(property.ir).get()
                             )
                         }, {sti ->
                                                          inputClass.functions.single {
@@ -435,7 +433,7 @@
                                                      }, returnTypeHint = property.type)
                     // local$i = localInput.decode...(...)
                     +irSet(
-                        serialPropertiesMap.getValue(property.descriptor).symbol,
+                        serialPropertiesMap.getValue(property.ir).symbol,
                         decodeFuncToCall
                     )
                     // bitMask[i] |= 1 << x
@@ -492,9 +490,9 @@
         )
 
         val typeArgs = (loadFunc.returnType as IrSimpleType).arguments.map { (it as IrTypeProjection).type }
-        val deserCtor: IrConstructorSymbol? = serializableSyntheticConstructor(serializableIrClass)
+        val deserCtor: IrConstructorSymbol? = serializableIrClass.serializableSyntheticConstructor()
         if (serializableIrClass.isInternalSerializable && deserCtor != null) {
-            var args: List<IrExpression> = serializableProperties.map { serialPropertiesMap.getValue(it.descriptor).get() }
+            var args: List<IrExpression> = serializableProperties.map { serialPropertiesMap.getValue(it.ir).get() }
             args = bitMasks.map { irGet(it) } + args + irNull()
             +irReturn(irInvoke(null, deserCtor, typeArgs, args))
         } else {
@@ -574,9 +572,9 @@
         bitMasks: List<IrVariable>
     ) {
         for (property in properties.serializableStandaloneProperties) {
-            val localPropIndex = propIndexes(property.descriptor)
+            val localPropIndex = propIndexes(property.ir)
             // generate setter call
-            val setter = property.descriptor.setter!!
+            val setter = property.ir.setter!!
             val propSeenTest =
                 irNotEquals(
                     irInt(0),
@@ -587,13 +585,12 @@
                     )
                 )
 
-            val setterInvokeExpr = irSet(setter.returnType, irGet(serializableVar), setter.symbol, irGet(propVars(property.descriptor)))
+            val setterInvokeExpr = irSet(setter.returnType, irGet(serializableVar), setter.symbol, irGet(propVars(property.ir)))
 
             +irIfThen(propSeenTest, setterInvokeExpr)
         }
     }
 
-    // !!! TODO: this doesn't work with OLD FE !!!
     fun generate() {
         val prop = generatedSerialDescPropertyDescriptor?.let { generateSerializableClassProperty(it); true } ?: false
         if (prop)
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerSearchUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerSearchUtil.kt
new file mode 100644
index 0000000..048f837
--- /dev/null
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerSearchUtil.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2010-2022 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.ClassKind
+import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.ir.declarations.IrClass
+import org.jetbrains.kotlin.ir.expressions.IrClassReference
+import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
+import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.types.*
+import org.jetbrains.kotlin.ir.util.*
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlinx.serialization.compiler.backend.common.findStandardKotlinTypeSerializer
+import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
+import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
+import org.jetbrains.kotlinx.serialization.compiler.resolve.SpecialBuiltins
+
+class IrSerialTypeInfo(
+    val property: IrSerializableProperty,
+    val elementMethodPrefix: String,
+    val serializer: IrClassSymbol? = null
+)
+
+fun BaseIrGenerator.getIrSerialTypeInfo(property: IrSerializableProperty, ctx: SerializationPluginContext): IrSerialTypeInfo {
+    fun SerializableInfo(serializer: IrClassSymbol?) =
+        IrSerialTypeInfo(property, if (property.type.isNullable()) "Nullable" else "", serializer)
+
+    val T = property.type
+    property.serializableWith(ctx)?.let { return SerializableInfo(it) }
+    findAddOnSerializer(T, ctx)?.let { return SerializableInfo(it) }
+    T.overridenSerializer?.let { return SerializableInfo(it) }
+    return when {
+        T.isTypeParameter() -> IrSerialTypeInfo(property, if (property.type.isMarkedNullable()) "Nullable" else "", null)
+        T.isPrimitiveType() -> IrSerialTypeInfo(
+            property,
+            T.classFqName!!.asString().removePrefix("kotlin.")
+        )
+        T.isString() -> IrSerialTypeInfo(property, "String")
+        T.isArray() -> {
+            val serializer = property.serializableWith(ctx) ?: ctx.getClassFromInternalSerializationPackage(SpecialBuiltins.referenceArraySerializer)
+            SerializableInfo(serializer)
+        }
+        else -> {
+            val serializer =
+                findTypeSerializerOrContext(ctx, property.type)
+            SerializableInfo(serializer)
+        }
+    }
+}
+
+fun BaseIrGenerator.findAddOnSerializer(propertyType: IrType, ctx: SerializationPluginContext): IrClassSymbol? {
+    val classSymbol = propertyType.classOrNull ?: return null
+    additionalSerializersInScopeOfCurrentFile[classSymbol to propertyType.isNullable()]?.let { return it }
+    if (classSymbol in contextualKClassListInCurrentFile)
+        return ctx.getClassFromRuntime(SpecialBuiltins.contextSerializer)
+    if (classSymbol.owner.annotations.hasAnnotation(SerializationAnnotations.polymorphicFqName))
+        return ctx.getClassFromRuntime(SpecialBuiltins.polymorphicSerializer)
+    if (propertyType.isNullable()) return findAddOnSerializer(propertyType.makeNotNull(), ctx)
+    return null
+}
+
+fun BaseIrGenerator.findTypeSerializerOrContext(
+    context: SerializationPluginContext, kType: IrType
+): IrClassSymbol? {
+    if (kType.isTypeParameter()) return null
+    return findTypeSerializerOrContextUnchecked(context, kType) ?: error("Serializer for element of type ${kType.render()} has not been found")
+}
+
+fun BaseIrGenerator.findTypeSerializerOrContextUnchecked(
+    context: SerializationPluginContext, kType: IrType
+): IrClassSymbol? {
+    val annotations = kType.annotations
+    if (kType.isTypeParameter()) return null
+    annotations.serializableWith()?.let { return it }
+    additionalSerializersInScopeOfCurrentFile[kType.classOrNull!! to kType.isNullable()]?.let {
+        return it
+    }
+    if (kType.isMarkedNullable()) return findTypeSerializerOrContextUnchecked(context, kType.makeNotNull())
+    if (kType.classOrNull in contextualKClassListInCurrentFile) return context.referenceClass(contextSerializerId)
+    return analyzeSpecialSerializers(context, annotations) ?: findTypeSerializer(context, kType)
+}
+
+fun analyzeSpecialSerializers(
+    context: SerializationPluginContext,
+    annotations: List<IrConstructorCall>
+): IrClassSymbol? = when {
+    annotations.hasAnnotation(SerializationAnnotations.contextualFqName) || annotations.hasAnnotation(SerializationAnnotations.contextualOnPropertyFqName) ->
+        context.referenceClass(contextSerializerId)
+    // can be annotation on type usage, e.g. List<@Polymorphic Any>
+    annotations.hasAnnotation(SerializationAnnotations.polymorphicFqName) ->
+        context.referenceClass(polymorphicSerializerId)
+    else -> null
+}
+
+
+fun findTypeSerializer(context: SerializationPluginContext, type: IrType): IrClassSymbol? {
+    type.overridenSerializer?.let { return it }
+    if (type.isTypeParameter()) return null
+    if (type.isArray()) return context.referenceClass(referenceArraySerializerId)
+    if (type.isGeneratedSerializableObject()) return context.referenceClass(objectSerializerId)
+    val stdSer = findStandardKotlinTypeSerializer(context, type) // see if there is a standard serializer
+        ?: findEnumTypeSerializer(context, type)
+    if (stdSer != null) return stdSer
+    if (type.isInterface() && type.classOrNull?.owner?.isSealedSerializableInterface == false) return context.referenceClass(
+        polymorphicSerializerId
+    )
+    return type.classOrNull?.owner.classSerializer(context) // check for serializer defined on the type
+}
+fun findEnumTypeSerializer(context: SerializationPluginContext, type: IrType): IrClassSymbol? {
+    val classSymbol = type.classOrNull?.owner ?: return null
+    return if (classSymbol.kind == ClassKind.ENUM_CLASS && !classSymbol.isEnumWithLegacyGeneratedSerializer())
+        context.referenceClass(enumSerializerId)
+    else null
+}
+
+internal fun IrClass?.classSerializer(context: SerializationPluginContext): IrClassSymbol? = this?.let {
+    // serializer annotation on class?
+    serializableWith?.let { return it }
+    // companion object serializer?
+    if (hasCompanionObjectAsSerializer) return companionObject()?.symbol
+    // can infer @Poly?
+    polymorphicSerializerIfApplicableAutomatically(context)?.let { return it }
+    // default serializable?
+    if (shouldHaveGeneratedSerializer) {
+        // $serializer nested class
+        return this.declarations
+            .filterIsInstance<IrClass>()
+            .singleOrNull { it.name == SerialEntityNames.SERIALIZER_CLASS_NAME }?.symbol
+    }
+    return null
+}
+
+internal fun IrClass.polymorphicSerializerIfApplicableAutomatically(context: SerializationPluginContext): IrClassSymbol? {
+    val serializer = when {
+        kind == ClassKind.INTERFACE && modality == Modality.SEALED -> SpecialBuiltins.sealedSerializer
+        kind == ClassKind.INTERFACE -> SpecialBuiltins.polymorphicSerializer
+        isInternalSerializable && modality == Modality.ABSTRACT -> SpecialBuiltins.polymorphicSerializer
+        isInternalSerializable && modality == Modality.SEALED -> SpecialBuiltins.sealedSerializer
+        else -> null
+    }
+    return serializer?.let {
+        context.getClassFromRuntimeOrNull(
+            it,
+            SerializationPackages.packageFqName,
+            SerializationPackages.internalPackageFqName
+        )
+    }
+}
+
+internal val IrType.overridenSerializer: IrClassSymbol?
+    get() {
+        val desc = this.classOrNull ?: return null
+        desc.owner.serializableWith?.let { return it }
+        return null
+    }
+
+internal val IrClass.serializableWith: IrClassSymbol?
+    get() = annotations.serializableWith()
+
+internal val IrClass.serializerForClass: IrClassSymbol?
+    get() = (annotations.findAnnotation(SerializationAnnotations.serializerAnnotationFqName)
+        ?.getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol
+
+fun findStandardKotlinTypeSerializer(context: SerializationPluginContext, type: IrType): IrClassSymbol? {
+    val typeName = type.classFqName?.toString()
+    val name = when (typeName) {
+        "Z" -> if (type.isBoolean()) "BooleanSerializer" else null
+        "B" -> if (type.isByte()) "ByteSerializer" else null
+        "S" -> if (type.isShort()) "ShortSerializer" else null
+        "I" -> if (type.isInt()) "IntSerializer" else null
+        "J" -> if (type.isLong()) "LongSerializer" else null
+        "F" -> if (type.isFloat()) "FloatSerializer" else null
+        "D" -> if (type.isDouble()) "DoubleSerializer" else null
+        "C" -> if (type.isChar()) "CharSerializer" else null
+        null -> null
+        else -> findStandardKotlinTypeSerializer(typeName)
+    } ?: return null
+    return context.getClassFromRuntimeOrNull(name, SerializationPackages.internalPackageFqName, SerializationPackages.packageFqName)
+}
+
+// @Serializable(X::class) -> X
+internal fun List<IrConstructorCall>.serializableWith(): IrClassSymbol? {
+    val annotation = findAnnotation(SerializationAnnotations.serializableAnnotationFqName) ?: return null
+    val arg = annotation.getValueArgument(0) as? IrClassReference ?: return null
+    return arg.symbol as? IrClassSymbol
+}
+
+internal fun getSerializableClassByCompanion(companionClass: IrClass): IrClass? {
+    if (companionClass.isSerializableObject) return companionClass
+    if (!companionClass.isCompanion) return null
+    val classDescriptor = (companionClass.parent as? IrClass) ?: return null
+    if (!classDescriptor.shouldHaveGeneratedMethodsInCompanion) return null
+    return classDescriptor
+}
+
+fun BaseIrGenerator.allSealedSerializableSubclassesFor(
+    irClass: IrClass,
+    context: SerializationPluginContext
+): Pair<List<IrSimpleType>, List<IrClassSymbol>> {
+    assert(irClass.modality == Modality.SEALED)
+    fun recursiveSealed(klass: IrClass): Collection<IrClass> {
+        return klass.sealedSubclasses.map { it.owner }.flatMap { if (it.modality == Modality.SEALED) recursiveSealed(it) else setOf(it) }
+    }
+
+    val serializableSubtypes = recursiveSealed(irClass).map { it.defaultType }
+    return serializableSubtypes.mapNotNull { subtype ->
+        findTypeSerializerOrContextUnchecked(context, subtype)?.let { Pair(subtype, it) }
+    }.unzip()
+}
+
+internal fun getSerializableClassDescriptorBySerializer(serializer: IrClass): IrClass? {
+    val serializerForClass = serializer.serializerForClass
+    if (serializerForClass != null) return serializerForClass.owner
+    if (serializer.name !in setOf(
+            SerialEntityNames.SERIALIZER_CLASS_NAME,
+            SerialEntityNames.GENERATED_SERIALIZER_CLASS
+        )
+    ) return null
+    val classDescriptor = (serializer.parent as? IrClass) ?: return null
+    if (!classDescriptor.shouldHaveGeneratedSerializer) return null
+    return classDescriptor
+}
+
+fun SerializationPluginContext.getClassFromRuntimeOrNull(className: String, vararg packages: FqName): IrClassSymbol? {
+    val listToSearch = if (packages.isEmpty()) SerializationPackages.allPublicPackages else packages.toList()
+    for (pkg in listToSearch) {
+        referenceClass(ClassId(pkg, Name.identifier(className)))?.let { return it }
+    }
+    return null
+}
+
+fun SerializationPluginContext.getClassFromRuntime(className: String, vararg packages: FqName): IrClassSymbol {
+    return getClassFromRuntimeOrNull(className, *packages) ?: error(
+        "Class $className wasn't found in ${packages.toList().ifEmpty { SerializationPackages.allPublicPackages }}. " +
+                "Check that you have correct version of serialization runtime in classpath."
+    )
+}
+
+fun SerializationPluginContext.getClassFromInternalSerializationPackage(className: String): IrClassSymbol =
+    getClassFromRuntimeOrNull(className, SerializationPackages.internalPackageFqName)
+        ?: error("Class $className wasn't found in ${SerializationPackages.internalPackageFqName}. Check that you have correct version of serialization runtime in classpath.")
+
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/Synthetics.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/Synthetics.kt
deleted file mode 100644
index 0f04d46..0000000
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/Synthetics.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.jetbrains.kotlinx.serialization.compiler.backend.ir
-
-import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.descriptors.annotations.Annotations
-import org.jetbrains.kotlin.descriptors.impl.FieldDescriptorImpl
-import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
-import org.jetbrains.kotlin.name.Name
-import org.jetbrains.kotlin.types.KotlinType
-
-
-/**
- * Simple property descriptor with backing field without getters/setters for ir generation purposes
- */
-class SimpleSyntheticPropertyDescriptor(
-    owner: ClassDescriptor,
-    name: String,
-    type: KotlinType,
-    isVar: Boolean = false,
-    visibility: DescriptorVisibility = DescriptorVisibilities.PRIVATE
-) : PropertyDescriptorImpl(
-    owner,
-    null,
-    Annotations.EMPTY,
-    Modality.FINAL,
-    visibility,
-    isVar,
-    Name.identifier(name),
-    CallableMemberDescriptor.Kind.SYNTHESIZED,
-    owner.source,
-    false, false, false, false, false, false
-    ) {
-
-    private val _backingField = FieldDescriptorImpl(Annotations.EMPTY, this)
-
-    init {
-        super.setType(type, emptyList(), owner.thisAsReceiverParameter, null, emptyList())
-        super.initialize(null, null, _backingField, null)
-    }
-}
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 6145145..1d55337 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
@@ -6,15 +6,6 @@
 package org.jetbrains.kotlinx.serialization.compiler.resolve
 
 import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.ir.declarations.IrClass
-import org.jetbrains.kotlin.ir.declarations.IrProperty
-import org.jetbrains.kotlin.ir.declarations.lazy.IrMaybeDeserializedClass
-import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
-import org.jetbrains.kotlin.ir.types.IrSimpleType
-import org.jetbrains.kotlin.ir.types.IrType
-import org.jetbrains.kotlin.ir.types.classOrNull
-import org.jetbrains.kotlin.ir.types.isAny
-import org.jetbrains.kotlin.ir.util.*
 import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.psi.KtDeclarationWithInitializer
 import org.jetbrains.kotlin.psi.KtParameter
@@ -27,15 +18,11 @@
 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
 import org.jetbrains.kotlin.serialization.deserialization.getName
 import org.jetbrains.kotlin.types.KotlinType
-import org.jetbrains.kotlin.types.checker.SimpleClassicTypeSystemContext.isInterface
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.isInitializePropertyFromParameter
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.isInternalSerializable
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.isInternallySerializableEnum
 import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SERIALIZABLE_PROPERTIES
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationDescriptorSerializerPlugin
 import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginMetadataExtensions
 
-interface ISerializableProperties<D, T, S : ISerializableProperty<D, T>> {
+interface ISerializableProperties<T, S : ISerializableProperty<T>> {
     val serializableProperties: List<S>
     val isExternallySerializable: Boolean
     val serializableConstructorProperties: List<S>
@@ -43,7 +30,7 @@
 }
 
 class SerializableProperties(private val serializableClass: ClassDescriptor, val bindingContext: BindingContext) :
-    ISerializableProperties<PropertyDescriptor, KotlinType, SerializableProperty> {
+    ISerializableProperties<KotlinType, SerializableProperty> {
     private val primaryConstructorParameters: List<ValueParameterDescriptor> =
         serializableClass.unsubstitutedPrimaryConstructor?.valueParameters ?: emptyList()
 
@@ -134,7 +121,7 @@
 }
 
 
-internal val ISerializableProperties<*, *, *>.goldenMask: Int
+internal val ISerializableProperties<*, *>.goldenMask: Int
     get() {
         var goldenMask = 0
         var requiredBit = 1
@@ -147,7 +134,7 @@
         return goldenMask
     }
 
-internal val ISerializableProperties<*, *, *>.goldenMaskList: List<Int>
+internal val ISerializableProperties<*, *>.goldenMaskList: List<Int>
     get() {
         val maskSlotCount = serializableProperties.bitMaskSlotCount()
         val goldenMaskList = MutableList(maskSlotCount) { 0 }
@@ -162,7 +149,7 @@
         return goldenMaskList
     }
 
-internal fun List<ISerializableProperty<*, *>>.bitMaskSlotCount() = size / 32 + 1
+internal fun List<ISerializableProperty<*>>.bitMaskSlotCount() = size / 32 + 1
 internal fun bitMaskSlotAt(propertyIndex: Int) = propertyIndex / 32
 
 internal fun BindingContext.serializablePropertiesFor(
@@ -180,75 +167,4 @@
         .map { descriptor.c.nameResolver.getName(it) }
     val propsMap = props.associateBy { it.descriptor.name }
     return correctOrder.map { propsMap.getValue(it) }
-}
-
-class IrSerializableProperties(
-    override val serializableProperties: List<IrSerializableProperty>,
-    override val isExternallySerializable: Boolean,
-    override val serializableConstructorProperties: List<IrSerializableProperty>,
-    override val serializableStandaloneProperties: List<IrSerializableProperty>
-) : ISerializableProperties<IrProperty, IrSimpleType, IrSerializableProperty> {
-}
-
-internal fun serializablePropertiesForIrBackend(
-    classDescriptor: IrClass,
-    serializationDescriptorSerializer: SerializationDescriptorSerializerPlugin? = null
-): IrSerializableProperties {
-    val properties = classDescriptor.properties.toList()
-    val primaryConstructorParams = classDescriptor.primaryConstructor?.valueParameters.orEmpty()
-    val primaryParamsAsProps = properties.associateBy { it.name }.let { namesMap ->
-        primaryConstructorParams.mapNotNull {
-            if (it.name !in namesMap) null else namesMap.getValue(it.name) to it.hasDefaultValue()
-        }.toMap()
-    }
-
-    fun isPropSerializable(it: IrProperty) =
-        if (classDescriptor.isInternalSerializable) !it.annotations.hasAnnotation(SerializationAnnotations.serialTransientFqName)
-        else !DescriptorVisibilities.isPrivate(it.visibility) && ((it.isVar && !it.annotations.hasAnnotation(SerializationAnnotations.serialTransientFqName)) || primaryParamsAsProps.contains(
-            it
-        ))
-
-    val (primaryCtorSerializableProps, bodySerializableProps) = properties
-        .asSequence()
-        .filter { !it.isFakeOverride && !it.isDelegated }
-        .filter(::isPropSerializable)
-        .map {
-            val isConstructorParameterWithDefault = primaryParamsAsProps[it] ?: false
-            // FIXME: workaround because IrLazyProperty doesn't deserialize information about backing fields. Fallback to descriptor won't work with FIR.
-            val isPropertyFromAnotherModuleDeclaresDefaultValue = it.descriptor is DeserializedPropertyDescriptor &&  it.descriptor.declaresDefaultValue()
-            val isPropertyWithBackingFieldFromAnotherModule = it.descriptor is DeserializedPropertyDescriptor && (it.descriptor.backingField != null || isPropertyFromAnotherModuleDeclaresDefaultValue)
-            IrSerializableProperty(
-                it,
-                isConstructorParameterWithDefault,
-                it.backingField != null || isPropertyWithBackingFieldFromAnotherModule,
-                it.backingField?.initializer.let { init -> init != null && !init.expression.isInitializePropertyFromParameter() } || isConstructorParameterWithDefault
-                        || isPropertyFromAnotherModuleDeclaresDefaultValue
-            )
-        }
-        .filterNot { it.transient }
-        .partition { primaryParamsAsProps.contains(it.descriptor) }
-
-    val serializableProps = run {
-        val supers = classDescriptor.getParentClassNotAny()
-        if (supers == null || !supers.isInternalSerializable)
-            primaryCtorSerializableProps + bodySerializableProps
-        else
-            serializablePropertiesForIrBackend(
-                supers,
-                serializationDescriptorSerializer
-            ).serializableProperties + primaryCtorSerializableProps + bodySerializableProps
-    } // todo: implement unsorting
-
-    val isExternallySerializable =
-        classDescriptor.isInternallySerializableEnum() || primaryConstructorParams.size == primaryParamsAsProps.size
-
-    return IrSerializableProperties(serializableProps, isExternallySerializable, primaryCtorSerializableProps, bodySerializableProps)
-}
-
-fun IrClass.getParentClassNotAny(): IrClass? {
-    val parentClass =
-        superTypes
-            .mapNotNull { it.classOrNull?.owner }
-            .singleOrNull { it.kind == ClassKind.CLASS || it.kind == ClassKind.ENUM_CLASS } ?: return null
-    return if (parentClass.defaultType.isAny()) null else parentClass
 }
\ No newline at end of file
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperty.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperty.kt
index d51337a..a06e5e4 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperty.kt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperty.kt
@@ -16,40 +16,12 @@
 
 package org.jetbrains.kotlinx.serialization.compiler.resolve
 
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
 import org.jetbrains.kotlin.descriptors.PropertyDescriptor
-import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
-import org.jetbrains.kotlin.ir.declarations.IrProperty
-import org.jetbrains.kotlin.ir.types.IrSimpleType
-import org.jetbrains.kotlin.ir.util.hasAnnotation
-import org.jetbrains.kotlin.psi.ValueArgument
 import org.jetbrains.kotlin.resolve.descriptorUtil.module
 import org.jetbrains.kotlin.types.KotlinType
 import org.jetbrains.kotlinx.serialization.compiler.backend.common.analyzeSpecialSerializers
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.genericIndex
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.serialNameValue
-import org.jetbrains.kotlinx.serialization.compiler.backend.common.serializableWith
-import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
 
-class SerializableProperty(
-    override val descriptor: PropertyDescriptor,
-    override val isConstructorParameterWithDefault: Boolean,
-    hasBackingField: Boolean,
-    declaresDefaultValue: Boolean
-) : ISerializableProperty<PropertyDescriptor, KotlinType> {
-    override val name = descriptor.annotations.serialNameValue ?: descriptor.name.asString()
-    override val type = descriptor.type
-    override val genericIndex = type.genericIndex
-    val module = descriptor.module
-    val serializableWith = descriptor.serializableWith ?: analyzeSpecialSerializers(module, descriptor.annotations)?.defaultType
-    override val optional = !descriptor.annotations.serialRequired && declaresDefaultValue
-    override val transient = descriptor.annotations.serialTransient || !hasBackingField
-    val annotationsWithArguments: List<Triple<ClassDescriptor, List<ValueArgument>, List<ValueParameterDescriptor>>> =
-        descriptor.annotationsWithArguments()
-}
-
-interface ISerializableProperty<D, T> {
-    val descriptor: D
+interface ISerializableProperty<T> {
     val isConstructorParameterWithDefault: Boolean
     val name: String
     val type: T
@@ -58,16 +30,18 @@
     val transient: Boolean
 }
 
-class IrSerializableProperty(
-    override val descriptor: IrProperty,
+class SerializableProperty(
+    val descriptor: PropertyDescriptor,
     override val isConstructorParameterWithDefault: Boolean,
     hasBackingField: Boolean,
     declaresDefaultValue: Boolean
-) : ISerializableProperty<IrProperty, IrSimpleType> {
+) : ISerializableProperty<KotlinType> {
     override val name = descriptor.annotations.serialNameValue ?: descriptor.name.asString()
-    override val type = descriptor.getter!!.returnType as IrSimpleType
+    override val type = descriptor.type
     override val genericIndex = type.genericIndex
-    fun serializableWith(ctx: SerializationPluginContext) = descriptor.annotations.serializableWith() ?: analyzeSpecialSerializers(ctx, descriptor.annotations)
-    override val optional = !descriptor.annotations.hasAnnotation(SerializationAnnotations.requiredAnnotationFqName) && declaresDefaultValue
-    override val transient = descriptor.annotations.hasAnnotation(SerializationAnnotations.serialTransientFqName) || !hasBackingField
-}
\ No newline at end of file
+    val module = descriptor.module
+    val serializableWith = descriptor.serializableWith ?: analyzeSpecialSerializers(module, descriptor.annotations)?.defaultType
+    override val optional = !descriptor.annotations.serialRequired && declaresDefaultValue
+    override val transient = descriptor.annotations.serialTransient || !hasBackingField
+}
+
diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Sealed.ir.txt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Sealed.ir.txt
index 077967a..9728edc 100644
--- a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Sealed.ir.txt
+++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Sealed.ir.txt
@@ -326,7 +326,9 @@
           DUP
           LDC (Result.Err)
           GETSTATIC (Result$Err, INSTANCE, LResult$Err;)
-          INVOKESPECIAL (kotlinx/serialization/internal/ObjectSerializer, <init>, (Ljava/lang/String;Ljava/lang/Object;)V)
+          ICONST_0
+          ANEWARRAY (java/lang/annotation/Annotation)
+          INVOKESPECIAL (kotlinx/serialization/internal/ObjectSerializer, <init>, (Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/annotation/Annotation;)V)
           CHECKCAST (kotlinx/serialization/KSerializer)
           AASTORE
           ALOAD (1)
@@ -335,7 +337,9 @@
           CHECKCAST (kotlinx/serialization/KSerializer)
           AASTORE
           ALOAD (1)
-          INVOKESPECIAL (kotlinx/serialization/SealedClassSerializer, <init>, (Ljava/lang/String;Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;)V)
+          ICONST_0
+          ANEWARRAY (java/lang/annotation/Annotation)
+          INVOKESPECIAL (kotlinx/serialization/SealedClassSerializer, <init>, (Ljava/lang/String;Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;[Ljava/lang/annotation/Annotation;)V)
           CHECKCAST (kotlinx/serialization/KSerializer)
           ARETURN
         LABEL (L1)
@@ -410,7 +414,9 @@
           DUP
           LDC (Result.Err)
           GETSTATIC (Result$Err, INSTANCE, LResult$Err;)
-          INVOKESPECIAL (kotlinx/serialization/internal/ObjectSerializer, <init>, (Ljava/lang/String;Ljava/lang/Object;)V)
+          ICONST_0
+          ANEWARRAY (java/lang/annotation/Annotation)
+          INVOKESPECIAL (kotlinx/serialization/internal/ObjectSerializer, <init>, (Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/annotation/Annotation;)V)
           CHECKCAST (kotlinx/serialization/KSerializer)
           ARETURN
         LABEL (L1)
@@ -859,7 +865,9 @@
           DUP
           LDC (Result.Err)
           GETSTATIC (Result$Err, INSTANCE, LResult$Err;)
-          INVOKESPECIAL (kotlinx/serialization/internal/ObjectSerializer, <init>, (Ljava/lang/String;Ljava/lang/Object;)V)
+          ICONST_0
+          ANEWARRAY (java/lang/annotation/Annotation)
+          INVOKESPECIAL (kotlinx/serialization/internal/ObjectSerializer, <init>, (Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/annotation/Annotation;)V)
           CHECKCAST (kotlinx/serialization/KSerializer)
           AASTORE
           ALOAD (1)
@@ -868,7 +876,9 @@
           CHECKCAST (kotlinx/serialization/KSerializer)
           AASTORE
           ALOAD (1)
-          INVOKESPECIAL (kotlinx/serialization/SealedClassSerializer, <init>, (Ljava/lang/String;Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;)V)
+          ICONST_0
+          ANEWARRAY (java/lang/annotation/Annotation)
+          INVOKESPECIAL (kotlinx/serialization/SealedClassSerializer, <init>, (Ljava/lang/String;Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;[Ljava/lang/annotation/Annotation;)V)
           CHECKCAST (kotlinx/serialization/KSerializer)
           ARETURN
         LABEL (L1)