Support skipping values equals to defaults in output stream for JS and IR backends

This commit is contained in:
Leonid Startsev
2018-10-16 14:04:58 +03:00
parent dba6396e95
commit 662e918a7b
7 changed files with 103 additions and 48 deletions
@@ -380,6 +380,23 @@ interface IrBuilderExtension {
)
}
fun buildInitializersRemapping(irClass: IrClass): (IrField) -> IrExpression? {
val original = irClass.constructors.singleOrNull { it.isPrimary }
?: throw IllegalStateException("Serializable class must have single primary constructor")
// default arguments of original constructor
val defaultsMap: Map<ParameterDescriptor, IrExpression?> =
original.valueParameters.associate { it.descriptor to it.defaultValue?.expression }
return fun(f: IrField): IrExpression? {
val i = f.initializer?.expression ?: return null
return if (i is IrGetValueImpl && i.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER) {
// this is a primary constructor property, use corresponding default of value parameter
defaultsMap.getValue(i.descriptor as ParameterDescriptor)
} else {
i
}
}
}
fun findEnumValuesMethod(enumClass: ClassDescriptor): IrFunction {
assert(enumClass.kind == ClassKind.ENUM_CLASS)
return compilerContext.externalSymbols.referenceClass(enumClass).owner.functions
@@ -6,7 +6,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrAnonymousInitializer
import org.jetbrains.kotlin.ir.declarations.IrClass
@@ -18,7 +17,6 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.TypeTranslator
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.descriptorUtil.module
@@ -40,19 +38,7 @@ class SerializableIrGenerator(
override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) =
irClass.contributeConstructor(constructorDescriptor) { ctor ->
val original = irClass.constructors.singleOrNull { it.isPrimary } ?: throw IllegalStateException("Serializable class must have single primary constructor")
// default arguments of original constructor
val defaultsMap: Map<ParameterDescriptor, IrExpression?> = original.valueParameters.associate { it.descriptor to it.defaultValue?.expression }
fun transformFieldInitializer(f: IrField): IrExpression? {
val i = f.initializer?.expression ?: return null
return if (i is IrGetValueImpl && i.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER) {
// this is a primary constructor property, use corresponding default of value parameter
defaultsMap.getValue(i.descriptor as ParameterDescriptor)
} else {
i
}
}
val transformFieldInitializer = buildInitializersRemapping(irClass)
// Missing field exception parts
val exceptionCtor =
@@ -7,6 +7,7 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.ir
import org.jetbrains.kotlin.backend.common.BackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.backend.common.lower.irThrow
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -14,11 +15,8 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.expressions.mapValueParameters
import org.jetbrains.kotlin.ir.expressions.mapValueParametersIndexed
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
@@ -48,6 +46,8 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
override val BackendContext.localSymbolTable: SymbolTable
get() = _table
private val serializableIrClass = compilerContext.externalSymbols.referenceClass(serializableDescriptor)
override fun generateSerialDesc() {
val desc: PropertyDescriptor = generatedSerialDescPropertyDescriptor ?: return
val serialDescImplClass = serializerDescriptor
@@ -157,6 +157,9 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
override fun generateSave(function: FunctionDescriptor) = irClass.contributeFunction(function) { saveFunc ->
val fieldInitializer: (SerializableProperty) -> IrExpression? =
buildInitializersRemapping(serializableIrClass.owner).run { { invoke(it.irField) } }
fun irThis(): IrExpression =
IrGetValueImpl(startOffset, endOffset, saveFunc.dispatchReceiverParameter!!.symbol)
@@ -183,35 +186,51 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
val serialObjectSymbol = saveFunc.valueParameters[1]
val localOutput = irTemporary(call, "output")
fun SerializableProperty.irGet(): IrGetField = irGetField(irGet(serialObjectSymbol), irField)
// internal serialization via virtual calls?
val labeledProperties = orderedProperties.filter { !it.transient }
for (index in labeledProperties.indices) {
val property = labeledProperties[index]
if (property.transient) continue
for ((index, property) in orderedProperties.filter { !it.transient }.withIndex()) {
// output.writeXxxElementValue(classDesc, index, value)
val sti = getSerialTypeInfo(property)
val innerSerial = serializerInstance(this@SerializerIrGenerator, serializableDescriptor, sti.serializer, property.module, property.type, property.genericIndex)
if (innerSerial == null) {
val elementCall = if (innerSerial == null) {
val writeFunc =
kOutputClass.referenceMethod("${CallingConventions.encode}${sti.elementMethodPrefix}${CallingConventions.elementPostfix}")
+irInvoke(
irInvoke(
irGet(localOutput),
writeFunc,
irGet(localSerialDesc),
irInt(index),
irGetField(irGet(serialObjectSymbol), property.irField)
property.irGet()
)
} else {
val writeFunc = kOutputClass.referenceMethod("${CallingConventions.encode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}")
+irInvoke(
irInvoke(
irGet(localOutput),
writeFunc,
irGet(localSerialDesc),
irInt(index),
innerSerial,
irGetField(irGet(serialObjectSymbol), property.irField)
property.irGet()
)
}
// check for call to .shouldEncodeElementDefault
if (!property.optional) {
// emit call right away
+elementCall
} else {
// emit check:
// if (obj.prop != DEFAULT_VALUE || output.shouldEncodeElementDefault(this.descriptor, i))
// output.encodeIntElement(this.descriptor, i, obj.prop)
val shouldEncodeFunc = kOutputClass.referenceMethod(CallingConventions.shouldEncodeDefault)
val partA = irNotEquals(property.irGet(), fieldInitializer(property)!!) // todo: props w/o backing fields
val partB = 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
val condition = irIfThenElse(compilerContext.irBuiltIns.booleanType, partA, irTrue(), partB)
+irIfThen(condition, elementCall)
}
}
// output.writeEnd(serialClassDesc)
@@ -350,7 +369,7 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
// todo: set properties in external deserialization
var args: List<IrExpression> = localProps.map { it.get() }
val ctor: IrConstructorSymbol = if (serializableDescriptor.isInternalSerializable) {
val ctorDesc = compilerContext.externalSymbols.referenceClass(serializableDescriptor)
val ctorDesc = serializableIrClass
.owner.constructors.single { it.origin == SERIALIZABLE_PLUGIN_ORIGIN }
args = listOf(irGet(bitMasks[0])) + args + irNull()
ctorDesc.symbol
@@ -16,20 +16,21 @@
package org.jetbrains.kotlinx.serialization.compiler.backend.js
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPureClassOrObject
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertiesDescriptorsMap
import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializerOrContext
import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryPropertiesDescriptorsMap
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.contextSerializerId
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.enumSerializerId
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId
@@ -171,4 +172,9 @@ internal fun SerializerJsTranslator.createGetKClassExpression(classDescriptor: C
JsInvocation(
context.namer().kotlin("getKClass"),
context.translateQualifiedReference(classDescriptor)
)
)
fun TranslationContext.buildInitializersRemapping(forClass: KtPureClassOrObject): Map<PropertyDescriptor, KtExpression?> = forClass.run {
(bodyPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } +
primaryPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.defaultValue })
}
@@ -43,10 +43,7 @@ class SerializableJsTranslator(
val context: TranslationContext
) : SerializableCodegen(descriptor, context.bindingContext()) {
private val initMap: Map<PropertyDescriptor, KtExpression?> = declaration.run {
(bodyPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } +
primaryPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.defaultValue })
}
private val initMap: Map<PropertyDescriptor, KtExpression?> = context.buildInitializersRemapping(declaration)
override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) {
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.declaration.DeclarationBodyVisitor
@@ -28,6 +29,7 @@ import org.jetbrains.kotlin.js.translate.general.Translation
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPureClassOrObject
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen
@@ -123,12 +125,19 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
context.addDeclarationStatement(f.makeStmt())
}
private fun TranslationContext.referenceMethod(clazz: ClassDescriptor, name: String) =
clazz.getFuncDesc(name).single().let { getNameForDescriptor(it) }
override fun generateSave(function: FunctionDescriptor) = generateFunction(function) { jsFun, ctx ->
val encoderClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.ENCODER_CLASS)
val kOutputClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.STRUCTURE_ENCODER_CLASS)
val wBeginFunc = ctx.getNameForDescriptor(
encoderClass.getFuncDesc(CallingConventions.begin).single { it.valueParameters.size == 2 })
val serialClassDescRef = JsNameRef(context.getNameForDescriptor(anySerialDescProperty!!), JsThisRef())
val initializersMap: Map<PropertyDescriptor, KtExpression?> = context.buildInitializersRemapping(
(serializableDescriptor.findPsi() as? KtPureClassOrObject)
?: throw AssertionError("Serializable descriptor $serializableDescriptor must have source file to build initializers map")
)
// output.writeBegin(desc, [])
val typeParams = serializableDescriptor.declaredTypeParameters.mapIndexed { idx, _ ->
@@ -143,6 +152,8 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
val localOutputRef = JsNameRef(localOutputName)
+JsVars(JsVars.JsVar(localOutputName, call))
fun SerializableProperty.jsNameRef() = JsNameRef(ctx.getNameForDescriptor(descriptor), objRef)
// todo: internal serialization via virtual calls
val labeledProperties = orderedProperties.filter { !it.transient }
for (index in labeledProperties.indices) {
@@ -151,24 +162,42 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
// output.writeXxxElementValue(classDesc, index, value)
val sti = getSerialTypeInfo(property)
val innerSerial = serializerInstance(sti.serializer, property.module, property.type, property.genericIndex)
if (innerSerial == null) {
val invocation = if (innerSerial == null) {
val writeFunc =
kOutputClass.getFuncDesc("${CallingConventions.encode}${sti.elementMethodPrefix}${CallingConventions.elementPostfix}").single()
.let { ctx.getNameForDescriptor(it) }
+JsInvocation(JsNameRef(writeFunc, localOutputRef),
serialClassDescRef,
JsIntLiteral(index),
JsNameRef(ctx.getNameForDescriptor(property.descriptor), objRef)).makeStmt()
JsInvocation(
JsNameRef(writeFunc, localOutputRef),
serialClassDescRef,
JsIntLiteral(index),
property.jsNameRef()
).makeStmt()
}
else {
val writeFunc =
kOutputClass.getFuncDesc("${CallingConventions.encode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}").single()
.let { ctx.getNameForDescriptor(it) }
+JsInvocation(JsNameRef(writeFunc, localOutputRef),
serialClassDescRef,
JsIntLiteral(index),
innerSerial,
JsNameRef(ctx.getNameForDescriptor(property.descriptor), objRef)).makeStmt()
JsInvocation(
JsNameRef(writeFunc, localOutputRef),
serialClassDescRef,
JsIntLiteral(index),
innerSerial,
property.jsNameRef()
).makeStmt()
}
if (!property.optional) {
+invocation
} else {
val shouldEncodeFunc = ctx.referenceMethod(kOutputClass, CallingConventions.shouldEncodeDefault)
val defaultValue =
initializersMap.getValue(property.descriptor)?.let { Translation.translateAsExpression(it, ctx) }
?: throw IllegalStateException("Optional property does not have an initializer?")
val partA = JsBinaryOperation(JsBinaryOperator.NEQ, property.jsNameRef(), defaultValue)
val partB =
JsInvocation(JsNameRef(shouldEncodeFunc, localOutputRef), serialClassDescRef, JsIntLiteral(index))
val cond = JsBinaryOperation(JsBinaryOperator.OR, partA, partB)
+JsIf(cond, invocation)
}
}
@@ -87,6 +87,7 @@ object CallingConventions {
const val encode = "encode"
const val decodeElementIndex = "decodeElementIndex"
const val elementPostfix = "Element"
const val shouldEncodeDefault = "shouldEncodeElementDefault"
const val addElement = "addElement"
const val addAnnotation = "pushAnnotation"